Skip to content

Commit 20f80af

Browse files
authored
Fix false negatives for escape sequence (#32)
- Fix false negatives for escape sequence - Fix false negatives for octal and binary literals in JSON5.
1 parent cd8e308 commit 20f80af

12 files changed

+688
-3
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"test:base": "mocha --require ts-node/register \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
1616
"test": "npm run test:base",
1717
"test:nyc": "nyc --reporter=lcov npm run test:base",
18-
"test:debug": "mocha --require ts-node/register --inspect \"tests/src/**/*.ts\" --reporter dot",
18+
"test:debug": "mocha --require ts-node/register/transpile-only --inspect \"tests/src/**/*.ts\" --reporter dot",
1919
"update": "ts-node ./tools/update.ts && npm run eslint-fix && npm run test:nyc",
2020
"preversion": "npm test && npm run update && git add .",
2121
"version": "npm run eslint-fix && git add .",
@@ -67,6 +67,7 @@
6767
"vue-eslint-parser": "^7.2.0"
6868
},
6969
"dependencies": {
70+
"eslint-utils": "^2.1.0",
7071
"eslint-visitor-keys": "^2.0.0",
7172
"espree": "^6.0.0 || ^7.2.0"
7273
}

src/parser/convert.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
Expression,
1414
} from "estree"
1515
import type { AST } from "eslint"
16+
import { PatternMatcher } from "eslint-utils"
1617
import type {
1718
JSONNode,
1819
JSONProgram,
@@ -48,6 +49,10 @@ import type { TokenStore, MaybeNodeOrToken } from "./token-store"
4849
import { isComma } from "./token-store"
4950

5051
const lineBreakPattern = /\r\n|[\r\n\u2028\u2029]/u
52+
const codePointEscapeMatcher = new PatternMatcher(/\\u\{[\da-fA-F]+\}/gu)
53+
const octalNumericLiteralPattern = /^0[oO]/u
54+
const legacyOctalNumericLiteralPattern = /^0[0-7]/u
55+
const binaryNumericLiteralPattern = /^0[bB]/u
5156

5257
export type JSONSyntaxContext = {
5358
trailingCommas: boolean
@@ -59,6 +64,9 @@ export type JSONSyntaxContext = {
5964
infinities: boolean
6065
nans: boolean
6166
numericSeparators: boolean
67+
binaryNumericLiterals: boolean
68+
octalNumericLiterals: boolean
69+
legacyOctalNumericLiterals: boolean
6270
invalidJsonNumbers: boolean
6371
//
6472
multilineStrings: boolean
@@ -70,6 +78,8 @@ export type JSONSyntaxContext = {
7078
regExpLiterals: boolean
7179
templateLiterals: boolean
7280
bigintLiterals: boolean
81+
unicodeCodepointEscapes: boolean
82+
escapeSequenceInIdentifier: boolean
7383
}
7484

7585
export function convertNode(
@@ -141,7 +151,7 @@ export function convertNode(
141151
return convertUnaryExpressionNode(node, tokens, ctx)
142152
}
143153
if (node.type === "Identifier") {
144-
return convertIdentifierNode(node, tokens)
154+
return convertIdentifierNode(node, tokens, ctx)
145155
}
146156
if (node.type === "TemplateLiteral") {
147157
return convertTemplateLiteralNode(node, tokens, ctx)
@@ -461,6 +471,24 @@ function validateLiteral(node: Literal, ctx: JSONSyntaxContext) {
461471
})
462472
}
463473
}
474+
if (!ctx.octalNumericLiterals) {
475+
if (octalNumericLiteralPattern.test(text)) {
476+
return throwUnexpectedError("octal numeric literal", node)
477+
}
478+
}
479+
if (!ctx.legacyOctalNumericLiterals) {
480+
if (legacyOctalNumericLiteralPattern.test(text)) {
481+
return throwUnexpectedError(
482+
"legacy octal numeric literal",
483+
node,
484+
)
485+
}
486+
}
487+
if (!ctx.binaryNumericLiterals) {
488+
if (binaryNumericLiteralPattern.test(text)) {
489+
return throwUnexpectedError("binary numeric literal", node)
490+
}
491+
}
464492
if (!ctx.invalidJsonNumbers) {
465493
try {
466494
JSON.parse(text)
@@ -470,7 +498,9 @@ function validateLiteral(node: Literal, ctx: JSONSyntaxContext) {
470498
}
471499
}
472500
if (
473-
(!ctx.multilineStrings || !ctx.singleQuotes) &&
501+
(!ctx.multilineStrings ||
502+
!ctx.singleQuotes ||
503+
!ctx.unicodeCodepointEscapes) &&
474504
typeof value === "string"
475505
) {
476506
if (!ctx.singleQuotes) {
@@ -483,6 +513,11 @@ function validateLiteral(node: Literal, ctx: JSONSyntaxContext) {
483513
return throwUnexpectedError("multiline string", node)
484514
}
485515
}
516+
if (!ctx.unicodeCodepointEscapes) {
517+
if (codePointEscapeMatcher.test(node.raw!)) {
518+
return throwUnexpectedError("unicode codepoint escape", node)
519+
}
520+
}
486521
}
487522

488523
return undefined
@@ -547,11 +582,18 @@ function convertUnaryExpressionNode(
547582
function convertIdentifierNode(
548583
node: Identifier,
549584
tokens: TokenStore,
585+
ctx: JSONSyntaxContext,
550586
): JSONIdentifier {
551587
/* istanbul ignore next */
552588
if (node.type !== "Identifier") {
553589
return throwUnexpectedNodeError(node, tokens)
554590
}
591+
592+
if (!ctx.escapeSequenceInIdentifier) {
593+
if (node.name.length < node.range![1] - node.range![0]) {
594+
return throwUnexpectedError("escape sequence", node)
595+
}
596+
}
555597
const nn: JSONIdentifier = {
556598
type: "JSONIdentifier",
557599
name: node.name,
@@ -591,6 +633,11 @@ function convertTemplateLiteralNode(
591633
return throwUnexpectedTokenError("$", loc)
592634
}
593635

636+
if (!ctx.unicodeCodepointEscapes) {
637+
if (codePointEscapeMatcher.test(node.quasis[0].value.raw)) {
638+
return throwUnexpectedError("unicode codepoint escape", node)
639+
}
640+
}
594641
const quasis: [JSONTemplateElement] = [
595642
convertTemplateElementNode(node.quasis[0], tokens),
596643
]

src/parser/parser.ts

+20
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
170170
infinities: false,
171171
nans: false,
172172
numericSeparators: false,
173+
binaryNumericLiterals: false,
174+
octalNumericLiterals: false,
175+
legacyOctalNumericLiterals: false,
173176
invalidJsonNumbers: false,
174177
multilineStrings: false,
175178
unquoteProperties: false,
@@ -180,6 +183,8 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
180183
regExpLiterals: false,
181184
templateLiterals: false,
182185
bigintLiterals: false,
186+
unicodeCodepointEscapes: false,
187+
escapeSequenceInIdentifier: false,
183188
}
184189
}
185190
if (upperCase === "JSONC") {
@@ -192,6 +197,9 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
192197
infinities: false,
193198
nans: false,
194199
numericSeparators: false,
200+
binaryNumericLiterals: false,
201+
octalNumericLiterals: false,
202+
legacyOctalNumericLiterals: false,
195203
invalidJsonNumbers: false,
196204
multilineStrings: false,
197205
unquoteProperties: false,
@@ -202,6 +210,8 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
202210
regExpLiterals: false,
203211
templateLiterals: false,
204212
bigintLiterals: false,
213+
unicodeCodepointEscapes: false,
214+
escapeSequenceInIdentifier: false,
205215
}
206216
}
207217
if (upperCase === "JSON5") {
@@ -214,6 +224,9 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
214224
infinities: true,
215225
nans: true,
216226
numericSeparators: false,
227+
binaryNumericLiterals: false,
228+
octalNumericLiterals: false,
229+
legacyOctalNumericLiterals: false,
217230
invalidJsonNumbers: true,
218231
multilineStrings: true,
219232
unquoteProperties: true,
@@ -224,6 +237,8 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
224237
regExpLiterals: false,
225238
templateLiterals: false,
226239
bigintLiterals: false,
240+
unicodeCodepointEscapes: false,
241+
escapeSequenceInIdentifier: false,
227242
}
228243
}
229244
return {
@@ -235,6 +250,9 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
235250
infinities: true,
236251
nans: true,
237252
numericSeparators: true,
253+
binaryNumericLiterals: true,
254+
octalNumericLiterals: true,
255+
legacyOctalNumericLiterals: true,
238256
invalidJsonNumbers: true,
239257
multilineStrings: true,
240258
unquoteProperties: true,
@@ -245,5 +263,7 @@ function getJSONSyntaxContext(str?: string | null): JSONSyntaxContext {
245263
regExpLiterals: true,
246264
templateLiterals: true,
247265
bigintLiterals: true,
266+
unicodeCodepointEscapes: true,
267+
escapeSequenceInIdentifier: true,
248268
}
249269
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"\u{31}":"\u{31}"}

0 commit comments

Comments
 (0)