type Whitespace = ' ' | '\n' | '\r' | '\f' | '\t' type Trim = S extends `${infer T}${Whitespace}` ? Trim : S extends `${Whitespace}${infer T}` ? Trim : S type Combinators = ' ' | '>' | '~' | '+' type GetLastTag = I extends `${string}${Combinators}${infer Right}` ? Right extends '' // right arm can't be empty ? unknown : GetLastTag : I type PseudoPrefix = | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'h' | 'i' | 'l' | 'n' | 'o' | 'p' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | ':-' type Split = S extends `${string},` // invalid selector ? unknown : S extends '' ? '' : SplitRec type SplitRec = S extends `${infer Left},${infer Right}` ? SplitRec : S extends '' ? Acc : SplitRec<'', Acc | S> type Quotes = '"' | "'" // DO NOT use union type like `${infer L},${Whitespace}${infer R}` here, // or it may cause OOM when running tsc in downstream projects. type PreprocessGrouping = I extends `${infer L}, ${infer R}` ? PreprocessGrouping<`${L},${R}`> : I extends `${infer L},\n${infer R}` ? PreprocessGrouping<`${L},${R}`> : I extends `${infer L},\r${infer R}` ? PreprocessGrouping<`${L},${R}`> : I extends `${infer L},\f${infer R}` ? PreprocessGrouping<`${L},${R}`> : I extends `${infer L},\t${infer R}` ? PreprocessGrouping<`${L},${R}`> : I type Preprocess = I extends `${string}[]${string}` // invalid selector ? unknown : PreprocessUnchecked type PreprocessUnchecked = I extends `${infer L}\\${Quotes}${infer R}` // remove escaped quotes ? PreprocessUnchecked<`${L}${R}`> : I extends `${infer L}"${string}"${infer R}` // remove quoted content in attribute ? PreprocessUnchecked<`${L}${R}`> : I extends `${infer L}'${string}'${infer R}` // remove quoted content in attribute ? PreprocessUnchecked<`${L}${R}`> : I extends `${infer L}[${string}]${infer R}` // process attribute ? PreprocessUnchecked<`${L}#x${R}`> // replace it with a fake ID selector : I extends `${infer L}[${string}${infer R}` // process unclosed attribute ? PreprocessUnchecked<`${L}#x${R}`> // replace it with a fake ID selector : I /** Parse `:is()` and `:where()` */ type ExpandFunctions = I extends `${infer L}:is(${infer Args})${infer R}` ? Split> extends string ? ExpandFunctions<`${L}&${Split>}${R}`> : unknown : I extends `${infer L}:where(${infer Args})${infer R}` ? Split> extends string ? ExpandFunctions<`${L}&${Split>}${R}`> : unknown : I extends `${infer L}:${infer Pseudo}(${string})${infer R}` ? IsIdentifier extends true ? ExpandFunctions<`${L}${R}`> : I : I /** Check whether each tag is valid or not. */ type Postprocess = PostprocessEach> extends string ? PostprocessEach> : unknown /** Postprocess each tag with simple validation. */ type PostprocessEach = I extends `${string}.` // invalid class selector ? unknown : I extends `${string}#` // invalid ID selector ? unknown : PostprocessEachUnchecked type PostprocessEachUnchecked = I extends `${infer Tag}.${string}&${infer Rest}` ? PostprocessEachUnchecked<`${Tag}&${Rest}`> : I extends `${infer Tag}.${string}` ? PostprocessEachUnchecked : I extends `${infer Tag}#${string}&${infer Rest}` ? PostprocessEachUnchecked<`${Tag}&${Rest}`> : I extends `${infer Tag}#${string}` ? PostprocessEachUnchecked : I extends `${infer Tag}:${PseudoPrefix}${string}&${infer Rest}` ? PostprocessEachUnchecked<`${Tag}&${Rest}`> : I extends `${infer Tag}:${PseudoPrefix}${string}` ? PostprocessEachUnchecked : I extends `${string}|${infer Tag}` // namespace prefix ? PostprocessEachUnchecked : I type ParseSelectorToTagNames = Trim extends '' ? unknown : Postprocess>>>>> export type ParseSelector< I extends string, Fallback extends Element = Element, > = ParseSelectorToTagNames extends string ? ExpandAnd, Fallback> extends Fallback ? ExpandAnd, Fallback> : Fallback : Fallback /** * Wrapper for `...&...` syntax expander. * * `&` is valid, but the expander will return the default result which is `unknown`, * so we must check the result and if it's `unknown` we will turn it into `Fallback`. */ type ExpandAnd< I extends string, Fallback extends Element, > = unknown extends ExpandAndInner ? Fallback : ExpandAndInner /** * Actually expand the `...&...` syntax. * * The reason why we choose `unknown` as initial type is * that `unknown & T` equals to `T`. */ type ExpandAndInner< I extends string, Fallback extends Element, Result extends Element | unknown = unknown, > = I extends `${'' | '*'}&${infer Rest}` ? ExpandAndInner : I extends `${infer Tag}&${infer Rest}` ? ExpandAndInner> : I extends '' | '*' ? Result : ExpandAndInner<'', Fallback, Result & TagNameToElement> export type TagNameToElement< Tag extends string, Fallback extends Element = Element, > = Tag extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Tag] : Tag extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Tag] : Fallback // -------------------------------------------------------- // Strict Parser // -------------------------------------------------------- // Specification is here: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram // but we don't plan to comply that fully, // otherwise it will increase type-checking time and the complexity of parser. type LowerCaseLetter = | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' type IdentifierFirstChar = | LowerCaseLetter | Uppercase | '-' | '_' type IdentifierChar = IdentifierFirstChar | Digit type IsIdentifier = S extends `${infer FirstChar}${infer Rest}` ? FirstChar extends IdentifierFirstChar ? IsValidRestChars : false : false type IsValidTags = S extends `${infer Head}&${infer Rest}` ? IsValidTagName extends true ? IsValidTags : false : IsValidTagName type IsValidTagName = S extends '' | '*' ? true : S extends `${infer H}${infer Rest}` ? H extends IdentifierFirstChar ? IsValidRestChars : false : string extends S ? true : false type IsValidRestChars = S extends `${infer H}${infer Rest}` ? H extends IdentifierChar ? IsValidRestChars : false : true // no characters left, so it's OK export type StrictlyParseSelector< S extends string, Fallback extends Element = Element, > = string extends S ? Fallback : ParseSelectorToTagNames extends infer Tags ? IsValidTags extends true ? Tags extends string ? ExpandAnd : never : never : never