Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions toml/_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,11 +785,28 @@ export const value = or([

export const pair = kv(dottedKey, "=", value);

function documentStatement<T>(parser: ParserComponent<T>): ParserComponent<T> {
return (scanner: Scanner) => {
const result = parser(scanner);
if (!result.ok) return result;
scanner.skipWhitespaces();
// Leave comments and newlines for nextUntilChar().
if (
!scanner.eof() && !scanner.isCurrentCharEOL() && scanner.char() !== "#"
) {
throw new SyntaxError(
"Cannot parse the TOML: Expected a newline or end of input after a statement",
);
}
return result;
};
}

export function block(
scanner: Scanner,
): ParseResult<Block> {
scanner.nextUntilChar();
const result = merge(repeat(pair))(scanner);
const result = merge(repeat(documentStatement(pair)))(scanner);
if (result.ok) return success({ type: "Block", value: result.body });
return failure();
}
Expand All @@ -798,7 +815,7 @@ export const tableHeader = surround("[", dottedKey, "]");

export function table(scanner: Scanner): ParseResult<Table> {
scanner.nextUntilChar();
const header = tableHeader(scanner);
const header = documentStatement(tableHeader)(scanner);
if (!header.ok) return failure();
scanner.nextUntilChar();
const b = block(scanner);
Expand All @@ -815,7 +832,7 @@ export function tableArray(
scanner: Scanner,
): ParseResult<TableArray> {
scanner.nextUntilChar();
const header = tableArrayHeader(scanner);
const header = documentStatement(tableArrayHeader)(scanner);
if (!header.ok) return failure();
scanner.nextUntilChar();
const b = block(scanner);
Expand Down
4 changes: 0 additions & 4 deletions toml/official_parse_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,8 @@ const ignored = [
"invalid/inline-table/overwrite-08",
"invalid/inline-table/overwrite-10",
"invalid/integer/capital-hex",
"invalid/key/after-",
"invalid/key/dotted-redefine-table-",
"invalid/key/duplicate-keys-",
"invalid/key/no-eol-01",
"invalid/key/no-eol-03",
"invalid/key/no-eol-04",
"invalid/local-datetime/hour-over",
"invalid/local-datetime/no-secs",
"invalid/local-time/hour-over",
Expand Down
80 changes: 80 additions & 0 deletions toml/parse_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,3 +893,83 @@ Deno.test({
assert(success);
},
});

Deno.test("parse() rejects missing newlines after key/value pairs", () => {
for (
const input of [
"a = 1 b = 2",
"a = true\tb = 2",
'a = "hello"b = 2',
"a = 'hello'\"b\" = 2",
'a = """hello""" b = 2',
"a = '''hello''' b = 2",
'a = """first\nlast""" b = 2',
"a = '''first\nlast''' b = 2",
"a = [1, 2] b = 2",
"a = [\n1,\n2,\n] b = 2",
"a = { x = 1, y = [2] } b = 2",
"a = 1 [next]",
"a = [1][[next]]",
]
) {
for (const prefix of ["", "[table]\n", "[[table]]\n"]) {
assertThrows(() => parse(prefix + input), SyntaxError);
}
}
});

Deno.test("parse() rejects missing newlines after table headers", () => {
for (const header of ["[table]", "[[table]]"]) {
for (const separator of ["", " ", "\t", "\r"]) {
for (const next of ["a = 1", "[next]", "[[next]]"]) {
assertThrows(() => parse(header + separator + next), SyntaxError);
}
}
}
});

Deno.test("parse() accepts newlines and comments after document statements", () => {
for (const newline of ["\n", "\r\n"]) {
for (const ending of ["", " \t", "#", " # comment"]) {
const input = [
"a = { x = 1, y = [2, 3] }",
"[table]",
"b = [1, # array comment" + newline + "2]",
'c = """first' + newline + 'last"""',
"[[items]]",
"d = '''first" + newline + "last'''",
].join(ending + newline) + ending + newline;
assertEquals(parse(input), {
a: { x: 1, y: [2, 3] },
table: { b: [1, 2], c: "first" + newline + "last" },
items: [{ d: "first" + newline + "last" }],
});
}
}
});

Deno.test("parse() accepts document statements at EOF", () => {
for (const ending of ["", " \t", "#", " # comment"]) {
assertEquals(parse("a = 1" + ending), { a: 1 });
assertEquals(parse("[table]" + ending), { table: {} });
assertEquals(parse("[[table]]" + ending), { table: [{}] });
}
});

Deno.test("parse() reports the position of a missing statement newline", () => {
assertThrows(
() => parse("a = 1 b = 2"),
SyntaxError,
"line 1, column 6",
);
assertThrows(
() => parse("# comment\r\n[table] a = 1"),
SyntaxError,
"line 2, column 8",
);
assertThrows(
() => parse('a = """first\nlast""" b = 2'),
SyntaxError,
"line 2, column 8",
);
});
Loading