Skip to content

Commit 96989df

Browse files
authored
fix: SyntaxError message for v flag with RegExpValidator#validatePattern (#110)
1 parent 31dac9d commit 96989df

File tree

4 files changed

+125
-7
lines changed

4 files changed

+125
-7
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"clean": "rimraf .temp index.*",
5454
"lint": "eslint . --ext .ts",
5555
"test": "nyc _mocha \"test/*.ts\" --reporter dot --timeout 10000",
56+
"debug": "mocha --require ts-node/register/transpile-only \"test/*.ts\" --reporter dot --timeout 10000",
5657
"update:test": "ts-node scripts/update-fixtures.ts",
5758
"update:unicode": "run-s update:unicode:*",
5859
"update:unicode:ids": "ts-node scripts/update-unicode-ids.ts",

src/regexp-syntax-error.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ export class RegExpSyntaxError extends SyntaxError {
33

44
public constructor(
55
source: string,
6-
uFlag: boolean,
6+
flags: { unicode: boolean; unicodeSets: boolean },
77
index: number,
88
message: string,
99
) {
1010
/*eslint-disable no-param-reassign */
1111
if (source) {
1212
if (!source.startsWith("/")) {
13-
source = `/${source}/${uFlag ? "u" : ""}`
13+
source = `/${source}/${flags.unicode ? "u" : ""}${
14+
flags.unicodeSets ? "v" : ""
15+
}`
1416
}
1517
source = `: ${source}`
1618
}

src/validator.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ export class RegExpValidator {
786786
}
787787
| undefined = undefined,
788788
): void {
789-
const mode = this._parseFlagsOptionToMode(uFlagOrFlags)
789+
const mode = this._parseFlagsOptionToMode(uFlagOrFlags, source, end)
790790

791791
this._unicodeMode = mode.unicodeMode
792792
this._nFlag = mode.nFlag
@@ -812,7 +812,9 @@ export class RegExpValidator {
812812
unicode?: boolean
813813
unicodeSets?: boolean
814814
}
815-
| undefined = undefined,
815+
| undefined,
816+
source: string,
817+
sourceEnd: number,
816818
): {
817819
unicodeMode: boolean
818820
nFlag: boolean
@@ -835,7 +837,12 @@ export class RegExpValidator {
835837
if (unicode && unicodeSets) {
836838
// 1. If v is true and u is true, then
837839
// a. Let parseResult be a List containing one SyntaxError object.
838-
this.raise("Invalid regular expression flags")
840+
throw new RegExpSyntaxError(
841+
source,
842+
{ unicode, unicodeSets },
843+
sourceEnd + 1 /* `/` */,
844+
"Invalid regular expression flags",
845+
)
839846
}
840847

841848
const unicodeMode = unicode || unicodeSets
@@ -1210,7 +1217,10 @@ export class RegExpValidator {
12101217
private raise(message: string): never {
12111218
throw new RegExpSyntaxError(
12121219
this.source,
1213-
this._unicodeMode,
1220+
{
1221+
unicode: this._unicodeMode && !this._unicodeSetsMode,
1222+
unicodeSets: this._unicodeSetsMode,
1223+
},
12141224
this.index,
12151225
message,
12161226
)

test/parser.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import assert from "assert"
2-
import { parseRegExpLiteral, RegExpParser } from "../src/index"
2+
import { parseRegExpLiteral, RegExpParser, RegExpValidator } from "../src/index"
33
import type { RegExpSyntaxError } from "../src/regexp-syntax-error"
44
import { cloneWithoutCircular } from "../scripts/clone-without-circular"
55
import { fixturesData } from "./fixtures/parser/literal"
6+
import {
7+
ASTERISK,
8+
isLineTerminator,
9+
LEFT_SQUARE_BRACKET,
10+
REVERSE_SOLIDUS,
11+
RIGHT_SQUARE_BRACKET,
12+
SOLIDUS,
13+
} from "../src/unicode"
614

715
function generateAST(source: string, options: RegExpParser.Options): object {
816
return cloneWithoutCircular(parseRegExpLiteral(source, options))
@@ -56,6 +64,45 @@ describe("parseRegExpLiteral function:", () => {
5664
}
5765
assert.fail("Should fail, but succeeded.")
5866
})
67+
68+
const validator = new RegExpValidator(options)
69+
const extracted = extractPatternAndFlags(source, validator)
70+
if (extracted) {
71+
it(`${source} should throw syntax error with RegExpValidator#validatePattern.`, () => {
72+
const expected = result.error
73+
try {
74+
validator.validatePattern(
75+
extracted.pattern,
76+
undefined,
77+
undefined,
78+
{
79+
unicode: extracted.flags.includes("u"),
80+
unicodeSets:
81+
extracted.flags.includes("v"),
82+
},
83+
)
84+
} catch (err) {
85+
const error = err as RegExpSyntaxError
86+
const expectedMessage =
87+
expected.message.replace(
88+
/\/([a-z]+?):/u,
89+
(_, flagsInLiteral: string) =>
90+
`/${flagsInLiteral.replace(
91+
/[^uv]/gu,
92+
"",
93+
)}:`,
94+
)
95+
const expectedIndex = expected.index - 1
96+
assert.strictEqual(
97+
error.message,
98+
expectedMessage,
99+
)
100+
assert.strictEqual(error.index, expectedIndex)
101+
return
102+
}
103+
assert.fail("Should fail, but succeeded.")
104+
})
105+
}
59106
}
60107
}
61108
})
@@ -79,3 +126,61 @@ describe("RegExpParser:", () => {
79126
})
80127
})
81128
})
129+
130+
function extractPatternAndFlags(
131+
source: string,
132+
validator: RegExpValidator,
133+
): { pattern: string; flags: string } | null {
134+
let inClass = false
135+
let escaped = false
136+
137+
const chars = [...source]
138+
139+
if (chars[0] !== "/") {
140+
return null
141+
}
142+
chars.shift()
143+
144+
const pattern: string[] = []
145+
146+
let first = true
147+
// https://tc39.es/ecma262/2022/multipage/ecmascript-language-lexical-grammar.html#prod-RegularExpressionBody
148+
for (;;) {
149+
const char = chars.shift()
150+
if (!char) {
151+
return null
152+
}
153+
const cp = char.charCodeAt(0)!
154+
if (isLineTerminator(cp)) {
155+
return null
156+
}
157+
if (escaped) {
158+
escaped = false
159+
} else if (cp === REVERSE_SOLIDUS) {
160+
escaped = true
161+
} else if (cp === LEFT_SQUARE_BRACKET) {
162+
inClass = true
163+
} else if (cp === RIGHT_SQUARE_BRACKET) {
164+
inClass = false
165+
} else if (cp === ASTERISK && first) {
166+
return null
167+
} else if (cp === SOLIDUS && !inClass) {
168+
break
169+
}
170+
pattern.push(char)
171+
first = false
172+
}
173+
174+
const flags = chars.join("")
175+
if (pattern.length === 0) {
176+
return null
177+
}
178+
179+
try {
180+
validator.validateFlags(flags)
181+
} catch {
182+
return null
183+
}
184+
185+
return { pattern: pattern.join(""), flags }
186+
}

0 commit comments

Comments
 (0)