Skip to content

Commit fffe02d

Browse files
committed
Make stringify produce parser-compatible quoted names
Param names that aren't bare identifiers were quoted with JSON.stringify, which emits escapes like \n that parse() reads back as the literal letter n, so a name containing a control character didn't survive a parse->stringify->parse round trip. Quote with only the escapes the parser understands instead.
1 parent c83a925 commit fffe02d

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

src/cases.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ export const STRINGIFY_TESTS: StringifyTestSet[] = [
221221
]),
222222
expected: "\\\\:test",
223223
},
224+
{
225+
data: new TokenData([
226+
{ type: "text", value: "/" },
227+
{ type: "param", name: "a\nb" },
228+
]),
229+
expected: '/:"a\nb"',
230+
},
224231
{
225232
data: {
226233
tokens: [

src/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,14 +655,25 @@ export function stringify(data: TokenData): string {
655655
return stringifyTokens(data.tokens, 0);
656656
}
657657

658+
/**
659+
* Wrap a parameter name in quotes, escaping only the characters the parser
660+
* treats specially inside a quoted name (`"` and `\`). `JSON.stringify` can't
661+
* be used here because it emits escapes like `\n` that the parser reads back as
662+
* the literal character `n`, so names with control characters wouldn't survive
663+
* a `parse` -> `stringify` -> `parse` round trip.
664+
*/
665+
function quoteName(name: string): string {
666+
return `"${name.replace(/["\\]/g, "\\$&")}"`;
667+
}
668+
658669
/**
659670
* Stringify a parameter name, escaping when it cannot be emitted directly.
660671
*/
661672
function stringifyName(name: string, next: Token | undefined): string {
662-
if (!ID.test(name)) return JSON.stringify(name);
673+
if (!ID.test(name)) return quoteName(name);
663674

664675
if (next?.type === "text" && ID_CONTINUE.test(next.value[0])) {
665-
return JSON.stringify(name);
676+
return quoteName(name);
666677
}
667678

668679
return name;

0 commit comments

Comments
 (0)