@@ -18,7 +18,7 @@ const enum CharCode {
1818 LeftSquareBracket = 91 ,
1919 RightSquareBracket = 93 ,
2020 Comma = 44 ,
21- Dot = 46 ,
21+ Period = 46 ,
2222 Colon = 58 ,
2323 SingleQuote = 39 ,
2424 DoubleQuote = 34 ,
@@ -57,18 +57,6 @@ const actionTypes = new Map<number, AttributeAction>([
5757 [ CharCode . Pipe , AttributeAction . Hyphen ] ,
5858] ) ;
5959
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-
7260// Pseudos, whose data property is parsed as well.
7361const unpackPseudos = new Set ( [
7462 "has" ,
@@ -217,7 +205,6 @@ function parseSelector(
217205 selectorIndex : number
218206) : number {
219207 let tokens : Selector [ ] = [ ] ;
220- let sawWS = false ;
221208
222209 function getName ( offset : number ) : string {
223210 const match = selector . slice ( selectorIndex + offset ) . match ( reName ) ;
@@ -234,9 +221,14 @@ function parseSelector(
234221 }
235222
236223 function stripWhitespace ( offset : number ) {
237- while ( isWhitespace ( selector . charCodeAt ( selectorIndex + offset ) ) )
238- offset ++ ;
239224 selectorIndex += offset ;
225+
226+ while (
227+ selectorIndex < selector . length &&
228+ isWhitespace ( selector . charCodeAt ( selectorIndex ) )
229+ ) {
230+ selectorIndex ++ ;
231+ }
240232 }
241233
242234 function isEscaped ( pos : number ) : boolean {
@@ -252,56 +244,112 @@ function parseSelector(
252244 }
253245 }
254246
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+
255295 stripWhitespace ( 0 ) ;
256296
257- for ( ; ; ) {
297+ if ( selector . length === selectorIndex ) {
298+ return selectorIndex ;
299+ }
300+
301+ loop: while ( selectorIndex < selector . length ) {
258302 const firstChar = selector . charCodeAt ( selectorIndex ) ;
259303
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 ;
272321 }
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 ;
282327 }
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 ;
290332 }
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 : {
305353 stripWhitespace ( 1 ) ;
306354
307355 // Determine attribute name and namespace
@@ -445,7 +493,9 @@ function parseSelector(
445493 } ;
446494
447495 tokens . push ( attributeSelector ) ;
448- } else if ( firstChar === CharCode . Colon ) {
496+ break ;
497+ }
498+ case CharCode . Colon : {
449499 if ( selector . charCodeAt ( selectorIndex + 1 ) === CharCode . Colon ) {
450500 tokens . push ( {
451501 type : SelectorType . PseudoElement ,
@@ -533,35 +583,38 @@ function parseSelector(
533583 }
534584
535585 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+
537606 let namespace = null ;
538607 let name : string ;
539608
540609 if ( firstChar === CharCode . Asterisk ) {
541610 selectorIndex += 1 ;
542611 name = "*" ;
612+ } else if ( firstChar === CharCode . Pipe ) {
613+ name = "" ;
543614 } else if ( reName . test ( selector . slice ( selectorIndex ) ) ) {
544- if ( selector . charCodeAt ( selectorIndex ) === CharCode . Pipe ) {
545- namespace = "" ;
546- selectorIndex += 1 ;
547- }
548615 name = getName ( 0 ) ;
549616 } 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;
565618 }
566619
567620 if ( selector . charCodeAt ( selectorIndex ) === CharCode . Pipe ) {
@@ -589,12 +642,7 @@ function parseSelector(
589642 }
590643 }
591644 }
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- }
598645
599- subselects . push ( tokens ) ;
646+ finalizeSubselector ( ) ;
647+ return selectorIndex ;
600648}
0 commit comments