diff --git a/internal/regexpchecker/regexpchecker.go b/internal/regexpchecker/regexpchecker.go index 244b5a1ebd..51e152dfd6 100644 --- a/internal/regexpchecker/regexpchecker.go +++ b/internal/regexpchecker/regexpchecker.go @@ -44,28 +44,37 @@ var charCodeToRegExpFlag = map[rune]regExpFlags{ // regExpValidator is used to validate regular expressions type regExpValidator struct { - text string - pos int - end int - languageVersion core.ScriptTarget - languageVariant core.LanguageVariant - onError scanner.ErrorCallback - regExpFlags regExpFlags - annexB bool - unicodeSetsMode bool - anyUnicodeMode bool - anyUnicodeModeOrNonAnnexB bool - namedCaptureGroups bool - numberOfCapturingGroups int - groupSpecifiers map[string]bool - groupNameReferences []namedReference - decimalEscapes []decimalEscape - namedCapturingGroupsScopeStack []map[string]bool - topNamedCapturingGroupsScope map[string]bool - mayContainStrings bool - isCharacterComplement bool - tokenValue string - surrogateState *surrogatePairState // For non-Unicode mode: tracks partial surrogate pair + text string + pos int + end int + languageVersion core.ScriptTarget + languageVariant core.LanguageVariant + onError scanner.ErrorCallback + regExpFlags regExpFlags + annexB bool + unicodeSetsMode bool + anyUnicodeMode bool + anyUnicodeModeOrNonAnnexB bool + hasNamedCapturingGroups bool + numberOfCapturingGroups int + groupSpecifiers map[string]bool + groupNameReferences []namedReference + decimalEscapes []decimalEscape + disjunctionsScopesStack []disjunctionsScope + topDisjunctionsScope disjunctionsScope + mayContainStrings bool + isCharacterComplement bool + tokenValue string + surrogateState *surrogatePairState // For non-Unicode mode: tracks partial surrogate pair +} + +type disjunction struct { + groupName string // Not a named capturing group if empty +} + +type disjunctionsScope struct { + disjunctions []disjunction + currentAlternativeIndex int } // surrogatePairState tracks when we're in the middle of emitting a surrogate pair @@ -119,33 +128,36 @@ func Check( v.anyUnicodeMode = v.regExpFlags®ExpFlagsAnyUnicodeMode != 0 v.annexB = true v.anyUnicodeModeOrNonAnnexB = v.anyUnicodeMode || !v.annexB - v.namedCaptureGroups = v.detectNamedCaptureGroups() + v.hasNamedCapturingGroups = v.detectNamedCapturingGroups() v.scanDisjunction(false) v.validateGroupReferences() v.validateDecimalEscapes() } -// detectNamedCaptureGroups performs a quick scan of the pattern to detect -// if it contains any named capture groups (?...). This is needed because +// detectNamedCapturingGroups performs a quick scan of the pattern to detect +// if it contains any named capturing groups (?...). This is needed because // the presence of named groups changes the interpretation of \k escapes: // - Without named groups: \k is an identity escape (matches literal 'k') // - With named groups: \k must be followed by or it's a syntax error // This matches the behavior in scanner.ts's reScanSlashToken. -func (v *regExpValidator) detectNamedCaptureGroups() bool { +func (v *regExpValidator) detectNamedCapturingGroups() bool { inEscape := false inCharacterClass := false text := v.text[v.pos:v.end] for i, ch := range text { + if inEscape { + inEscape = false + continue + } + // Only check ASCII characters for the pattern (?< - if ch >= 0x80 { + if ch >= utf8.RuneSelf { continue } - if inEscape { - inEscape = false - } else if ch == '\\' { + if ch == '\\' { inEscape = true } else if ch == '[' { inCharacterClass = true @@ -158,7 +170,7 @@ func (v *regExpValidator) detectNamedCaptureGroups() bool { text[i+2] == '<' && text[i+3] != '=' && text[i+3] != '!' { - // Found (?< that's not (?<= or (?') + // TODO: Move to LanguageFeatureMinimumTarget.RegularExpressionNamedCapturingGroups if v.languageVersion < core.ScriptTargetES2018 { v.error(diagnostics.Named_capturing_groups_are_only_available_when_targeting_ES2018_or_later, groupNameStart, v.pos-groupNameStart) } @@ -291,7 +305,15 @@ func (v *regExpValidator) scanAlternative(isInGroup bool) { v.numberOfCapturingGroups++ isPreviousTermQuantifiable = true } + disjunction := disjunction{groupName} + v.topDisjunctionsScope.disjunctions = append(v.topDisjunctionsScope.disjunctions, disjunction) + oldTopDisjunctionsScope := v.topDisjunctionsScope + oldDisjunctionsScopesStack := v.disjunctionsScopesStack + v.disjunctionsScopesStack = append(v.disjunctionsScopesStack, v.topDisjunctionsScope) v.scanDisjunction(true) + oldTopDisjunctionsScope.disjunctions = append(oldTopDisjunctionsScope.disjunctions, v.topDisjunctionsScope.disjunctions...) + v.topDisjunctionsScope = oldTopDisjunctionsScope + v.disjunctionsScopesStack = oldDisjunctionsScopesStack v.scanExpectedChar(')') case '{': v.pos++ @@ -314,16 +336,10 @@ func (v *regExpValidator) scanAlternative(isInGroup bool) { isPreviousTermQuantifiable = true break } - } else if maxVal != "" { - minInt := 0 - maxInt := 0 - for _, c := range minVal { - minInt = minInt*10 + int(c-'0') - } - for _, c := range maxVal { - maxInt = maxInt*10 + int(c-'0') - } - if minInt > maxInt && (v.anyUnicodeModeOrNonAnnexB || v.charAtOffset(0) == '}') { + } else if maxVal != "" && (v.anyUnicodeModeOrNonAnnexB || v.charAtOffset(0) == '}') { + minInt := parseDecimalValue(minVal, 0, len(minVal)) + maxInt := parseDecimalValue(maxVal, 0, len(maxVal)) + if minInt > maxInt { v.error(diagnostics.Numbers_out_of_order_in_quantifier, digitsStart, v.pos-digitsStart) } } @@ -347,6 +363,7 @@ func (v *regExpValidator) scanAlternative(isInGroup bool) { case '*', '+', '?': v.pos++ if v.charAtOffset(0) == '?' { + // Non-greedy v.pos++ } if !isPreviousTermQuantifiable { @@ -393,9 +410,7 @@ func (v *regExpValidator) validateGroupReferences() { if len(v.groupSpecifiers) > 0 { // Convert map keys to slice candidates := make([]string, 0, len(v.groupSpecifiers)) - for name := range v.groupSpecifiers { - candidates = append(candidates, name) - } + candidates = slices.AppendSeq(candidates, maps.Keys(v.groupSpecifiers)) suggestion := core.GetSpellingSuggestion(ref.name, candidates, core.Identity[string]) if suggestion != "" { v.error(diagnostics.Did_you_mean_0, ref.pos, ref.end-ref.pos, suggestion) @@ -446,7 +461,7 @@ func (v *regExpValidator) scanFlags(currFlags regExpFlags, checkModifiers bool) v.error(diagnostics.Unknown_regular_expression_flag, v.pos, size) } else if currFlags&flag != 0 { v.error(diagnostics.Duplicate_regular_expression_flag, v.pos, size) - } else if (currFlags|flag)®ExpFlagsAnyUnicodeMode == regExpFlagsAnyUnicodeMode { + } else if !checkModifiers && (currFlags|flag)®ExpFlagsAnyUnicodeMode == regExpFlagsAnyUnicodeMode { v.error(diagnostics.The_Unicode_u_flag_and_the_Unicode_Sets_v_flag_cannot_be_set_simultaneously, v.pos, size) } else if checkModifiers && flag®ExpFlagsModifiers == 0 { v.error(diagnostics.This_regular_expression_flag_cannot_be_toggled_within_a_subpattern, v.pos, size) @@ -471,7 +486,7 @@ func (v *regExpValidator) scanAtomEscape() { v.pos++ v.scanGroupName(true) v.scanExpectedChar('>') - } else if v.anyUnicodeModeOrNonAnnexB || v.namedCaptureGroups { + } else if v.anyUnicodeModeOrNonAnnexB || v.hasNamedCapturingGroups { v.error(diagnostics.X_k_must_be_followed_by_a_capturing_group_name_enclosed_in_angle_brackets, v.pos-2, 2) } case 'q': @@ -493,10 +508,7 @@ func (v *regExpValidator) scanDecimalEscape() bool { if ch >= '1' && ch <= '9' { start := v.pos v.scanDigits() - value := 0 - for _, c := range v.tokenValue { - value = value*10 + int(c-'0') - } + value := parseDecimalValue(v.tokenValue, 0, len(v.tokenValue)) v.decimalEscapes = append(v.decimalEscapes, decimalEscape{pos: start, end: v.pos, value: value}) return true } @@ -535,7 +547,7 @@ func (v *regExpValidator) scanUnicodePropertyValueExpression(isCharacterCompleme start := v.pos - 3 propertyNameOrValueStart := v.pos - v.scanIdentifier(v.charAtOffset(0)) + v.scanWordCharacters(v.charAtOffset(0)) propertyNameOrValue := v.tokenValue if v.charAtOffset(0) == '=' { @@ -548,9 +560,7 @@ func (v *regExpValidator) scanUnicodePropertyValueExpression(isCharacterCompleme v.error(diagnostics.Unknown_Unicode_property_name, propertyNameOrValueStart, v.pos-propertyNameOrValueStart) // Provide spelling suggestion candidates := make([]string, 0, len(nonBinaryUnicodePropertyNames)) - for key := range nonBinaryUnicodePropertyNames { - candidates = append(candidates, key) - } + candidates = slices.AppendSeq(candidates, maps.Keys(nonBinaryUnicodePropertyNames)) suggestion := core.GetSpellingSuggestion(propertyNameOrValue, candidates, core.Identity[string]) if suggestion != "" { v.error(diagnostics.Did_you_mean_0, propertyNameOrValueStart, v.pos-propertyNameOrValueStart, suggestion) @@ -559,7 +569,7 @@ func (v *regExpValidator) scanUnicodePropertyValueExpression(isCharacterCompleme } v.pos++ propertyValueStart := v.pos - v.scanIdentifier(v.charAtOffset(0)) + v.scanWordCharacters(v.charAtOffset(0)) propertyValue := v.tokenValue if v.pos == propertyValueStart { v.error(diagnostics.Expected_a_Unicode_property_value, propertyValueStart, 0) @@ -568,9 +578,10 @@ func (v *regExpValidator) scanUnicodePropertyValueExpression(isCharacterCompleme // Provide spelling suggestion based on the property name canonicalName := nonBinaryUnicodePropertyNames[propertyNameOrValue] var candidates []string - if canonicalName == "General_Category" { + switch canonicalName { + case "General_Category": candidates = generalCategoryValues.KeysSlice() - } else if canonicalName == "Script" || canonicalName == "Script_Extensions" { + case "Script", "Script_Extensions": candidates = scriptValues.KeysSlice() } if len(candidates) > 0 { @@ -616,13 +627,29 @@ func (v *regExpValidator) scanUnicodePropertyValueExpression(isCharacterCompleme } } +func (v *regExpValidator) scanWordCharacters(ch rune) { + start := v.pos + if ch != 0 && scanner.IsWordCharacter(ch) { + v.pos++ + for v.pos < v.end { + ch = v.charAtOffset(0) + if scanner.IsWordCharacter(ch) { + v.pos++ + } else { + break + } + } + } + v.tokenValue = v.text[start:v.pos] +} + func (v *regExpValidator) scanIdentifier(ch rune) { start := v.pos - if ch != 0 && (scanner.IsIdentifierStart(ch) || ch == '_' || ch == '$') { + if ch != 0 && scanner.IsIdentifierStart(ch) { v.pos++ for v.pos < v.end { ch = v.charAtOffset(0) - if scanner.IsIdentifierPart(ch) || ch == '_' || ch == '$' { + if scanner.IsIdentifierPart(ch) { v.pos++ } else { break @@ -647,7 +674,7 @@ func (v *regExpValidator) scanCharacterEscape(atomEscape bool) string { } if v.anyUnicodeModeOrNonAnnexB { v.error(diagnostics.X_c_must_be_followed_by_an_ASCII_letter, v.pos-2, 2) - } else if atomEscape { + } else { v.pos-- return "\\" } @@ -696,10 +723,7 @@ func (v *regExpValidator) scanEscapeSequence(atomEscape bool) string { v.pos++ } // Always report errors for octal escapes in regexp mode - code := 0 - for i := start + 1; i < v.pos; i++ { - code = code*8 + int(v.text[i]-'0') - } + code := parseOctalValue(v.text, start+1, v.pos) hexCode := fmt.Sprintf("\\x%02x", code) if !atomEscape && ch != '0' { v.error(diagnostics.Octal_escape_sequences_and_backreferences_are_not_allowed_in_a_character_class_If_this_was_intended_as_an_escape_sequence_use_the_syntax_0_instead, start, v.pos-start, hexCode) @@ -733,18 +757,13 @@ func (v *regExpValidator) scanEscapeSequence(atomEscape bool) string { case 'x': // Hex escape '\xDD' hexStart := v.pos - validHex := true for range 2 { if v.pos >= v.end || !stringutil.IsHexDigit(v.charAtOffset(0)) { - validHex = false - break + v.error(diagnostics.Hexadecimal_digit_expected, v.pos, 0) + return v.text[start:v.pos] } v.pos++ } - if !validHex { - v.error(diagnostics.Hexadecimal_digit_expected, hexStart, v.pos-hexStart) - return v.text[start:v.pos] - } code := parseHexValue(v.text, hexStart, v.pos) return string(rune(code)) @@ -765,8 +784,8 @@ func (v *regExpValidator) scanEscapeSequence(atomEscape bool) string { } if v.charAtOffset(0) == '}' { v.pos++ - } else if hasDigits { - v.error(diagnostics.Unterminated_Unicode_escape_sequence, start, v.pos-start) + } else { + v.error(diagnostics.Unterminated_Unicode_escape_sequence, v.pos, 0) return v.text[start:v.pos] } // Parse hex value (-1 to skip closing brace) @@ -782,18 +801,13 @@ func (v *regExpValidator) scanEscapeSequence(atomEscape bool) string { } else { // Standard unicode escape '\uDDDD' hexStart := v.pos - validHex := true for range 4 { if v.pos >= v.end || !stringutil.IsHexDigit(v.charAtOffset(0)) { - validHex = false - break + v.error(diagnostics.Hexadecimal_digit_expected, v.pos, 0) + return v.text[start:v.pos] } v.pos++ } - if !validHex { - v.error(diagnostics.Hexadecimal_digit_expected, hexStart, v.pos-hexStart) - return v.text[start:v.pos] - } code := parseHexValue(v.text, hexStart, v.pos) // For surrogates, we need to preserve the actual value since string(rune(surrogate)) // converts to 0xFFFD. We encode the surrogate as UTF-16BE bytes. @@ -806,7 +820,7 @@ func (v *regExpValidator) scanEscapeSequence(atomEscape bool) string { } // In Unicode mode, check for surrogate pairs if v.anyUnicodeMode && isHighSurrogate(rune(code)) && - v.pos+6 <= v.end && v.text[v.pos:v.pos+2] == "\\u" { + v.pos+6 <= v.end && v.charAtOffset(0) == '\\' && v.charAtOffset(1) == 'u' { // High surrogate followed by potential low surrogate nextStart := v.pos nextPos := v.pos + 2 @@ -845,6 +859,24 @@ func (v *regExpValidator) scanEscapeSequence(atomEscape bool) string { } } +// parses octal digits from text and returns the integer value +func parseOctalValue(text string, start, end int) int { + code := 0 + for i := start; i < end; i++ { + code = code*8 + int(text[i]-'0') + } + return code +} + +// parses decimal digits from text and returns the integer value +func parseDecimalValue(text string, start, end int) int { + code := 0 + for i := start; i < end; i++ { + code = code*10 + int(text[i]-'0') + } + return code +} + // parseHexValue parses hexadecimal digits from text and returns the integer value func parseHexValue(text string, start, end int) int { code := 0 @@ -866,24 +898,22 @@ func (v *regExpValidator) scanGroupName(isReference bool) { v.scanIdentifier(v.charAtOffset(0)) if v.pos == tokenStart { v.error(diagnostics.Expected_a_capturing_group_name, v.pos, 0) - } else if isReference { + } + if isReference { v.groupNameReferences = append(v.groupNameReferences, namedReference{pos: tokenStart, end: v.pos, name: v.tokenValue}) } else { - // Check for duplicate names in scope - if v.topNamedCapturingGroupsScope != nil && v.topNamedCapturingGroupsScope[v.tokenValue] { - v.error(diagnostics.Named_capturing_groups_with_the_same_name_must_be_mutually_exclusive_to_each_other, tokenStart, v.pos-tokenStart) - } else { - for _, scope := range v.namedCapturingGroupsScopeStack { - if scope != nil && scope[v.tokenValue] { - v.error(diagnostics.Named_capturing_groups_with_the_same_name_must_be_mutually_exclusive_to_each_other, tokenStart, v.pos-tokenStart) - break + if v.tokenValue != "" { + // Check for duplicate names in scope + outer: + for _, scope := range append(v.disjunctionsScopesStack, v.topDisjunctionsScope) { + for i := scope.currentAlternativeIndex; i < len(scope.disjunctions); i++ { + if scope.disjunctions[i].groupName == v.tokenValue { + v.error(diagnostics.Named_capturing_groups_with_the_same_name_must_be_mutually_exclusive_to_each_other, tokenStart, v.pos-tokenStart) + break outer + } } } } - if v.topNamedCapturingGroupsScope == nil { - v.topNamedCapturingGroupsScope = make(map[string]bool) - } - v.topNamedCapturingGroupsScope[v.tokenValue] = true if v.groupSpecifiers == nil { v.groupSpecifiers = make(map[string]bool) } @@ -948,7 +978,7 @@ func (v *regExpValidator) scanClassRanges() { } atomStart := v.pos atom := v.scanClassAtom() - if v.charAtOffset(0) == '-' && v.charAtOffset(1) != ']' { + if v.charAtOffset(0) == '-' { v.pos++ if v.isClassContentExit(v.charAtOffset(0)) { return @@ -1069,15 +1099,14 @@ func (v *regExpValidator) scanClassSetExpression() { var operand string // Check for operators at the start - slice := v.text[v.pos:min(v.pos+2, v.end)] - if slice == "--" || slice == "&&" { + if ch == v.charAtOffset(1) && (ch == '-' || ch == '&') { v.error(diagnostics.Expected_a_class_set_operand, v.pos, 0) v.mayContainStrings = false } else { operand = v.scanClassSetOperand() } - // Check what follows the first operand + // Use the first operator to determine the expression type switch v.charAtOffset(0) { case '-': if v.charAtOffset(1) == '-' { @@ -1098,8 +1127,6 @@ func (v *regExpValidator) scanClassSetExpression() { expressionMayContainStrings = v.mayContainStrings v.mayContainStrings = !isCharacterComplement && expressionMayContainStrings return - } else { - v.error(diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, v.pos, 1, string(ch)) } default: if isCharacterComplement && v.mayContainStrings { @@ -1108,7 +1135,7 @@ func (v *regExpValidator) scanClassSetExpression() { expressionMayContainStrings = v.mayContainStrings } - // Continue scanning operands + // Neither a classSetExpressionIntersection nor a classSetExpressionSubtraction, scan as class union for { ch = v.charAtOffset(0) if ch == 0 { @@ -1176,8 +1203,8 @@ func (v *regExpValidator) scanClassSetExpression() { } start = v.pos - slice = v.text[v.pos:min(v.pos+2, v.end)] - if slice == "--" || slice == "&&" { + ch = v.charAtOffset(0) + if ch == v.charAtOffset(1) && (ch == '-' || ch == '&') { v.error(diagnostics.Operators_must_not_be_mixed_within_a_character_class_Wrap_it_in_a_nested_class_instead, v.pos, 2) v.pos += 2 operand = v.text[start:v.pos] diff --git a/internal/regexpchecker/tables.go b/internal/regexpchecker/tables.go index ddf554b31e..fd6087c442 100644 --- a/internal/regexpchecker/tables.go +++ b/internal/regexpchecker/tables.go @@ -550,10 +550,10 @@ func isValidNonBinaryUnicodePropertyName(name string) bool { func isValidUnicodeProperty(name, value string) bool { canonicalName := nonBinaryUnicodePropertyNames[name] - if canonicalName == "General_Category" { + switch canonicalName { + case "General_Category": return generalCategoryValues.Has(value) - } - if canonicalName == "Script" || canonicalName == "Script_Extensions" { + case "Script", "Script_Extensions": return scriptValues.Has(value) } return false diff --git a/internal/regexpchecker/utf16.go b/internal/regexpchecker/utf16.go index 3475a4ad82..ffa4862718 100644 --- a/internal/regexpchecker/utf16.go +++ b/internal/regexpchecker/utf16.go @@ -106,10 +106,18 @@ func utf16Length(s string) int { return 1 } - // Otherwise, count UTF-16 code units from UTF-8 runes + // Otherwise, count UTF-16 code units from UTF-8 string + sLength := len(s) length := 0 - for _, r := range s { - length += charSize(r) + // ASCII fast path similar to stdlib utf8.RuneCount + for ; length < sLength; length++ { + if ch := s[length]; ch == 0 || ch >= utf8.RuneSelf { + // non-ASCII slow path, count from runes + for _, r := range s[length:] { + length += charSize(r) + } + return length + } } return length } diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 1ee64b62f1..90a402c293 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -1401,7 +1401,7 @@ func (s *Scanner) scanIdentifier(prefixLength int) bool { for { s.pos++ ch = s.char() - if !(isWordCharacter(ch) || ch == '$') { + if !(IsWordCharacter(ch) || ch == '$') { break } } @@ -2007,7 +2007,7 @@ func IsValidIdentifier(s string) bool { } // Section 6.1.4 -func isWordCharacter(ch rune) bool { +func IsWordCharacter(ch rune) bool { return stringutil.IsASCIILetter(ch) || stringutil.IsDigit(ch) || ch == '_' } @@ -2020,7 +2020,7 @@ func IsIdentifierPart(ch rune) bool { } func IsIdentifierPartEx(ch rune, languageVariant core.LanguageVariant) bool { - return isWordCharacter(ch) || ch == '$' || + return IsWordCharacter(ch) || ch == '$' || ch >= utf8.RuneSelf && isUnicodeIdentifierPart(ch) || languageVariant == core.LanguageVariantJSX && (ch == '-' || ch == ':') // "-" and ":" are valid in JSX Identifiers } diff --git a/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.errors.txt b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.errors.txt new file mode 100644 index 0000000000..d6fd036b97 --- /dev/null +++ b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.errors.txt @@ -0,0 +1,106 @@ +regularExpressionDuplicateCapturingGroupName.ts(2,13): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(3,14): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(4,15): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(5,16): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(6,16): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(7,18): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(8,16): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(9,18): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(11,19): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(12,21): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(14,20): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(15,20): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(16,20): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(19,12): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(20,13): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(23,50): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(23,57): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(23,64): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(24,35): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(24,42): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(24,66): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(24,74): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(24,81): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. +regularExpressionDuplicateCapturingGroupName.ts(24,89): error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + + +==== regularExpressionDuplicateCapturingGroupName.ts (24 errors) ==== + // Adjacent homonymous capturing groups + /(?)(?)/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?)((?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /((?))(?)/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /((?))((?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?)(?=(?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?<=(?))(?)/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?)(?!(?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?))(?)/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + + /((?))((?=(?)))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /((?)))((?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + + /(?=(?))(?=(?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?!(?))(?!(?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?=(?))(?!(?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + + // Nested homonymous capturing groups + /(?(?))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?((?)))/; + ~~~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + + // Complicated cases + /(?)((?)((?)|(?))|(?)|((?)))(?)((?)|(?))/; + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + /(?)(((?)|(?))((?)|(?)|(?))|(?)|((?)))(?)(((?)|(?))|(?))/; + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + ~ +!!! error TS1515: Named capturing groups with the same name must be mutually exclusive to each other. + + // Should not error + /(?)|(?)/; + /(?)|((?))/; + /((?))|(?)/; + /((?))|((?))/; + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.js b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.js new file mode 100644 index 0000000000..d864ac8915 --- /dev/null +++ b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.js @@ -0,0 +1,61 @@ +//// [tests/cases/compiler/regularExpressionDuplicateCapturingGroupName.ts] //// + +//// [regularExpressionDuplicateCapturingGroupName.ts] +// Adjacent homonymous capturing groups +/(?)(?)/; +/(?)((?))/; +/((?))(?)/; +/((?))((?))/; +/(?)(?=(?))/; +/(?<=(?))(?)/; +/(?)(?!(?))/; +/(?))(?)/; + +/((?))((?=(?)))/; +/((?)))((?))/; + +/(?=(?))(?=(?))/; +/(?!(?))(?!(?))/; +/(?=(?))(?!(?))/; + +// Nested homonymous capturing groups +/(?(?))/; +/(?((?)))/; + +// Complicated cases +/(?)((?)((?)|(?))|(?)|((?)))(?)((?)|(?))/; +/(?)(((?)|(?))((?)|(?)|(?))|(?)|((?)))(?)(((?)|(?))|(?))/; + +// Should not error +/(?)|(?)/; +/(?)|((?))/; +/((?))|(?)/; +/((?))|((?))/; + + +//// [regularExpressionDuplicateCapturingGroupName.js] +// Adjacent homonymous capturing groups +/(?)(?)/; +/(?)((?))/; +/((?))(?)/; +/((?))((?))/; +/(?)(?=(?))/; +/(?<=(?))(?)/; +/(?)(?!(?))/; +/(?))(?)/; +/((?))((?=(?)))/; +/((?)))((?))/; +/(?=(?))(?=(?))/; +/(?!(?))(?!(?))/; +/(?=(?))(?!(?))/; +// Nested homonymous capturing groups +/(?(?))/; +/(?((?)))/; +// Complicated cases +/(?)((?)((?)|(?))|(?)|((?)))(?)((?)|(?))/; +/(?)(((?)|(?))((?)|(?)|(?))|(?)|((?)))(?)(((?)|(?))|(?))/; +// Should not error +/(?)|(?)/; +/(?)|((?))/; +/((?))|(?)/; +/((?))|((?))/; diff --git a/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.symbols b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.symbols new file mode 100644 index 0000000000..fdc97a61fc --- /dev/null +++ b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.symbols @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/regularExpressionDuplicateCapturingGroupName.ts] //// + +=== regularExpressionDuplicateCapturingGroupName.ts === + +// Adjacent homonymous capturing groups +/(?)(?)/; +/(?)((?))/; +/((?))(?)/; +/((?))((?))/; +/(?)(?=(?))/; +/(?<=(?))(?)/; +/(?)(?!(?))/; +/(?))(?)/; + +/((?))((?=(?)))/; +/((?)))((?))/; + +/(?=(?))(?=(?))/; +/(?!(?))(?!(?))/; +/(?=(?))(?!(?))/; + +// Nested homonymous capturing groups +/(?(?))/; +/(?((?)))/; + +// Complicated cases +/(?)((?)((?)|(?))|(?)|((?)))(?)((?)|(?))/; +/(?)(((?)|(?))((?)|(?)|(?))|(?)|((?)))(?)(((?)|(?))|(?))/; + +// Should not error +/(?)|(?)/; +/(?)|((?))/; +/((?))|(?)/; +/((?))|((?))/; + diff --git a/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.types b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.types new file mode 100644 index 0000000000..32118926b0 --- /dev/null +++ b/testdata/baselines/reference/compiler/regularExpressionDuplicateCapturingGroupName.types @@ -0,0 +1,70 @@ +//// [tests/cases/compiler/regularExpressionDuplicateCapturingGroupName.ts] //// + +=== regularExpressionDuplicateCapturingGroupName.ts === +// Adjacent homonymous capturing groups +/(?)(?)/; +>/(?)(?)/ : RegExp + +/(?)((?))/; +>/(?)((?))/ : RegExp + +/((?))(?)/; +>/((?))(?)/ : RegExp + +/((?))((?))/; +>/((?))((?))/ : RegExp + +/(?)(?=(?))/; +>/(?)(?=(?))/ : RegExp + +/(?<=(?))(?)/; +>/(?<=(?))(?)/ : RegExp + +/(?)(?!(?))/; +>/(?)(?!(?))/ : RegExp + +/(?))(?)/; +>/(?))(?)/ : RegExp + +/((?))((?=(?)))/; +>/((?))((?=(?)))/ : RegExp + +/((?)))((?))/; +>/((?)))((?))/ : RegExp + +/(?=(?))(?=(?))/; +>/(?=(?))(?=(?))/ : RegExp + +/(?!(?))(?!(?))/; +>/(?!(?))(?!(?))/ : RegExp + +/(?=(?))(?!(?))/; +>/(?=(?))(?!(?))/ : RegExp + +// Nested homonymous capturing groups +/(?(?))/; +>/(?(?))/ : RegExp + +/(?((?)))/; +>/(?((?)))/ : RegExp + +// Complicated cases +/(?)((?)((?)|(?))|(?)|((?)))(?)((?)|(?))/; +>/(?)((?)((?)|(?))|(?)|((?)))(?)((?)|(?))/ : RegExp + +/(?)(((?)|(?))((?)|(?)|(?))|(?)|((?)))(?)(((?)|(?))|(?))/; +>/(?)(((?)|(?))((?)|(?)|(?))|(?)|((?)))(?)(((?)|(?))|(?))/ : RegExp + +// Should not error +/(?)|(?)/; +>/(?)|(?)/ : RegExp + +/(?)|((?))/; +>/(?)|((?))/ : RegExp + +/((?))|(?)/; +>/((?))|(?)/ : RegExp + +/((?))|((?))/; +>/((?))|((?))/ : RegExp + diff --git a/testdata/baselines/reference/submodule/conformance/parser.numericSeparators.unicodeEscape.errors.txt b/testdata/baselines/reference/submodule/conformance/parser.numericSeparators.unicodeEscape.errors.txt index 10c3bfa576..6e8015ba66 100644 --- a/testdata/baselines/reference/submodule/conformance/parser.numericSeparators.unicodeEscape.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/parser.numericSeparators.unicodeEscape.errors.txt @@ -1,7 +1,7 @@ 1.ts(1,7): error TS1199: Unterminated Unicode escape sequence. 10.ts(1,5): error TS1125: Hexadecimal digit expected. 11.ts(1,5): error TS1125: Hexadecimal digit expected. -12.ts(1,4): error TS1125: Hexadecimal digit expected. +12.ts(1,5): error TS1125: Hexadecimal digit expected. 13.ts(1,5): error TS1125: Hexadecimal digit expected. 14.ts(1,5): error TS1125: Hexadecimal digit expected. 15.ts(1,5): error TS1125: Hexadecimal digit expected. @@ -19,28 +19,28 @@ 25.ts(1,11): error TS1199: Unterminated Unicode escape sequence. 26.ts(1,11): error TS1199: Unterminated Unicode escape sequence. 27.ts(1,11): error TS1199: Unterminated Unicode escape sequence. -28.ts(1,2): error TS1199: Unterminated Unicode escape sequence. +28.ts(1,11): error TS1199: Unterminated Unicode escape sequence. 28.ts(1,12): error TS1508: Unexpected '}'. Did you mean to escape it with backslash? 3.ts(1,7): error TS1199: Unterminated Unicode escape sequence. 37.ts(1,7): error TS1199: Unterminated Unicode escape sequence. 38.ts(1,7): error TS1199: Unterminated Unicode escape sequence. 39.ts(1,7): error TS1199: Unterminated Unicode escape sequence. -4.ts(1,2): error TS1199: Unterminated Unicode escape sequence. +4.ts(1,7): error TS1199: Unterminated Unicode escape sequence. 4.ts(1,12): error TS1508: Unexpected '}'. Did you mean to escape it with backslash? -40.ts(1,2): error TS1199: Unterminated Unicode escape sequence. +40.ts(1,7): error TS1199: Unterminated Unicode escape sequence. 40.ts(1,13): error TS1508: Unexpected '}'. Did you mean to escape it with backslash? 41.ts(1,6): error TS1125: Hexadecimal digit expected. 42.ts(1,6): error TS1125: Hexadecimal digit expected. 43.ts(1,6): error TS1125: Hexadecimal digit expected. -44.ts(1,4): error TS1125: Hexadecimal digit expected. +44.ts(1,6): error TS1125: Hexadecimal digit expected. 45.ts(1,5): error TS1125: Hexadecimal digit expected. 46.ts(1,5): error TS1125: Hexadecimal digit expected. 47.ts(1,5): error TS1125: Hexadecimal digit expected. -48.ts(1,4): error TS1125: Hexadecimal digit expected. +48.ts(1,5): error TS1125: Hexadecimal digit expected. 5.ts(1,6): error TS1125: Hexadecimal digit expected. 6.ts(1,6): error TS1125: Hexadecimal digit expected. 7.ts(1,6): error TS1125: Hexadecimal digit expected. -8.ts(1,4): error TS1125: Hexadecimal digit expected. +8.ts(1,6): error TS1125: Hexadecimal digit expected. 9.ts(1,5): error TS1125: Hexadecimal digit expected. @@ -61,7 +61,7 @@ ==== 4.ts (2 errors) ==== /\u{10_ffff}/u - ~~~~~ + !!! error TS1199: Unterminated Unicode escape sequence. ~ !!! error TS1508: Unexpected '}'. Did you mean to escape it with backslash? @@ -83,7 +83,7 @@ ==== 8.ts (1 errors) ==== /\uff_ff/u - ~~ + !!! error TS1125: Hexadecimal digit expected. ==== 9.ts (1 errors) ==== @@ -103,7 +103,7 @@ ==== 12.ts (1 errors) ==== /\xf_f/u - ~ + !!! error TS1125: Hexadecimal digit expected. ==== 13.ts (1 errors) ==== @@ -185,7 +185,7 @@ ==== 28.ts (2 errors) ==== /\u{10ffff_}/u - ~~~~~~~~~ + !!! error TS1199: Unterminated Unicode escape sequence. ~ !!! error TS1508: Unexpected '}'. Did you mean to escape it with backslash? @@ -231,7 +231,7 @@ ==== 40.ts (2 errors) ==== /\u{10__ffff}/u - ~~~~~ + !!! error TS1199: Unterminated Unicode escape sequence. ~ !!! error TS1508: Unexpected '}'. Did you mean to escape it with backslash? @@ -253,7 +253,7 @@ ==== 44.ts (1 errors) ==== /\uff__ff/u - ~~ + !!! error TS1125: Hexadecimal digit expected. ==== 45.ts (1 errors) ==== @@ -273,6 +273,6 @@ ==== 48.ts (1 errors) ==== /\xf__f/u - ~ + !!! error TS1125: Hexadecimal digit expected. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/parser.numericSeparators.unicodeEscape.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/parser.numericSeparators.unicodeEscape.errors.txt.diff deleted file mode 100644 index 0cce15a967..0000000000 --- a/testdata/baselines/reference/submodule/conformance/parser.numericSeparators.unicodeEscape.errors.txt.diff +++ /dev/null @@ -1,108 +0,0 @@ ---- old.parser.numericSeparators.unicodeEscape.errors.txt -+++ new.parser.numericSeparators.unicodeEscape.errors.txt -@@= skipped -0, +0 lines =@@ - 1.ts(1,7): error TS1199: Unterminated Unicode escape sequence. - 10.ts(1,5): error TS1125: Hexadecimal digit expected. - 11.ts(1,5): error TS1125: Hexadecimal digit expected. --12.ts(1,5): error TS1125: Hexadecimal digit expected. -+12.ts(1,4): error TS1125: Hexadecimal digit expected. - 13.ts(1,5): error TS1125: Hexadecimal digit expected. - 14.ts(1,5): error TS1125: Hexadecimal digit expected. - 15.ts(1,5): error TS1125: Hexadecimal digit expected. -@@= skipped -18, +18 lines =@@ - 25.ts(1,11): error TS1199: Unterminated Unicode escape sequence. - 26.ts(1,11): error TS1199: Unterminated Unicode escape sequence. - 27.ts(1,11): error TS1199: Unterminated Unicode escape sequence. --28.ts(1,11): error TS1199: Unterminated Unicode escape sequence. -+28.ts(1,2): error TS1199: Unterminated Unicode escape sequence. - 28.ts(1,12): error TS1508: Unexpected '}'. Did you mean to escape it with backslash? - 3.ts(1,7): error TS1199: Unterminated Unicode escape sequence. - 37.ts(1,7): error TS1199: Unterminated Unicode escape sequence. - 38.ts(1,7): error TS1199: Unterminated Unicode escape sequence. - 39.ts(1,7): error TS1199: Unterminated Unicode escape sequence. --4.ts(1,7): error TS1199: Unterminated Unicode escape sequence. -+4.ts(1,2): error TS1199: Unterminated Unicode escape sequence. - 4.ts(1,12): error TS1508: Unexpected '}'. Did you mean to escape it with backslash? --40.ts(1,7): error TS1199: Unterminated Unicode escape sequence. -+40.ts(1,2): error TS1199: Unterminated Unicode escape sequence. - 40.ts(1,13): error TS1508: Unexpected '}'. Did you mean to escape it with backslash? - 41.ts(1,6): error TS1125: Hexadecimal digit expected. - 42.ts(1,6): error TS1125: Hexadecimal digit expected. - 43.ts(1,6): error TS1125: Hexadecimal digit expected. --44.ts(1,6): error TS1125: Hexadecimal digit expected. -+44.ts(1,4): error TS1125: Hexadecimal digit expected. - 45.ts(1,5): error TS1125: Hexadecimal digit expected. - 46.ts(1,5): error TS1125: Hexadecimal digit expected. - 47.ts(1,5): error TS1125: Hexadecimal digit expected. --48.ts(1,5): error TS1125: Hexadecimal digit expected. -+48.ts(1,4): error TS1125: Hexadecimal digit expected. - 5.ts(1,6): error TS1125: Hexadecimal digit expected. - 6.ts(1,6): error TS1125: Hexadecimal digit expected. - 7.ts(1,6): error TS1125: Hexadecimal digit expected. --8.ts(1,6): error TS1125: Hexadecimal digit expected. -+8.ts(1,4): error TS1125: Hexadecimal digit expected. - 9.ts(1,5): error TS1125: Hexadecimal digit expected. - - -@@= skipped -42, +42 lines =@@ - - ==== 4.ts (2 errors) ==== - /\u{10_ffff}/u -- -+ ~~~~~ - !!! error TS1199: Unterminated Unicode escape sequence. - ~ - !!! error TS1508: Unexpected '}'. Did you mean to escape it with backslash? -@@= skipped -22, +22 lines =@@ - - ==== 8.ts (1 errors) ==== - /\uff_ff/u -- -+ ~~ - !!! error TS1125: Hexadecimal digit expected. - - ==== 9.ts (1 errors) ==== -@@= skipped -20, +20 lines =@@ - - ==== 12.ts (1 errors) ==== - /\xf_f/u -- -+ ~ - !!! error TS1125: Hexadecimal digit expected. - - ==== 13.ts (1 errors) ==== -@@= skipped -82, +82 lines =@@ - - ==== 28.ts (2 errors) ==== - /\u{10ffff_}/u -- -+ ~~~~~~~~~ - !!! error TS1199: Unterminated Unicode escape sequence. - ~ - !!! error TS1508: Unexpected '}'. Did you mean to escape it with backslash? -@@= skipped -46, +46 lines =@@ - - ==== 40.ts (2 errors) ==== - /\u{10__ffff}/u -- -+ ~~~~~ - !!! error TS1199: Unterminated Unicode escape sequence. - ~ - !!! error TS1508: Unexpected '}'. Did you mean to escape it with backslash? -@@= skipped -22, +22 lines =@@ - - ==== 44.ts (1 errors) ==== - /\uff__ff/u -- -+ ~~ - !!! error TS1125: Hexadecimal digit expected. - - ==== 45.ts (1 errors) ==== -@@= skipped -20, +20 lines =@@ - - ==== 48.ts (1 errors) ==== - /\xf__f/u -- -+ ~ - !!! error TS1125: Hexadecimal digit expected. - \ No newline at end of file diff --git a/testdata/tests/cases/compiler/regularExpressionDuplicateCapturingGroupName.ts b/testdata/tests/cases/compiler/regularExpressionDuplicateCapturingGroupName.ts new file mode 100644 index 0000000000..2af5114c0f --- /dev/null +++ b/testdata/tests/cases/compiler/regularExpressionDuplicateCapturingGroupName.ts @@ -0,0 +1,33 @@ +// @strict: true +// @target: esnext + +// Adjacent homonymous capturing groups +/(?)(?)/; +/(?)((?))/; +/((?))(?)/; +/((?))((?))/; +/(?)(?=(?))/; +/(?<=(?))(?)/; +/(?)(?!(?))/; +/(?))(?)/; + +/((?))((?=(?)))/; +/((?)))((?))/; + +/(?=(?))(?=(?))/; +/(?!(?))(?!(?))/; +/(?=(?))(?!(?))/; + +// Nested homonymous capturing groups +/(?(?))/; +/(?((?)))/; + +// Complicated cases +/(?)((?)((?)|(?))|(?)|((?)))(?)((?)|(?))/; +/(?)(((?)|(?))((?)|(?)|(?))|(?)|((?)))(?)(((?)|(?))|(?))/; + +// Should not error +/(?)|(?)/; +/(?)|((?))/; +/((?))|(?)/; +/((?))|((?))/;