fix: resolve REG-04 — frontmatter inline array parser now respects quoted commas (#1695)
Fixes #1694 The inline array parser used .split(',') which ignored quote boundaries, splitting "a, b" into two items. Replaced with a quote-aware splitter that tracks single/double quote state. Updated REG-04 test to assert correct behavior and added coverage for single-quoted and mixed-quote inline arrays.
This commit is contained in:
@@ -8,6 +8,38 @@ const { safeReadFile, normalizeMd, output, error } = require('./core.cjs');
|
||||
|
||||
// ─── Parsing engine ───────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Split a YAML inline array body on commas, respecting quoted strings.
|
||||
* e.g. '"a, b", c' → ['a, b', 'c']
|
||||
*/
|
||||
function splitInlineArray(body) {
|
||||
const items = [];
|
||||
let current = '';
|
||||
let inQuote = null; // null | '"' | "'"
|
||||
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const ch = body[i];
|
||||
if (inQuote) {
|
||||
if (ch === inQuote) {
|
||||
inQuote = null;
|
||||
} else {
|
||||
current += ch;
|
||||
}
|
||||
} else if (ch === '"' || ch === "'") {
|
||||
inQuote = ch;
|
||||
} else if (ch === ',') {
|
||||
const trimmed = current.trim();
|
||||
if (trimmed) items.push(trimmed);
|
||||
current = '';
|
||||
} else {
|
||||
current += ch;
|
||||
}
|
||||
}
|
||||
const trimmed = current.trim();
|
||||
if (trimmed) items.push(trimmed);
|
||||
return items;
|
||||
}
|
||||
|
||||
function extractFrontmatter(content) {
|
||||
const frontmatter = {};
|
||||
// Find ALL frontmatter blocks at the start of the file.
|
||||
@@ -53,8 +85,8 @@ function extractFrontmatter(content) {
|
||||
// Push new context for potential nested content
|
||||
stack.push({ obj: current.obj[key], key: null, indent });
|
||||
} else if (value.startsWith('[') && value.endsWith(']')) {
|
||||
// Inline array: key: [a, b, c]
|
||||
current.obj[key] = value.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
||||
// Inline array: key: [a, b, c] — quote-aware split (REG-04 fix)
|
||||
current.obj[key] = splitInlineArray(value.slice(1, -1));
|
||||
current.key = null;
|
||||
} else {
|
||||
// Simple key: value
|
||||
|
||||
@@ -54,21 +54,22 @@ describe('extractFrontmatter', () => {
|
||||
assert.deepStrictEqual(result.key, ['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
test('handles quoted commas in inline arrays — REG-04 known limitation', () => {
|
||||
// REG-04: The split(',') on line 53 does NOT respect quotes.
|
||||
// The parser WILL split on commas inside quotes, producing wrong results.
|
||||
// This test documents the CURRENT (buggy) behavior.
|
||||
test('handles quoted commas in inline arrays — REG-04 fixed', () => {
|
||||
const content = '---\nkey: ["a, b", c]\n---\n';
|
||||
const result = extractFrontmatter(content);
|
||||
// Current behavior: splits on ALL commas, producing 3 items instead of 2
|
||||
// Expected correct behavior would be: ["a, b", "c"]
|
||||
// Actual current behavior: ["a", "b", "c"] (split ignores quotes)
|
||||
assert.ok(Array.isArray(result.key), 'should produce an array');
|
||||
assert.ok(result.key.length >= 2, 'should produce at least 2 items from comma split');
|
||||
// The bug produces ["a", "b\"", "c"] or similar — the exact output depends on
|
||||
// how the regex strips quotes after the split.
|
||||
// We verify the key insight: the result has MORE items than intended (known limitation).
|
||||
assert.ok(result.key.length > 2, 'REG-04: split produces more items than intended due to quoted comma bug');
|
||||
assert.deepStrictEqual(result.key, ['a, b', 'c']);
|
||||
});
|
||||
|
||||
test('handles single-quoted commas in inline arrays', () => {
|
||||
const content = "---\nkey: ['x, y', z]\n---\n";
|
||||
const result = extractFrontmatter(content);
|
||||
assert.deepStrictEqual(result.key, ['x, y', 'z']);
|
||||
});
|
||||
|
||||
test('handles mixed quotes in inline arrays', () => {
|
||||
const content = '---\nkey: ["a, b", \'c, d\', e]\n---\n';
|
||||
const result = extractFrontmatter(content);
|
||||
assert.deepStrictEqual(result.key, ['a, b', 'c, d', 'e']);
|
||||
});
|
||||
|
||||
test('returns empty object for no frontmatter', () => {
|
||||
|
||||
Reference in New Issue
Block a user