-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Correct Regular Expressions Behavior Related to Annex B #58320
Changes from 7 commits
e049438
358eb30
8facb0a
f5c0b60
cff993f
603c3cf
2e62d25
ed08ef7
8b67d77
b48f0d0
c72f92f
70a3214
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1556,9 +1556,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
tokenFlags |= TokenFlags.ContainsInvalidEscape; | ||||||||||||||||||
if (isRegularExpression || shouldEmitInvalidEscapeError) { | ||||||||||||||||||
const code = parseInt(text.substring(start + 1, pos), 8); | ||||||||||||||||||
if (isRegularExpression !== "annex-b") { | ||||||||||||||||||
error(Diagnostics.Octal_escape_sequences_are_not_allowed_Use_the_syntax_0, start, pos - start, "\\x" + code.toString(16).padStart(2, "0")); | ||||||||||||||||||
} | ||||||||||||||||||
error(Diagnostics.Octal_escape_sequences_are_not_allowed_Use_the_syntax_0, start, pos - start, "\\x" + code.toString(16).padStart(2, "0")); | ||||||||||||||||||
return String.fromCharCode(code); | ||||||||||||||||||
} | ||||||||||||||||||
return text.substring(start, pos); | ||||||||||||||||||
|
@@ -2426,6 +2424,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
// Quickly get to the end of regex such that we know the flags | ||||||||||||||||||
let p = tokenStart + 1; | ||||||||||||||||||
let inEscape = false; | ||||||||||||||||||
let namedCaptureGroups = false; | ||||||||||||||||||
// Although nested character classes are allowed in Unicode Sets mode, | ||||||||||||||||||
// an unescaped slash is nevertheless invalid even in a character class in Unicode mode. | ||||||||||||||||||
// Additionally, parsing nested character classes will misinterpret regexes like `/[[]/` | ||||||||||||||||||
|
@@ -2469,6 +2468,15 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
else if (ch === CharacterCodes.closeBracket) { | ||||||||||||||||||
inCharacterClass = false; | ||||||||||||||||||
} | ||||||||||||||||||
else if ( | ||||||||||||||||||
ch === CharacterCodes.openParen | ||||||||||||||||||
&& charCodeUnchecked(p + 1) === CharacterCodes.question | ||||||||||||||||||
&& charCodeUnchecked(p + 2) === CharacterCodes.lessThan | ||||||||||||||||||
&& charCodeUnchecked(p + 3) !== CharacterCodes.equals | ||||||||||||||||||
&& charCodeUnchecked(p + 3) !== CharacterCodes.exclamation | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed this in my last review. These need to be range checked:
Suggested change
|
||||||||||||||||||
) { | ||||||||||||||||||
namedCaptureGroups = true; | ||||||||||||||||||
} | ||||||||||||||||||
p++; | ||||||||||||||||||
} | ||||||||||||||||||
const isUnterminated = !!(tokenFlags & TokenFlags.Unterminated); | ||||||||||||||||||
|
@@ -2505,7 +2513,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
const saveEnd = end; | ||||||||||||||||||
pos = tokenStart + 1; | ||||||||||||||||||
end = endOfBody; | ||||||||||||||||||
scanRegularExpressionWorker(regExpFlags, isUnterminated, /*annexB*/ true); | ||||||||||||||||||
scanRegularExpressionWorker(regExpFlags, isUnterminated, /*annexB*/ true, namedCaptureGroups); | ||||||||||||||||||
tokenStart = saveTokenStart; | ||||||||||||||||||
tokenFlags = saveTokenFlags; | ||||||||||||||||||
pos = savePos; | ||||||||||||||||||
|
@@ -2517,7 +2525,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
return token; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
function scanRegularExpressionWorker(regExpFlags: RegularExpressionFlags, isUnterminated: boolean, annexB: boolean) { | ||||||||||||||||||
function scanRegularExpressionWorker(regExpFlags: RegularExpressionFlags, isUnterminated: boolean, annexB: boolean, namedCaptureGroups: boolean) { | ||||||||||||||||||
// Why var? It avoids TDZ checks in the runtime which can be costly. | ||||||||||||||||||
// See: https://github.com/microsoft/TypeScript/issues/52924 | ||||||||||||||||||
/* eslint-disable no-var */ | ||||||||||||||||||
|
@@ -2527,10 +2535,8 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
/** Grammar parameter */ | ||||||||||||||||||
var unicodeMode = !!(regExpFlags & RegularExpressionFlags.UnicodeMode); | ||||||||||||||||||
|
||||||||||||||||||
if (unicodeMode) { | ||||||||||||||||||
// Annex B treats any unicode mode as the strict syntax. | ||||||||||||||||||
annexB = false; | ||||||||||||||||||
} | ||||||||||||||||||
// Annex B treats any unicode mode as the strict syntax. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment explained why we set
Suggested change
|
||||||||||||||||||
var anyUnicodeModeOrNonAnnexB = unicodeMode || !annexB; | ||||||||||||||||||
|
||||||||||||||||||
/** @see {scanClassSetExpression} */ | ||||||||||||||||||
var mayContainStrings = false; | ||||||||||||||||||
|
@@ -2626,7 +2632,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
case CharacterCodes.exclamation: | ||||||||||||||||||
pos++; | ||||||||||||||||||
// In Annex B, `(?=Disjunction)` and `(?!Disjunction)` are quantifiable | ||||||||||||||||||
isPreviousTermQuantifiable = annexB; | ||||||||||||||||||
isPreviousTermQuantifiable = !anyUnicodeModeOrNonAnnexB; | ||||||||||||||||||
break; | ||||||||||||||||||
case CharacterCodes.lessThan: | ||||||||||||||||||
const groupNameStart = pos; | ||||||||||||||||||
|
@@ -2675,6 +2681,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
const digitsStart = pos; | ||||||||||||||||||
scanDigits(); | ||||||||||||||||||
const min = tokenValue; | ||||||||||||||||||
if (!anyUnicodeModeOrNonAnnexB && !min) { | ||||||||||||||||||
isPreviousTermQuantifiable = true; | ||||||||||||||||||
break; | ||||||||||||||||||
} | ||||||||||||||||||
if (charCodeChecked(pos) === CharacterCodes.comma) { | ||||||||||||||||||
pos++; | ||||||||||||||||||
scanDigits(); | ||||||||||||||||||
|
@@ -2684,26 +2694,32 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
error(Diagnostics.Incomplete_quantifier_Digit_expected, digitsStart, 0); | ||||||||||||||||||
} | ||||||||||||||||||
else { | ||||||||||||||||||
if (unicodeMode) { | ||||||||||||||||||
error(Diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, start, 1, String.fromCharCode(ch)); | ||||||||||||||||||
} | ||||||||||||||||||
error(Diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, start, 1, String.fromCharCode(ch)); | ||||||||||||||||||
isPreviousTermQuantifiable = true; | ||||||||||||||||||
break; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
if (max && Number.parseInt(min) > Number.parseInt(max)) { | ||||||||||||||||||
else if (max && Number.parseInt(min) > Number.parseInt(max) && (anyUnicodeModeOrNonAnnexB || text.charCodeAt(pos) === CharacterCodes.closeBrace)) { | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
error(Diagnostics.Numbers_out_of_order_in_quantifier, digitsStart, pos - digitsStart); | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
else if (!min) { | ||||||||||||||||||
if (unicodeMode) { | ||||||||||||||||||
if (anyUnicodeModeOrNonAnnexB) { | ||||||||||||||||||
error(Diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, start, 1, String.fromCharCode(ch)); | ||||||||||||||||||
} | ||||||||||||||||||
isPreviousTermQuantifiable = true; | ||||||||||||||||||
break; | ||||||||||||||||||
} | ||||||||||||||||||
scanExpectedChar(CharacterCodes.closeBrace); | ||||||||||||||||||
pos--; | ||||||||||||||||||
if (charCodeChecked(pos) !== CharacterCodes.closeBrace) { | ||||||||||||||||||
if (anyUnicodeModeOrNonAnnexB) { | ||||||||||||||||||
error(Diagnostics._0_expected, pos, 0, String.fromCharCode(CharacterCodes.closeBrace)); | ||||||||||||||||||
pos--; | ||||||||||||||||||
} | ||||||||||||||||||
else { | ||||||||||||||||||
isPreviousTermQuantifiable = true; | ||||||||||||||||||
break; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
// falls through | ||||||||||||||||||
case CharacterCodes.asterisk: | ||||||||||||||||||
case CharacterCodes.plus: | ||||||||||||||||||
|
@@ -2744,7 +2760,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
// Assume what starting from the character to be outside of the regex | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
if (unicodeMode || ch === CharacterCodes.closeParen) { | ||||||||||||||||||
if (anyUnicodeModeOrNonAnnexB || ch === CharacterCodes.closeParen) { | ||||||||||||||||||
error(Diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, pos, 1, String.fromCharCode(ch)); | ||||||||||||||||||
} | ||||||||||||||||||
pos++; | ||||||||||||||||||
|
@@ -2801,7 +2817,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
scanGroupName(/*isReference*/ true); | ||||||||||||||||||
scanExpectedChar(CharacterCodes.greaterThan); | ||||||||||||||||||
} | ||||||||||||||||||
else if (unicodeMode) { | ||||||||||||||||||
else if (namedCaptureGroups) { | ||||||||||||||||||
error(Diagnostics.k_must_be_followed_by_a_capturing_group_name_enclosed_in_angle_brackets, pos - 2, 2); | ||||||||||||||||||
} | ||||||||||||||||||
break; | ||||||||||||||||||
|
@@ -2851,10 +2867,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
pos++; | ||||||||||||||||||
return String.fromCharCode(ch & 0x1f); | ||||||||||||||||||
} | ||||||||||||||||||
if (unicodeMode) { | ||||||||||||||||||
if (anyUnicodeModeOrNonAnnexB) { | ||||||||||||||||||
error(Diagnostics.c_must_be_followed_by_an_ASCII_letter, pos - 2, 2); | ||||||||||||||||||
} | ||||||||||||||||||
else if (atomEscape && annexB) { | ||||||||||||||||||
else if (atomEscape) { | ||||||||||||||||||
// Annex B treats | ||||||||||||||||||
// | ||||||||||||||||||
// ExtendedAtom : `\` [lookahead = `c`] | ||||||||||||||||||
|
@@ -2887,7 +2903,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
return "\\"; | ||||||||||||||||||
} | ||||||||||||||||||
pos--; | ||||||||||||||||||
return scanEscapeSequence(/*shouldEmitInvalidEscapeError*/ unicodeMode, /*isRegularExpression*/ annexB ? "annex-b" : true); | ||||||||||||||||||
return scanEscapeSequence(/*shouldEmitInvalidEscapeError*/ unicodeMode, /*isRegularExpression*/ anyUnicodeModeOrNonAnnexB || "annex-b"); | ||||||||||||||||||
rbuckton marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -2936,12 +2952,12 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
if (isClassContentExit(ch)) { | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
if (!minCharacter && !annexB) { | ||||||||||||||||||
if (!minCharacter && anyUnicodeModeOrNonAnnexB) { | ||||||||||||||||||
error(Diagnostics.A_character_class_range_must_not_be_bounded_by_another_character_class, minStart, pos - 1 - minStart); | ||||||||||||||||||
} | ||||||||||||||||||
const maxStart = pos; | ||||||||||||||||||
const maxCharacter = scanClassAtom(); | ||||||||||||||||||
if (!maxCharacter && !annexB) { | ||||||||||||||||||
if (!maxCharacter && anyUnicodeModeOrNonAnnexB) { | ||||||||||||||||||
error(Diagnostics.A_character_class_range_must_not_be_bounded_by_another_character_class, maxStart, pos - maxStart); | ||||||||||||||||||
continue; | ||||||||||||||||||
} | ||||||||||||||||||
|
@@ -3437,9 +3453,13 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
error(Diagnostics.Unicode_property_value_expressions_are_only_available_when_the_Unicode_u_flag_or_the_Unicode_Sets_v_flag_is_set, start, pos - start); | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
else if (unicodeMode) { | ||||||||||||||||||
else if (anyUnicodeModeOrNonAnnexB) { | ||||||||||||||||||
error(Diagnostics._0_must_be_followed_by_a_Unicode_property_value_expression_enclosed_in_braces, pos - 2, 2, String.fromCharCode(ch)); | ||||||||||||||||||
} | ||||||||||||||||||
else { | ||||||||||||||||||
pos--; | ||||||||||||||||||
return false; | ||||||||||||||||||
} | ||||||||||||||||||
return true; | ||||||||||||||||||
} | ||||||||||||||||||
return false; | ||||||||||||||||||
|
@@ -3483,7 +3503,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean | |||||||||||||||||
forEach(decimalEscapes, escape => { | ||||||||||||||||||
// in AnnexB, if a DecimalEscape is greater than the number of capturing groups then it is treated as | ||||||||||||||||||
// either a LegacyOctalEscapeSequence or IdentityEscape | ||||||||||||||||||
if (!annexB && escape.value > numberOfCapturingGroups) { | ||||||||||||||||||
if (anyUnicodeModeOrNonAnnexB && escape.value > numberOfCapturingGroups) { | ||||||||||||||||||
if (numberOfCapturingGroups) { | ||||||||||||||||||
error(Diagnostics.This_backreference_refers_to_a_group_that_does_not_exist_There_are_only_0_capturing_groups_in_this_regular_expression, escape.pos, escape.end - escape.pos, numberOfCapturingGroups); | ||||||||||||||||||
} | ||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this was the only place we check for
"annex-b"
, we can makeisRegularExpression
aboolean
again.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the same too, but there is a
isRegularExpression === true
at the bottomThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably is worth being refactored, since
shouldEmitInvalidEscapeError
does not explain itself but is taken over to meananyUnicodeMode
for regular expressions. But this will be yet another PR I guess.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you use an enum for such a case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I found that I once created a enum named
ClassSetExpressionType
.