@@ -241,6 +241,8 @@ class _Tokenizer {
241241 }
242242 } else if ( this . _attemptCharCode ( chars . $SLASH ) ) {
243243 this . _consumeTagClose ( start ) ;
244+ } else if ( this . _attemptCharCode ( chars . $QUESTION ) ) {
245+ this . _consumeProcessingInstruction ( start ) ;
244246 } else {
245247 this . _consumeTagOpen ( start ) ;
246248 }
@@ -470,30 +472,7 @@ class _Tokenizer {
470472 private _consumeLetDeclarationValue ( ) : void {
471473 const start = this . _cursor . clone ( ) ;
472474 this . _beginToken ( TokenType . LET_VALUE , start ) ;
473-
474- while ( this . _cursor . peek ( ) !== chars . $EOF ) {
475- const char = this . _cursor . peek ( ) ;
476-
477- // `@let` declarations terminate with a semicolon.
478- if ( char === chars . $SEMICOLON ) {
479- break ;
480- }
481-
482- // If we hit a quote, skip over its content since we don't care what's inside.
483- if ( chars . isQuote ( char ) ) {
484- this . _cursor . advance ( ) ;
485- this . _attemptCharCodeUntilFn ( ( inner ) => {
486- if ( inner === chars . $BACKSLASH ) {
487- this . _cursor . advance ( ) ;
488- return false ;
489- }
490- return inner === char ;
491- } ) ;
492- }
493-
494- this . _cursor . advance ( ) ;
495- }
496-
475+ this . _attemptUntilIgnoreQuotes ( ( char ) => char === chars . $SEMICOLON ) ; // `@let` declarations terminate with a semicolon.
497476 this . _endToken ( [ this . _cursor . getChars ( start ) ] ) ;
498477 }
499478
@@ -665,6 +644,29 @@ class _Tokenizer {
665644 }
666645 }
667646
647+ private _attemptUntilIgnoreQuotes ( predicate : ( code : number ) => boolean ) {
648+ while ( this . _cursor . peek ( ) !== chars . $EOF ) {
649+ const char = this . _cursor . peek ( ) ;
650+
651+ if ( predicate ( char ) ) {
652+ break ;
653+ }
654+
655+ if ( chars . isQuote ( char ) ) {
656+ this . _cursor . advance ( ) ;
657+ this . _attemptCharCodeUntilFn ( ( inner ) => {
658+ if ( inner === chars . $BACKSLASH ) {
659+ this . _cursor . advance ( ) ;
660+ return false ;
661+ }
662+ return inner === char ;
663+ } ) ;
664+ }
665+
666+ this . _cursor . advance ( ) ;
667+ }
668+ }
669+
668670 private _readChar ( ) : string {
669671 // Don't rely upon reading directly from `_input` as the actual char value
670672 // may have been generated from an escape sequence.
@@ -800,6 +802,21 @@ class _Tokenizer {
800802 this . _endToken ( [ content ] ) ;
801803 }
802804
805+ private _consumeProcessingInstruction ( start : CharacterCursor ) {
806+ this . _beginToken ( TokenType . PROCESSING_INSTRUCTION , start ) ;
807+ const contentStart = this . _cursor . clone ( ) ;
808+ this . _attemptUntilIgnoreQuotes ( ( char ) => char === chars . $QUESTION || char === chars . $GT ) ;
809+ const endChar = this . _cursor . peek ( ) ;
810+ const content = this . _cursor . getChars ( contentStart ) ;
811+ this . _cursor . advance ( ) ;
812+
813+ if ( endChar === chars . $QUESTION ) {
814+ this . _requireCharCode ( chars . $GT ) ;
815+ }
816+
817+ this . _endToken ( [ content ] ) ;
818+ }
819+
803820 private _consumePrefixAndName ( endPredicate : ( code : number ) => boolean ) : string [ ] {
804821 const nameOrPrefixStart = this . _cursor . clone ( ) ;
805822 let prefix : string = '' ;
@@ -1420,13 +1437,14 @@ class _Tokenizer {
14201437 // We assume that `<` followed by whitespace is not the start of an HTML element.
14211438 const tmp = this . _cursor . clone ( ) ;
14221439 tmp . advance ( ) ;
1423- // If the next character is alphabetic, ! nor / then it is a tag start
1440+ // If the next character is alphabetic, !, ? nor / then it is a tag start
14241441 const code = tmp . peek ( ) ;
14251442 if (
14261443 ( chars . $a <= code && code <= chars . $z ) ||
14271444 ( chars . $A <= code && code <= chars . $Z ) ||
14281445 code === chars . $SLASH ||
1429- code === chars . $BANG
1446+ code === chars . $BANG ||
1447+ code === chars . $QUESTION
14301448 ) {
14311449 return true ;
14321450 }
0 commit comments