readQuoted treated \X as an escape for any X, so a single literal backslash in any quoted attribute value was silently dropped (data-path="C:\Users" parsed as C:Users) and a doubled backslash collapsed to one. Only the delimiter and the backslash itself are escapes now; every other backslash is a literal character. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { parse } from "../src/index.ts";
|
|
|
|
// One literal backslash character. Composing the .wrn sources below by
|
|
// concatenation keeps the escaping unambiguous at a glance.
|
|
const BS = String.fromCharCode(92);
|
|
const Q = '"';
|
|
|
|
const view = (attr: string) =>
|
|
`page Esc {\n view {\n <main>\n <div ${attr}>x</div>\n </main>\n }\n}\n`;
|
|
|
|
const attrValue = (source: string): string => {
|
|
const ast = parse(source) as unknown;
|
|
const found: string[] = [];
|
|
const walk = (node: unknown): void => {
|
|
if (!node || typeof node !== "object") return;
|
|
if (Array.isArray(node)) {
|
|
node.forEach(walk);
|
|
return;
|
|
}
|
|
const record = node as Record<string, unknown>;
|
|
if (typeof record.value === "string" && typeof record.name === "string") {
|
|
found.push(record.value);
|
|
}
|
|
for (const key of Object.keys(record)) walk(record[key]);
|
|
};
|
|
walk(ast);
|
|
return found[found.length - 1] ?? "";
|
|
};
|
|
|
|
test("a backslash-escaped delimiter becomes a bare quote", () => {
|
|
// api="fn({ a: \"b\" })" -> fn({ a: "b" })
|
|
const attr = `api=${Q}fn({ a: ${BS}${Q}b${BS}${Q} })${Q}`;
|
|
expect(attrValue(view(attr))).toBe(`fn({ a: ${Q}b${Q} })`);
|
|
});
|
|
|
|
test("a doubled backslash collapses to exactly one", () => {
|
|
// data-x="a\\b" -> a\b
|
|
const attr = `data-x=${Q}a${BS}${BS}b${Q}`;
|
|
expect(attrValue(view(attr))).toBe(`a${BS}b`);
|
|
});
|
|
|
|
test("a lone backslash before an ordinary character is preserved", () => {
|
|
// data-path="C:\Users\name" must survive the lexer intact.
|
|
const attr = `data-path=${Q}C:${BS}Users${BS}name${Q}`;
|
|
expect(attrValue(view(attr))).toBe(`C:${BS}Users${BS}name`);
|
|
});
|
|
|
|
test("a lone backslash before an escape-looking character is preserved verbatim", () => {
|
|
// data-x="a\nb" is two characters a, backslash, n, b -- not a newline.
|
|
const attr = `data-x=${Q}a${BS}nb${Q}`;
|
|
expect(attrValue(view(attr))).toBe(`a${BS}nb`);
|
|
});
|