@@ -599,17 +599,26 @@ export default class Tokenizer {
599
599
600
600
// Read a string value, interpreting backslash-escapes.
601
601
602
- readCodePoint ( ) {
602
+ readCodePoint ( throwOnInvalid ) {
603
603
const ch = this . input . charCodeAt ( this . state . pos ) ;
604
604
let code ;
605
605
606
- if ( ch === 123 ) {
606
+ if ( ch === 123 ) { // '{'
607
607
const codePos = ++ this . state . pos ;
608
- code = this . readHexChar ( this . input . indexOf ( "}" , this . state . pos ) - this . state . pos ) ;
608
+ code = this . readHexChar ( this . input . indexOf ( "}" , this . state . pos ) - this . state . pos , throwOnInvalid ) ;
609
609
++ this . state . pos ;
610
- if ( code > 0x10FFFF ) this . raise ( codePos , "Code point out of bounds" ) ;
610
+ if ( code === null ) {
611
+ -- this . state . invalidTemplateEscapePosition ; // to point to the '\'' instead of the 'u'
612
+ } else if ( code > 0x10FFFF ) {
613
+ if ( throwOnInvalid ) {
614
+ this . raise ( codePos , "Code point out of bounds" ) ;
615
+ } else {
616
+ this . state . invalidTemplateEscapePosition = codePos - 2 ;
617
+ return null ;
618
+ }
619
+ }
611
620
} else {
612
- code = this . readHexChar ( 4 ) ;
621
+ code = this . readHexChar ( 4 , throwOnInvalid ) ;
613
622
}
614
623
return code ;
615
624
}
@@ -636,7 +645,7 @@ export default class Tokenizer {
636
645
// Reads template string tokens.
637
646
638
647
readTmplToken ( ) {
639
- let out = "" , chunkStart = this . state . pos ;
648
+ let out = "" , chunkStart = this . state . pos , containsInvalid = false ;
640
649
for ( ; ; ) {
641
650
if ( this . state . pos >= this . input . length ) this . raise ( this . state . start , "Unterminated template" ) ;
642
651
const ch = this . input . charCodeAt ( this . state . pos ) ;
@@ -651,11 +660,16 @@ export default class Tokenizer {
651
660
}
652
661
}
653
662
out += this . input . slice ( chunkStart , this . state . pos ) ;
654
- return this . finishToken ( tt . template , out ) ;
663
+ return this . finishToken ( tt . template , containsInvalid ? null : out ) ;
655
664
}
656
665
if ( ch === 92 ) { // '\'
657
666
out += this . input . slice ( chunkStart , this . state . pos ) ;
658
- out += this . readEscapedChar ( true ) ;
667
+ const escaped = this . readEscapedChar ( true ) ;
668
+ if ( escaped === null ) {
669
+ containsInvalid = true ;
670
+ } else {
671
+ out += escaped ;
672
+ }
659
673
chunkStart = this . state . pos ;
660
674
} else if ( isNewLine ( ch ) ) {
661
675
out += this . input . slice ( chunkStart , this . state . pos ) ;
@@ -682,13 +696,20 @@ export default class Tokenizer {
682
696
// Used to read escaped characters
683
697
684
698
readEscapedChar ( inTemplate ) {
699
+ const throwOnInvalid = ! inTemplate ;
685
700
const ch = this . input . charCodeAt ( ++ this . state . pos ) ;
686
701
++ this . state . pos ;
687
702
switch ( ch ) {
688
703
case 110 : return "\n" ; // 'n' -> '\n'
689
704
case 114 : return "\r" ; // 'r' -> '\r'
690
- case 120 : return String . fromCharCode ( this . readHexChar ( 2 ) ) ; // 'x'
691
- case 117 : return codePointToString ( this . readCodePoint ( ) ) ; // 'u'
705
+ case 120 : { // 'x'
706
+ const code = this . readHexChar ( 2 , throwOnInvalid ) ;
707
+ return code === null ? null : String . fromCharCode ( code ) ;
708
+ }
709
+ case 117 : { // 'u'
710
+ const code = this . readCodePoint ( throwOnInvalid ) ;
711
+ return code === null ? null : codePointToString ( code ) ;
712
+ }
692
713
case 116 : return "\t" ; // 't' -> '\t'
693
714
case 98 : return "\b" ; // 'b' -> '\b'
694
715
case 118 : return "\u000b" ; // 'v' -> '\u000b'
@@ -700,19 +721,24 @@ export default class Tokenizer {
700
721
return "" ;
701
722
default :
702
723
if ( ch >= 48 && ch <= 55 ) {
724
+ const codePos = this . state . pos - 1 ;
703
725
let octalStr = this . input . substr ( this . state . pos - 1 , 3 ) . match ( / ^ [ 0 - 7 ] + / ) [ 0 ] ;
704
726
let octal = parseInt ( octalStr , 8 ) ;
705
727
if ( octal > 255 ) {
706
728
octalStr = octalStr . slice ( 0 , - 1 ) ;
707
729
octal = parseInt ( octalStr , 8 ) ;
708
730
}
709
731
if ( octal > 0 ) {
710
- if ( ! this . state . containsOctal ) {
732
+ if ( inTemplate ) {
733
+ this . state . invalidTemplateEscapePosition = codePos ;
734
+ return null ;
735
+ } else if ( this . state . strict ) {
736
+ this . raise ( codePos , "Octal literal in strict mode" ) ;
737
+ } else if ( ! this . state . containsOctal ) {
738
+ // These properties are only used to throw an error for an octal which occurs
739
+ // in a directive which occurs prior to a "use strict" directive.
711
740
this . state . containsOctal = true ;
712
- this . state . octalPosition = this . state . pos - 2 ;
713
- }
714
- if ( this . state . strict || inTemplate ) {
715
- this . raise ( this . state . pos - 2 , "Octal literal in strict mode" ) ;
741
+ this . state . octalPosition = codePos ;
716
742
}
717
743
}
718
744
this . state . pos += octalStr . length - 1 ;
@@ -722,12 +748,19 @@ export default class Tokenizer {
722
748
}
723
749
}
724
750
725
- // Used to read character escape sequences ('\x', '\u', '\U' ).
751
+ // Used to read character escape sequences ('\x', '\u').
726
752
727
- readHexChar ( len ) {
753
+ readHexChar ( len , throwOnInvalid ) {
728
754
const codePos = this . state . pos ;
729
755
const n = this . readInt ( 16 , len ) ;
730
- if ( n === null ) this . raise ( codePos , "Bad character escape sequence" ) ;
756
+ if ( n === null ) {
757
+ if ( throwOnInvalid ) {
758
+ this . raise ( codePos , "Bad character escape sequence" ) ;
759
+ } else {
760
+ this . state . pos = codePos - 1 ;
761
+ this . state . invalidTemplateEscapePosition = codePos - 1 ;
762
+ }
763
+ }
731
764
return n ;
732
765
}
733
766
@@ -755,7 +788,7 @@ export default class Tokenizer {
755
788
}
756
789
757
790
++ this . state . pos ;
758
- const esc = this . readCodePoint ( ) ;
791
+ const esc = this . readCodePoint ( true ) ;
759
792
if ( ! ( first ? isIdentifierStart : isIdentifierChar ) ( esc , true ) ) {
760
793
this . raise ( escStart , "Invalid Unicode escape" ) ;
761
794
}
0 commit comments