Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 37 additions & 28 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
stringify,
pathToRegexp,
TokenData,
PathError,
} from "./index.js";
import {
PARSER_TESTS,
Expand All @@ -18,44 +19,62 @@ import {
* Dynamically generate the entire test suite.
*/
describe("path-to-regexp", () => {
describe("ParseError", () => {
it("should contain original path and debug url", () => {
const error = new PathError(
"Unexpected END at index 7, expected }",
"/{:foo,",
);

expect(error).toBeInstanceOf(TypeError);
expect(error.message).toBe(
"Unexpected END at index 7, expected }: /{:foo,; visit https://git.new/pathToRegexpError for info",
);
expect(error.originalPath).toBe("/{:foo,");
});

it("should omit original url when undefined", () => {
const error = new PathError(
"Unexpected END at index 7, expected }",
undefined,
);

expect(error).toBeInstanceOf(TypeError);
expect(error.message).toBe(
"Unexpected END at index 7, expected }; visit https://git.new/pathToRegexpError for info",
);
expect(error.originalPath).toBeUndefined();
});
});

describe("parse errors", () => {
it("should throw on unbalanced group", () => {
expect(() => parse("/{:foo,")).toThrow(
new TypeError(
"Unexpected END at index 7, expected }: /{:foo,; visit https://git.new/pathToRegexpError for info",
),
new PathError("Unexpected END at index 7, expected }", "/{:foo,"),
);
});

it("should throw on nested unbalanced group", () => {
expect(() => parse("/{:foo/{x,y}")).toThrow(
new TypeError(
"Unexpected END at index 12, expected }: /{:foo/{x,y}; visit https://git.new/pathToRegexpError for info",
),
new PathError("Unexpected END at index 12, expected }", "/{:foo/{x,y}"),
);
});

it("should throw on missing param name", () => {
expect(() => parse("/:/")).toThrow(
new TypeError(
"Missing parameter name at index 2: /:/; visit https://git.new/pathToRegexpError for info",
),
new PathError("Missing parameter name at index 2", "/:/"),
);
});

it("should throw on missing wildcard name", () => {
expect(() => parse("/*/")).toThrow(
new TypeError(
"Missing parameter name at index 2: /*/; visit https://git.new/pathToRegexpError for info",
),
new PathError("Missing parameter name at index 2", "/*/"),
);
});

it("should throw on unterminated quote", () => {
expect(() => parse('/:"foo')).toThrow(
new TypeError(
'Unterminated quote at index 2: /:"foo; visit https://git.new/pathToRegexpError for info',
),
new PathError("Unterminated quote at index 2", '/:"foo'),
);
});
});
Expand Down Expand Up @@ -105,9 +124,7 @@ describe("path-to-regexp", () => {
describe("pathToRegexp errors", () => {
it("should throw when missing text between params", () => {
expect(() => pathToRegexp("/:foo:bar")).toThrow(
new TypeError(
'Missing text before "bar": /:foo:bar; visit https://git.new/pathToRegexpError for info',
),
new PathError('Missing text before "bar" param', "/:foo:bar"),
);
});

Expand All @@ -119,11 +136,7 @@ describe("path-to-regexp", () => {
{ type: "param", name: "b" },
]),
),
).toThrow(
new TypeError(
'Missing text before "b"; visit https://git.new/pathToRegexpError for info',
),
);
).toThrow(new PathError('Missing text before "b" param', undefined));
});

it("should throw with `originalPath` when missing text between params using TokenData", () => {
Expand All @@ -137,11 +150,7 @@ describe("path-to-regexp", () => {
"/[a][b]",
),
),
).toThrow(
new TypeError(
'Missing text before "b": /[a][b]; visit https://git.new/pathToRegexpError for info',
),
);
).toThrow(new PathError('Missing text before "b" param', "/[a][b]"));
});

it("should contain the error line", () => {
Expand Down
47 changes: 23 additions & 24 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const DEFAULT_DELIMITER = "/";
const NOOP_VALUE = (value: string) => value;
const ID_START = /^[$_\p{ID_Start}]$/u;
const ID_CONTINUE = /^[$\u200c\u200d\p{ID_Continue}]$/u;
const DEBUG_URL = "https://git.new/pathToRegexpError";

/**
* Encode a string into another string.
Expand Down Expand Up @@ -112,16 +111,6 @@ function escape(str: string) {
return str.replace(/[.+*?^${}()[\]|/\\]/g, "\\$&");
}

/**
* Format error so it's easier to debug.
*/
function errorMessage(text: string, originalPath: string | undefined) {
let message = text;
if (originalPath !== undefined) message += `: ${originalPath}`;
message += `; visit ${DEBUG_URL} for info`;
return message;
}

/**
* Plain text.
*/
Expand Down Expand Up @@ -179,6 +168,21 @@ export class TokenData {
) {}
}

/**
* ParseError is thrown when there is an error processing the path.
*/
export class PathError extends TypeError {
constructor(
message: string,
public readonly originalPath: string | undefined,
) {
let text = message;
if (originalPath) text += `: ${originalPath}`;
text += `; visit https://git.new/pathToRegexpError for info`;
super(text);
}
}

/**
* Parse a string for the raw tokens.
*/
Expand Down Expand Up @@ -215,16 +219,12 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
}

if (pos) {
throw new TypeError(
errorMessage(`Unterminated quote at index ${pos}`, str),
);
throw new PathError(`Unterminated quote at index ${pos}`, str);
}
}

if (!value) {
throw new TypeError(
errorMessage(`Missing parameter name at index ${index}`, str),
);
throw new PathError(`Missing parameter name at index ${index}`, str);
}

return value;
Expand Down Expand Up @@ -292,11 +292,9 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
continue;
}

throw new TypeError(
errorMessage(
`Unexpected ${type} at index ${index}, expected ${endType}`,
str,
),
throw new PathError(
`Unexpected ${type} at index ${index}, expected ${endType}`,
str,
);
}

Expand Down Expand Up @@ -564,8 +562,9 @@ function toRegExpSource(

if (token.type === "param" || token.type === "wildcard") {
if (!isSafeSegmentParam && !backtrack) {
throw new TypeError(
errorMessage(`Missing text before "${token.name}"`, originalPath),
throw new PathError(
`Missing text before "${token.name}" ${token.type}`,
originalPath,
);
}

Expand Down