@@ -18,7 +18,7 @@ const enum CharCode {
18
18
LeftSquareBracket = 91 ,
19
19
RightSquareBracket = 93 ,
20
20
Comma = 44 ,
21
- Dot = 46 ,
21
+ Period = 46 ,
22
22
Colon = 58 ,
23
23
SingleQuote = 39 ,
24
24
DoubleQuote = 34 ,
@@ -57,18 +57,6 @@ const actionTypes = new Map<number, AttributeAction>([
57
57
[ CharCode . Pipe , AttributeAction . Hyphen ] ,
58
58
] ) ;
59
59
60
- const Traversals : Map < number , TraversalType > = new Map ( [
61
- [ CharCode . GreaterThan , SelectorType . Child ] ,
62
- [ CharCode . LessThan , SelectorType . Parent ] ,
63
- [ CharCode . Tilde , SelectorType . Sibling ] ,
64
- [ CharCode . Plus , SelectorType . Adjacent ] ,
65
- ] ) ;
66
-
67
- const attribSelectors : Map < number , [ string , AttributeAction ] > = new Map ( [
68
- [ CharCode . Hash , [ "id" , AttributeAction . Equals ] ] ,
69
- [ CharCode . Dot , [ "class" , AttributeAction . Element ] ] ,
70
- ] ) ;
71
-
72
60
// Pseudos, whose data property is parsed as well.
73
61
const unpackPseudos = new Set ( [
74
62
"has" ,
@@ -217,7 +205,6 @@ function parseSelector(
217
205
selectorIndex : number
218
206
) : number {
219
207
let tokens : Selector [ ] = [ ] ;
220
- let sawWS = false ;
221
208
222
209
function getName ( offset : number ) : string {
223
210
const match = selector . slice ( selectorIndex + offset ) . match ( reName ) ;
@@ -234,9 +221,14 @@ function parseSelector(
234
221
}
235
222
236
223
function stripWhitespace ( offset : number ) {
237
- while ( isWhitespace ( selector . charCodeAt ( selectorIndex + offset ) ) )
238
- offset ++ ;
239
224
selectorIndex += offset ;
225
+
226
+ while (
227
+ selectorIndex < selector . length &&
228
+ isWhitespace ( selector . charCodeAt ( selectorIndex ) )
229
+ ) {
230
+ selectorIndex ++ ;
231
+ }
240
232
}
241
233
242
234
function isEscaped ( pos : number ) : boolean {
@@ -252,56 +244,112 @@ function parseSelector(
252
244
}
253
245
}
254
246
247
+ function addTraversal ( type : TraversalType ) {
248
+ if (
249
+ tokens . length > 0 &&
250
+ tokens [ tokens . length - 1 ] . type === SelectorType . Descendant
251
+ ) {
252
+ tokens [ tokens . length - 1 ] . type = type ;
253
+ return ;
254
+ }
255
+
256
+ ensureNotTraversal ( ) ;
257
+
258
+ tokens . push ( { type } ) ;
259
+ }
260
+
261
+ function addSpecialAttribute ( name : string , action : AttributeAction ) {
262
+ tokens . push ( {
263
+ type : SelectorType . Attribute ,
264
+ name,
265
+ action,
266
+ value : getName ( 1 ) ,
267
+ namespace : null ,
268
+ // TODO: Add quirksMode option, which makes `ignoreCase` `true` for HTML.
269
+ ignoreCase : options . xmlMode ? null : false ,
270
+ } ) ;
271
+ }
272
+
273
+ /**
274
+ * We have finished parsing the current part of the selector.
275
+ *
276
+ * Remove descendant tokens at the end if they exist,
277
+ * and return the last index, so that parsing can be
278
+ * picked up from here.
279
+ */
280
+ function finalizeSubselector ( ) {
281
+ if (
282
+ tokens . length &&
283
+ tokens [ tokens . length - 1 ] . type === SelectorType . Descendant
284
+ ) {
285
+ tokens . pop ( ) ;
286
+ }
287
+
288
+ if ( tokens . length === 0 ) {
289
+ throw new Error ( "Empty sub-selector" ) ;
290
+ }
291
+
292
+ subselects . push ( tokens ) ;
293
+ }
294
+
255
295
stripWhitespace ( 0 ) ;
256
296
257
- for ( ; ; ) {
297
+ if ( selector . length === selectorIndex ) {
298
+ return selectorIndex ;
299
+ }
300
+
301
+ loop: while ( selectorIndex < selector . length ) {
258
302
const firstChar = selector . charCodeAt ( selectorIndex ) ;
259
303
260
- if ( isWhitespace ( firstChar ) ) {
261
- sawWS = true ;
262
- stripWhitespace ( 1 ) ;
263
- } else if ( Traversals . has ( firstChar ) ) {
264
- ensureNotTraversal ( ) ;
265
- tokens . push ( { type : Traversals . get ( firstChar ) ! } ) ;
266
- sawWS = false ;
267
-
268
- stripWhitespace ( 1 ) ;
269
- } else if ( firstChar === CharCode . Comma ) {
270
- if ( tokens . length === 0 ) {
271
- throw new Error ( "Empty sub-selector" ) ;
304
+ switch ( firstChar ) {
305
+ // Whitespace
306
+ case CharCode . Space :
307
+ case CharCode . Tab :
308
+ case CharCode . NewLine :
309
+ case CharCode . FormFeed :
310
+ case CharCode . CarriageReturn : {
311
+ if (
312
+ tokens . length === 0 ||
313
+ tokens [ 0 ] . type !== SelectorType . Descendant
314
+ ) {
315
+ ensureNotTraversal ( ) ;
316
+ tokens . push ( { type : SelectorType . Descendant } ) ;
317
+ }
318
+
319
+ stripWhitespace ( 1 ) ;
320
+ break ;
272
321
}
273
- subselects . push ( tokens ) ;
274
- tokens = [ ] ;
275
- sawWS = false ;
276
- stripWhitespace ( 1 ) ;
277
- } else if ( selector . startsWith ( "/*" , selectorIndex ) ) {
278
- const endIndex = selector . indexOf ( "*/" , selectorIndex + 2 ) ;
279
-
280
- if ( endIndex < 0 ) {
281
- throw new Error ( "Comment was not terminated" ) ;
322
+ // Traversals
323
+ case CharCode . GreaterThan : {
324
+ addTraversal ( SelectorType . Child ) ;
325
+ stripWhitespace ( 1 ) ;
326
+ break ;
282
327
}
283
-
284
- selectorIndex = endIndex + 2 ;
285
- } else {
286
- if ( sawWS ) {
287
- ensureNotTraversal ( ) ;
288
- tokens . push ( { type : SelectorType . Descendant } ) ;
289
- sawWS = false ;
328
+ case CharCode . LessThan : {
329
+ addTraversal ( SelectorType . Parent ) ;
330
+ stripWhitespace ( 1 ) ;
331
+ break ;
290
332
}
291
-
292
- const attribSelector = attribSelectors . get ( firstChar ) ;
293
- if ( attribSelector ) {
294
- const [ name , action ] = attribSelector ;
295
- tokens . push ( {
296
- type : SelectorType . Attribute ,
297
- name,
298
- action,
299
- value : getName ( 1 ) ,
300
- namespace : null ,
301
- // TODO: Add quirksMode option, which makes `ignoreCase` `true` for HTML.
302
- ignoreCase : options . xmlMode ? null : false ,
303
- } ) ;
304
- } else if ( firstChar === CharCode . LeftSquareBracket ) {
333
+ case CharCode . Tilde : {
334
+ addTraversal ( SelectorType . Sibling ) ;
335
+ stripWhitespace ( 1 ) ;
336
+ break ;
337
+ }
338
+ case CharCode . Plus : {
339
+ addTraversal ( SelectorType . Adjacent ) ;
340
+ stripWhitespace ( 1 ) ;
341
+ break ;
342
+ }
343
+ // Special attribute selectors: .class, #id
344
+ case CharCode . Period : {
345
+ addSpecialAttribute ( "class" , AttributeAction . Element ) ;
346
+ break ;
347
+ }
348
+ case CharCode . Hash : {
349
+ addSpecialAttribute ( "id" , AttributeAction . Equals ) ;
350
+ break ;
351
+ }
352
+ case CharCode . LeftSquareBracket : {
305
353
stripWhitespace ( 1 ) ;
306
354
307
355
// Determine attribute name and namespace
@@ -445,7 +493,9 @@ function parseSelector(
445
493
} ;
446
494
447
495
tokens . push ( attributeSelector ) ;
448
- } else if ( firstChar === CharCode . Colon ) {
496
+ break ;
497
+ }
498
+ case CharCode . Colon : {
449
499
if ( selector . charCodeAt ( selectorIndex + 1 ) === CharCode . Colon ) {
450
500
tokens . push ( {
451
501
type : SelectorType . PseudoElement ,
@@ -533,35 +583,38 @@ function parseSelector(
533
583
}
534
584
535
585
tokens . push ( { type : SelectorType . Pseudo , name, data } ) ;
536
- } else {
586
+ break ;
587
+ }
588
+ case CharCode . Comma : {
589
+ finalizeSubselector ( ) ;
590
+ tokens = [ ] ;
591
+ stripWhitespace ( 1 ) ;
592
+ break ;
593
+ }
594
+ default : {
595
+ if ( selector . startsWith ( "/*" , selectorIndex ) ) {
596
+ const endIndex = selector . indexOf ( "*/" , selectorIndex + 2 ) ;
597
+
598
+ if ( endIndex < 0 ) {
599
+ throw new Error ( "Comment was not terminated" ) ;
600
+ }
601
+
602
+ selectorIndex = endIndex + 2 ;
603
+ break ;
604
+ }
605
+
537
606
let namespace = null ;
538
607
let name : string ;
539
608
540
609
if ( firstChar === CharCode . Asterisk ) {
541
610
selectorIndex += 1 ;
542
611
name = "*" ;
612
+ } else if ( firstChar === CharCode . Pipe ) {
613
+ name = "" ;
543
614
} else if ( reName . test ( selector . slice ( selectorIndex ) ) ) {
544
- if ( selector . charCodeAt ( selectorIndex ) === CharCode . Pipe ) {
545
- namespace = "" ;
546
- selectorIndex += 1 ;
547
- }
548
615
name = getName ( 0 ) ;
549
616
} else {
550
- /*
551
- * We have finished parsing the selector.
552
- * Remove descendant tokens at the end if they exist,
553
- * and return the last index, so that parsing can be
554
- * picked up from here.
555
- */
556
- if (
557
- tokens . length &&
558
- tokens [ tokens . length - 1 ] . type ===
559
- SelectorType . Descendant
560
- ) {
561
- tokens . pop ( ) ;
562
- }
563
- addToken ( subselects , tokens ) ;
564
- return selectorIndex ;
617
+ break loop;
565
618
}
566
619
567
620
if ( selector . charCodeAt ( selectorIndex ) === CharCode . Pipe ) {
@@ -589,12 +642,7 @@ function parseSelector(
589
642
}
590
643
}
591
644
}
592
- }
593
-
594
- function addToken ( subselects : Selector [ ] [ ] , tokens : Selector [ ] ) {
595
- if ( subselects . length > 0 && tokens . length === 0 ) {
596
- throw new Error ( "Empty sub-selector" ) ;
597
- }
598
645
599
- subselects . push ( tokens ) ;
646
+ finalizeSubselector ( ) ;
647
+ return selectorIndex ;
600
648
}
0 commit comments