forked from g-plane/typed-query-selector
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.d.ts
250 lines (229 loc) · 7 KB
/
parser.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
type Whitespace = ' ' | '\n' | '\r' | '\f' | '\t'
type Trim<S extends string> = S extends `${infer T}${Whitespace}`
? Trim<T>
: S extends `${Whitespace}${infer T}`
? Trim<T>
: S
type Combinators = ' ' | '>' | '~' | '+'
type GetLastTag<I> = I extends `${string}${Combinators}${infer Right}`
? Right extends '' // right arm can't be empty
? unknown
: GetLastTag<Right>
: I
type PseudoClassesFirstChar =
| 'a'
| 'b'
| 'c'
| 'd'
| 'e'
| 'f'
| 'h'
| 'i'
| 'l'
| 'n'
| 'o'
| 'p'
| 'r'
| 's'
| 't'
| 'u'
| 'v'
| 'w'
type Split<S> = S extends `${string},` // invalid selector
? unknown
: SplitRec<S>
type SplitRec<S, Acc = never> = S extends `${infer Left},${infer Right}`
? SplitRec<Right, Acc | Left>
: 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> = 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> = I extends `${string}[]${string}` // invalid selector
? unknown
: PreprocessUnchecked<I>
type PreprocessUnchecked<I> = 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
/** Parse `:is()` and `:where()` */
type ExpandFunctions<I> = I extends `${infer L}:is(${infer Args})${infer R}`
? Split<Trim<Args>> extends string
? ExpandFunctions<`${L}&${Split<Trim<Args>>}${R}`>
: unknown
: I extends `${infer L}:where(${infer Args})${infer R}`
? Split<Trim<Args>> extends string
? ExpandFunctions<`${L}&${Split<Trim<Args>>}${R}`>
: unknown
: I extends `${infer L}:${infer Pseudo}(${string})${infer R}`
? IsIdentifier<Pseudo> extends true
? ExpandFunctions<`${L}${R}`>
: I
: I
/** Check whether each tag is valid or not. */
type Postprocess<Tags> = PostprocessEach<GetLastTag<Tags>> extends string
? PostprocessEach<GetLastTag<Tags>>
: unknown
/** Postprocess each tag with simple validation. */
type PostprocessEach<I> = I extends `${string}.` // invalid class selector
? unknown
: I extends `${string}#` // invalid ID selector
? unknown
: PostprocessEachUnchecked<I>
type PostprocessEachUnchecked<I> =
I extends `${infer Tag}.${string}&${infer Rest}`
? PostprocessEachUnchecked<`${Tag}&${Rest}`>
: I extends `${infer Tag}.${string}`
? PostprocessEachUnchecked<Tag>
: I extends `${infer Tag}#${string}&${infer Rest}`
? PostprocessEachUnchecked<`${Tag}&${Rest}`>
: I extends `${infer Tag}#${string}`
? PostprocessEachUnchecked<Tag>
: I extends `${infer Tag}:${PseudoClassesFirstChar}${string}&${infer Rest}`
? PostprocessEachUnchecked<`${Tag}&${Rest}`>
: I extends `${infer Tag}:${PseudoClassesFirstChar}${string}`
? PostprocessEachUnchecked<Tag>
: I extends `${string}|${infer Tag}` // namespace prefix
? PostprocessEachUnchecked<Tag>
: I
type ParseSelectorToTagNames<I extends string> = Trim<I> extends ''
? unknown
: Postprocess<Split<ExpandFunctions<Preprocess<PreprocessGrouping<Trim<I>>>>>>
export type ParseSelector<
I extends string,
Fallback extends Element = Element,
> = ParseSelectorToTagNames<I> extends string
? ExpandAnd<ParseSelectorToTagNames<I>, Fallback> extends Fallback
? ExpandAnd<ParseSelectorToTagNames<I>, 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<I, Fallback>
? Fallback
: ExpandAndInner<I, Fallback>
/**
* 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<Rest, Fallback, Result>
: I extends `${infer Tag}&${infer Rest}`
? ExpandAndInner<Rest, Fallback, Result & TagNameToElement<Tag, Fallback>>
: I extends '' | '*'
? Result
: ExpandAndInner<'', Fallback, Result & TagNameToElement<I, Fallback>>
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<LowerCaseLetter>
| '-'
| '_'
type IdentifierChar = IdentifierFirstChar | Digit
type IsIdentifier<S> = S extends `${infer FirstChar}${infer Rest}`
? FirstChar extends IdentifierFirstChar
? IsValidRestChars<Rest>
: false
: false
type IsValidTags<S> = S extends `${infer Head}&${infer Rest}`
? IsValidTagName<Head> extends true
? IsValidTags<Rest>
: false
: IsValidTagName<S>
type IsValidTagName<S> = S extends '' | '*'
? true
: S extends `${infer H}${infer Rest}`
? H extends IdentifierFirstChar
? IsValidRestChars<Rest>
: false
: string extends S
? true
: false
type IsValidRestChars<S extends string> = S extends `${infer H}${infer Rest}`
? H extends IdentifierChar
? IsValidRestChars<Rest>
: false
: true // no characters left, so it's OK
export type StrictlyParseSelector<
S extends string,
Fallback extends Element = Element,
> = string extends S
? Fallback
: ParseSelectorToTagNames<S> extends infer Tags
? IsValidTags<Tags> extends true
? Tags extends string
? ExpandAnd<Tags, Fallback>
: never
: never
: never