Skip to content

Commit 7e4a9a3

Browse files
authored
fix(front-matter): handle empty frontMatter data (#6481)
1 parent 9943308 commit 7e4a9a3

File tree

8 files changed

+103
-17
lines changed

8 files changed

+103
-17
lines changed

front_matter/_formats.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,20 @@ export type Format = "yaml" | "toml" | "json";
88

99
const BOM = "\\ufeff?";
1010

11-
const YAML_DELIMITER = "= yaml =|---";
12-
const YAML_HEADER = `(?:---yaml|${YAML_DELIMITER})`;
13-
const YAML_FOOTER = `(?:---|${YAML_DELIMITER})`;
11+
const YAML_HEADER = `(?:---yaml|= yaml =|---)`;
12+
const YAML_FOOTER = `(?:= yaml =|---)`;
1413

15-
const TOML_DELIMITER = "\\+\\+\\+|= toml =";
16-
const TOML_HEADER = `(?:---toml|${TOML_DELIMITER})`;
17-
const TOML_FOOTER = `(?:---|${TOML_DELIMITER})`;
14+
const TOML_HEADER = `(?:---toml|\\+\\+\\+|= toml =)`;
15+
const TOML_FOOTER = `(?:---|\\+\\+\\+|= toml =)`;
1816

19-
const JSON_DELIMITER = `= json =`;
20-
const JSON_HEADER = `(?:---json|${JSON_DELIMITER})`;
21-
const JSON_FOOTER = `(?:---|${JSON_DELIMITER})`;
17+
const JSON_HEADER = `(?:---json|= json =)`;
18+
const JSON_FOOTER = `(?:---|= json =)`;
2219

2320
const WHITESPACES = "\\s*";
2421
const NEWLINE = "\\r?\\n";
2522

2623
const FRONT_MATTER = "(?<frontMatter>.+?)";
27-
const BODY = "(?<body>.*)";
24+
const BODY = "(?:\\r?\\n(?<body>.+))?";
2825

2926
export const RECOGNIZE_YAML_REGEXP = new RegExp(
3027
`^${YAML_HEADER}${WHITESPACES}${NEWLINE}`,
@@ -40,15 +37,15 @@ export const RECOGNIZE_JSON_REGEXP = new RegExp(
4037
);
4138

4239
export const EXTRACT_YAML_REGEXP = new RegExp(
43-
`^${BOM}${YAML_HEADER}${WHITESPACES}${NEWLINE}${WHITESPACES}${FRONT_MATTER}${WHITESPACES}${NEWLINE}${YAML_FOOTER}${WHITESPACES}${NEWLINE}?${BODY}$`,
40+
`^${BOM}${YAML_HEADER}${WHITESPACES}${NEWLINE}${WHITESPACES}(?:${FRONT_MATTER}${WHITESPACES}${NEWLINE})?${YAML_FOOTER}${WHITESPACES}${BODY}$`,
4441
"is",
4542
);
4643
export const EXTRACT_TOML_REGEXP = new RegExp(
47-
`^${BOM}${TOML_HEADER}${WHITESPACES}${NEWLINE}${WHITESPACES}${FRONT_MATTER}${WHITESPACES}${NEWLINE}${TOML_FOOTER}${WHITESPACES}${NEWLINE}?${BODY}$`,
44+
`^${BOM}${TOML_HEADER}${WHITESPACES}${NEWLINE}${WHITESPACES}(?:${FRONT_MATTER}${WHITESPACES}${NEWLINE})?${TOML_FOOTER}${WHITESPACES}${BODY}$`,
4845
"is",
4946
);
5047
export const EXTRACT_JSON_REGEXP = new RegExp(
51-
`^${BOM}${JSON_HEADER}${WHITESPACES}${NEWLINE}${WHITESPACES}${FRONT_MATTER}${WHITESPACES}${NEWLINE}${JSON_FOOTER}${WHITESPACES}${NEWLINE}?${BODY}$`,
48+
`^${BOM}${JSON_HEADER}${WHITESPACES}${NEWLINE}${WHITESPACES}(?:${FRONT_MATTER}${WHITESPACES}${NEWLINE})?${JSON_FOOTER}${WHITESPACES}${BODY}$`,
5249
"is",
5350
);
5451

front_matter/json.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ export type { Extract };
3535
*/
3636
export function extract<T>(text: string): Extract<T> {
3737
const { frontMatter, body } = extractFrontMatter(text, EXTRACT_JSON_REGEXP);
38-
const attrs = JSON.parse(frontMatter) as T;
38+
const attrs = (frontMatter ? JSON.parse(frontMatter) : {}) as T;
3939
return { frontMatter, body, attrs };
4040
}

front_matter/json_test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,27 @@ Deno.test("extractJson() allows whitespaces after the header", () => {
5656
assertEquals(extract('---json \n{"foo": 0}\n---\n').attrs, { foo: 0 });
5757
assertEquals(extract('= json = \n{"foo": 0}\n---\n').attrs, { foo: 0 });
5858
});
59+
60+
Deno.test("extractJson() handles empty frontMatter", () => {
61+
assertEquals(
62+
extract("---json\n---\n"),
63+
{ attrs: {}, body: "", frontMatter: "" },
64+
);
65+
66+
assertEquals(
67+
extract("---json\n\n---\n"),
68+
{ attrs: {}, body: "", frontMatter: "" },
69+
);
70+
assertEquals(
71+
extract("---json\n \n---\n"),
72+
{ attrs: {}, body: "", frontMatter: "" },
73+
);
74+
});
75+
76+
Deno.test("extractJson() throws at missing newline before body", () => {
77+
assertThrows(
78+
() => extract('---json\n{ "foo": "bar" }\n---body'),
79+
TypeError,
80+
"Unexpected end of input",
81+
);
82+
});

front_matter/test_test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3-
import { assert, assertThrows } from "@std/assert";
3+
import { assert, assertFalse, assertThrows } from "@std/assert";
44

55
import { type Format, test } from "./test.ts";
66

@@ -84,3 +84,14 @@ Deno.test("test() handles invalid toml input", () => {
8484
assert(!test("= toml =\n"));
8585
assert(!test("---\nasdasdasd"));
8686
});
87+
88+
Deno.test("test() handles wrong format", () => {
89+
const result = test(
90+
`---json
91+
{"title": "Three dashes followed by format marks the spot"}
92+
---
93+
`,
94+
["yaml"],
95+
);
96+
assertFalse(result);
97+
});

front_matter/toml.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export type { Extract };
3636
*/
3737
export function extract<T>(text: string): Extract<T> {
3838
const { frontMatter, body } = extractFrontMatter(text, EXTRACT_TOML_REGEXP);
39-
const attrs = parse(frontMatter) as T;
39+
40+
const attrs = (frontMatter ? parse(frontMatter) : {}) as T;
4041
return { frontMatter, body, attrs };
4142
}

front_matter/toml_test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,26 @@ Deno.test("extractToml() allows whitespaces after the header", () => {
7878
assertEquals(extract("+++ \nfoo = 0\n--- \n").attrs, { foo: 0 });
7979
assertEquals(extract("= toml = \nfoo = 0\n---\n").attrs, { foo: 0 });
8080
});
81+
82+
Deno.test("extractToml() handles empty frontMatter", () => {
83+
assertEquals(
84+
extract("---toml\n---"),
85+
{ attrs: {}, body: "", frontMatter: "" },
86+
);
87+
assertEquals(
88+
extract("---toml\n\n---\n"),
89+
{ attrs: {}, body: "", frontMatter: "" },
90+
);
91+
assertEquals(
92+
extract("---toml\n \n---\n"),
93+
{ attrs: {}, body: "", frontMatter: "" },
94+
);
95+
});
96+
97+
Deno.test("extractToml() throws at missing newline before body", () => {
98+
assertThrows(
99+
() => extract('---toml\nfoo = "bar"\n---body'),
100+
TypeError,
101+
"Unexpected end of input",
102+
);
103+
});

front_matter/yaml.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,6 @@ export type { Extract };
3939
*/
4040
export function extract<T>(text: string): Extract<T> {
4141
const { frontMatter, body } = extractFrontMatter(text, EXTRACT_YAML_REGEXP);
42-
const attrs = parse(frontMatter) as T;
42+
const attrs = (frontMatter ? parse(frontMatter) : {}) as T;
4343
return { frontMatter, body, attrs };
4444
}

front_matter/yaml_test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,33 @@ Deno.test("(unstable)extractYaml() parses with schema options", () => {
9696
},
9797
);
9898
});
99+
100+
Deno.test("extractYaml() handles empty frontMatter", () => {
101+
assertEquals(
102+
extract("---\n---\n"),
103+
{ attrs: {}, body: "", frontMatter: "" },
104+
);
105+
106+
assertEquals(
107+
extract("---\n\n---\n"),
108+
{ attrs: {}, body: "", frontMatter: "" },
109+
);
110+
assertEquals(
111+
extract("---\n \n---\n"),
112+
{ attrs: {}, body: "", frontMatter: "" },
113+
);
114+
115+
assertThrows(
116+
() => extract("---yaml\nfoo: bar\n---body"),
117+
TypeError,
118+
"Unexpected end of input",
119+
);
120+
});
121+
122+
Deno.test("extractYaml() throws at missing newline before body", () => {
123+
assertThrows(
124+
() => extract("---yaml\nfoo: bar\n---body"),
125+
TypeError,
126+
"Unexpected end of input",
127+
);
128+
});

0 commit comments

Comments
 (0)