Skip to content
Closed
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
2 changes: 1 addition & 1 deletion packages/formatter/src/addMissingParentheses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function addMissingParentheses(type: string): string {

validType = (validType + "\n..." + missingClosingChars.join("")).replace(
// Change (param: ...) to (param) => __RETURN_TYPE__ if needed
/(\([a-zA-Z0-9]*:[^)]*\))/,
/(\([a-zA-Z0-9]*:[^()\n]*\))/,
(p1) => `${p1} => ...`
);

Expand Down
31 changes: 18 additions & 13 deletions packages/formatter/src/errorMessagePrettifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {

return [
{
pattern: /(?:\s)'"(.*?)(?<!\\)"'(?:\s|:|.|$)/g,
pattern: /(?:\s)'"((?:(?!\s'"|"'|\n).)*)"'(?:\s|:|.|$)/g,
replacer: async (p1: string) => formatTypeBlock("", `"${p1}"`, codeBlock),
},
{
pattern: /['“](declare module )['”](.*)['“];['”]/g,
pattern: /['“](declare module )['”]([^'“”]*)['“];['”]/g,
replacer: (p1: string, p2: string) =>
formatTypeScriptBlock(`${p1} "${p2}"`),
},
{
pattern:
/(is missing the following properties from type\s?)'(.*)': ((?:#?\w+, )*(?:(?!and)\w+)?)/g,
/(is missing the following properties from type\s?)'([^']*)': ((?:#?\w+, )*(?:(?!and)\w+)?)/g,
replacer: async (pre: string, type: string, post: string) => {
const formattedType = await formatTypeBlock("", type, codeBlock);
const list = post
Expand All @@ -73,7 +73,7 @@ async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {
},
},
{
pattern: /(types) ['“](.*?)['”] and ['“](.*?)['”][.]?/gi,
pattern: /(types) ['“]([^'“”]*)['”] and ['“]([^'“”]*)['”][.]?/gi,
replacer: async (p1: string, p2: string, p3: string) => {
const [left, right] = await Promise.all([
formatTypeBlock(p1, p2, codeBlock),
Expand All @@ -83,7 +83,8 @@ async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {
},
},
{
pattern: /type annotation must be ['“](.*?)['”] or ['“](.*?)['”][.]?/gi,
pattern:
/type annotation must be ['“]([^'“”]*)['”] or ['“]([^'“”]*)['”][.]?/gi,
replacer: async (p1: string, p2: string, p3: string | number) => {
if (typeof p3 === "string") {
const [left, right] = await Promise.all([
Expand All @@ -100,7 +101,7 @@ async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {
},
},
{
pattern: /(Overload \d of \d), ['“](.*?)['”], /gi,
pattern: /(Overload \d of \d), ['“]([^'“”]*)['”], /gi,
replacer: async (p1: string, p2: string) =>
`${p1}${await formatTypeBlock("", p2, codeBlock)}`,
},
Expand All @@ -114,18 +115,18 @@ async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {
},
{
pattern:
/(module|file|file name|imported via) ['"“](.*?)['"“](?=[\s(.|,]|$)/gi,
/(module|file|file name|imported via) ['"“]([^'"“”]*)['"“](?=[\s(.|,]|$)/gi,
replacer: async (p1: string, p2: string) =>
formatTypeBlock(p1, `"${p2}"`, codeBlock),
},
{
pattern:
/(type|type alias|interface|module|file|file name|class|method's|subtype of constraint) ['“](.*?)['“](?=[\s(.|,)]|$)/gi,
/\b(type|type alias|interface|module|file|file name|class|method's|subtype of constraint) ['“]((?:[^'“\n]|'[^'\n]*'(?=:))*)['“](?=[\s(.|,)]|$)/gi,
replacer: (p1: string, p2: string) => formatTypeOrModuleBlock(p1, p2),
},
{
pattern:
/['“]([^>]*)['”] (type|interface|return type|file|module|is (not )?assignable)/gi,
/['“]((?:[^'“”>\n]|'[^'\n]*'(?=:))*)['”] (type|interface|return type|file|module|is (not )?assignable)/gi,
replacer: async (p1: string, p2: string) =>
`${await formatTypeOrModuleBlock("", p1)} ${p2}`,
},
Expand All @@ -136,16 +137,20 @@ async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {
},
{
pattern:
/['“](import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)['”]/g,
replacer: (p1: string, p2: string) => formatTypeScriptBlock(`${p1}${p2}`),
/['“](import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await)( ?[^'“”\n]*)?['”]/g,
replacer: (p1: string, p2 = "") => formatTypeScriptBlock(`${p1}${p2}`),
},
{
pattern: /['“]([0-9]+)['”]/g,
replacer: formatTypeScriptBlock,
},
{
pattern: /(return|operator) ['“](.*?)['”]/gi,
pattern: /(return|operator) ['“]([^'“”]*)['”]/gi,
replacer: (p1: string, p2: string) =>
`${p1} ${formatTypeScriptBlock(p2)}`,
},
{
pattern: /(?<!\w)'((?:(?!["]).)*?)'(?!\w)/g,
pattern: /(?<!\w)'([^'"]*)'(?!\w)/g,
replacer: (p1: string) => ` ${codeBlock(p1)} `,
},
];
Expand Down
33 changes: 33 additions & 0 deletions packages/formatter/test/formatter.vitest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createErrorMessagePrettifier,
type CodeBlockFn,
} from "../src/errorMessagePrettifier";
import { performance } from "node:perf_hooks";
import { addMissingParentheses } from "../src/addMissingParentheses";
import { formatType } from "../src/formatTypeBlock";
import { d } from "@pretty-ts-errors/utils";
Expand All @@ -27,6 +28,38 @@ describe("formatter", (context) => {
);
});

it("adds missing parentheses without pathological backtracking", () => {
const startedAt = performance.now();
const result = addMissingParentheses("(:".repeat(38730));

expect(result).toBeTypeOf("string");
expect(performance.now() - startedAt).toBeLessThan(2000);
});

it("prettifies malformed quote-heavy messages without pathological backtracking", async () => {
const inputs = [
"\\" + "\t'\"\t".repeat(27387) + "\n",
";'declare module ”".repeat(12910) + ";”\n'declare module '“;'",
"is missing the following properties from type's".repeat(6382) + "\n",
"TYPES “".repeat(20702) + "'\nTYPES “” AND ''",
"TYPE ANNOTATION MUST BE “".repeat(10955) + "'",
"OVERLOAD 0 OF 0, “".repeat(12910) + "\nOVERLOAD 0 OF 0, '', ",
" " + 'FILE "P'.repeat(20702) + "\n",
"E" + "MTYPE 'R".repeat(19365) + "\n",
" FILE“".repeat(22361) + " FILE",
"'0" + "0".repeat(54773) + "\n“0”",
"RETURN “".repeat(19365) + "\nRETURN '”",
"'" + "'0'0\0".repeat(24503) + "\n",
];

const startedAt = performance.now();
for (const input of inputs) {
await expect(prettifyErrorMessage(input)).resolves.toBeTypeOf("string");
}

expect(performance.now() - startedAt).toBeLessThan(2000);
});

it("formats Special characters in object keys", async () => {
expect(await prettifyErrorMessage(errorWithSpecialCharsInObjectKeys)).toBe(
'Type `string` is not assignable to type `{ "abc*bc": string }`.'
Expand Down