Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added prettifying to error messages #180

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
12 changes: 9 additions & 3 deletions apps/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,17 @@
"publish:ovsx": "npx ovsx publish --no-dependencies $(find . -iname *.vsix)"
},
"dependencies": {
"front-matter": "^4.0.2"
"front-matter": "^4.0.2",
"lz-string": "^1.5.0",
"prettier": "^2.8.8",
"ts-dedent": "^2.2.0",
"vscode-languageserver-types": "^3.17.3",
"vscode-uri": "^3.0.7"
},
"devDependencies": {
"@types/prettier": "^2.7.3",
"@total-typescript/error-translation-engine": "workspace:*",
"@total-typescript/tips-parser": "workspace:*",
"@types/glob": "^7.2.0",
"@types/mocha": "^9.1.0",
"@types/node": "14.x",
Expand All @@ -84,7 +91,6 @@
"eslint": "^8.46.0",
"glob": "^7.2.0",
"mocha": "^9.2.2",
"typescript": "^5.1.6",
"@total-typescript/tips-parser": "workspace:*"
"typescript": "^5.1.6"
}
}
8 changes: 6 additions & 2 deletions apps/vscode/src/bundleErrors.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"code": "1313"
},
"2304": {
"body": "I can't find the variable you're trying to access.\n",
"body": "I can't find the variable you're trying to access, '{0}', in the current scope.\n\n\n",
"code": "2304"
},
"2307": {
Expand All @@ -60,7 +60,7 @@
"code": "2314"
},
"2322": {
"body": "I was expecting a type matching A, but instead you passed B.\n",
"body": "I was expecting a type matching '{1}' but instead you passed '{0}'.\n",
"code": "2322"
},
"2324": {
Expand Down Expand Up @@ -135,6 +135,10 @@
"body": "You've created a union type that's too complex for me to handle! 🤯 I can only represent 100,000 combinations in the same union, and you've gone over that limit.\n",
"code": "2590"
},
"2739": {
"body": "Type '{0}' is missing the following properties from type '{1}': {2}",
"code": "2739"
},
"2741": {
"body": "You haven't passed all the required properties to '{2}' - '{1}' is missing the '{0}' property\n",
"code": "2741"
Expand Down
42 changes: 42 additions & 0 deletions apps/vscode/src/components/codeBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { d } from "../utils";
import { miniLine } from "./miniLine";
import { spanBreak } from "./spanBreak";

/**
* @returns markdown string that will be rendered as a code block (`supportHtml` required)
* We're using codicon here since it's the only thing that can be `inline-block`
* and have a background color in hovers due to strict sanitization of markdown on
* VSCode [code](https://github.com/microsoft/vscode/blob/735aff6d962db49423e02c2344e60d418273ae39/src/vs/base/browser/markdownRenderer.ts#L372)
*/
const codeBlock = (code: string, language: string) =>
spanBreak(d/*html*/ `
<span class="codicon codicon-none" style="background-color:var(--vscode-textCodeBlock-background);">

\`\`\`${language}
${code}
\`\`\`

</span>
`);

export const inlineCodeBlock = (code: string, language: string) =>
codeBlock(` ${code} `, language);

export const multiLineCodeBlock = (code: string, language: string) => {
const codeLines = code.split("\n");
//this line is finding the longest line
const maxLineChars = codeLines.reduce(
(acc, curr) => (curr.length > acc ? curr.length : acc),
0
);
// codicon class align the code to the center, so we must pad it with spaces
const paddedCode = codeLines
.map((line) => line.padEnd(maxLineChars + 2))
.join("\n");

return d/*html*/ `
${miniLine}
${codeBlock(paddedCode, language)}
${miniLine}
`;
};
31 changes: 31 additions & 0 deletions apps/vscode/src/components/consts/knownErrorNumbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Diagnostic } from "vscode-languageserver-types";

/** Should be updated from:
* https://typescript.tv/errors
* Just run this in the console:
* ```javascript
* $$('h2')
* .filter(node => node.textContent.match(/TS[0-9]{1,5}/))
* .map(node => Number(node.textContent.match(/TS([0-9]+)/)[1]))
* ```
*/
export const KNOWN_ERROR_NUMBERS: Set<Diagnostic["code"]> = new Set([
1002, 1005, 1006, 1015, 1016, 1029, 1036, 1038, 1039, 1046, 1055, 1056, 1064,
1066, 1068, 1070, 1095, 1103, 1109, 1117, 1127, 1128, 1149, 1155, 1160, 1183,
1192, 1196, 1202, 1208, 1218, 1219, 1225, 1228, 1243, 1244, 1254, 1259, 1308,
1337, 1357, 1361, 1363, 1371, 1375, 1378, 1385, 1389, 1431, 1432, 1434, 1471,
2300, 2304, 2305, 2306, 2307, 2314, 2315, 2322, 2335, 2339, 2344, 2345, 2348,
2349, 2351, 2352, 2355, 2361, 2362, 2364, 2365, 2366, 2367, 2368, 2369, 2370,
2371, 2372, 2377, 2378, 2390, 2391, 2394, 2395, 2403, 2411, 2420, 2428, 2430,
2440, 2445, 2448, 2451, 2454, 2456, 2459, 2475, 2476, 2488, 2497, 2503, 2507,
2511, 2512, 2515, 2528, 2531, 2532, 2533, 2538, 2550, 2551, 2552, 2554, 2556,
2558, 2559, 2564, 2567, 2571, 2574, 2577, 2580, 2582, 2583, 2584, 2588, 2595,
2611, 2613, 2616, 2652, 2654, 2656, 2661, 2663, 2664, 2665, 2668, 2669, 2677,
2678, 2680, 2683, 2684, 2686, 2687, 2689, 2691, 2693, 2694, 2695, 2706, 2709,
2715, 2717, 2720, 2722, 2724, 2730, 2732, 2739, 2740, 2741, 2742, 2749, 2769,
2774, 2779, 2786, 2792, 2794, 2813, 2814, 4020, 4025, 4060, 4063, 4075, 4081,
4104, 4112, 4113, 4114, 5023, 5024, 5025, 5054, 5055, 5058, 5069, 5070, 5083,
5087, 5101, 6053, 6059, 6133, 6138, 6196, 6198, 6504, 7006, 7008, 7009, 7010,
7016, 7017, 7022, 7023, 7026, 7027, 7030, 7031, 7034, 7041, 7044, 7053, 8020,
17000, 17004, 17009, 18004, 18016, 80005,
]);
4 changes: 4 additions & 0 deletions apps/vscode/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./codeBlock";
export * from "./miniLine";
export * from "./spanBreak";
export * from "./unStyledCodeBlock";
4 changes: 4 additions & 0 deletions apps/vscode/src/components/miniLine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { spanBreak } from "./spanBreak";

/** May be useful for line separations */
export const miniLine = spanBreak(/*html*/ `<p></p>`);
12 changes: 12 additions & 0 deletions apps/vscode/src/components/spanBreak.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { d } from "../utils";

/**
* Since every thing in the extension hover split into spans,
* we need to close the previous span before we're opening a new one
* Note: the line breaks is important here
*/
export const spanBreak = (children: string) => d/*html*/ `
</span>
${children}
<span>
`;
10 changes: 10 additions & 0 deletions apps/vscode/src/components/unStyledCodeBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { inlineCodeBlock, multiLineCodeBlock } from "./codeBlock";
import { d } from "../utils";

/**
* Code block without syntax highlighting like.
* For syntax highlighting, use {@link inlineCodeBlock} or {@link multiLineCodeBlock}
*/
export const unStyledCodeBlock = (content: string) => d/*html*/ `
\`${content}\`
`;
56 changes: 56 additions & 0 deletions apps/vscode/src/format/addMissingParentheses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { has, invert, keys, values } from "../utils";

const parentheses = {
"(": ")",
"{": "}",
"[": "]",
} as const;

const openParentheses = keys(parentheses);
const closeParentheses = values(parentheses);

export function addMissingParentheses(type: string): string {
let openStack: (typeof openParentheses)[number][] = [];
let missingClosingChars = "";

for (const char of type) {
if (has(openParentheses, char)) {
openStack.push(char);
} else if (has(closeParentheses, char)) {
if (
openStack.length === 0 ||
parentheses[openStack[openStack.length - 1]] !== char
) {
// Add the correct opening character before the current closing character
openStack.push(invert(parentheses)[char]);
} else {
openStack.pop();
}
}
}

// Add the missing closing characters at the end of the string
while (openStack.length > 0) {
const openChar = openStack.pop()!;
const closingChar = parentheses[openChar];
missingClosingChars += closingChar;
}

let validType = type;

// Close the last string if it's not closed
if ((validType.match(/\"/g) ?? []).length % 2 === 1) {
validType = validType + '..."';
}
if ((validType.match(/\'/g) ?? []).length % 2 === 1) {
validType = validType + "...'";
}

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

return validType;
}
82 changes: 82 additions & 0 deletions apps/vscode/src/format/formatDiagnosticMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { inlineCodeBlock, unStyledCodeBlock } from '../components';
import { formatTypeBlock } from './formatTypeBlock';

const formatTypeScriptBlock = (_: string, code: string) =>
inlineCodeBlock(code, 'typescript');

const formatSimpleTypeBlock = (_: string, code: string) =>
inlineCodeBlock(code, 'type');

export const formatDiagnosticMessage = (message: string) =>
message
// format declare module snippet
.replaceAll(
/['“](declare module )['”](.*)['“];['”]/g,
(_: string, p1: string, p2: string) =>
formatTypeScriptBlock(_, `${p1} "${p2}"`),
)
// format missing props error
.replaceAll(
/(is missing the following properties from type )'(.*)': (.+?)(?=and|$)/g,
(_, pre, type, post) =>
`${pre}${formatTypeBlock('', type)}: <ul>${post
.split(', ')
.filter(Boolean)
.map((prop: string) => `<li>${prop}</li>`)
.join('')}</ul>`,
)
// Format type pairs
.replaceAll(
/(types) ['“](.*?)['”] and ['“](.*?)['”][\.]?/gi,
(_: string, p1: string, p2: string, p3: string) =>
`${formatTypeBlock(p1, p2)} and ${formatTypeBlock('', p3)}`,
)
// Format type annotation options
.replaceAll(
/type annotation must be ['“](.*?)['”] or ['“](.*?)['”][\.]?/gi,
(_: string, p1: string, p2: string, p3: string) =>
`${formatTypeBlock(p1, p2)} or ${formatTypeBlock('', p3)}`,
)
.replaceAll(
/(Overload \d of \d), ['“](.*?)['”], /gi,
(_, p1: string, p2: string) => `${p1}${formatTypeBlock('', p2)}`,
)
// format simple strings
.replaceAll(/^['“]"[^"]*"['”]$/g, formatTypeScriptBlock)
// Format string types
.replaceAll(
/(module|file|file name) "(.*?)"(?=[\s(.|,)])/gi,
(_, p1: string, p2: string) => formatTypeBlock(p1, `"${p2}"`),
)
// Format types
.replaceAll(
/(type|type alias|interface|module|file|file name|method's|subtype of constraint) ['“](.*?)['“](?=[\s(.|,)])/gi,
(_, p1: string, p2: string) => formatTypeBlock(p1, p2),
)
// Format reversed types
.replaceAll(
/(.*)['“]([^>]*)['”] (type|interface|return type|file|module|is (not )?assignable)/gi,
(_: string, p1: string, p2: string, p3: string) =>
`${p1}${formatTypeBlock('', p2)} ${p3}`,
)
// Format simple types that didn't captured before
.replaceAll(
/['“]((void|null|undefined|any|boolean|string|number|bigint|symbol)(\[\])?)['”]/g,
formatSimpleTypeBlock,
)
// Format some typescript key words
.replaceAll(
/['“](import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)['”]/g,
(_: string, p1: string, p2: string) =>
formatTypeScriptBlock(_, `${p1}${p2}`),
)
// Format return values
.replaceAll(
/(return|operator) ['“](.*?)['”]/gi,
(_, p1: string, p2: string) => `${p1} ${formatTypeScriptBlock('', p2)}`,
)
// Format regular code blocks
.replaceAll(
/['“]((?:(?!:\s*}).)*?)['“] (?!\s*:)/g,
(_: string, p1: string) => `${unStyledCodeBlock(p1)} `,
);
75 changes: 75 additions & 0 deletions apps/vscode/src/format/formatTypeBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
inlineCodeBlock,
multiLineCodeBlock,
unStyledCodeBlock,
} from "../components";
import { addMissingParentheses } from "./addMissingParentheses";
import { prettify } from "./prettify";

export function formatTypeBlock(
prefix: string,
type: string,
) {
// Return a simple code block if it's just a parenthesis
if (type.match(/^(\[\]|\{\})$/)) {
return `${prefix} ${unStyledCodeBlock(type)}`;
}

if (
// Skip formatting if it's a simple type
type.match(
/^((void|null|undefined|any|number|string|bigint|symbol|readonly|typeof)(\[\])?)$/
)
) {
return `${prefix} ${inlineCodeBlock(type, "type")}`;
}

const prettyType = prettifyType(type);

if (prettyType.includes("\n")) {
return `${prefix}: ${multiLineCodeBlock(prettyType, "type")}`;
} else {
return `${prefix} ${inlineCodeBlock(prettyType, "type")}`;
}
}
/**
* Try to make type prettier with prettier
*/
export function prettifyType(
type: string,
options?: { throwOnError?: boolean }
) {
try {
// Wrap type with valid statement, format it and extract the type back
return convertToOriginalType(prettify(convertToValidType(type)));
} catch (e) {
if (options?.throwOnError) {
throw e;
}
return type;
}
}

const convertToValidType = (type: string) =>
`type x = ${type
// Add missing parentheses when the type ends with "...""
.replace(/(.*)\.\.\.$/, (_, p1) => addMissingParentheses(p1))
// Replace single parameter function destructuring because it's not a valid type
// .replaceAll(/\((\{.*\})\:/g, (_, p1) => `(param: /* ${p1} */`)
// Change `(...): return` which is invalid to `(...) => return`
.replace(/^(\(.*\)): /, (_, p1) => `${p1} =>`)
.replaceAll(/... (\d{0,}) more .../g, (_, p1) => `___${p1}MORE___`)
.replaceAll(/... (\d{0,}) more ...;/g, (_, p1) => `___MORE___: ${p1};`)
.replaceAll("...;", "___KEY___: ___THREE_DOTS___;")
.replaceAll("...", "__THREE_DOTS__")};`;

const convertToOriginalType = (type: string) =>
type
.replaceAll("___KEY___: ___THREE_DOTS___", "...;")
.replaceAll("__THREE_DOTS__", "...")
.replaceAll(/___MORE___: (\d{0,});/g, (_, p1) => `... ${p1} more ...;`)
.replaceAll(/___(\d{0,})MORE___/g, (_, p1) => `... ${p1} more ...`)
.replaceAll(/... (\d{0,}) more .../g, (_, p1) => `/* ${p1} more */`) // ... x more ... not shown sell
// .replaceAll(/\(param\: \/\* (\{ .* \}) \*\//g, (_, p1) => `(${p1}: `)
.replace(/type x =[ ]?((.|\n)*);.*/g, "$1")
.trim();
Loading