Skip to content

Commit 024832e

Browse files
committed
Add a stringify API
1 parent ed1095e commit 024832e

File tree

3 files changed

+117
-26
lines changed

3 files changed

+117
-26
lines changed

src/cases.spec.ts

+72-23
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import type {
2-
MatchOptions,
3-
Match,
4-
ParseOptions,
5-
Token,
6-
CompileOptions,
7-
ParamData,
1+
import {
2+
type MatchOptions,
3+
type Match,
4+
type ParseOptions,
5+
type Token,
6+
type CompileOptions,
7+
type ParamData,
8+
TokenData,
89
} from "./index.js";
910

1011
export interface ParserTestSet {
1112
path: string;
1213
options?: ParseOptions;
13-
expected: Token[];
14+
expected: TokenData;
15+
}
16+
17+
export interface StringifyTestSet {
18+
data: TokenData;
19+
options?: ParseOptions;
20+
expected: string;
1421
}
1522

1623
export interface CompileTestSet {
@@ -34,56 +41,98 @@ export interface MatchTestSet {
3441
export const PARSER_TESTS: ParserTestSet[] = [
3542
{
3643
path: "/",
37-
expected: [{ type: "text", value: "/" }],
44+
expected: new TokenData([{ type: "text", value: "/" }]),
3845
},
3946
{
4047
path: "/:test",
41-
expected: [
48+
expected: new TokenData([
4249
{ type: "text", value: "/" },
4350
{ type: "param", name: "test" },
44-
],
51+
]),
4552
},
4653
{
4754
path: '/:"0"',
48-
expected: [
55+
expected: new TokenData([
4956
{ type: "text", value: "/" },
5057
{ type: "param", name: "0" },
51-
],
58+
]),
5259
},
5360
{
5461
path: "/:_",
55-
expected: [
62+
expected: new TokenData([
5663
{ type: "text", value: "/" },
5764
{ type: "param", name: "_" },
58-
],
65+
]),
5966
},
6067
{
6168
path: "/:café",
62-
expected: [
69+
expected: new TokenData([
6370
{ type: "text", value: "/" },
6471
{ type: "param", name: "café" },
65-
],
72+
]),
6673
},
6774
{
6875
path: '/:"123"',
69-
expected: [
76+
expected: new TokenData([
7077
{ type: "text", value: "/" },
7178
{ type: "param", name: "123" },
72-
],
79+
]),
7380
},
7481
{
7582
path: '/:"1\\"\\2\\"3"',
76-
expected: [
83+
expected: new TokenData([
7784
{ type: "text", value: "/" },
7885
{ type: "param", name: '1"2"3' },
79-
],
86+
]),
8087
},
8188
{
8289
path: "/*path",
83-
expected: [
90+
expected: new TokenData([
8491
{ type: "text", value: "/" },
8592
{ type: "wildcard", name: "path" },
86-
],
93+
]),
94+
},
95+
];
96+
97+
export const STRINGIFY_TESTS: StringifyTestSet[] = [
98+
{
99+
data: new TokenData([{ type: "text", value: "/" }]),
100+
expected: "/",
101+
},
102+
{
103+
data: new TokenData([
104+
{ type: "text", value: "/" },
105+
{ type: "param", name: "test" },
106+
]),
107+
expected: "/:test",
108+
},
109+
{
110+
data: new TokenData([
111+
{ type: "text", value: "/" },
112+
{ type: "param", name: "café" },
113+
]),
114+
expected: "/:café",
115+
},
116+
{
117+
data: new TokenData([
118+
{ type: "text", value: "/" },
119+
{ type: "param", name: "0" },
120+
]),
121+
expected: '/:"0"',
122+
},
123+
{
124+
data: new TokenData([
125+
{ type: "text", value: "/" },
126+
{ type: "wildcard", name: "test" },
127+
]),
128+
expected: "/*test",
129+
},
130+
{
131+
data: new TokenData([
132+
{ type: "text", value: "/" },
133+
{ type: "wildcard", name: "0" },
134+
]),
135+
expected: '/*"0"',
87136
},
88137
];
89138

src/index.spec.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { describe, it, expect } from "vitest";
2-
import { parse, compile, match } from "./index.js";
3-
import { PARSER_TESTS, COMPILE_TESTS, MATCH_TESTS } from "./cases.spec.js";
2+
import { parse, compile, match, stringify } from "./index.js";
3+
import {
4+
PARSER_TESTS,
5+
COMPILE_TESTS,
6+
MATCH_TESTS,
7+
STRINGIFY_TESTS,
8+
} from "./cases.spec.js";
49

510
/**
611
* Dynamically generate the entire test suite.
@@ -94,7 +99,17 @@ describe("path-to-regexp", () => {
9499
({ path, options, expected }) => {
95100
it("should parse the path", () => {
96101
const data = parse(path, options);
97-
expect(data.tokens).toEqual(expected);
102+
expect(data).toEqual(expected);
103+
});
104+
},
105+
);
106+
107+
describe.each(STRINGIFY_TESTS)(
108+
"stringify $tokens with $options",
109+
({ data, expected }) => {
110+
it("should stringify the path", () => {
111+
const path = stringify(data);
112+
expect(path).toEqual(expected);
98113
});
99114
},
100115
);

src/index.ts

+27
Original file line numberDiff line numberDiff line change
@@ -595,3 +595,30 @@ function negate(delimiter: string, backtrack: string) {
595595
if (isSimple) return `[^${escape(values.join(""))}]`;
596596
return `(?:(?!${values.map(escape).join("|")}).)`;
597597
}
598+
599+
/**
600+
* Stringify token data into a path string.
601+
*/
602+
export function stringify(data: TokenData) {
603+
let result = "";
604+
for (const token of data.tokens) result += stringifyToken(token);
605+
return result;
606+
}
607+
608+
function stringifyToken(token: Token) {
609+
if (token.type === "text") return token.value;
610+
if (token.type === "group") return `{${stringifyToken(token)}}`;
611+
612+
const isSafe = isNameSafe(token.name);
613+
const key = isSafe ? token.name : JSON.stringify(token.name);
614+
615+
if (token.type === "param") return `:${key}`;
616+
if (token.type === "wildcard") return `*${key}`;
617+
throw new TypeError(`Unexpected token: ${token}`);
618+
}
619+
620+
function isNameSafe(name: string) {
621+
const [first, ...rest] = name;
622+
if (!ID_START.test(first)) return false;
623+
return rest.every((char) => ID_CONTINUE.test(char));
624+
}

0 commit comments

Comments
 (0)