diff --git a/packages/formatter/src/addMissingParentheses.ts b/packages/formatter/src/addMissingParentheses.ts index fc65a00..c3cfad5 100644 --- a/packages/formatter/src/addMissingParentheses.ts +++ b/packages/formatter/src/addMissingParentheses.ts @@ -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} => ...` ); diff --git a/packages/formatter/src/errorMessagePrettifier.ts b/packages/formatter/src/errorMessagePrettifier.ts index 952687e..45bd524 100644 --- a/packages/formatter/src/errorMessagePrettifier.ts +++ b/packages/formatter/src/errorMessagePrettifier.ts @@ -51,17 +51,17 @@ async function getRules(codeBlock: CodeBlockFn): Promise { return [ { - pattern: /(?:\s)'"(.*?)(? 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 @@ -73,7 +73,7 @@ async function getRules(codeBlock: CodeBlockFn): Promise { }, }, { - 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), @@ -83,7 +83,8 @@ async function getRules(codeBlock: CodeBlockFn): Promise { }, }, { - 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([ @@ -100,7 +101,7 @@ async function getRules(codeBlock: CodeBlockFn): Promise { }, }, { - pattern: /(Overload \d of \d), ['“](.*?)['”], /gi, + pattern: /(Overload \d of \d), ['“]([^'“”]*)['”], /gi, replacer: async (p1: string, p2: string) => `${p1}${await formatTypeBlock("", p2, codeBlock)}`, }, @@ -114,18 +115,18 @@ async function getRules(codeBlock: CodeBlockFn): Promise { }, { 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}`, }, @@ -136,16 +137,20 @@ async function getRules(codeBlock: CodeBlockFn): Promise { }, { 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: /(? ` ${codeBlock(p1)} `, }, ]; diff --git a/packages/formatter/test/formatter.vitest.ts b/packages/formatter/test/formatter.vitest.ts index 7266401..aa5423d 100644 --- a/packages/formatter/test/formatter.vitest.ts +++ b/packages/formatter/test/formatter.vitest.ts @@ -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"; @@ -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 }`.'