Skip to content

Commit 40d05ea

Browse files
authored
fix(toml): fix handling of leading and trailing underscores in number literals (#6605)
1 parent 839309c commit 40d05ea

File tree

2 files changed

+40
-11
lines changed

2 files changed

+40
-11
lines changed

toml/_parser.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ function kv<T>(
235235
): ParserComponent<{ [key: string]: unknown }> {
236236
const Separator = character(separator);
237237
return (scanner: Scanner): ParseResult<{ [key: string]: unknown }> => {
238+
const position = scanner.position;
238239
const key = keyParser(scanner);
239240
if (!key.ok) return failure();
240241
const sep = Separator(scanner);
@@ -243,9 +244,12 @@ function kv<T>(
243244
}
244245
const value = valueParser(scanner);
245246
if (!value.ok) {
246-
throw new SyntaxError(
247-
`Value of key/value pair is invalid data format`,
248-
);
247+
const lineEndIndex = scanner.source.indexOf("\n", scanner.position);
248+
const endPosition = lineEndIndex > 0
249+
? lineEndIndex
250+
: scanner.source.length;
251+
const line = scanner.source.slice(position, endPosition);
252+
throw new SyntaxError(`Cannot parse value on line '${line}'`);
249253
}
250254
return success(unflat(key.body, value.body));
251255
};
@@ -520,7 +524,7 @@ export function symbols(scanner: Scanner): ParseResult<unknown> {
520524

521525
export const dottedKey = join(or([bareKey, basicString, literalString]), ".");
522526

523-
const BINARY_REGEXP = /0b[01_]+/y;
527+
const BINARY_REGEXP = /0b[01]+(?:_[01]+)*\b/y;
524528
export function binary(scanner: Scanner): ParseResult<number | string> {
525529
scanner.skipWhitespaces();
526530
const match = scanner.match(BINARY_REGEXP)?.[0];
@@ -531,7 +535,7 @@ export function binary(scanner: Scanner): ParseResult<number | string> {
531535
return isNaN(number) ? failure() : success(number);
532536
}
533537

534-
const OCTAL_REGEXP = /0o[0-7_]+/y;
538+
const OCTAL_REGEXP = /0o[0-7]+(?:_[0-7]+)*\b/y;
535539
export function octal(scanner: Scanner): ParseResult<number | string> {
536540
scanner.skipWhitespaces();
537541
const match = scanner.match(OCTAL_REGEXP)?.[0];
@@ -542,7 +546,7 @@ export function octal(scanner: Scanner): ParseResult<number | string> {
542546
return isNaN(number) ? failure() : success(number);
543547
}
544548

545-
const HEX_REGEXP = /0x[0-9a-f_]+/yi;
549+
const HEX_REGEXP = /0x[0-9a-f]+(?:_[0-9a-f]+)*\b/yi;
546550
export function hex(scanner: Scanner): ParseResult<number | string> {
547551
scanner.skipWhitespaces();
548552
const match = scanner.match(HEX_REGEXP)?.[0];
@@ -553,7 +557,7 @@ export function hex(scanner: Scanner): ParseResult<number | string> {
553557
return isNaN(number) ? failure() : success(number);
554558
}
555559

556-
const INTEGER_REGEXP = /[+-]?[0-9_]+/y;
560+
const INTEGER_REGEXP = /[+-]?[0-9]+(?:_[0-9]+)*\b/y;
557561
export function integer(scanner: Scanner): ParseResult<number | string> {
558562
scanner.skipWhitespaces();
559563
const match = scanner.match(INTEGER_REGEXP)?.[0];
@@ -564,7 +568,8 @@ export function integer(scanner: Scanner): ParseResult<number | string> {
564568
return success(int);
565569
}
566570

567-
const FLOAT_REGEXP = /[+-]?[0-9_]+(?:\.[0-9_]+)?(?:e[+-]?[0-9_]+)?/yi;
571+
const FLOAT_REGEXP =
572+
/[+-]?[0-9]+(?:_[0-9]+)*(?:\.[0-9]+(?:_[0-9]+)*)?(?:e[+-]?[0-9]+(?:_[0-9]+)*)?\b/yi;
568573
export function float(scanner: Scanner): ParseResult<number> {
569574
scanner.skipWhitespaces();
570575
const match = scanner.match(FLOAT_REGEXP)?.[0];
@@ -576,7 +581,7 @@ export function float(scanner: Scanner): ParseResult<number> {
576581
return success(float);
577582
}
578583

579-
const DATE_TIME_REGEXP = /\d{4}-\d{2}-\d{2}(?:[ 0-9TZ.:+-]+)?/y;
584+
const DATE_TIME_REGEXP = /\d{4}-\d{2}-\d{2}(?:[ 0-9TZ.:+-]+)?\b/y;
580585
export function dateTime(scanner: Scanner): ParseResult<Date> {
581586
scanner.skipWhitespaces();
582587
// example: 1979-05-27
@@ -591,7 +596,7 @@ export function dateTime(scanner: Scanner): ParseResult<Date> {
591596
return success(date);
592597
}
593598

594-
const LOCAL_TIME_REGEXP = /(\d{2}):(\d{2}):(\d{2})(?:\.[0-9]+)?/y;
599+
const LOCAL_TIME_REGEXP = /(\d{2}):(\d{2}):(\d{2})(?:\.[0-9]+)?\b/y;
595600
export function localTime(scanner: Scanner): ParseResult<string> {
596601
scanner.skipWhitespaces();
597602

toml/parse_test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,20 +241,28 @@ Deno.test({
241241
fn() {
242242
const parse = parserFactory(binary);
243243
assertEquals(parse("0b11010110"), 0b11010110); // 0b11010110 = 214
244+
assertEquals(parse("0b1101_0110"), 0b11010110);
244245
assertThrows(() => parse(""));
245246
assertThrows(() => parse("+Z"));
246247
assertThrows(() => parse("0x"));
248+
assertThrows(() => parse("0b_11010110"));
249+
assertThrows(() => parse("0b11010110_"));
250+
assertThrows(() => parse("0b1101__0110"));
247251
},
248252
});
249253
Deno.test({
250254
name: "parse() handles octal",
251255
fn() {
252256
const parse = parserFactory(octal);
253257
assertEquals(parse("0o01234567"), 0o01234567); // 0o01234567 = 342391
258+
assertEquals(parse("0o0123_4567"), 0o01234567); // 0o01234567 = 342391
254259
assertEquals(parse("0o755"), 0o755); // 0o755 = 493
255260
assertThrows(() => parse(""));
256261
assertThrows(() => parse("+Z"));
257262
assertThrows(() => parse("0x"));
263+
assertThrows(() => parse("0o_755"));
264+
assertThrows(() => parse("0o755_"));
265+
assertThrows(() => parse("0o0123__4567"));
258266
},
259267
});
260268
Deno.test({
@@ -263,11 +271,15 @@ Deno.test({
263271
const parse = parserFactory(hex);
264272

265273
assertEquals(parse("0xDEADBEEF"), 0xDEADBEEF); // 0xDEADBEEF = 3735928559
274+
assertEquals(parse("0xDEAD_BEEF"), 0xDEADBEEF); // 0xDEADBEEF = 3735928559
266275
assertEquals(parse("0xdeadbeef"), 0xdeadbeef); // 0xdeadbeef = 3735928559
267276
assertEquals(parse("0xdead_beef"), 0xdead_beef); // 0xdead_beef = 3735928559
268277
assertThrows(() => parse(""));
269278
assertThrows(() => parse("+Z"));
270279
assertThrows(() => parse("0x"));
280+
assertThrows(() => parse("0x_DEADBEEF"));
281+
assertThrows(() => parse("0xDEADBEEF_"));
282+
assertThrows(() => parse("0xDEAD__BEEF"));
271283
},
272284
});
273285
Deno.test({
@@ -281,6 +293,9 @@ Deno.test({
281293
assertThrows(() => parse(""));
282294
assertThrows(() => parse("+Z"));
283295
assertThrows(() => parse("0x"));
296+
assertThrows(() => parse("_123"));
297+
assertThrows(() => parse("123_"));
298+
assertThrows(() => parse("123__456"));
284299
},
285300
});
286301

@@ -299,6 +314,15 @@ Deno.test({
299314
assertThrows(() => parse(""));
300315
assertThrows(() => parse("X"));
301316
assertThrows(() => parse("e_+-"));
317+
assertThrows(() => parse("_3.1415"));
318+
assertThrows(() => parse("3_.1415"));
319+
assertThrows(() => parse("3._1415"));
320+
assertThrows(() => parse("3.1415_"));
321+
assertThrows(() => parse("3.14__15"));
322+
assertThrows(() => parse("_1e06"));
323+
assertThrows(() => parse("1_e06"));
324+
assertThrows(() => parse("1e_06"));
325+
assertThrows(() => parse("1e06_"));
302326
},
303327
});
304328

@@ -1358,7 +1382,7 @@ foo = BAR
13581382
`);
13591383
},
13601384
Error,
1361-
`invalid data format`,
1385+
"Parse error on line 2, column 6: Cannot parse value on line 'foo = BAR'",
13621386
);
13631387
},
13641388
});

0 commit comments

Comments
 (0)