├── apps
└── vscode-extension
│ ├── .gitignore
│ ├── assets
│ └── icon.png
│ ├── scripts
│ ├── process-shim.js
│ └── build.js
│ ├── src
│ ├── provider
│ │ ├── uriStore.ts
│ │ ├── hoverProvider.ts
│ │ └── selectedTextHoverProvider.ts
│ ├── test
│ │ ├── suite
│ │ │ ├── extension.test.ts
│ │ │ └── index.ts
│ │ └── runTest.ts
│ └── extension.ts
│ ├── tsconfig.json
│ ├── syntaxes
│ └── type.tmGrammar.json
│ ├── LICENSE
│ └── package.json
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── assets
├── empty.png
├── icon.png
├── this.png
├── errors-hover.png
├── mentions
│ ├── vote.png
│ ├── js-nation.png
│ ├── theo-dark.png
│ ├── tanner-dark.png
│ ├── theo-light.png
│ ├── theo-video.png
│ ├── johnson-dark.png
│ ├── johnson-light.png
│ └── tanner-light.png
└── instead-of-that.png
├── packages
├── vscode-formatter
│ ├── README.md
│ ├── src
│ │ ├── index.ts
│ │ ├── components
│ │ │ ├── index.ts
│ │ │ ├── miniLine.ts
│ │ │ ├── plainCodeBlock.ts
│ │ │ ├── spanBreak.ts
│ │ │ ├── anyCodeBlock.ts
│ │ │ ├── codeBlock.ts
│ │ │ └── title.ts
│ │ └── format
│ │ │ ├── formatDiagnostic.ts
│ │ │ ├── embedSymbolLinks.ts
│ │ │ └── identSentences.ts
│ ├── tsconfig.json
│ └── package.json
├── formatter
│ ├── src
│ │ ├── index.ts
│ │ ├── prettify.ts
│ │ ├── addMissingParentheses.ts
│ │ ├── formatTypeBlock.ts
│ │ └── formatDiagnosticMessage.ts
│ ├── tsconfig.json
│ ├── README.md
│ ├── vitest.config.ts
│ ├── package.json
│ └── test
│ │ ├── formatter.vitest.ts
│ │ └── errorMessageMocks.ts
└── utils
│ ├── tsconfig.json
│ ├── package.json
│ └── src
│ └── index.ts
├── .prettierrc
├── .gitignore
├── examples
├── examples.type
├── errors.vue
├── errors.ts
└── errors.js
├── CONTRIBUTING.md
├── .vscode
├── extensions.json
├── settings.json
├── tasks.json
└── launch.json
├── turbo.json
├── tsconfig.json
├── package.json
├── docs
├── pretty-ts-errors-hack.css
└── hide-original-errors.md
├── tsconfig.base.json
├── eslint.config.mjs
├── LICENSE
└── README.md
/apps/vscode-extension/.gitignore:
--------------------------------------------------------------------------------
1 | README.md
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [yoavbls, kevinramharak]
2 |
--------------------------------------------------------------------------------
/assets/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/empty.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/this.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/this.png
--------------------------------------------------------------------------------
/packages/vscode-formatter/README.md:
--------------------------------------------------------------------------------
1 | # Pretty TypeScript Errors - Formatter for VSCode hovers
2 |
--------------------------------------------------------------------------------
/assets/errors-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/errors-hover.png
--------------------------------------------------------------------------------
/assets/mentions/vote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/vote.png
--------------------------------------------------------------------------------
/packages/formatter/src/index.ts:
--------------------------------------------------------------------------------
1 | export { formatDiagnosticMessage } from "./formatDiagnosticMessage";
2 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/index.ts:
--------------------------------------------------------------------------------
1 | export { formatDiagnostic } from "./format/formatDiagnostic";
2 |
--------------------------------------------------------------------------------
/assets/instead-of-that.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/instead-of-that.png
--------------------------------------------------------------------------------
/assets/mentions/js-nation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/js-nation.png
--------------------------------------------------------------------------------
/assets/mentions/theo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/theo-dark.png
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "trailingComma": "es5"
6 | }
7 |
--------------------------------------------------------------------------------
/assets/mentions/tanner-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/tanner-dark.png
--------------------------------------------------------------------------------
/assets/mentions/theo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/theo-light.png
--------------------------------------------------------------------------------
/assets/mentions/theo-video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/theo-video.png
--------------------------------------------------------------------------------
/assets/mentions/johnson-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/johnson-dark.png
--------------------------------------------------------------------------------
/assets/mentions/johnson-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/johnson-light.png
--------------------------------------------------------------------------------
/assets/mentions/tanner-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/assets/mentions/tanner-light.png
--------------------------------------------------------------------------------
/apps/vscode-extension/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/HEAD/apps/vscode-extension/assets/icon.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 | .turbo
7 | .vercel
8 | .idea
9 | **/.DS_Store
10 | tsconfig.tsbuildinfo
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { inlineCodeBlock, multiLineCodeBlock } from "./codeBlock";
2 | export { unstyledCodeBlock } from "./plainCodeBlock";
3 |
--------------------------------------------------------------------------------
/examples/examples.type:
--------------------------------------------------------------------------------
1 | {
2 | fistName: string;
3 | lastName: string;
4 | age: number;
5 | } | undefined | null;
6 |
7 | {
8 | x: string;
9 | y: number;
10 | }[]
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/components/miniLine.ts:
--------------------------------------------------------------------------------
1 | import { spanBreak } from "./spanBreak";
2 |
3 | /** May be useful for line separations */
4 | export const miniLine = spanBreak(/*html*/ `
`);
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contribute good stuff
2 |
3 | If you're looking for ideas, check out our [board](https://github.com/users/yoavbls/projects/3) and [open issues](https://github.com/yoavbls/pretty-ts-errors/issues)
4 |
--------------------------------------------------------------------------------
/apps/vscode-extension/scripts/process-shim.js:
--------------------------------------------------------------------------------
1 | // https://esbuild.github.io/api/#inject
2 |
3 | let _cwd = "/";
4 |
5 | export let process = {
6 | cwd: () => _cwd,
7 | chdir: (newCwd) => (_cwd = newCwd),
8 | env: {},
9 | };
10 |
--------------------------------------------------------------------------------
/apps/vscode-extension/src/provider/uriStore.ts:
--------------------------------------------------------------------------------
1 | import { MarkdownString, Range, Uri } from "vscode";
2 |
3 | export const uriStore: Record<
4 | Uri["path"],
5 | {
6 | range: Range;
7 | contents: MarkdownString[];
8 | }[]
9 | > = {};
10 |
--------------------------------------------------------------------------------
/examples/errors.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint",
6 | "connor4312.esbuild-problem-matchers"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/vscode-extension/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "target": "ES2022",
6 | "rootDir": "src",
7 | "outDir": "out",
8 | "moduleResolution": "node",
9 | },
10 | "include": ["src/**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "noEmit": false,
5 | "outDir": "dist",
6 | "rootDir": "src",
7 | "composite": true,
8 | "declaration": true,
9 | "declarationMap": true
10 | },
11 | "include": ["src/**/*"]
12 | }
13 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": ["**/.env.*local"],
4 | "tasks": {
5 | "build": {
6 | "outputs": ["dist/**"]
7 | },
8 | "lint": {},
9 | "dev": {
10 | "cache": false,
11 | "persistent": true
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "noEmit": false,
5 | "outDir": "dist",
6 | "rootDir": "src",
7 | "composite": true,
8 | "declaration": true,
9 | "declarationMap": true
10 | },
11 | "include": ["src/**/*"]
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./tsconfig.base.json",
4 | "files": [],
5 | "references": [
6 | { "path": "packages/utils" },
7 | { "path": "packages/formatter" },
8 | { "path": "packages/vscode-formatter" },
9 | { "path": "apps/vscode-extension" }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "@pretty-ts-errors/utils",
4 | "files": [
5 | "dist/**"
6 | ],
7 | "main": "dist/index.js",
8 | "types": "dist/index.d.ts",
9 | "scripts": {
10 | "build": "tsc -p ."
11 | },
12 | "devDependencies": {},
13 | "dependencies": {
14 | "ts-dedent": "^2.2.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/formatter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "noEmit": false,
5 | "outDir": "dist",
6 | "rootDir": "src",
7 | "composite": true,
8 | "declaration": true,
9 | "declarationMap": true
10 | },
11 | "include": ["src/**/*"],
12 | "references": [{ "path": "../utils" }]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/components/plainCodeBlock.ts:
--------------------------------------------------------------------------------
1 | import { d } from "@pretty-ts-errors/utils";
2 |
3 | /**
4 | * Code block without syntax highlighting like.
5 | * For syntax highlighting, use {@link inlineCodeBlock} or {@link multiLineCodeBlock}
6 | */
7 | export const unstyledCodeBlock = (content: string) => d/*html*/ `
8 | ${content}
9 | `;
10 |
--------------------------------------------------------------------------------
/packages/formatter/src/prettify.ts:
--------------------------------------------------------------------------------
1 | import parserTypescript from "prettier/parser-typescript";
2 | import { format } from "prettier/standalone";
3 |
4 | export function prettify(text: string) {
5 | return format(text, {
6 | plugins: [parserTypescript],
7 | parser: "typescript",
8 | printWidth: 60,
9 | singleAttributePerLine: false,
10 | arrowParens: "avoid",
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/apps/vscode-extension/syntaxes/type.tmGrammar.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TypeScript Type",
3 | "scopeName": "source.type",
4 | "patterns": [
5 | {
6 | "include": "source.ts#type"
7 | },
8 | {
9 | "include": "source.ts#directives"
10 | },
11 | {
12 | "include": "source.ts#statements"
13 | },
14 | {
15 | "include": "source.ts#shebang"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/components/spanBreak.ts:
--------------------------------------------------------------------------------
1 | import { d } from "@pretty-ts-errors/utils";
2 |
3 | /**
4 | * Since every thing in the extension hover split into spans,
5 | * we need to close the previous span before we're opening a new one
6 | * Note: the line breaks is important here
7 | */
8 | export const spanBreak = (children: string) => d/*html*/ `
9 |
10 | ${children}
11 |
12 | `;
13 |
--------------------------------------------------------------------------------
/apps/vscode-extension/src/test/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | // You can import and use all API from the 'vscode' module
2 | // as well as import your extension to test it
3 | import * as vscode from "vscode";
4 |
5 | suite("Extension Test Suite", () => {
6 | /**
7 | * The tests moved to the formatter package, I'm leaving
8 | * this here for future tests to the VSCode extension
9 | */
10 | vscode.window.showInformationMessage("Start all tests.");
11 | });
12 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/components/anyCodeBlock.ts:
--------------------------------------------------------------------------------
1 | import { inlineCodeBlock, multiLineCodeBlock } from "./codeBlock";
2 | import { unstyledCodeBlock } from "./plainCodeBlock";
3 |
4 | export const anyCodeBlock = (
5 | code: string,
6 | language?: string,
7 | multiLine?: boolean
8 | ) => {
9 | if (!language) {
10 | return unstyledCodeBlock(code);
11 | }
12 | if (multiLine) {
13 | return multiLineCodeBlock(code, language);
14 | }
15 | return inlineCodeBlock(code, language);
16 | };
17 |
--------------------------------------------------------------------------------
/packages/formatter/README.md:
--------------------------------------------------------------------------------
1 | # Pretty TypeScript Error formatter
2 |
3 | The formatting package of [pretty-ts-errors](https://github.com/yoavbls/pretty-ts-errors)
4 |
5 | # Usage
6 |
7 | ```typescript
8 | import { formatDiagnosticMessage } from "@pretty-ts-errors/formatter";
9 |
10 | function codeBlock(code: string, language?: string, multiLine?: boolean) {
11 | return `\`\`\`${language}
12 | ${code}
13 | \`\`\`
14 | `;
15 | }
16 |
17 | formatDiagnosticMessage(
18 | `Type 'string' is not assignable to type 'number'.`,
19 | codeBlock
20 | );
21 | ```
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: bug
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **Expected behavior**
13 | A clear and concise description of what you expected to happen.
14 |
15 | **Original error**
16 | If this bug is related to an error that is not formatting well, please
17 | attach the original error in a code block:
18 |
19 | Type 'number' is not assignable to type 'string'.ts(2322)
20 |
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files
5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true, // set this to false to include "out" folder in search results
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12 | "typescript.tsc.autoDetect": "off"
13 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "turbo run build",
5 | "dev": "turbo run dev",
6 | "lint": "turbo run lint",
7 | "format": "prettier --write \"**/*.{ts,tsx,md}\""
8 | },
9 | "engines": {
10 | "node": ">=20"
11 | },
12 | "devDependencies": {
13 | "@eslint/js": "^9.15.0",
14 | "@types/node": "^20.19.21",
15 | "eslint": "^9.15.0",
16 | "globals": "^16.4.0",
17 | "prettier": "^2.8.8",
18 | "turbo": "^2.1.3",
19 | "typescript": "^5.7.2",
20 | "typescript-eslint": "^8.15.0"
21 | },
22 | "name": "pretty-ts-errors-mono",
23 | "packageManager": "npm@10.0.0",
24 | "workspaces": [
25 | "packages/*",
26 | "apps/*"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/apps/vscode-extension/src/provider/hoverProvider.ts:
--------------------------------------------------------------------------------
1 | import { HoverProvider } from "vscode";
2 | import { uriStore } from "./uriStore";
3 |
4 | export const hoverProvider: HoverProvider = {
5 | provideHover(document, position, _token) {
6 | const itemsInUriStore = uriStore[document.uri.fsPath];
7 |
8 | if (!itemsInUriStore) {
9 | return null;
10 | }
11 |
12 | const itemInRange = itemsInUriStore.filter((item) =>
13 | item.range.contains(position)
14 | );
15 |
16 | if (itemInRange.length === 0) {
17 | return null;
18 | }
19 |
20 | const first = itemInRange[0];
21 | if (!first) {
22 | return null;
23 | }
24 | return {
25 | range: first.range,
26 | contents: itemInRange.flatMap((item) => item.contents),
27 | };
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/format/formatDiagnostic.ts:
--------------------------------------------------------------------------------
1 | import { formatDiagnosticMessage } from "@pretty-ts-errors/formatter";
2 | import { Diagnostic } from "vscode-languageserver-types";
3 | import { title } from "../components/title";
4 | import { anyCodeBlock } from "../components/anyCodeBlock";
5 | import { d } from "@pretty-ts-errors/utils";
6 | import { embedSymbolLinks } from "./embedSymbolLinks";
7 | import { identSentences } from "./identSentences";
8 |
9 | export function formatDiagnostic(diagnostic: Diagnostic) {
10 | const newDiagnostic = embedSymbolLinks(diagnostic);
11 |
12 | return d/*html*/ `
13 | ${title(diagnostic)}
14 |
15 | ${formatDiagnosticMessage(
16 | identSentences(newDiagnostic.message),
17 | anyCodeBlock
18 | )}
19 |
20 | `;
21 | }
22 |
--------------------------------------------------------------------------------
/docs/pretty-ts-errors-hack.css:
--------------------------------------------------------------------------------
1 | /* Allow copying */
2 | .codicon-none {
3 | user-select: text !important;
4 | -webkit-user-select: text !important;
5 | }
6 |
7 | /* Hide errors */
8 | div.monaco-hover-content:has(.codicon-none) > .hover-row:first-child {
9 | display: none !important;
10 | }
11 | div.monaco-hover-content:has([style="color:#f96363;"]) > .hover-row:first-child {
12 | display: none !important
13 | }
14 |
15 | /* Change order */
16 | .monaco-hover .monaco-hover-content {
17 | display: flex;
18 | flex-direction: column;
19 | }
20 | .monaco-hover .monaco-hover-content .hover-row {
21 | order: 2;
22 | }
23 | .monaco-hover .monaco-hover-content .hover-row:has(.codicon-none) {
24 | order: 1;
25 | }
26 | .monaco-hover .monaco-hover-content .hover-row:has([style="color:#f96363;"]) {
27 | order: 1;
28 | }
--------------------------------------------------------------------------------
/packages/formatter/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import path from "path";
3 |
4 | export default defineConfig({
5 | test: {
6 | environment: "node",
7 | globals: true,
8 | include: ["test/**/*.vitest.{ts,tsx}", "test/**/*.spec.{ts,tsx}"],
9 | },
10 | resolve: {
11 | alias: [
12 | {
13 | // Map direct src imports of the sibling vscode-formatter package
14 | find: "@pretty-ts-errors/vscode-formatter/src",
15 | replacement: path.resolve(__dirname, "../vscode-formatter/src"),
16 | },
17 | {
18 | // Map the package root to its src index for local testing
19 | find: "@pretty-ts-errors/vscode-formatter",
20 | replacement: path.resolve(
21 | __dirname,
22 | "../vscode-formatter/src/index.ts"
23 | ),
24 | },
25 | ],
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/apps/vscode-extension/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | import Mocha from "mocha";
3 | import { glob } from "glob";
4 |
5 | export function run(
6 | testsRoot: string,
7 | cb: (error: unknown | null, failures?: number) => void
8 | ): void {
9 | // Create the mocha test
10 | const mocha = new Mocha({
11 | ui: "tdd",
12 | color: true,
13 | });
14 |
15 | glob("**/**.test.js", { cwd: testsRoot })
16 | .then((files) => {
17 | // Add files to the test suite
18 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
19 |
20 | try {
21 | // Run the mocha test
22 | mocha.run((failures) => {
23 | cb(null, failures);
24 | });
25 | } catch (err) {
26 | console.error(err);
27 | cb(err);
28 | }
29 | })
30 | .catch((err) => {
31 | return cb(err);
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/apps/vscode-extension/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 |
3 | import { runTests } from "@vscode/test-electron";
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../");
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, "./suite/index");
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({
17 | version: "1.77.0",
18 | extensionDevelopmentPath,
19 | extensionTestsPath,
20 | launchArgs: ["--disable-extensions"],
21 | });
22 | } catch (err) {
23 | console.error("Failed to run tests");
24 | process.exit(1);
25 | }
26 | }
27 |
28 | main();
29 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/format/embedSymbolLinks.ts:
--------------------------------------------------------------------------------
1 | import { Diagnostic } from "vscode-languageserver-types";
2 |
3 | export function embedSymbolLinks(diagnostic: Diagnostic): Diagnostic {
4 | if (
5 | !diagnostic?.relatedInformation?.[0]?.message?.includes("is declared here")
6 | ) {
7 | return diagnostic;
8 | }
9 | const ref = diagnostic.relatedInformation[0];
10 | const symbol = ref?.message.match(/(?'.*?') is declared here./)
11 | ?.groups?.["symbol"];
12 |
13 | if (!symbol) {
14 | return diagnostic;
15 | }
16 |
17 | return {
18 | ...diagnostic,
19 | message: diagnostic.message.replaceAll(
20 | symbol,
21 | `${symbol} `
26 | ),
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/format/identSentences.ts:
--------------------------------------------------------------------------------
1 | import { d } from "@pretty-ts-errors/utils";
2 |
3 | export const identSentences = (message: string): string =>
4 | message
5 | .split("\n")
6 | .map((line) => {
7 | let whiteSpacesCount = line.search(/\S/);
8 | if (whiteSpacesCount === -1) {
9 | whiteSpacesCount = 0;
10 | }
11 | if (whiteSpacesCount === 0) {
12 | return line;
13 | }
14 | if (whiteSpacesCount >= 2) {
15 | whiteSpacesCount -= 2;
16 | }
17 |
18 | return d/*html*/ `
19 |
20 |
21 |
22 |
23 |
24 | |
25 | ${" ".repeat(3).repeat(whiteSpacesCount)}
26 |
27 |
28 | |
29 | ${line} |
30 |
31 |
32 | `;
33 | })
34 | .join("");
35 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "CommonJS",
5 | "moduleResolution": "Node",
6 | "lib": ["ES2022", "ES2021.String"],
7 | "baseUrl": ".",
8 | "strict": true,
9 | "noUncheckedIndexedAccess": true,
10 | "exactOptionalPropertyTypes": true,
11 | "noImplicitOverride": true,
12 | "noPropertyAccessFromIndexSignature": true,
13 | "useDefineForClassFields": true,
14 | "noFallthroughCasesInSwitch": true,
15 | "noImplicitReturns": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "forceConsistentCasingInFileNames": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "resolveJsonModule": true,
22 | "sourceMap": true,
23 | "allowJs": true,
24 | "checkJs": true,
25 | "moduleDetection": "force",
26 | "declaration": true,
27 | "declarationMap": true,
28 | "composite": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/errors.ts:
--------------------------------------------------------------------------------
1 | interface Person {
2 | name: string;
3 | age: number;
4 | address: {
5 | street: string;
6 | city: string;
7 | country: string;
8 | };
9 | }
10 |
11 | const john: Person = {
12 | name: "John Doe",
13 | age: 30,
14 | address: {
15 | street: "123 Main St",
16 | city: "New York",
17 | },
18 | };
19 |
20 | type GetUserFunction = () => {
21 | user: {
22 | name: string;
23 | email: `${string}@${string}.${string}`;
24 | age: number;
25 | };
26 | };
27 |
28 | const getPerson: GetUserFunction = () => ({
29 | person: {
30 | username: "usr",
31 | email: "usr@usr.io",
32 | },
33 | });
34 |
35 | interface Animal {
36 | name: string;
37 | age: number;
38 | }
39 |
40 | function run(animal: T) {
41 | return animal;
42 | }
43 |
44 | run({ firstName: "John", weight: 20 });
45 |
46 | type MyError = {
47 | code: number;
48 | };
49 |
50 | try {
51 | // ...
52 | } catch (error: MyError) {
53 | console.log(error.code);
54 | }
55 |
56 | export {};
57 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import eslint from "@eslint/js";
4 | import tseslint from "typescript-eslint";
5 | import globals from "globals";
6 |
7 | export default tseslint.config(
8 | eslint.configs.recommended,
9 | ...tseslint.configs.strict,
10 | ...tseslint.configs.stylistic,
11 | {
12 | ignores: [
13 | "apps/*/scripts/*",
14 | "apps/*/dist/*",
15 | "packages/*/scripts/*",
16 | "packages/*/dist/*",
17 | "examples/*",
18 | ".vscode-test/*",
19 | ],
20 | },
21 | {
22 | languageOptions: {
23 | parser: tseslint.parser,
24 | parserOptions: {
25 | ecmaVersion: "latest",
26 | sourceType: "module",
27 | },
28 | globals: {
29 | ...globals.node,
30 | },
31 | },
32 | plugins: {
33 | "@typescript-eslint": tseslint.plugin,
34 | },
35 | rules: {
36 | "@typescript-eslint/no-unused-vars": "off",
37 | "@typescript-eslint/no-non-null-assertion": "off",
38 | curly: "warn",
39 | eqeqeq: "warn",
40 | "no-throw-literal": "warn",
41 | semi: "off",
42 | },
43 | }
44 | );
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Yoav Balasiano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/utils/src/index.ts:
--------------------------------------------------------------------------------
1 | import dedent from "ts-dedent";
2 |
3 | export function objectKeys>(
4 | obj: T
5 | ): (keyof T & string)[] {
6 | return Object.keys(obj) as (keyof T & string)[];
7 | }
8 |
9 | export function invert>(
10 | obj: T
11 | ): Partial<{
12 | [K in T[keyof T]]: { [P in keyof T]: K extends T[P] ? P : never }[keyof T];
13 | }> {
14 | const result: Partial<{
15 | [K in T[keyof T]]: { [P in keyof T]: K extends T[P] ? P : never }[keyof T];
16 | }> = {};
17 | for (const key in obj) {
18 | const value = obj[key];
19 | if (value !== undefined) {
20 | result[value] = key;
21 | }
22 | }
23 | return result;
24 | }
25 |
26 | /**
27 | * d stands for dedent.
28 | * it allow us to indent html in template literals without affecting the output
29 | */
30 | export const d = dedent;
31 |
32 | /**
33 | * Check if an array contains a string.
34 | * Type guard the string if it does.
35 | */
36 | export const has = (
37 | array: unknown[],
38 | item: string
39 | ): item is Extract<(typeof array)[number], string> => array.includes(item);
40 |
--------------------------------------------------------------------------------
/apps/vscode-extension/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Yoav Balasiano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@pretty-ts-errors/vscode-formatter",
3 | "version": "0.1.0",
4 | "description": "Pretty TypeScript Errors Formatter for VSCode hovers",
5 | "files": [
6 | "src/**",
7 | "dist/**"
8 | ],
9 | "main": "./dist/index.js",
10 | "types": "./dist/index.d.ts",
11 | "scripts": {
12 | "build": "tsdown src/index.ts --format cjs,esm --dts",
13 | "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
14 | "lint": "tsc -p . --noEmit",
15 | "publish": "npm run build && npm publish --access public"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/yoavbls/pretty-ts-errors.git"
20 | },
21 | "keywords": [
22 | "typescript",
23 | "errors"
24 | ],
25 | "author": "yoavbls",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/yoavbls/pretty-ts-errors/issues"
29 | },
30 | "homepage": "https://github.com/yoavbls/pretty-ts-errors#readme",
31 | "devDependencies": {
32 | "tsdown": "^0.15.6"
33 | },
34 | "dependencies": {
35 | "@pretty-ts-errors/formatter": "*",
36 | "@pretty-ts-errors/utils": "*",
37 | "lz-string": "^1.5.0",
38 | "vscode-languageserver-types": "^3.17.5"
39 | }
40 | }
--------------------------------------------------------------------------------
/examples/errors.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /**
4 | * @typedef {Object} Person
5 | * @property {string} name
6 | * @property {number} age
7 | * @property {Object} address
8 | * @property {string} address.street
9 | * @property {string} address.city
10 | * @property {string} address.country
11 | */
12 |
13 | /**
14 | * @type {Person}
15 | */
16 | const john = {
17 | name: "John Doe",
18 | age: 30,
19 | address: {
20 | street: "123 Main St",
21 | city: "New York",
22 | },
23 | };
24 |
25 | /**
26 | * @typedef {Function} GetUserFunction
27 | * @returns {{ user: { name: string, email: string, age: number } }}
28 | */
29 |
30 | const getPerson = () => ({
31 | person: {
32 | username: "usr",
33 | email: "usr@usr.io",
34 | },
35 | });
36 |
37 |
38 | /**
39 | * @typedef {Object} JSAnimal
40 | * @property {string} name
41 | * @property {number} age
42 | */
43 |
44 | /**
45 | * @template {JSAnimal} T
46 | * @param {T} animal
47 | * @returns
48 | */
49 | function run(animal) {
50 | return animal;
51 | }
52 |
53 | run({ firstName: "John", weight: 20 });
54 |
55 | /**
56 | * @typedef {Object} MyError
57 | * @property {number} code
58 | */
59 |
60 | try {
61 | // ...
62 | } catch (/** @type {MyError} */ error) {
63 | console.log(error.code);
64 | }
65 |
66 | export {}
--------------------------------------------------------------------------------
/packages/formatter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@pretty-ts-errors/formatter",
3 | "version": "0.1.8",
4 | "description": "Pretty TypeScript Errors Formatter",
5 | "files": [
6 | "src/**",
7 | "dist/**"
8 | ],
9 | "main": "./dist/index.js",
10 | "types": "./dist/index.d.ts",
11 | "scripts": {
12 | "build": "tsdown src/index.ts --format cjs,esm --dts",
13 | "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
14 | "lint": "tsc -p . --noEmit",
15 | "test": "vitest run",
16 | "test:watch": "vitest",
17 | "publish": "npm run build && npm publish --access public"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/yoavbls/pretty-ts-errors.git"
22 | },
23 | "keywords": [
24 | "typescript",
25 | "errors"
26 | ],
27 | "author": "yoavbls",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/yoavbls/pretty-ts-errors/issues"
31 | },
32 | "homepage": "https://github.com/yoavbls/pretty-ts-errors#readme",
33 | "dependencies": {
34 | "@pretty-ts-errors/utils": "*"
35 | },
36 | "devDependencies": {
37 | "@types/prettier": "^2.7.3",
38 | "tsdown": "^0.15.6",
39 | "vitest": "^3.2.4"
40 | },
41 | "peerDependencies": {
42 | "prettier": "^2.8.8"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/docs/hide-original-errors.md:
--------------------------------------------------------------------------------
1 | To hide the original errors, display only the prettified ones, and make type blocks copyable, you can use the following hack:
2 |
3 | ## The Hack
4 |
5 | 1. Install the [Custom CSS and JS Loader](https://marketplace.visualstudio.com/items?itemName=be5invis.vscode-custom-css) extension from the VSCode marketplace.
6 |
7 | 2. Follow the installation instructions provided by the extension, and use [this CSS file](./pretty-ts-errors-hack.css).
8 |
9 | ## Why Do We Need This Hack?
10 |
11 | ### Hiding Original Errors
12 |
13 | Unfortunately, VSCode doesn't currently support formatted diagnostics. Once it does, we'll be able to convert the extension to a TypeScript LSP Plugin that replaces the original error with the prettified version.
14 | For updates on this feature, follow [this issue](https://github.com/yoavbls/pretty-ts-errors/issues/3).
15 |
16 | ### Making Type Blocks Copyable
17 |
18 | VSCode sanitizes and removes most CSS properties for security reasons. We've opened an [issue](https://github.com/microsoft/vscode/issues/180496) and submitted a [PR](https://github.com/microsoft/vscode/pull/180498) to allow the use of the `display` property. This would enable us to layout the types in a way that allows copying.
19 |
20 | Until this change is approved, you can use the hack described above as a workaround.
21 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/components/codeBlock.ts:
--------------------------------------------------------------------------------
1 | import { d } from "@pretty-ts-errors/utils";
2 | import { miniLine } from "./miniLine";
3 | import { spanBreak } from "./spanBreak";
4 |
5 | /**
6 | * @returns markdown string that will be rendered as a code block (`supportHtml` required)
7 | * We're using codicon here since it's the only thing that can be `inline-block`
8 | * and have a background color in hovers due to strict sanitization of markdown on
9 | * VSCode [code](https://github.com/microsoft/vscode/blob/735aff6d962db49423e02c2344e60d418273ae39/src/vs/base/browser/markdownRenderer.ts#L372)
10 | */
11 | const codeBlock = (code: string, language: string) =>
12 | spanBreak(d/*html*/ `
13 |
14 |
15 | \`\`\`${language}
16 | ${code}
17 | \`\`\`
18 |
19 |
20 | `);
21 |
22 | export const inlineCodeBlock = (code: string, language: string) =>
23 | codeBlock(` ${code} `, language);
24 |
25 | export const multiLineCodeBlock = (code: string, language: string) => {
26 | const codeLines = code.split("\n");
27 | //this line is finding the longest line
28 | const maxLineChars = codeLines.reduce(
29 | (acc, curr) => (curr.length > acc ? curr.length : acc),
30 | 0
31 | );
32 | // codicon class align the code to the center, so we must pad it with spaces
33 | const paddedCode = codeLines
34 | .map((line) => line.padEnd(maxLineChars + 2))
35 | .join("\n");
36 |
37 | return d/*html*/ `
38 | ${miniLine}
39 | ${codeBlock(paddedCode, language)}
40 | ${miniLine}
41 | `;
42 | };
43 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "watch - apps/vscode-extension",
8 | "type": "shell",
9 | "command": "npm run watch --silent",
10 | "options": {
11 | "cwd": "${workspaceFolder}/apps/vscode-extension"
12 | },
13 | "problemMatcher": {
14 | "owner": "esbuild",
15 | "fileLocation": "autoDetect",
16 | "pattern": [
17 | {
18 | "regexp": "^✘ \\[ERROR\\] (.*)$",
19 | "message": 1
20 | },
21 | {
22 | "regexp": "^\\s*(.*):(\\d+):(\\d+):$",
23 | "file": 1,
24 | "line": 2,
25 | "column": 3
26 | }
27 | ],
28 | "background": {
29 | "activeOnStart": true,
30 | "beginsPattern": "^\\[watch\\] build started$",
31 | "endsPattern": "^\\[watch\\] build finished$"
32 | }
33 | },
34 | "isBackground": true,
35 | "presentation": {
36 | "reveal": "never",
37 | "group": "watchers"
38 | },
39 | "group": {
40 | "kind": "build",
41 | "isDefault": true
42 | }
43 | },
44 | {
45 | "type": "npm",
46 | "script": "watch-tests",
47 | "path": "apps/vscode-extension",
48 | "problemMatcher": "$tsc-watch",
49 | "isBackground": true,
50 | "presentation": {
51 | "reveal": "never",
52 | "group": "watchers"
53 | },
54 | "group": "build"
55 | },
56 | {
57 | "label": "tasks: watch-tests",
58 | "dependsOn": ["watch - apps/vscode-extension", "npm: watch-tests"],
59 | "problemMatcher": []
60 | }
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/packages/vscode-formatter/src/components/title.ts:
--------------------------------------------------------------------------------
1 | import { compressToEncodedURIComponent } from "lz-string";
2 | import { Diagnostic } from "vscode-languageserver-types";
3 | import { d } from "@pretty-ts-errors/utils";
4 | import { miniLine } from "./miniLine";
5 |
6 | export const title = (diagnostic: Diagnostic) => d/*html*/ `
7 | ⚠ Error ${
8 | typeof diagnostic.code === "number"
9 | ? d/*html*/ `
10 |
11 | (TS${diagnostic.code})
12 | ${errorCodeExplanationLink(diagnostic.code)} |
13 | ${errorMessageTranslationLink(diagnostic.message)} |
14 | ${copyErrorLink(diagnostic.message)}
15 |
16 | `
17 | : ""
18 | }
19 |
20 | ${miniLine}
21 | `;
22 |
23 | const errorCodeExplanationLink = (errorCode: Diagnostic["code"]) =>
24 | d/*html*/ `
25 |
26 |
27 |
28 | `;
29 |
30 | const errorMessageTranslationLink = (message: Diagnostic["message"]) => {
31 | const encodedMessage = compressToEncodedURIComponent(message);
32 |
33 | return d/*html*/ `
34 |
35 |
36 |
37 | `;
38 | };
39 |
40 | const copyErrorLink = (message: Diagnostic["message"]) => {
41 | const args = encodeURIComponent(JSON.stringify(message));
42 | return d/*html*/ `
43 |
44 |
45 |
46 | `;
47 | };
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/apps/vscode-extension/dist/**/*.js",
17 | "${workspaceFolder}/apps/vscode-extension/out/**/*.js"
18 | ],
19 | "preLaunchTask": "watch - apps/vscode-extension"
20 | },
21 | {
22 | "name": "Run Extension (all extensions disabled)",
23 | "type": "extensionHost",
24 | "request": "launch",
25 | "args": [
26 | "--disable-extensions",
27 | "--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension"
28 | ],
29 | "outFiles": [
30 | "${workspaceFolder}/apps/vscode-extension/dist/**/*.js",
31 | "${workspaceFolder}/apps/vscode-extension/out/**/*.js"
32 | ],
33 | "preLaunchTask": "watch - apps/vscode-extension"
34 | },
35 | {
36 | "name": "Extension Tests",
37 | "type": "extensionHost",
38 | "request": "launch",
39 | "args": [
40 | "--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension",
41 | "--extensionTestsPath=${workspaceFolder}/apps/vscode-extension/out/test/suite/index"
42 | ],
43 | "outFiles": [
44 | "${workspaceFolder}/apps/vscode-extension/out/**/*.js",
45 | "${workspaceFolder}/apps/vscode-extension/dist/**/*.js"
46 | ],
47 | "preLaunchTask": "tasks: watch-tests"
48 | }
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/packages/formatter/src/addMissingParentheses.ts:
--------------------------------------------------------------------------------
1 | import { has, invert, objectKeys } from "@pretty-ts-errors/utils";
2 |
3 | const parentheses = {
4 | "(": ")",
5 | "{": "}",
6 | "[": "]",
7 | } as const;
8 |
9 | const openParentheses = objectKeys(parentheses);
10 | const closeParentheses = Object.values(parentheses);
11 |
12 | export function addMissingParentheses(type: string): string {
13 | const openStack: (typeof openParentheses)[number][] = [];
14 | const missingClosingChars: string[] = [];
15 |
16 | for (const char of type) {
17 | if (has(openParentheses, char)) {
18 | openStack.push(char);
19 | } else if (has(closeParentheses, char)) {
20 | const lastOpen = openStack[openStack.length - 1];
21 | if (lastOpen === undefined || parentheses[lastOpen] !== char) {
22 | // Add the correct opening character before the current closing character
23 | openStack.push(invert(parentheses)[char]);
24 | } else {
25 | openStack.pop();
26 | }
27 | }
28 | }
29 |
30 | // Add the missing closing characters at the end of the string
31 | while (openStack.length > 0) {
32 | const openChar = openStack.pop()!;
33 | const closingChar = parentheses[openChar];
34 | missingClosingChars.push(closingChar);
35 | }
36 |
37 | let validType = type;
38 |
39 | // Close the last string if it's not closed
40 | const quoteMatches = validType.match(/['"]/g);
41 | if (quoteMatches) {
42 | const lastQuote = quoteMatches[quoteMatches.length - 1];
43 | if (quoteMatches.length % 2 === 1) {
44 | validType += `...${lastQuote}`;
45 | }
46 | }
47 |
48 | if (validType.endsWith(":")) {
49 | validType += "...";
50 | }
51 |
52 | validType = (validType + "\n..." + missingClosingChars.join("")).replace(
53 | // Change (param: ...) to (param) => __RETURN_TYPE__ if needed
54 | /(\([a-zA-Z0-9]*:.*\))/,
55 | (p1) => `${p1} => ...`
56 | );
57 |
58 | return validType;
59 | }
60 |
--------------------------------------------------------------------------------
/apps/vscode-extension/src/provider/selectedTextHoverProvider.ts:
--------------------------------------------------------------------------------
1 | import { d } from "@pretty-ts-errors/utils";
2 | import { formatDiagnostic } from "@pretty-ts-errors/vscode-formatter";
3 | import {
4 | ExtensionContext,
5 | ExtensionMode,
6 | MarkdownString,
7 | languages,
8 | window,
9 | } from "vscode";
10 | import { createConverter } from "vscode-languageclient/lib/common/codeConverter";
11 |
12 | /**
13 | * Register an hover provider in debug only.
14 | * It format selected text and help test things visually easier.
15 | */
16 | export function registerSelectedTextHoverProvider(context: ExtensionContext) {
17 | const converter = createConverter();
18 |
19 | if (context.extensionMode !== ExtensionMode.Development) {
20 | return;
21 | }
22 |
23 | context.subscriptions.push(
24 | languages.registerHoverProvider(
25 | {
26 | language: "typescript",
27 | pattern: "**/test/**/*.ts",
28 | },
29 | {
30 | provideHover(document, position) {
31 | const editor = window.activeTextEditor;
32 | const range = document.getWordRangeAtPosition(position);
33 | const message = editor ? document.getText(editor.selection) : "";
34 |
35 | if (!range || !message) {
36 | return null;
37 | }
38 |
39 | const markdown = new MarkdownString(
40 | debugHoverHeader +
41 | formatDiagnostic(
42 | converter.asDiagnostic({
43 | message,
44 | range,
45 | severity: 0,
46 | source: "ts",
47 | code: 1337,
48 | })
49 | )
50 | );
51 |
52 | markdown.isTrusted = true;
53 | markdown.supportHtml = true;
54 |
55 | return {
56 | contents: [markdown],
57 | };
58 | },
59 | }
60 | )
61 | );
62 | }
63 |
64 | const debugHoverHeader = d/*html*/ `
65 |
66 |
67 | Formatted selected text (debug only)
68 |
69 |
70 |
71 |
72 | `;
73 |
--------------------------------------------------------------------------------
/packages/formatter/src/formatTypeBlock.ts:
--------------------------------------------------------------------------------
1 | import { addMissingParentheses } from "./addMissingParentheses";
2 | import { prettify } from "./prettify";
3 |
4 | export function formatTypeBlock(
5 | prefix: string,
6 | type: string,
7 | codeBlock: (code: string, language?: string, multiLine?: boolean) => string
8 | ) {
9 | // Return a simple code block if it's just a parenthesis
10 | if (type.match(/^(\[\]|\{\})$/)) {
11 | return `${prefix} ${codeBlock(type)}`;
12 | }
13 |
14 | if (
15 | // Skip formatting if it's a simple type
16 | type.match(
17 | /^((void|null|undefined|any|number|string|bigint|symbol|readonly|typeof)(\[\])?)$/
18 | )
19 | ) {
20 | return `${prefix} ${codeBlock(type, "type")}`;
21 | }
22 |
23 | const prettyType = prettifyType(type);
24 |
25 | if (prettyType.includes("\n")) {
26 | return `${prefix}: ${codeBlock(prettyType, "type", true)}`;
27 | } else {
28 | return `${prefix} ${codeBlock(prettyType, "type")}`;
29 | }
30 | }
31 | /**
32 | * Try to make type prettier with prettier
33 | */
34 | export function prettifyType(
35 | type: string,
36 | options?: { throwOnError?: boolean }
37 | ) {
38 | try {
39 | // Wrap type with valid statement, format it and extract the type back
40 | return convertToOriginalType(prettify(convertToValidType(type)));
41 | } catch (e) {
42 | if (options?.throwOnError) {
43 | throw e;
44 | }
45 | return type;
46 | }
47 | }
48 |
49 | const convertToValidType = (type: string) =>
50 | `type x = ${type
51 | // Add missing parentheses when the type ends with "...""
52 | .replace(/(.*)\.\.\.$/, (_, p1) => addMissingParentheses(p1))
53 | // Replace single parameter function destructuring because it's not a valid type
54 | // .replaceAll(/\((\{.*\})\:/g, (_, p1) => `(param: /* ${p1} */`)
55 | // Change `(...): return` which is invalid to `(...) => return`
56 | .replace(/^(\(.*\)): /, (_, p1) => `${p1} =>`)
57 | .replaceAll(/... (\d{0,}) more .../g, (_, p1) => `___${p1}MORE___`)
58 | .replaceAll(/... (\d{0,}) more ...;/g, (_, p1) => `___MORE___: ${p1};`)
59 | .replaceAll("...;", "___KEY___: ___THREE_DOTS___;")
60 | .replaceAll("...", "__THREE_DOTS__")};`;
61 |
62 | const convertToOriginalType = (type: string) =>
63 | type
64 | .replaceAll("___KEY___: ___THREE_DOTS___", "...;")
65 | .replaceAll("__THREE_DOTS__", "...")
66 | .replaceAll(/___MORE___: (\d{0,});/g, (_, p1) => `... ${p1} more ...;`)
67 | .replaceAll(/___(\d{0,})MORE___/g, (_, p1) => `... ${p1} more ...`)
68 | .replaceAll(/... (\d{0,}) more .../g, (_, p1) => `/* ${p1} more */`) // ... x more ... not shown sell
69 | // .replaceAll(/\(param\: \/\* (\{ .* \}) \*\//g, (_, p1) => `(${p1}: `)
70 | .replace(/type x =[ ]?((.|\n)*);.*/g, "$1")
71 | .trim();
72 |
--------------------------------------------------------------------------------
/packages/formatter/test/formatter.vitest.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | import { formatDiagnosticMessage } from "@pretty-ts-errors/formatter";
3 | import { addMissingParentheses } from "@pretty-ts-errors/formatter/src/addMissingParentheses";
4 | import { prettifyType } from "@pretty-ts-errors/formatter/src/formatTypeBlock";
5 | import {
6 | inlineCodeBlock,
7 | multiLineCodeBlock,
8 | } from "@pretty-ts-errors/vscode-formatter/src/components";
9 | import { d } from "@pretty-ts-errors/utils";
10 | import {
11 | errorWithDashInObjectKeys,
12 | errorWithSpecialCharsInObjectKeys,
13 | } from "./errorMessageMocks";
14 |
15 | describe("formatter", () => {
16 | it("adds missing parentheses", () => {
17 | expect(addMissingParentheses("Hello, {world! [This] is a (test.")).toBe(
18 | "Hello, {world! [This] is a (test.\n...)}"
19 | );
20 | });
21 |
22 | // Provide a codeBlock fn similar to the extension's renderer
23 | const htmlCodeBlock = (
24 | code: string,
25 | language?: string,
26 | multiLine?: boolean
27 | ) =>
28 | multiLine
29 | ? multiLineCodeBlock(code, language || "")
30 | : inlineCodeBlock(code, language || "");
31 |
32 | it("formats Special characters in object keys", () => {
33 | expect(
34 | formatDiagnosticMessage(errorWithSpecialCharsInObjectKeys, htmlCodeBlock)
35 | ).toBe(
36 | "Type " +
37 | inlineCodeBlock("string", "type") +
38 | " is not assignable to type " +
39 | inlineCodeBlock(`{ "abc*bc": string }`, "type") +
40 | "."
41 | );
42 | });
43 |
44 | it("formats method's word in the error", () => {
45 | expect(
46 | formatDiagnosticMessage(errorWithDashInObjectKeys, htmlCodeBlock)
47 | ).toBe(
48 | "Type " +
49 | inlineCodeBlock(`{ person: { "first-name": string } }`, "type") +
50 | " is not assignable to type " +
51 | inlineCodeBlock("string", "type") +
52 | "."
53 | );
54 | });
55 |
56 | it("prettifies type with params destructuring", () => {
57 | expect(() =>
58 | prettifyType(
59 | d` { $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }
60 | `,
61 | { throwOnError: true }
62 | )
63 | ).not.toThrow();
64 | });
65 |
66 | it("prettifies truncated type", () => {
67 | expect(() =>
68 | prettifyType(
69 | d` { b: { name: string; icon: undefined; }; c: { name: string; icon: undefined; }; d: { name: string; icon: undefined; }; e: { name: string; icon: undefined; }; f: { ...; }; g: { ...; }; h:...`,
70 | { throwOnError: true }
71 | )
72 | ).not.toThrow();
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/apps/vscode-extension/scripts/build.js:
--------------------------------------------------------------------------------
1 | const process = require("node:process");
2 | const console = require("node:console");
3 | const production = process.argv.includes("--production");
4 | const watch = process.argv.includes("--watch");
5 |
6 | /**
7 | * @see https://code.visualstudio.com/api/working-with-extensions/bundling-extension#using-esbuild
8 | */
9 | async function main() {
10 | const ctx = await require("esbuild").context({
11 | entryPoints: {
12 | extension: "./src/extension.ts",
13 | },
14 | bundle: true,
15 | outdir: "./dist",
16 | external: ["vscode"],
17 | format: "cjs",
18 | inject: ["./scripts/process-shim.js"],
19 | tsconfig: "./tsconfig.json",
20 | define: production ? { "process.env.NODE_ENV": '"production"' } : undefined,
21 | minify: production,
22 | sourcemap: !production,
23 | plugins: [workspacePackagesPlugin, esbuildProblemMatcherPlugin],
24 | });
25 | if (watch) {
26 | await ctx.watch();
27 | } else {
28 | await ctx.rebuild();
29 | await ctx.dispose();
30 | }
31 | }
32 |
33 | /**
34 | * @type {import('esbuild').Plugin}
35 | */
36 | const esbuildProblemMatcherPlugin = {
37 | name: "esbuild-problem-matcher",
38 | setup(build) {
39 | build.onStart(() => {
40 | console.log("[watch] build started");
41 | });
42 | build.onEnd((result) => {
43 | result.errors.forEach(({ text, location }) => {
44 | console.error(`✘ [ERROR] ${text}`);
45 | console.error(
46 | ` ${location.file}:${location.line}:${location.column}:`
47 | );
48 | });
49 | console.log("[watch] build finished");
50 | });
51 | },
52 | };
53 |
54 | /**
55 | * Resolve internal workspace packages to their source files so we bundle them in watch/debug.
56 | * This makes changes in packages/* reflected immediately without separate watchers.
57 | * @type {import('esbuild').Plugin}
58 | */
59 | const workspacePackagesPlugin = {
60 | name: "workspace-packages",
61 | setup(build) {
62 | const path = require("node:path");
63 | const pkgRoot = path.resolve(__dirname, "../../../packages");
64 | /** @type {Record} */
65 | const alias = {
66 | "@pretty-ts-errors/utils": path.join(pkgRoot, "utils/src/index.ts"),
67 | "@pretty-ts-errors/formatter": path.join(
68 | pkgRoot,
69 | "formatter/src/index.ts"
70 | ),
71 | "@pretty-ts-errors/vscode-formatter": path.join(
72 | pkgRoot,
73 | "vscode-formatter/src/index.ts"
74 | ),
75 | };
76 | build.onResolve(
77 | { filter: /^@pretty-ts-errors\/(utils|formatter|vscode-formatter)$/ },
78 | (args) => {
79 | const target = alias[args.path];
80 | return target ? { path: target } : undefined;
81 | }
82 | );
83 | },
84 | };
85 |
86 | main().catch((e) => {
87 | console.error(e);
88 | process.exit(1);
89 | });
90 |
--------------------------------------------------------------------------------
/apps/vscode-extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pretty-ts-errors",
3 | "displayName": "Pretty TypeScript Errors",
4 | "publisher": "YoavBls",
5 | "description": "Make TypeScript errors prettier and more human-readable in VSCode",
6 | "version": "0.6.3",
7 | "icon": "assets/icon.png",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/yoavbls/pretty-ts-errors",
11 | "directory": "apps/vscode-extension"
12 | },
13 | "homepage": "https://github.com/yoavbls/pretty-ts-errors",
14 | "engines": {
15 | "vscode": "^1.77.0"
16 | },
17 | "vsce": {
18 | "dependencies": false
19 | },
20 | "categories": [
21 | "Programming Languages",
22 | "Debuggers",
23 | "Visualization",
24 | "Other"
25 | ],
26 | "galleryBanner": {
27 | "color": "#133b55",
28 | "theme": "dark"
29 | },
30 | "activationEvents": [
31 | "onLanguage:typescript",
32 | "onLanguage:javascript",
33 | "onLanguage:typescriptreact",
34 | "onLanguage:javascriptreact",
35 | "onLanguage:astro",
36 | "onLanguage:svelte",
37 | "onLanguage:vue",
38 | "onLanguage:mdx",
39 | "onLanguage:glimmer-js",
40 | "onLanguage:glimmer-ts"
41 | ],
42 | "main": "./dist/extension.js",
43 | "browser": "./dist/extension.js",
44 | "files": [
45 | "dist/**/*",
46 | "assets/**/*",
47 | "syntaxes/**/*",
48 | "LICENSE"
49 | ],
50 | "contributes": {
51 | "languages": [
52 | {
53 | "id": "type",
54 | "extensions": [
55 | ".type"
56 | ]
57 | }
58 | ],
59 | "grammars": [
60 | {
61 | "language": "type",
62 | "scopeName": "source.type",
63 | "path": "./syntaxes/type.tmGrammar.json"
64 | }
65 | ]
66 | },
67 | "scripts": {
68 | "vscode:prepublish": "cp ../../README.md README.md && npm run package",
69 | "compile": "node scripts/build",
70 | "watch": "npm run compile -- --watch",
71 | "dev": "npm-run-all --parallel _dev:*",
72 | "_dev:ext": "npm run watch",
73 | "_dev:formatter": "npm run -w @pretty-ts-errors/formatter dev",
74 | "_dev:vscode-formatter": "npm run -w @pretty-ts-errors/vscode-formatter dev",
75 | "build": "vsce package",
76 | "package": "node scripts/build -- --production",
77 | "compile-tests": "tsc -p . --outDir out",
78 | "watch-tests": "tsc -p . -w --outDir out",
79 | "pretest": "npm run compile-tests && npm run compile && npm run lint",
80 | "lint": "tsc -p . --noEmit",
81 | "test": "node ./out/test/runTest.js"
82 | },
83 | "devDependencies": {
84 | "@types/mocha": "^10.0.10",
85 | "@types/node": "^16.11.68",
86 | "@types/vscode": "^1.77.0",
87 | "@vscode/test-electron": "^2.5.2",
88 | "esbuild": "^0.25.11",
89 | "glob": "^11.0.3",
90 | "mocha": "^11.7.4",
91 | "npm-run-all": "^4.1.5"
92 | },
93 | "dependencies": {
94 | "@pretty-ts-errors/formatter": "*",
95 | "@pretty-ts-errors/vscode-formatter": "*",
96 | "vscode-languageclient": "^9.0.1"
97 | }
98 | }
--------------------------------------------------------------------------------
/apps/vscode-extension/src/extension.ts:
--------------------------------------------------------------------------------
1 | import { has } from "@pretty-ts-errors/utils";
2 | import { formatDiagnostic } from "@pretty-ts-errors/vscode-formatter";
3 | import {
4 | commands,
5 | env,
6 | ExtensionContext,
7 | languages,
8 | MarkdownString,
9 | Range,
10 | window,
11 | } from "vscode";
12 | import { createConverter } from "vscode-languageclient/lib/common/codeConverter";
13 | import { hoverProvider } from "./provider/hoverProvider";
14 | import { registerSelectedTextHoverProvider } from "./provider/selectedTextHoverProvider";
15 | import { uriStore } from "./provider/uriStore";
16 |
17 | const cache = new Map();
18 |
19 | export function activate(context: ExtensionContext) {
20 | const registeredLanguages = new Set();
21 | const converter = createConverter();
22 |
23 | // register the copy command
24 | context.subscriptions.push(
25 | commands.registerCommand(
26 | "prettyTsErrors.copyError",
27 | async (errorMessage: string) => {
28 | await env.clipboard.writeText(errorMessage);
29 | }
30 | )
31 | );
32 |
33 | registerSelectedTextHoverProvider(context);
34 |
35 | context.subscriptions.push(
36 | languages.onDidChangeDiagnostics(async (e) => {
37 | e.uris.forEach((uri) => {
38 | const diagnostics = languages.getDiagnostics(uri);
39 |
40 | const items: {
41 | range: Range;
42 | contents: MarkdownString[];
43 | }[] = [];
44 |
45 | let hasTsDiagnostic = false;
46 |
47 | diagnostics
48 | .filter((diagnostic) =>
49 | diagnostic.source
50 | ? has(
51 | ["ts", "ts-plugin", "deno-ts", "js", "glint"],
52 | diagnostic.source
53 | )
54 | : false
55 | )
56 | .forEach(async (diagnostic) => {
57 | // formatDiagnostic converts message based on LSP Diagnostic type, not VSCode Diagnostic type, so it can be used in other IDEs.
58 | // Here we convert VSCode Diagnostic to LSP Diagnostic to make formatDiagnostic recognize it.
59 | let formattedMessage = cache.get(diagnostic.message);
60 |
61 | if (!formattedMessage) {
62 | const markdownString = new MarkdownString(
63 | formatDiagnostic(converter.asDiagnostic(diagnostic))
64 | );
65 |
66 | markdownString.isTrusted = true;
67 | markdownString.supportHtml = true;
68 |
69 | formattedMessage = markdownString;
70 | cache.set(diagnostic.message, formattedMessage);
71 |
72 | if (cache.size > 100) {
73 | const firstCacheKey = cache.keys().next().value;
74 | cache.delete(firstCacheKey);
75 | }
76 | }
77 |
78 | items.push({
79 | range: diagnostic.range,
80 | contents: [formattedMessage],
81 | });
82 |
83 | hasTsDiagnostic = true;
84 | });
85 |
86 | uriStore[uri.fsPath] = items;
87 |
88 | if (hasTsDiagnostic) {
89 | const editor = window.visibleTextEditors.find(
90 | (editor) => editor.document.uri.toString() === uri.toString()
91 | );
92 | if (editor && !registeredLanguages.has(editor.document.languageId)) {
93 | registeredLanguages.add(editor.document.languageId);
94 | context.subscriptions.push(
95 | languages.registerHoverProvider(
96 | {
97 | language: editor.document.languageId,
98 | },
99 | hoverProvider
100 | )
101 | );
102 | }
103 | }
104 | });
105 | })
106 | );
107 | }
108 |
--------------------------------------------------------------------------------
/packages/formatter/src/formatDiagnosticMessage.ts:
--------------------------------------------------------------------------------
1 | import { formatTypeBlock } from "./formatTypeBlock";
2 |
3 | export const formatDiagnosticMessage = (
4 | message: string,
5 | codeBlock: (code: string, language?: string, multiLine?: boolean) => string
6 | ) => {
7 | const formatTypeScriptBlock = (_: string, code: string) =>
8 | codeBlock(code, "typescript");
9 |
10 | const formatSimpleTypeBlock = (_: string, code: string) =>
11 | codeBlock(code, "type");
12 |
13 | const formatTypeOrModuleBlock = (_: string, prefix: string, code: string) =>
14 | formatTypeBlock(
15 | prefix,
16 | ["module", "file", "file name"].includes(prefix.toLowerCase())
17 | ? `"${code}"`
18 | : code,
19 | codeBlock
20 | );
21 |
22 | return (
23 | message
24 | // format strings wrapped like '"..."' (double quotes inside single quotes)
25 | .replaceAll(
26 | /(?:\s)'"(.*?)(? formatTypeBlock("", `"${p1}"`, codeBlock)
28 | )
29 | // format declare module snippet
30 | .replaceAll(
31 | /['“](declare module )['”](.*)['“];['”]/g,
32 | (_: string, p1: string, p2: string) =>
33 | formatTypeScriptBlock(_, `${p1} "${p2}"`)
34 | )
35 | // format missing props error
36 | .replaceAll(
37 | /(is missing the following properties from type\s?)'(.*)': ((?:#?\w+, )*(?:(?!and)\w+)?)/g,
38 | (_, pre, type, post) =>
39 | `${pre}${formatTypeBlock("", type, codeBlock)}: ${post
40 | .split(", ")
41 | .filter(Boolean)
42 | .map((prop: string) => `- ${prop}
`)
43 | .join("")}
`
44 | )
45 | // Format type pairs
46 | .replaceAll(
47 | /(types) ['“](.*?)['”] and ['“](.*?)['”][.]?/gi,
48 | (_: string, p1: string, p2: string, p3: string) =>
49 | `${formatTypeBlock(p1, p2, codeBlock)} and ${formatTypeBlock(
50 | "",
51 | p3,
52 | codeBlock
53 | )}`
54 | )
55 | // Format type annotation options
56 | .replaceAll(
57 | /type annotation must be ['“](.*?)['”] or ['“](.*?)['”][.]?/gi,
58 | (_: string, p1: string, p2: string, p3: string | number) => {
59 | if (typeof p3 === "string") {
60 | return `${formatTypeBlock(p1, p2, codeBlock)} or ${formatTypeBlock(
61 | "",
62 | p3,
63 | codeBlock
64 | )}`;
65 | } else {
66 | // If p3 is a number, it is matching a ts(1196) error, see #121
67 | return `${formatTypeBlock("", p1, codeBlock)} or ${formatTypeBlock(
68 | "",
69 | p2,
70 | codeBlock
71 | )}`;
72 | }
73 | }
74 | )
75 | .replaceAll(
76 | /(Overload \d of \d), ['“](.*?)['”], /gi,
77 | (_, p1: string, p2: string) =>
78 | `${p1}${formatTypeBlock("", p2, codeBlock)}`
79 | )
80 | // format simple strings
81 | .replaceAll(/^['“]"[^"]*"['”]$/g, formatTypeScriptBlock)
82 | // Replace module 'x' by module "x" for ts error #2307
83 | .replaceAll(
84 | /(module )'([^"]*?)'/gi,
85 | (_, p1: string, p2: string) => `${p1}"${p2}"`
86 | )
87 | // Format string types
88 | .replaceAll(
89 | /(module|file|file name|imported via) ['"“](.*?)['"“](?=[\s(.|,]|$)/gi,
90 | (_, p1: string, p2: string) => formatTypeBlock(p1, `"${p2}"`, codeBlock)
91 | )
92 | // Format types
93 | .replaceAll(
94 | /(type|type alias|interface|module|file|file name|class|method's|subtype of constraint) ['“](.*?)['“](?=[\s(.|,)]|$)/gi,
95 | (_, p1: string, p2: string) => formatTypeOrModuleBlock(_, p1, p2)
96 | )
97 | // Format reversed types
98 | .replaceAll(
99 | /(.*)['“]([^>]*)['”] (type|interface|return type|file|module|is (not )?assignable)/gi,
100 | (_: string, p1: string, p2: string, p3: string) =>
101 | `${p1}${formatTypeOrModuleBlock(_, "", p2)} ${p3}`
102 | )
103 | // Format simple types that didn't captured before
104 | .replaceAll(
105 | /['“]((void|null|undefined|any|boolean|string|number|bigint|symbol)(\[\])?)['”]/g,
106 | formatSimpleTypeBlock
107 | )
108 | // Format some typescript key words
109 | .replaceAll(
110 | /['“](import|export|require|in|continue|break|let|false|true|const|new|throw|await|for await|[0-9]+)( ?.*?)['”]/g,
111 | (_: string, p1: string, p2: string) =>
112 | formatTypeScriptBlock(_, `${p1}${p2}`)
113 | )
114 | // Format return values
115 | .replaceAll(
116 | /(return|operator) ['“](.*?)['”]/gi,
117 | (_, p1: string, p2: string) => `${p1} ${formatTypeScriptBlock("", p2)}`
118 | )
119 | // Format regular code blocks
120 | .replaceAll(
121 | /(? ` ${codeBlock(p1)} `
123 | )
124 | );
125 | };
126 |
--------------------------------------------------------------------------------
/packages/formatter/test/errorMessageMocks.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file contains mocks of error messages, only some of them
3 | * are used in tests but all of them can be used to test and debug
4 | * the formatting visually, you can try to select them on debug and check the hover.
5 | */
6 |
7 | import { d } from "@pretty-ts-errors/utils";
8 |
9 | export const errorWithSpecialCharsInObjectKeys = d`
10 | Type 'string' is not assignable to type '{ 'abc*bc': string; }'.
11 | `;
12 |
13 | export const errorWithDashInObjectKeys = d`
14 | Type '{ person: { 'first-name': string; }; }' is not assignable to type 'string'.
15 | `;
16 |
17 | /**
18 | * Formatting error from this issue: https://github.com/yoavbls/pretty-ts-errors/issues/20
19 | */
20 | export const errorWithMethodsWordInIt = d`
21 | The 'this' context of type 'ElementHandle' is not assignable to method's 'this' of type 'ElementHandle'.
22 | Type 'Node' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 114 more.
23 | `;
24 |
25 | export const errorWithParamsDestructuring = d`
26 | Argument of type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' is not assignable to parameter of type 'VTableConfig'.
27 | Property 'data' is missing in type '{ $ref: null; ref: (ref: any) => any; columns: ({ label: string; prop: string; } | { label: string; formatter: ({ ip_type }: any) => any; } | { actions: { label: string; disabled: ({ contract_id }: any) => boolean; handler({ contract_id }: any): void; }[]; })[]; ... 4 more ...; load(): Promise<...>; }' but required in type 'VTableConfig'.
28 | `;
29 |
30 | export const errorWithLongType = d`
31 | Property 'isFlying' is missing in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }' but required in type '{ animal: { __typename?: "Animal" | undefined; id: string; name: string; age: number; isAlived: boolean; isFlying: boolean; ... 8 more ...; attributes: { ...; } | ... 3 more ... | { ...; }; }; }'.
32 | `;
33 |
34 | export const errorWithTruncatedType2 = d`
35 | Type '{ '!top': string[]; 'xsl:declaration': { attrs: { 'default-collation': null; 'exclude-result-prefixes': null; 'extension-element-prefixes': null; 'use-when': null; 'xpath-default-namespace': null; }; }; 'xsl:instruction': { ...; }; ... 49 more ...; 'xsl:literal-result-element': {}; }' is missing the following properties from type 'GraphQLSchema': description, extensions, astNode, extensionASTNodes, and 21 more.
36 | `;
37 |
38 | export const variableNotUsedEror = d`
39 | 'a' is declared but its value is never read.
40 | `;
41 |
42 | export const errorWithSimpleIndentations = d`
43 | Type '(newIds: number[]) => void' is not assignable to type '(selectedId: string[]) => void'.
44 | Types of parameters 'newIds' and 'selectedId' are incompatible.
45 | Type 'string[]' is not assignable to type 'number[]'.
46 | Type 'string' is not assignable to type 'number'.
47 | `;
48 |
49 | export const errorWithComma = d`
50 | Argument of type '{ filters: Filters; } & T' is not assignable to parameter of type 'T & F'.
51 | Type '{ filters: Filters; } & T' is not assignable to type 'F'.
52 | '{ filters: Filters; } & T' is assignable to the constraint of type 'F', but 'F' could be instantiated with a different subtype of constraint '{ filters: Filters; }'.
53 | `;
54 |
55 | export const missingPropertyError =
56 | "\
57 | Property 'user' is missing in type '{ person: { username: string; email: string; }; }' but required in type '{ user: { name: string; email: `${string}@${string}.${string}`; age: number; }; }'.\
58 | ";
59 |
60 | export const missingReactPropsError = d`
61 | Type '{ style: { backgroundColor: string; }; }' is not assignable to type 'DropDownPickerProps