@@ -1094,6 +1094,116 @@ namespace ts {
10941094 }
10951095 }
10961096
1097+ function scanPotentiallyOctalNumberFragment ( ) : { fragment : string , isOctalCandidate : boolean } {
1098+ let start = pos ;
1099+ let allowSeparator = false ;
1100+ let isPreviousTokenSeparator = false ;
1101+ let shouldShowLeadingZeroError = true ;
1102+ let isOctalCandidate = pos + 1 < end && isOctalDigit ( text . charCodeAt ( pos + 1 ) ) ;
1103+ let result = "" ;
1104+ while ( true ) {
1105+ const ch = text . charCodeAt ( pos ) ;
1106+ if ( ch === CharacterCodes . _ ) {
1107+ tokenFlags |= TokenFlags . ContainsSeparator ;
1108+ isOctalCandidate = false ;
1109+ if ( allowSeparator ) {
1110+ allowSeparator = false ;
1111+ isPreviousTokenSeparator = true ;
1112+ result += text . substring ( start , pos ) ;
1113+ }
1114+ else if ( isPreviousTokenSeparator ) {
1115+ error ( Diagnostics . Multiple_consecutive_numeric_separators_are_not_permitted , pos , 1 ) ;
1116+ }
1117+ else {
1118+ error ( Diagnostics . Numeric_separators_are_not_allowed_here , pos , 1 ) ;
1119+ }
1120+ if ( shouldShowLeadingZeroError ) {
1121+ error ( Diagnostics . Numeric_separators_are_not_allowed_in_numbers_that_start_with_0 , pos , 1 ) ;
1122+ shouldShowLeadingZeroError = false ;
1123+ }
1124+ pos ++ ;
1125+ start = pos ;
1126+ continue ;
1127+ }
1128+ if ( isDigit ( ch ) ) {
1129+ if ( ! isOctalDigit ( ch ) ) {
1130+ isOctalCandidate = false ;
1131+ }
1132+ allowSeparator = true ;
1133+ isPreviousTokenSeparator = false ;
1134+ pos ++ ;
1135+ continue ;
1136+ }
1137+ break ;
1138+ }
1139+ if ( text . charCodeAt ( pos - 1 ) === CharacterCodes . _ ) {
1140+ error ( Diagnostics . Numeric_separators_are_not_allowed_here , pos - 1 , 1 ) ;
1141+ }
1142+ return { fragment : result + text . substring ( start , pos ) , isOctalCandidate } ;
1143+ }
1144+
1145+ function scanZeroLeadingNumber ( ) : { type : SyntaxKind , value : string } {
1146+ const start = pos ;
1147+ const { fragment : mainFragment , isOctalCandidate } = scanPotentiallyOctalNumberFragment ( ) ;
1148+ let decimalFragment : string | undefined ;
1149+ let scientificFragment : string | undefined ;
1150+ if ( text . charCodeAt ( pos ) === CharacterCodes . dot && ! ( isOctalCandidate && isIdentifierStart ( codePointAt ( text , pos + 1 ) , languageVersion ) ) ) {
1151+ pos ++ ;
1152+ decimalFragment = scanNumberFragment ( ) ;
1153+ }
1154+ let end = pos ;
1155+ if ( text . charCodeAt ( pos ) === CharacterCodes . E || text . charCodeAt ( pos ) === CharacterCodes . e ) {
1156+ pos ++ ;
1157+ tokenFlags |= TokenFlags . Scientific ;
1158+ if ( text . charCodeAt ( pos ) === CharacterCodes . plus || text . charCodeAt ( pos ) === CharacterCodes . minus ) pos ++ ;
1159+ const preNumericPart = pos ;
1160+ const finalFragment = scanNumberFragment ( ) ;
1161+ if ( ! finalFragment ) {
1162+ error ( Diagnostics . Digit_expected ) ;
1163+ }
1164+ else {
1165+ scientificFragment = text . substring ( end , preNumericPart ) + finalFragment ;
1166+ end = pos ;
1167+ }
1168+ }
1169+ let result : string ;
1170+ if ( tokenFlags & TokenFlags . ContainsSeparator ) {
1171+ result = mainFragment ;
1172+ if ( decimalFragment ) {
1173+ result += "." + decimalFragment ;
1174+ }
1175+ if ( scientificFragment ) {
1176+ result += scientificFragment ;
1177+ }
1178+ }
1179+ else {
1180+ result = text . substring ( start , end ) ; // No need to use all the fragments; no _ removal needed
1181+ }
1182+
1183+ if ( decimalFragment !== undefined || tokenFlags & TokenFlags . Scientific ) {
1184+ checkForIdentifierStartAfterNumericLiteral ( start , decimalFragment === undefined && ! ! ( tokenFlags & TokenFlags . Scientific ) ) ;
1185+ return {
1186+ type : SyntaxKind . NumericLiteral ,
1187+ value : "" + + result // if value is not an integer, it can be safely coerced to a number
1188+ } ;
1189+ }
1190+ else {
1191+ let type = SyntaxKind . NumericLiteral ;
1192+ tokenValue = "" + + result ;
1193+ if ( text . charCodeAt ( pos ) === CharacterCodes . n ) {
1194+ type = SyntaxKind . BigIntLiteral ;
1195+ tokenValue = parsePseudoBigInt ( tokenValue + "n" ) + "n" ;
1196+ pos ++ ;
1197+ error ( Diagnostics . A_bigint_literal_cannot_start_with_0 , start , pos - start ) ;
1198+ }
1199+ else if ( isOctalCandidate ) {
1200+ tokenFlags |= TokenFlags . Octal ;
1201+ }
1202+ checkForIdentifierStartAfterNumericLiteral ( start ) ;
1203+ return { type, value : tokenValue } ;
1204+ }
1205+ }
1206+
10971207 function checkForIdentifierStartAfterNumericLiteral ( numericStart : number , isScientific ?: boolean ) {
10981208 if ( ! isIdentifierStart ( codePointAt ( text , pos ) , languageVersion ) ) {
10991209 return ;
@@ -1116,14 +1226,6 @@ namespace ts {
11161226 }
11171227 }
11181228
1119- function scanOctalDigits ( ) : number {
1120- const start = pos ;
1121- while ( isOctalDigit ( text . charCodeAt ( pos ) ) ) {
1122- pos ++ ;
1123- }
1124- return + ( text . substring ( start , pos ) ) ;
1125- }
1126-
11271229 /**
11281230 * Scans the given number of hexadecimal digits in the text,
11291231 * returning -1 if the given number is unavailable.
@@ -1852,16 +1954,13 @@ namespace ts {
18521954 tokenFlags |= TokenFlags . OctalSpecifier ;
18531955 return token = checkBigIntSuffix ( ) ;
18541956 }
1855- // Try to parse as an octal
1856- if ( pos + 1 < end && isOctalDigit ( text . charCodeAt ( pos + 1 ) ) ) {
1857- tokenValue = "" + scanOctalDigits ( ) ;
1858- tokenFlags |= TokenFlags . Octal ;
1859- return token = SyntaxKind . NumericLiteral ;
1957+ else if ( pos + 1 < end && ( isDigit ( text . charCodeAt ( pos + 1 ) ) || text . charCodeAt ( pos + 1 ) === CharacterCodes . _ ) ) {
1958+ tokenFlags |= TokenFlags . StartsWithZero ;
1959+ ( { type : token , value : tokenValue } = scanZeroLeadingNumber ( ) ) ;
1960+ return token ;
18601961 }
1861- // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero
1862- // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being
1863- // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do).
1864- // falls through
1962+
1963+ // Fallthrough only for single digit number '0' or number starting with '0.' which don't need special checks
18651964 case CharacterCodes . _1 :
18661965 case CharacterCodes . _2 :
18671966 case CharacterCodes . _3 :
0 commit comments