Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
66 changes: 39 additions & 27 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,
ParseError,
} from "./index.js";
import {
PARSER_TESTS,
Expand All @@ -18,44 +19,65 @@ import {
* Dynamically generate the entire test suite.
*/
describe("path-to-regexp", () => {
describe("ParseError", () => {
it("should contain original path and debug url", () => {
const error = new ParseError(
"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 ParseError(
"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 ParseError("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 ParseError(
"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 ParseError("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 ParseError("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 ParseError("Unterminated quote at index 2", '/:"foo'),
);
});
});
Expand Down Expand Up @@ -105,9 +127,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 ParseError('Missing text before "bar" param', "/:foo:bar"),
);
});

Expand All @@ -119,11 +139,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 ParseError('Missing text before "b" param', undefined));
});

it("should throw with `originalPath` when missing text between params using TokenData", () => {
Expand All @@ -137,11 +153,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 ParseError('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 ParseError 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 ParseError(`Unterminated quote at index ${pos}`, str);
}
}

if (!value) {
throw new TypeError(
errorMessage(`Missing parameter name at index ${index}`, str),
);
throw new ParseError(`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 ParseError(
`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 ParseError(
`Missing text before "${token.name}" ${token.type}`,
originalPath,
);
}

Expand Down