Repository: yoavbls/pretty-ts-errors
Branch: main
Commit: 89b004805a2d
Files: 87
Total size: 113.9 KB
Directory structure:
gitextract_npj6yo6v/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ └── workflows/
│ └── pr-ci.yml
├── .gitignore
├── .prettierrc
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── apps/
│ └── vscode-extension/
│ ├── .gitignore
│ ├── LICENSE
│ ├── package.json
│ ├── scripts/
│ │ ├── build.js
│ │ └── process-shim.js
│ ├── src/
│ │ ├── commands/
│ │ │ ├── copyError.ts
│ │ │ ├── execute.ts
│ │ │ ├── pinError.ts
│ │ │ ├── revealSelection.ts
│ │ │ ├── showErrorInSidebar.ts
│ │ │ ├── unpinError.ts
│ │ │ └── validate.ts
│ │ ├── diagnostics.ts
│ │ ├── extension.ts
│ │ ├── formattedDiagnosticsStore.ts
│ │ ├── globals.d.ts
│ │ ├── logger.ts
│ │ ├── provider/
│ │ │ ├── hoverProvider.ts
│ │ │ ├── markdownWebviewProvider.ts
│ │ │ ├── selectedTextHoverProvider.ts
│ │ │ └── webviewViewProvider.ts
│ │ ├── supportedLanguageIds.ts
│ │ └── test/
│ │ ├── runTest.ts
│ │ └── suite/
│ │ ├── extension.test.ts
│ │ └── index.ts
│ ├── syntaxes/
│ │ └── type.tmGrammar.json
│ ├── tsconfig.json
│ └── webview/
│ ├── index.html
│ ├── index.js
│ ├── style.css
│ └── vendor/
│ └── codicon.css
├── docs/
│ ├── hide-original-errors.md
│ ├── pretty-ts-errors-hack.css
│ └── vscode-logs.md
├── eslint.config.mjs
├── examples/
│ ├── errors.js
│ ├── errors.ts
│ ├── errors.vue
│ └── examples.type
├── package.json
├── packages/
│ ├── formatter/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── addMissingParentheses.ts
│ │ │ ├── errorMessagePrettifier.ts
│ │ │ ├── formatTypeBlock.ts
│ │ │ ├── formatTypeWithPrettier.ts
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── errorMessageMocks.ts
│ │ │ └── formatter.vitest.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── utils/
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ └── vscode-formatter/
│ ├── README.md
│ ├── package.json
│ ├── src/
│ │ ├── components/
│ │ │ ├── actions.ts
│ │ │ ├── errorTitle.ts
│ │ │ ├── hoverCodeBlock.ts
│ │ │ ├── htmlCodeBlock.ts
│ │ │ ├── miniLine.ts
│ │ │ ├── plainCodeBlock.ts
│ │ │ └── spanBreak.ts
│ │ ├── format/
│ │ │ ├── embedSymbolLinks.ts
│ │ │ ├── identSentences.ts
│ │ │ ├── prettifyDiagnosticForHover.ts
│ │ │ └── prettifyDiagnosticForSidebar.ts
│ │ └── index.ts
│ ├── test/
│ │ └── vscode-formatter.vitest.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── tsconfig.base.json
├── tsconfig.json
├── tsdown.config.mjs
└── turbo.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: [yoavbls, kevinramharak]
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: bug
assignees: ""
---
### **Describe the bug**
A clear and concise description of what the bug is.
### **Expected behavior**
A clear and concise description of what you expected to happen.
### **Original error**
If this bug is related to an error that is not formatting well, please
attach the original error in a code block:
```
Type 'number' is not assignable to type 'string'.ts(2322)
```
### **Logs**
Add the logs to help debugging what went wrong. See [these instructions](https://github.com/yoavbls/pretty-ts-errors/blob/main/docs/vscode-logs.md) on how to find and export the logs.
Either add it as an external file or put them in between these `<pre><code>` tags below:
<details>
<summary>Logs</summary>
<pre><code>
<!-- replace this comment with your log output -->
</code></pre>
</details>
### **Screenshots**
If applicable, add screenshots to help explain your problem.
================================================
FILE: .github/workflows/pr-ci.yml
================================================
name: PR CI
on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions:
contents: read
concurrency:
group: pr-ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
validate:
name: Install, Build, and Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm install
- name: Install VSCE CLI
run: npm install -g @vscode/vsce
- name: Check formatting
run: npm run format:check
- name: Build all workspaces
run: npm run build
- name: Run formatter tests
run: npm -w @pretty-ts-errors/formatter run test -- --reporter=verbose
- name: Run VS Code formatter tests
run: npm -w @pretty-ts-errors/vscode-formatter run test -- --reporter=verbose
- name: Run VS Code extension tests
run: xvfb-run -a npm --workspace apps/vscode-extension run test
================================================
FILE: .gitignore
================================================
out
dist
node_modules
.vscode-test/
*.vsix
.turbo
.vercel
.idea
**/.DS_Store
tsconfig.tsbuildinfo
================================================
FILE: .prettierrc
================================================
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5"
}
================================================
FILE: .vscode/extensions.json
================================================
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"connor4312.esbuild-problem-matchers"
]
}
================================================
FILE: .vscode/launch.json
================================================
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension"
],
"outFiles": [
"${workspaceFolder}/apps/vscode-extension/dist/**/*.js",
"${workspaceFolder}/apps/vscode-extension/out/**/*.js"
],
"preLaunchTask": "watch - apps/vscode-extension"
},
{
"name": "Run Extension (all extensions disabled)",
"type": "extensionHost",
"request": "launch",
"args": [
"--disable-extensions",
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension"
],
"outFiles": [
"${workspaceFolder}/apps/vscode-extension/dist/**/*.js",
"${workspaceFolder}/apps/vscode-extension/out/**/*.js"
],
"preLaunchTask": "watch - apps/vscode-extension"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension",
"--extensionTestsPath=${workspaceFolder}/apps/vscode-extension/out/test/suite/index"
],
"outFiles": [
"${workspaceFolder}/apps/vscode-extension/out/**/*.js",
"${workspaceFolder}/apps/vscode-extension/dist/**/*.js"
],
"preLaunchTask": "tasks: watch-tests"
}
]
}
================================================
FILE: .vscode/settings.json
================================================
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false, // set this to true to hide the "out" folder with the compiled JS files
"dist": false // set this to true to hide the "dist" folder with the compiled JS files
},
"search.exclude": {
"out": true, // set this to false to include "out" folder in search results
"dist": true // set this to false to include "dist" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"chat.tools.terminal.autoApprove": {
"npx vitest": true
}
}
================================================
FILE: .vscode/tasks.json
================================================
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"label": "watch - apps/vscode-extension",
"type": "shell",
"command": "npm run watch --silent",
"options": {
"cwd": "${workspaceFolder}/apps/vscode-extension"
},
"problemMatcher": {
"owner": "esbuild",
"fileLocation": "autoDetect",
"pattern": [
{
"regexp": "^✘ \\[ERROR\\] (.*)$",
"message": 1
},
{
"regexp": "^\\s*(.*):(\\d+):(\\d+):$",
"file": 1,
"line": 2,
"column": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "^\\[watch\\] build started$",
"endsPattern": "^\\[watch\\] build finished$"
}
},
"isBackground": true,
"presentation": {
"reveal": "never",
"group": "watchers"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "watch-tests",
"path": "apps/vscode-extension",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never",
"group": "watchers"
},
"group": "build"
},
{
"label": "tasks: watch-tests",
"dependsOn": ["watch - apps/vscode-extension", "npm: watch-tests"],
"problemMatcher": []
}
]
}
================================================
FILE: CONTRIBUTING.md
================================================
Contribute good stuff
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)
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Yoav Balasiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<a href="https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors" style="display: none;">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/icon.png" width="140">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/icon.png" width="140">
<img src="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/empty.png" alt="Logo">
</picture>
</a>
# Pretty `TypeScript` Errors
<b>Make TypeScript errors prettier and human-readable in VSCode.</b>
[](https://GitHub.com/yoavbls/pretty-ts-errors/stargazers/)
[](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) [](https://github.com/yoavbls/pretty-ts-errors/blob/main/LICENSE) [](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)
<a href="https://github.com/yoavbls/pretty-ts-errors/discussions/43#user-content-jetbrains-support"><img src="https://cdn.icon-icons.com/icons2/2530/PNG/512/jetbrains_webstorm_button_icon_151873.png" height="20" alt="Webstorm logo"></a>
[](https://open-vsx.org/extension/yoavbls/pretty-ts-errors)
TypeScript errors become messier as the complexity of types increases. At some point, TypeScript will throw on you a shitty heap of parentheses and `"..."`.
This extension will help you understand what's going on. For example, in this relatively simple error:
<img src="./assets/this.png" width="340.438px" /> <img src="./assets/instead-of-that.png" width="350px" />
## Watch this
<a href="https://www.youtube.com/watch?v=9RM2aErJs-s" target="_blank">
<img src="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/theo-video.png" alt="Watch theo's video" width="600" />
</a>
and others from:
[Web Dev Simplified](https://www.youtube.com/watch?v=ccg-erZYO4k&list=PL0rc4JAdEsVpOriHzlAG7KUnhKIK9c7OR&index=1),
[Josh tried coding](https://www.youtube.com/watch?v=_9y29Cyo9uU&list=PL0rc4JAdEsVpOriHzlAG7KUnhKIK9c7OR&index=3),
[trash dev](https://www.youtube.com/watch?v=WJeD3DKlWT4&list=PL0rc4JAdEsVpOriHzlAG7KUnhKIK9c7OR&index=4&t=208),
and [more](https://www.youtube.com/playlist?list=PL0rc4JAdEsVpOriHzlAG7KUnhKIK9c7OR)
## Features
- Syntax highlighting with your theme colors for types in error messages, supporting both light and dark themes
- A button that leads you to the relevant type declaration next to the type in the error message
- A button that navigates you to the error at [typescript.tv](http://typescript.tv), where you can find a detailed explanation, sometimes with a video
- A button that navigates you to [ts-error-translator](https://ts-error-translator.vercel.app/), where you can read the error in plain English
## Supports
- Node and Deno TypeScript error reporters (in `.ts` files)
- JSDoc type errors (in `.js` and `.jsx` files)
- React, Solid and Qwik errors (in `.tsx` and `.mdx` files)
- Astro, Svelte and Vue files when TypeScript is enabled (in `.astro`, `.svelte` and `.vue` files)
- Ember and Glimmer TypeScript errors and template issues reported by Glint (in `.hbs`, `.gjs`, and `.gts` files)
## Installation
```
code --install-extension yoavbls.pretty-ts-errors
```
Or simply by searching for `pretty-ts-errors` in the [VSCode marketplace](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)
#### How to hide the original errors and make the types copyable
Follow the instructions [there](./docs/hide-original-errors.md). unfortunately, this hack is required because of VSCode limitations.
## Why isn't it trivial
1. TypeScript errors contain types that are not valid in TypeScript.
Yes, these types include things like `... more ...`, `{ ... }`, etc in an inconsistent manner. Some are also cutting in the middle because they're too long.
2. Types can't be syntax highlighted in code blocks because the part of `type X = ...` is missing, so I needed to create a new TextMate grammar, a superset of TypeScript grammar called `type`.
3. VSCode markdown blocks all styling options, so I had to find hacks to style the error messages. e.g., there isn't an inlined code block on VSCode markdown, so I used a code block inside a codicon icon, which is the only thing that can be inlined. That's why it can't be copied. but it isn't a problem because you can still hover on the error and copy things from the original error pane.
<img src="./assets/errors-hover.png" width="600" />
## Hype section
<a href="https://www.youtube.com/live/Zze1y2iZ3bQ?si=Yj1Qw2S8FbGbTA5c&t=11589">
<picture>
<img width="400" src="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/js-nation.png?raw=true" alt="Winning the Productivity Booster category at JSNation 2023">
</picture>
</a>
<a href="https://twitter.com/tannerlinsley/status/1647982562026090496">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/tanner-dark.png#gh-light-mode-only">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/tanner-light.png#gh-light-mode-only">
<img width="400" src="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/tanner-dark.png#gh-dark-mode-only" alt="Tanner's tweet">
</picture>
</a>
<a href="https://twitter.com/t3dotgg/status/1647759462709747713">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/theo-dark.png#gh-dark-mode-only">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/theo-light.png#gh-light-mode-only">
<img width="400" src="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/theo-dark.png#gh-dark-mode-only" alt="Theo's tweet">
</picture>
</a>
<a href="https://twitter.com/johnsoncodehk/status/1646214711204286465">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/johnson-dark.png#gh-dark-mode-only">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/johnson-light.png#gh-light-mode-only">
<img width="400" src="https://raw.githubusercontent.com/yoavbls/pretty-ts-errors/main/assets/mentions/johnson-dark.png#gh-dark-mode-only" alt="Johnson's tweet">
</picture>
</a>
### Stars from stars
<table>
<tbody>
<tr>
<td align="center">
<a href="https://github.com/kentcdodds">
<img src="https://images.weserv.nl/?url=github.com/kentcdodds.png&fit=cover&mask=circle" width="80"><br>
Kent C. Dodds
<a/>
</td>
<td align="center">
<a href="https://github.com/mattpocock">
<img src="https://images.weserv.nl/?url=github.com/mattpocock.png&fit=cover&mask=circle" width="80"><br>
Matt Pocock
<a/>
</td>
<td align="center">
<a href="https://github.com/katt">
<img src="https://images.weserv.nl/?url=github.com/katt.png&fit=cover&mask=circle" width="80"><br>
Alex / KATT
<a/>
</td>
<td align="center">
<a href="https://github.com/tannerlinsley">
<img src="https://images.weserv.nl/?url=github.com/tannerlinsley.png&fit=cover&mask=circle" width="80"><br>
Tanner Linsley
<a/>
</td>
<td align="center">
<a href="https://github.com/t3dotgg">
<img src="https://images.weserv.nl/?url=github.com/t3dotgg.png&fit=cover&mask=circle" width="80"><br>
Theo Browne
<a/>
</td>
</tr>
</tbody>
</table>
## Sponsorship
Every penny will be invested in other contributors to the project, especially ones that work
on things that I can't be doing myself like adding support to the extension for other IDEs 🫂
## Contribution
Help by upvoting or commenting on issues we need to be resolved [here](https://github.com/yoavbls/pretty-ts-errors/discussions/43)
Any other contribution is welcome. Feel free to open any issue / PR you think.
================================================
FILE: apps/vscode-extension/.gitignore
================================================
README.md
================================================
FILE: apps/vscode-extension/LICENSE
================================================
MIT License
Copyright (c) 2023 Yoav Balasiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: apps/vscode-extension/package.json
================================================
{
"name": "pretty-ts-errors",
"displayName": "Pretty TypeScript Errors",
"publisher": "YoavBls",
"description": "Make TypeScript errors prettier and more human-readable in VSCode",
"version": "0.8.4",
"icon": "assets/icon.png",
"repository": {
"type": "git",
"url": "https://github.com/yoavbls/pretty-ts-errors",
"directory": "apps/vscode-extension"
},
"homepage": "https://github.com/yoavbls/pretty-ts-errors",
"engines": {
"vscode": "^1.77.0"
},
"vsce": {
"dependencies": false
},
"categories": [
"Programming Languages",
"Debuggers",
"Visualization",
"Other"
],
"galleryBanner": {
"color": "#133b55",
"theme": "dark"
},
"activationEvents": [
"onLanguage:typescript",
"onLanguage:javascript",
"onLanguage:typescriptreact",
"onLanguage:javascriptreact",
"onLanguage:astro",
"onLanguage:svelte",
"onLanguage:vue",
"onLanguage:mdx",
"onLanguage:glimmer-js",
"onLanguage:glimmer-ts"
],
"main": "./dist/extension.js",
"browser": "./dist/extension.js",
"files": [
"dist/**/*",
"assets/**/*",
"syntaxes/**/*",
"webview/**/*",
"LICENSE"
],
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "prettyTsErrors",
"title": "Pretty TypeScript Errors",
"icon": "./assets/ribbon.svg"
}
]
},
"views": {
"prettyTsErrors": [
{
"id": "prettyTsErrors.sidePanel",
"icon": "./assets/ribbon.svg",
"name": "Side Panel",
"type": "webview"
}
]
},
"commands": [
{
"command": "prettyTsErrors.revealSelection",
"title": "Reveal the given selection",
"category": "Pretty TS Errors",
"enablement": "!isCommandPanel"
},
{
"command": "prettyTsErrors.showErrorInSidebar",
"title": "Show Error in Sidebar",
"category": "Pretty TS Errors",
"icon": "./assets/ribbon.svg",
"enablement": "!isCommandPanel"
},
{
"command": "prettyTsErrors.pinError",
"title": "Pin Error",
"category": "Pretty TS Errors",
"enablement": "!isCommandPanel"
},
{
"command": "prettyTsErrors.unpinError",
"title": "Unpin Error",
"category": "Pretty TS Errors",
"enablement": "!isCommandPanel"
}
],
"menus": {
"editor/title": [
{
"command": "prettyTsErrors.showErrorInSidebar",
"when": "prettyTsErrors.hasErrors && resourceLangId in prettyTsErrors.supportedLanguageIds",
"group": "navigation"
}
]
},
"languages": [
{
"id": "type",
"extensions": [
".type"
]
}
],
"grammars": [
{
"language": "type",
"scopeName": "source.type",
"path": "./syntaxes/type.tmGrammar.json"
}
]
},
"scripts": {
"vscode:prepublish": "cp ../../README.md README.md && npm run package",
"compile": "node scripts/build",
"watch": "npm run compile -- --watch",
"dev": "npm-run-all --parallel _dev:*",
"_dev:ext": "npm run watch",
"_dev:formatter": "npm run -w @pretty-ts-errors/formatter dev",
"_dev:vscode-formatter": "npm run -w @pretty-ts-errors/vscode-formatter dev",
"build": "vsce package",
"package": "node scripts/build -- --production",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "npm run compile-tests && npm run compile && npm run lint",
"lint": "tsc -p . --noEmit",
"test": "node ./out/test/runTest.js",
"webview": "npx http-server ./webview -o -a localhost -p 8080"
},
"devDependencies": {
"@shikijs/types": "^3.13.0",
"@types/mocha": "^10.0.10",
"@types/node": "^16.11.68",
"@types/vscode": "^1.77.0",
"@types/vscode-webview": "^1.57.5",
"@vscode/codicons": "^0.0.41",
"@vscode/test-electron": "^2.5.2",
"esbuild": "^0.25.11",
"glob": "^13.0.6",
"mocha": "12.0.0-beta-10",
"npm-run-all": "^4.1.5"
},
"dependencies": {
"@pretty-ts-errors/formatter": "*",
"@pretty-ts-errors/utils": "*",
"@pretty-ts-errors/vscode-formatter": "*",
"shiki": "^3.13.0",
"vscode-languageclient": "^9.0.1",
"vscode-shiki-bridge": "^0.5.2"
}
}
================================================
FILE: apps/vscode-extension/scripts/build.js
================================================
const process = require("node:process");
const console = require("node:console");
const fs = require("node:fs");
const path = require("node:path");
const production = process.argv.includes("--production");
const watch = process.argv.includes("--watch");
/**
* @see https://code.visualstudio.com/api/working-with-extensions/bundling-extension#using-esbuild
*/
async function main() {
const ctx = await require("esbuild").context({
entryPoints: {
extension: "./src/extension.ts",
},
bundle: true,
outdir: "./dist",
external: ["vscode"],
format: "cjs",
inject: ["./scripts/process-shim.js"],
tsconfig: "./tsconfig.json",
define: production ? { "process.env.NODE_ENV": '"production"' } : undefined,
minify: production,
sourcemap: !production,
plugins: [workspacePackagesPlugin, esbuildProblemMatcherPlugin],
});
if (watch) {
await ctx.watch();
} else {
fs.rmSync("./dist", { recursive: true, force: true });
await ctx.rebuild();
await ctx.dispose();
}
}
/**
* @type {import('esbuild').Plugin}
*/
const esbuildProblemMatcherPlugin = {
name: "esbuild-problem-matcher",
setup(build) {
build.onStart(() => {
console.log("[watch] build started");
});
build.onEnd((result) => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`);
console.error(
` ${location.file}:${location.line}:${location.column}:`
);
});
console.log("[watch] build finished");
});
},
};
/**
* Resolve internal workspace packages to their source files so we bundle them in watch/debug.
* This makes changes in packages/* reflected immediately without separate watchers.
* @type {import('esbuild').Plugin}
*/
const workspacePackagesPlugin = {
name: "workspace-packages",
setup(build) {
const pkgRoot = path.resolve(__dirname, "../../../packages");
/** @type {Record<string, string>} */
const alias = {
"@pretty-ts-errors/utils": path.join(pkgRoot, "utils/src/index.ts"),
"@pretty-ts-errors/formatter": path.join(
pkgRoot,
"formatter/src/index.ts"
),
"@pretty-ts-errors/vscode-formatter": path.join(
pkgRoot,
"vscode-formatter/src/index.ts"
),
};
build.onResolve(
{ filter: /^@pretty-ts-errors\/(utils|formatter|vscode-formatter)$/ },
(args) => {
const target = alias[args.path];
return target ? { path: target } : undefined;
}
);
},
};
main().catch((e) => {
console.error(e);
process.exit(1);
});
================================================
FILE: apps/vscode-extension/scripts/process-shim.js
================================================
// https://esbuild.github.io/api/#inject
let _cwd = "/";
export let process = {
cwd: () => _cwd,
chdir: (newCwd) => (_cwd = newCwd),
env: {},
};
================================================
FILE: apps/vscode-extension/src/commands/copyError.ts
================================================
import { commands, env, window, type ExtensionContext } from "vscode";
import { execute } from "./execute";
const COMMAND_ID = "prettyTsErrors.copyError";
export function registerCopyError(context: ExtensionContext) {
context.subscriptions.push(
commands.registerCommand(COMMAND_ID, async (errorMessage: unknown) =>
execute(COMMAND_ID, async () => {
if (typeof errorMessage !== "string") {
throw new Error("cannot write non-string value to clipboard", {
cause: errorMessage,
});
}
await env.clipboard.writeText(errorMessage);
window.showInformationMessage("Copied error message to clipboard!");
})
)
);
}
================================================
FILE: apps/vscode-extension/src/commands/execute.ts
================================================
import { window } from "vscode";
import { logger } from "../logger";
/**
* A wrapper function to execute command tasks while providing user feedback and logging on errors.
*/
export async function execute(
commandName: string,
task: (...args: unknown[]) => unknown | Promise<unknown>
) {
try {
return await task();
} catch (error) {
if (error instanceof Error) {
logger.error(error);
} else if (typeof error === "string") {
logger.error(error);
} else {
logger.error("caught non-string or error value: ", error);
}
window.showErrorMessage(`Failed to execute command: '${commandName}'`);
throw error;
}
}
================================================
FILE: apps/vscode-extension/src/commands/pinError.ts
================================================
import { commands, type ExtensionContext } from "vscode";
import { execute } from "./execute";
import { tryEnsureRange } from "./validate";
import { getViewProvider } from "../provider/webviewViewProvider";
const COMMAND_ID = "prettyTsErrors.pinError";
export function registerPinError(context: ExtensionContext) {
context.subscriptions.push(
commands.registerCommand(
COMMAND_ID,
async (maybeRangeLike: unknown, maybeMessage?: unknown) =>
execute(COMMAND_ID, async () => {
const { isValidRange, range } = tryEnsureRange(maybeRangeLike);
if (!isValidRange) {
throw new Error("cannot pin error with an invalid range", {
cause: maybeRangeLike,
});
}
const message =
typeof maybeMessage === "string" ? maybeMessage : undefined;
const viewProvider = getViewProvider();
await viewProvider?.pinDiagnostic(range, message);
try {
await commands.executeCommand("prettyTsErrors.sidePanel.focus");
} catch {}
})
)
);
}
================================================
FILE: apps/vscode-extension/src/commands/revealSelection.ts
================================================
import { type ExtensionContext, commands } from "vscode";
import { tryEnsureRange, tryEnsureUri } from "./validate";
import { execute } from "./execute";
const COMMAND_ID = "prettyTsErrors.revealSelection";
export function registerRevealSelection(context: ExtensionContext) {
context.subscriptions.push(
commands.registerCommand(
COMMAND_ID,
async (maybeUriLike: unknown, maybeRangeLike: unknown) =>
execute(COMMAND_ID, async () => {
const { isValidUri, uri } = tryEnsureUri(maybeUriLike);
const { isValidRange, range } = tryEnsureRange(maybeRangeLike);
if (!isValidUri || !isValidRange) {
throw new Error(
"cannot reveal selection with invalid range or uri",
{
cause: {
range: maybeRangeLike,
uri: maybeUriLike,
},
}
);
}
return commands.executeCommand("vscode.open", uri, {
selection: range,
});
})
)
);
}
================================================
FILE: apps/vscode-extension/src/commands/showErrorInSidebar.ts
================================================
import { commands, type ExtensionContext } from "vscode";
import { execute } from "./execute";
import { tryEnsureRange } from "./validate";
import { getViewProvider } from "../provider/webviewViewProvider";
const COMMAND_ID = "prettyTsErrors.showErrorInSidebar";
export function registerShowErrorInSidebar(context: ExtensionContext) {
context.subscriptions.push(
commands.registerCommand(
COMMAND_ID,
async (maybeRangeLike?: unknown, maybeMessage?: unknown) =>
execute(COMMAND_ID, async () => {
if (maybeRangeLike !== undefined) {
const { isValidRange, range } = tryEnsureRange(maybeRangeLike);
const message =
typeof maybeMessage === "string" ? maybeMessage : undefined;
const viewProvider = getViewProvider();
if (isValidRange && viewProvider) {
await viewProvider.lockToDiagnostic(range, message);
}
}
try {
await commands.executeCommand(
"workbench.view.extension.prettyTsErrors"
);
} catch {}
})
)
);
}
================================================
FILE: apps/vscode-extension/src/commands/unpinError.ts
================================================
import { commands, type ExtensionContext } from "vscode";
import { execute } from "./execute";
import { getViewProvider } from "../provider/webviewViewProvider";
const COMMAND_ID = "prettyTsErrors.unpinError";
export function registerUnpinError(context: ExtensionContext) {
context.subscriptions.push(
commands.registerCommand(COMMAND_ID, async () =>
execute(COMMAND_ID, async () => {
const viewProvider = getViewProvider();
viewProvider?.unpinDiagnostic();
})
)
);
}
================================================
FILE: apps/vscode-extension/src/commands/validate.ts
================================================
import { Range, Uri } from "vscode";
export function tryEnsureUri(
maybeUriLike: unknown
): { isValidUri: true; uri: Uri } | { isValidUri: false; uri?: undefined } {
if (maybeUriLike instanceof Uri) {
return { isValidUri: true, uri: maybeUriLike };
}
if (typeof maybeUriLike === "string") {
try {
return { isValidUri: true, uri: Uri.parse(maybeUriLike, true) };
} catch (error) {
return { isValidUri: false };
}
}
if (isUriLike(maybeUriLike)) {
return { isValidUri: true, uri: Uri.from(maybeUriLike) };
}
return { isValidUri: false };
}
type UriLike = Parameters<typeof Uri.from>[0];
function isUriLike(value: unknown): value is UriLike {
return (
typeof value === "object" &&
value != null &&
"scheme" in value &&
typeof value.scheme === "string"
);
}
export function tryEnsureRange(
maybeRangeLike: unknown
):
| { isValidRange: true; range: Range }
| { isValidRange: false; range?: undefined } {
if (maybeRangeLike instanceof Range) {
return { isValidRange: true, range: maybeRangeLike };
}
if (isRangeLike(maybeRangeLike)) {
return {
isValidRange: true,
range: new Range(
maybeRangeLike.start.line,
maybeRangeLike.start.character,
maybeRangeLike.end.line,
maybeRangeLike.end.character
),
};
}
return { isValidRange: false };
}
type RangeLike = { start: PositionLike; end: PositionLike };
function isRangeLike(value: unknown): value is RangeLike {
return (
typeof value === "object" &&
value != null &&
"start" in value &&
isPositionLike(value.start) &&
"end" in value &&
isPositionLike(value.end)
);
}
type PositionLike = { line: number; character: number };
function isPositionLike(value: unknown): value is PositionLike {
return (
typeof value === "object" &&
value !== null &&
"line" in value &&
typeof value.line === "number" &&
"character" in value &&
typeof value.character === "number"
);
}
================================================
FILE: apps/vscode-extension/src/diagnostics.ts
================================================
import { has } from "@pretty-ts-errors/utils";
import { prettifyDiagnosticForHover } from "@pretty-ts-errors/vscode-formatter";
import {
ExtensionContext,
languages,
MarkdownString,
window,
Uri,
type Diagnostic,
} from "vscode";
import {
createConverter,
type Converter,
} from "vscode-languageclient/lib/common/codeConverter";
import { hoverProvider } from "./provider/hoverProvider";
import {
formattedDiagnosticsStore,
type FormattedDiagnostic,
} from "./formattedDiagnosticsStore";
import { logger } from "./logger";
/**
* The list of diagnostic sources that pretty-ts-errors supports
*/
const supportedDiagnosticSources = [
"ts",
"ts-plugin",
"deno-ts",
"js",
"glint",
];
export function registerOnDidChangeDiagnostics(context: ExtensionContext) {
const converter = createConverter();
context.subscriptions.push(
languages.onDidChangeDiagnostics(async (e) => {
await logger.measure("onDidChangeDiagnostics", async () => {
for (const uri of e.uris) {
await logger.measure(
`diagnostics for: ${uri.toString(true)}`,
async () => {
const diagnostics = languages.getDiagnostics(uri);
const supportedDiagnostics = diagnostics.filter(
(diagnostic) =>
diagnostic.source &&
has(supportedDiagnosticSources, diagnostic.source)
);
const items: FormattedDiagnostic[] = await Promise.all(
supportedDiagnostics.map((diagnostic) =>
getFormattedDiagnostic(diagnostic, converter)
)
);
// TODO: we should check if never deleting the entries is a performance issue
// probably not, since solving all diagnostics for a file should set its value to an empty collection, but we should check anyway
// see: https://github.com/yoavbls/pretty-ts-errors/issues/139
formattedDiagnosticsStore.set(uri.fsPath, items);
if (items.length > 0) {
ensureHoverProviderIsRegistered(uri, context);
}
}
);
}
});
})
);
}
/**
* To prevent infinite memory consumption use a max size for the cache
*
* TODO: consider making this configurable to the end user with a sensible `min` and `max`
*/
const CACHE_SIZE_MAX = 100;
/**
* A local cache that maps TS diagnostics as `string` to their formatted `MarkdownString` counter part.
* @see https://github.com/yoavbls/pretty-ts-errors/pull/62
*
* One reason this cache is critical is because the TypeScript Language Features extension is very noisy and will constantly push all diagnostics for a file,
* even if there were no actual changes.
* @see https://github.com/yoavbls/pretty-ts-errors/issues/139#issuecomment-3401279357
*
* TODO: create a proper LRU cache, to prevent exceeding the cache size being a bottleneck
* @see https://github.com/yoavbls/pretty-ts-errors/issues/104
*/
const cache = new Map<string, MarkdownString>();
async function getFormattedDiagnostic(
diagnostic: Diagnostic,
converter: Converter
): Promise<FormattedDiagnostic> {
// formatDiagnosticForHover converts message based on LSP Diagnostic type, not VSCode Diagnostic type, so it can be used in other IDEs.
// Here we convert VSCode Diagnostic to LSP Diagnostic to make formatDiagnosticForHover recognize it.
const lspDiagnostic = converter.asDiagnostic(diagnostic);
let formattedMessage = cache.get(diagnostic.message);
if (!formattedMessage) {
const formattedDiagnostic = await prettifyDiagnosticForHover(lspDiagnostic);
const markdownString = new MarkdownString(formattedDiagnostic);
// TODO: consider using the `{ enabledCommands: string[] }` variant, to only allow whitelisted commands
markdownString.isTrusted = true;
markdownString.supportHtml = true;
formattedMessage = markdownString;
if (cache.size > CACHE_SIZE_MAX) {
const firstCacheKey = cache.keys().next().value!;
cache.delete(firstCacheKey);
}
cache.set(diagnostic.message, formattedMessage);
}
return {
range: diagnostic.range,
contents: [formattedMessage],
lspDiagnostic,
};
}
/**
* A set to prevent registering duplicate hover providers.
*/
const registeredLanguages = new Set<string>();
/**
* Ensure a hover provider is registered for any visible editors where pretty-ts-errors has a formatted diagnostic
*/
function ensureHoverProviderIsRegistered(uri: Uri, context: ExtensionContext) {
const editor = window.visibleTextEditors.find(
(editor) => editor.document.uri.toString() === uri.toString()
);
const languageId = editor?.document.languageId;
if (languageId && !registeredLanguages.has(languageId)) {
logger.debug(`registering hover provider for language id: ${languageId}`);
registeredLanguages.add(languageId);
context.subscriptions.push(
languages.registerHoverProvider(
{
language: languageId,
},
hoverProvider
)
);
}
}
================================================
FILE: apps/vscode-extension/src/extension.ts
================================================
import { ExtensionContext, commands } from "vscode";
import { registerOnDidChangeDiagnostics } from "./diagnostics";
import { logger } from "./logger";
import { registerCopyError } from "./commands/copyError";
import { registerRevealSelection } from "./commands/revealSelection";
import { registerShowErrorInSidebar } from "./commands/showErrorInSidebar";
import { registerPinError } from "./commands/pinError";
import { registerUnpinError } from "./commands/unpinError";
import { registerSelectedTextHoverProvider } from "./provider/selectedTextHoverProvider";
import { registerWebviewViewProvider } from "./provider/webviewViewProvider";
import { SUPPORTED_LANGUAGE_IDS } from "./supportedLanguageIds";
export function activate(context: ExtensionContext) {
logger.info("activating");
void commands.executeCommand(
"setContext",
"prettyTsErrors.supportedLanguageIds",
SUPPORTED_LANGUAGE_IDS
);
// logging and debug features
logger.register(context);
registerSelectedTextHoverProvider(context);
// prettify diagnostics feature
registerOnDidChangeDiagnostics(context);
// UI elements that show the prettified diagnostics
registerWebviewViewProvider(context);
// register commands
registerCopyError(context);
registerShowErrorInSidebar(context);
registerPinError(context);
registerUnpinError(context);
registerRevealSelection(context);
}
export function deactivate() {
logger.info("deactivating");
}
================================================
FILE: apps/vscode-extension/src/formattedDiagnosticsStore.ts
================================================
import { MarkdownString, Range, Uri } from "vscode";
import type { Diagnostic } from "vscode-languageserver-types";
type StoreKey = Uri["fsPath"];
export interface FormattedDiagnostic {
range: Range;
contents: MarkdownString[];
/** Original LSP diagnostic for on-demand sidebar formatting */
lspDiagnostic: Diagnostic;
}
/**
* A store for formatted diagnostics, where the key is the file path to a file, and the value a collection of formatted diagnostics for that file.
*
* The `onDidChangeDiagnostics` event handler will fill the store with formatted diagnostics, while other components will query the store to display these diagnostics.
*/
export const formattedDiagnosticsStore = new Map<
StoreKey,
FormattedDiagnostic[]
>();
================================================
FILE: apps/vscode-extension/src/globals.d.ts
================================================
// "@types/node": "^16.11.68" misses a bunch of global declarations, this file fixes the one's we use.
declare global {
declare const TextDecoder: new (
encoding?: string,
options?: {
fatal?: boolean | undefined;
ignoreBOM?: boolean | undefined;
}
) => import("util").TextDecoder;
}
export {};
================================================
FILE: apps/vscode-extension/src/logger.ts
================================================
import {
ExtensionMode,
LogOutputChannel,
window,
type ExtensionContext,
LogLevel as VSLogLevel,
} from "vscode";
let instance: null | LogOutputChannel = null;
function getLogger(): LogOutputChannel {
if (instance !== null) {
return instance;
}
instance = window.createOutputChannel("Pretty TypeScript Errors", {
log: true,
});
return instance;
}
function info(...args: Parameters<LogOutputChannel["info"]>) {
getLogger().info(...args);
}
function trace(...args: Parameters<LogOutputChannel["trace"]>) {
getLogger().trace(...args);
}
function debug(...args: Parameters<LogOutputChannel["debug"]>) {
getLogger().debug(...args);
}
function warn(...args: Parameters<LogOutputChannel["warn"]>) {
getLogger().warn(...args);
}
function error(...args: Parameters<LogOutputChannel["error"]>) {
getLogger().error(...args);
}
type LogLevel = "info" | "trace" | "debug" | "warn" | "error";
type LogLevelThresholds = Record<LogLevel, number>;
type SortedLogLevelThresholds = [LogLevel, number][];
const defaultThresholds: LogLevelThresholds = {
error: 5000,
warn: 1000,
info: 100,
debug: 50,
trace: 0,
};
/**
* Both in the browser and Node >= 16 (vscode 1.77 has node >= 16) have `performance` available as a global
* But `@types/node` is missing its global declaration, this fixes the type error we get from using it
*/
declare const performance: import("perf_hooks").Performance;
/**
* Measures the time it took to run `task` and reports it to the `logger` based on `logLevelThresholds`.
*
* @see {@link defaultThresholds} for the default thresholds
*/
function measure<T = unknown>(
name: string,
task: () => Promise<T>,
logLevelThresholds?: Partial<LogLevelThresholds>
): Promise<T>;
function measure<T = unknown>(
name: string,
task: () => T,
logLevelThresholds?: Partial<LogLevelThresholds>
): T;
function measure<T = unknown>(
name: string,
task: () => T | Promise<T>,
logLevelThresholds: Partial<LogLevelThresholds> = {}
): T | Promise<T> {
const start = performance.now();
const thresholds = normalizeThresholds(logLevelThresholds);
try {
const result = task();
if (isPromiseLike(result)) {
return result.then(
(value) => {
logMeasuredDuration(name, start, thresholds);
return value;
},
(error) => {
logMeasuredDuration(name, start, thresholds);
throw error;
}
);
}
logMeasuredDuration(name, start, thresholds);
return result;
} catch (error) {
logMeasuredDuration(name, start, thresholds);
throw error;
}
}
function logMeasuredDuration(
name: string,
start: number,
thresholds: SortedLogLevelThresholds
) {
const duration = performance.now() - start;
const level = findLogLevel(thresholds, duration);
getLogger()[level](`task ${name} took ${duration.toFixed(3)}ms`);
}
function normalizeThresholds(
logLevelThresholds: Partial<LogLevelThresholds>
): SortedLogLevelThresholds {
logLevelThresholds = Object.assign({}, defaultThresholds, logLevelThresholds);
// sort thresholds from high to low
// { info: 100, warn: 1000, trace: 0 } => [[warn, 1000], [info, 100], [trace, 0]]
return Object.entries(logLevelThresholds).sort(
([_a, a], [_b, b]) => b - a
) as SortedLogLevelThresholds;
}
function findLogLevel(
thresholds: SortedLogLevelThresholds,
duration: number,
defaultLogLevel: LogLevel = "debug"
): LogLevel {
return (
thresholds.find(([_, threshold]) => duration > threshold)?.[0] ??
defaultLogLevel
);
}
function dispose() {
if (instance !== null) {
instance.dispose();
instance = null;
}
}
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
return (
value != null &&
typeof value === "object" &&
"then" in value &&
typeof value["then"] === "function"
);
}
function register(context: ExtensionContext) {
if (context.extensionMode === ExtensionMode.Development) {
const instance = getLogger();
instance.show();
if (instance.logLevel !== VSLogLevel.Trace) {
instance.appendLine(
`To see more verbose logging, set this output's log level to "Trace" (gear icon next to the dropdown).`
);
}
}
context.subscriptions.push({ dispose });
}
export const logger = {
trace,
debug,
info,
warn,
error,
measure,
register,
dispose,
};
================================================
FILE: apps/vscode-extension/src/provider/hoverProvider.ts
================================================
import { HoverProvider } from "vscode";
import { formattedDiagnosticsStore } from "../formattedDiagnosticsStore";
export const hoverProvider: HoverProvider = {
provideHover(document, position, _token) {
const items = formattedDiagnosticsStore.get(document.uri.fsPath);
if (!items) {
return null;
}
const itemInRange = items.filter((item) => item.range.contains(position));
if (itemInRange.length === 0) {
return null;
}
const first = itemInRange[0];
if (!first) {
return null;
}
return {
range: first.range,
contents: itemInRange.flatMap((item) => item.contents),
};
},
};
================================================
FILE: apps/vscode-extension/src/provider/markdownWebviewProvider.ts
================================================
import * as vscode from "vscode";
/**
* @see https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample
*/
export class MarkdownWebviewProvider {
private webviewRootUri: vscode.Uri;
private webviewHtmlTemplate: Promise<string>;
constructor(private readonly context: vscode.ExtensionContext) {
this.webviewRootUri = vscode.Uri.joinPath(
this.context.extensionUri,
"webview"
);
this.webviewHtmlTemplate = this.loadWebviewHtmlTemplate();
}
private async loadWebviewHtmlTemplate(): Promise<string> {
const htmlTemplateUri = vscode.Uri.joinPath(
this.webviewRootUri,
"index.html"
);
const htmlTemplateBytes =
await vscode.workspace.fs.readFile(htmlTemplateUri);
const htmlTemplate = new TextDecoder("utf-8").decode(htmlTemplateBytes);
return htmlTemplate;
}
getWebviewOptions(): vscode.WebviewOptions {
return {
enableCommandUris: [
"prettyTsErrors.revealSelection",
"prettyTsErrors.copyError",
"prettyTsErrors.pinError",
"prettyTsErrors.unpinError",
],
enableScripts: true,
enableForms: false,
localResourceRoots: [this.webviewRootUri],
};
}
createOnDidReceiveMessage() {
return (message: { command: string; [key: string]: unknown }) => {
if (message && message.command) {
switch (message.command) {
case "notify": {
if (typeof message["text"] === "string") {
vscode.window.showInformationMessage(message["text"]);
}
break;
}
}
}
};
}
async getWebviewContent(
webview: vscode.Webview,
content: string,
classList: string[] = []
): Promise<string> {
const template = await this.webviewHtmlTemplate;
const html = this.patchCspSafeAttrs(template, webview);
return html.replace(
'<div id="content"></div>',
`<div id="content" class="${classList.join(" ")}">${content}</div>`
);
}
private patchCspSafeAttrs(html: string, webview: vscode.Webview) {
// replace stylesheet href's to webview uri's
html = html.replaceAll(
/<link\s+rel="stylesheet"\s+href="(\.\/.+)"\s+data-href-as-webview-uri\s*\/?>/gm,
(match, filePath) => {
const path = vscode.Uri.joinPath(this.webviewRootUri, filePath);
const uri = webview.asWebviewUri(path);
return match.replace(filePath, uri.toString());
}
);
// replace script src's to webiew uri's
html = html.replaceAll(
/<script\s+src="(\.\/.+)"\s+data-src-as-webview-uri\s*>/gm,
(match, filePath) => {
const path = vscode.Uri.joinPath(this.webviewRootUri, filePath);
const uri = webview.asWebviewUri(path);
return match.replace(filePath, uri.toString());
}
);
// replace the local development csp header with `webview.cspSource`
// @see https://code.visualstudio.com/api/extension-guides/webview#content-security-policy
html = html.replace(
/<meta\s+http-equiv="Content-Security-Policy"\s+data-csp-replace-content\s+content="(.+)"\s*\/>/m,
(match, content) => {
return match.replace(
content,
content
.replaceAll(
"style-src http://localhost:8080",
// TODO: remove `unsafe-inline` if vscode ever fixes their styles and api injection
`style-src ${webview.cspSource} 'unsafe-inline'`
)
.replaceAll(
"script-src http://localhost:8080",
// TODO: remove `unsafe-inline` if vscode ever fixes their styles and api injection
`script-src ${webview.cspSource} 'unsafe-inline'`
)
.replaceAll(
"font-src http://localhost:8080",
`font-src ${webview.cspSource}`
)
);
}
);
return html;
}
}
================================================
FILE: apps/vscode-extension/src/provider/selectedTextHoverProvider.ts
================================================
import { d } from "@pretty-ts-errors/utils";
import { prettifyDiagnosticForHover } from "@pretty-ts-errors/vscode-formatter";
import {
ExtensionContext,
ExtensionMode,
MarkdownString,
languages,
window,
} from "vscode";
import { createConverter } from "vscode-languageclient/lib/common/codeConverter";
import { formattedDiagnosticsStore } from "../formattedDiagnosticsStore";
/**
* Register an hover provider in debug only.
* It format selected text and help test things visually easier.
*/
export function registerSelectedTextHoverProvider(context: ExtensionContext) {
if (context.extensionMode !== ExtensionMode.Development) {
return;
}
const converter = createConverter();
context.subscriptions.push(
languages.registerHoverProvider(
{
language: "typescript",
pattern: "**/test/**/*.ts",
},
{
async provideHover(document, position) {
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const range = document.getWordRangeAtPosition(position);
const message = editor ? document.getText(editor.selection) : "";
if (!range || !message) {
return null;
}
const lspDiagnostic = converter.asDiagnostic({
message,
range,
severity: 0,
source: "ts",
code: 1337,
});
const markdown = new MarkdownString(
debugHoverHeader + (await prettifyDiagnosticForHover(lspDiagnostic))
);
markdown.isTrusted = true;
markdown.supportHtml = true;
formattedDiagnosticsStore.set(document.uri.fsPath, [
{
range,
contents: [markdown],
lspDiagnostic,
},
]);
return {
contents: [markdown],
};
},
}
)
);
}
const debugHoverHeader = d /*html*/ `
<span style="color:#f96363;">
<span class="codicon codicon-debug"></span>
Formatted selected text (debug only)
</span>
<br>
<hr>
<p></p>
`;
================================================
FILE: apps/vscode-extension/src/provider/webviewViewProvider.ts
================================================
import type { ExtensionContext } from "vscode";
import * as vscode from "vscode";
import { getUserLangs, getUserTheme } from "vscode-shiki-bridge";
import { createHighlighterCore } from "shiki/core";
import { createOnigurumaEngine } from "shiki/engine/oniguruma";
import { MarkdownWebviewProvider } from "./markdownWebviewProvider";
import {
formattedDiagnosticsStore,
type FormattedDiagnostic,
} from "../formattedDiagnosticsStore";
import { has } from "@pretty-ts-errors/utils";
import {
prettifyDiagnosticForSidebar,
initHighlighter,
} from "@pretty-ts-errors/vscode-formatter";
import { SUPPORTED_LANGUAGE_IDS } from "../supportedLanguageIds";
const NO_DIAGNOSTICS_MESSAGE =
"Select code with an error to show the prettified diagnostic in this view.";
type ViewMode = "cursor" | "locked";
interface DiagnosticItem {
html: string;
range: vscode.Range;
}
interface PinnedError {
html: string;
}
let viewProviderInstance: MarkdownWebviewViewProvider | null = null;
export function getViewProvider() {
return viewProviderInstance;
}
function updateHasErrorsContext() {
const editor = vscode.window.activeTextEditor;
if (editor && has(SUPPORTED_LANGUAGE_IDS, editor.document.languageId)) {
const diagnostics = vscode.languages.getDiagnostics(editor.document.uri);
const hasErrors = diagnostics.length > 0;
vscode.commands.executeCommand(
"setContext",
"prettyTsErrors.hasErrors",
hasErrors
);
} else {
vscode.commands.executeCommand(
"setContext",
"prettyTsErrors.hasErrors",
false
);
}
}
export function registerWebviewViewProvider(context: ExtensionContext) {
viewProviderInstance = new MarkdownWebviewViewProvider(
new MarkdownWebviewProvider(context)
);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
"prettyTsErrors.sidePanel",
viewProviderInstance
),
vscode.languages.onDidChangeDiagnostics(() => updateHasErrorsContext()),
vscode.window.onDidChangeActiveTextEditor(() => updateHasErrorsContext())
);
updateHasErrorsContext();
}
async function diagnosticToItem(
formattedDiagnostic: FormattedDiagnostic
): Promise<DiagnosticItem> {
return {
html: await prettifyDiagnosticForSidebar(formattedDiagnostic.lspDiagnostic),
range: formattedDiagnostic.range,
};
}
// TODO: adding a `MarkdownWebviewView` class would make this provider a lot simpler
class MarkdownWebviewViewProvider implements vscode.WebviewViewProvider {
private disposables = new Map<vscode.WebviewView, vscode.Disposable[]>();
private webview: vscode.Webview | null = null;
private mode: ViewMode = "cursor";
private lockedContent: DiagnosticItem | null = null;
private pinnedError: PinnedError | null = null;
private lastContent: string | null = null;
private skipNextSelectionChange = false;
private skipNextEditorChange = false;
private initialized = false;
constructor(private readonly provider: MarkdownWebviewProvider) {}
private async ensureInitialized() {
if (!this.initialized) {
const [theme, themes] = await getUserTheme();
const langs = await getUserLangs(["type", "ts"]);
const highlighter = await createHighlighterCore({
themes,
langs,
engine: createOnigurumaEngine(import("shiki/wasm")),
});
initHighlighter({
codeToHtml: (code: string, options: { lang: string }) =>
highlighter.codeToHtml(code, { ...options, theme }),
});
this.initialized = true;
}
}
async lockToDiagnostic(range: vscode.Range, message?: string) {
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
const diagnostics =
formattedDiagnosticsStore.get(activeEditor.document.uri.fsPath) ?? [];
const diagnostic = diagnostics.find(
(diagnostic) =>
diagnostic.range.isEqual(range) &&
(!message || diagnostic.lspDiagnostic.message === message)
);
if (diagnostic) {
await this.ensureInitialized();
this.mode = "locked";
this.lockedContent = await diagnosticToItem(diagnostic);
this.skipNextSelectionChange = true;
this.skipNextEditorChange = true;
this.lastContent = null;
if (this.webview) {
this.refresh(this.webview);
}
}
}
}
async pinDiagnostic(range: vscode.Range, message?: string) {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) return;
const diagnostics =
formattedDiagnosticsStore.get(activeEditor.document.uri.fsPath) ?? [];
const diagnostic = diagnostics.find(
(diagnostic) =>
diagnostic.range.isEqual(range) &&
(!message || diagnostic.lspDiagnostic.message === message)
);
if (!diagnostic) return;
await this.ensureInitialized();
const item = await diagnosticToItem(diagnostic);
// Toggle: if already pinned, unpin instead
if (this.pinnedError && this.pinnedError.html === item.html) {
this.pinnedError = null;
} else {
this.pinnedError = { html: item.html };
}
if (this.webview) {
this.refresh(this.webview);
}
}
unpinDiagnostic() {
this.pinnedError = null;
if (this.webview) {
this.refresh(this.webview);
}
}
async resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext
): Promise<void> {
this.webview = webviewView.webview;
const initialContent = await this.getActiveContentHtml();
webviewView.webview.html = await this.provider.getWebviewContent(
webviewView.webview,
initialContent,
["webview-panel"]
);
const disposables = this.ensureDisposables(webviewView);
webviewView.webview.options = this.provider.getWebviewOptions();
disposables.push(
webviewView.webview.onDidReceiveMessage(
this.provider.createOnDidReceiveMessage()
),
vscode.languages.onDidChangeDiagnostics(() =>
// TODO: since `onDidChangeDiagnostics` fires often, we should try and avoid calling refresh based on the event uris
this.refresh(webviewView.webview)
),
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (this.skipNextEditorChange) {
this.skipNextEditorChange = false;
return;
}
if (editor) {
if (this.mode === "locked") {
this.mode = "cursor";
}
this.refresh(webviewView.webview);
}
}),
vscode.window.onDidChangeTextEditorSelection((event) => {
const document = event.textEditor.document;
// this event fires often, including selecting text in output windows and terminal windows
// avoid doing unnessesary work, because it will cause noticable delays in the UI
if (!has(SUPPORTED_LANGUAGE_IDS, document.languageId)) {
return;
}
if (this.skipNextSelectionChange) {
this.skipNextSelectionChange = false;
return;
}
if (this.mode === "locked") {
this.mode = "cursor";
}
if (this.mode === "cursor") {
this.refresh(webviewView.webview);
}
}),
webviewView.onDidChangeVisibility(() => {
if (webviewView.visible) {
this.lastContent = null;
this.refresh(webviewView.webview);
}
})
);
webviewView.onDidDispose(() => {
const disposables = this.disposables.get(webviewView);
disposables?.forEach((disposable) => disposable.dispose());
this.disposables.delete(webviewView);
this.webview = null;
});
}
private ensureDisposables(webviewView: vscode.WebviewView) {
let disposables = this.disposables.get(webviewView);
if (!disposables) {
disposables = [];
this.disposables.set(webviewView, disposables);
}
return disposables;
}
private async getActiveContentHtml(): Promise<string> {
const items = await this.getActiveDiagnosticItems();
if (items.length === 0) return NO_DIAGNOSTICS_MESSAGE;
return items.map((item) => item.html).join("<hr>");
}
private async getActiveDiagnosticItems(): Promise<DiagnosticItem[]> {
await this.ensureInitialized();
switch (this.mode) {
case "cursor":
return this.getCursorDiagnosticItems();
case "locked":
return this.lockedContent ? [this.lockedContent] : [];
}
}
private async getCursorDiagnosticItems(): Promise<DiagnosticItem[]> {
const activeEditor = vscode.window.activeTextEditor;
const selection = activeEditor?.selection;
if (!activeEditor || !selection) return [];
const diagnostics =
formattedDiagnosticsStore.get(activeEditor.document.uri.fsPath) ?? [];
const selectedDiagnostics = diagnostics.filter(
(diagnostic) => diagnostic.range.intersection(selection) !== undefined
);
return Promise.all(selectedDiagnostics.map((d) => diagnosticToItem(d)));
}
async refresh(webview: vscode.Webview) {
const sections: string[] = [];
// Render pinned error section
if (this.pinnedError) {
sections.push(
`<div class="pinned-section">` +
`<div class="pinned-header">` +
`<span class="pinned-label">` +
`<span class="codicon codicon-pinned"></span>` +
` Pinned error` +
`</span>` +
`<a class="unpin-button codicon codicon-close" title="Unpin error" href="command:prettyTsErrors.unpinError"></a>` +
`</div>` +
this.pinnedError.html +
`</div>`
);
sections.push(`<hr>`);
}
// Render active diagnostic items
const items = await this.getActiveDiagnosticItems();
if (items.length === 0) {
sections.push(NO_DIAGNOSTICS_MESSAGE);
} else {
for (let i = 0; i < items.length; i++) {
if (i > 0) sections.push(`<hr>`);
const item = items[i]!;
if (this.pinnedError && item.html === this.pinnedError.html) {
sections.push(
`<div class="diagnostic-item pinned-message">` +
`<em>This item is pinned on top.</em>` +
`</div>`
);
} else {
sections.push(`<div class="diagnostic-item">${item.html}</div>`);
}
}
}
const fullHtml = sections.join("");
if (fullHtml !== this.lastContent) {
webview.postMessage({ command: "update-content", html: fullHtml });
this.lastContent = fullHtml;
}
}
}
================================================
FILE: apps/vscode-extension/src/supportedLanguageIds.ts
================================================
export const SUPPORTED_LANGUAGE_IDS = [
"typescript",
"typescriptreact",
"javascript",
"javascriptreact",
"astro",
"svelte",
"vue",
"mdx",
"glimmer-js",
"glimmer-ts",
];
================================================
FILE: apps/vscode-extension/src/test/runTest.ts
================================================
import * as path from "path";
import { runTests } from "@vscode/test-electron";
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, "../../");
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "./suite/index");
// Download VS Code, unzip it and run the integration test
await runTests({
version: "1.77.0",
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: ["--disable-extensions"],
});
} catch (err) {
console.error("Failed to run tests");
process.exit(1);
}
}
main();
================================================
FILE: apps/vscode-extension/src/test/suite/extension.test.ts
================================================
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from "vscode";
suite("Extension Test Suite", () => {
/**
* The tests moved to the formatter package, I'm leaving
* this here for future tests to the VSCode extension
*/
vscode.window.showInformationMessage("Start all tests.");
});
================================================
FILE: apps/vscode-extension/src/test/suite/index.ts
================================================
import * as path from "path";
import Mocha from "mocha";
import { glob } from "glob";
export function run(
testsRoot: string,
cb: (error: unknown | null, failures?: number) => void
): void {
// Create the mocha test
const mocha = new Mocha({
ui: "tdd",
color: true,
});
glob("**/**.test.js", { cwd: testsRoot })
.then((files) => {
// Add files to the test suite
files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
try {
// Run the mocha test
mocha.run((failures) => {
cb(null, failures);
});
} catch (err) {
console.error(err);
cb(err);
}
})
.catch((err) => {
return cb(err);
});
}
================================================
FILE: apps/vscode-extension/syntaxes/type.tmGrammar.json
================================================
{
"$schema": "https://raw.githubusercontent.com/RedCMD/TmLanguage-Syntax-Highlighter/main/vscode.tmLanguage.schema.json",
"name": "TypeScript Type",
"scopeName": "source.type",
"patterns": [
{
"include": "source.ts#type"
},
{
"include": "source.ts#directives"
},
{
"include": "source.ts#statements"
},
{
"include": "source.ts#shebang"
}
]
}
================================================
FILE: apps/vscode-extension/tsconfig.json
================================================
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "nodenext",
"target": "ES2022",
"rootDir": "src",
"outDir": "out",
"moduleResolution": "nodenext"
},
"include": ["src/**/*.ts"],
"references": [
{ "path": "../../packages/utils" },
{ "path": "../../packages/vscode-formatter" }
]
}
================================================
FILE: apps/vscode-extension/webview/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- @see https://code.visualstudio.com/api/extension-guides/webview#content-security-policy -->
<!-- NOTE: vscode classes, styles and acquireVsCodeApi are injected with inline scripts/styles, thus what is even the point of CSP headers? -->
<meta
http-equiv="Content-Security-Policy"
data-csp-replace-content
content="default-src 'none'; script-src http://localhost:8080; style-src http://localhost:8080; font-src http://localhost:8080;"
/>
<title>Pretty TS Errors - Markdown Preview</title>
<!-- TODO: add script to copy vendor/codicon files from node_modules/ to ./vendor-->
<link
rel="stylesheet"
href="./vendor/codicon.css"
data-href-as-webview-uri
/>
<link rel="stylesheet" href="./style.css" data-href-as-webview-uri />
</head>
<body>
<!-- #content will be filled with the markdown rendered content -->
<div id="content"></div>
<script src="./index.js" data-src-as-webview-uri></script>
</body>
</html>
================================================
FILE: apps/vscode-extension/webview/index.js
================================================
// @ts-check
// wrap this in IIFE because `vscode` should **NEVER** be leaked into the global scope
// @see https://code.visualstudio.com/api/extension-guides/webview#passing-messages-from-a-webview-to-an-extension
const api = (function () {
// fallback logs to console, keep it for local development
const vscode =
typeof acquireVsCodeApi === "function"
? acquireVsCodeApi()
: {
/**
* @param {unknown} message
*/
postMessage(message) {
console.log(`message: `, message);
},
};
return {
/**
* Show a notification message in vscode
* @param {string} text
*/
notify(text) {
vscode.postMessage({ command: "notify", text });
},
};
})();
const $content = window.document.querySelector("#content");
window.addEventListener("message", (event) => {
const message = event.data;
switch (message.command) {
case "update-content": {
if ($content) {
$content.innerHTML = message.html;
}
}
}
});
window.document.addEventListener("click", (event) => {
const element = /** @type {HTMLElement} */ (event.target);
if (
element.tagName.toLowerCase() === "button" &&
element.hasAttribute("data-copy-content")
) {
handleCopyContentEvent(element);
}
});
/**
*
* @param {HTMLElement} element
*/
function handleCopyContentEvent(element) {
const parent = element.parentElement;
if (parent?.classList.contains("code-container")) {
const pre = parent.querySelector("pre");
const code = pre?.querySelector("code");
const content = code?.innerText;
if (content) {
copyToClipboard(content);
}
}
}
/**
* Copy `text` to the user's clipboard
* @param {string} text
*/
async function copyToClipboard(text) {
await navigator.clipboard.writeText(text);
api.notify("Copied type to clipboard!");
}
================================================
FILE: apps/vscode-extension/webview/style.css
================================================
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 4px 0 12px;
color: var(--vscode-editor-foreground);
}
#content {
padding: 0 12px;
}
/* Tables from identSentences */
#content table {
width: 100%;
}
#content table td:first-child {
white-space: nowrap;
}
#content table td:last-child {
width: 100%;
max-width: 0;
word-break: break-word;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: var(--vscode-foreground);
margin-top: 1.5em;
margin-bottom: 0.5em;
}
pre {
background-color: var(--vscode-textCodeBlock-background) !important;
border: 1px solid var(--vscode-widget-border);
border-radius: 4px;
padding: 12px 16px;
overflow-x: auto;
margin: 0.75em 0;
line-height: 1.5;
}
/* Override Shiki's background to match VS Code */
pre code {
background-color: transparent !important;
border-radius: 3px;
padding: 0 !important;
font-weight: var(--vscode-editor-font-weight, normal);
font-size: var(--vscode-editor-font-size, medium);
font-family:
var(--vscode-editor-font-family), "SF Mono", Monaco, "Cascadia Code",
"Roboto Mono", Consolas, "Courier New", monospace;
}
.copy-button {
position: absolute;
top: 8px;
right: 8px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 3px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
opacity: 0;
transition: opacity 0.2s;
}
.code-container {
position: relative;
max-width: 100%;
overflow-x: auto;
}
.code-container pre {
margin: 0;
}
.code-container:hover .copy-button {
opacity: 1;
}
.copy-button:hover {
background: var(--vscode-button-hoverBackground);
}
#content a {
text-decoration: none;
}
#content hr {
border: none;
border-top: 1px solid var(--vscode-widget-border, rgba(128, 128, 128, 0.2));
margin: 12px 0;
}
.webview-panel .title-actions {
float: right;
}
.pinned-section {
margin: 0 -12px;
background-color: rgba(255, 235, 59, 0.04);
padding: 4px 12px;
margin-bottom: 8px;
overflow: hidden;
}
.pinned-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
font-size: 12px;
}
.pinned-label {
display: flex;
align-items: center;
gap: 4px;
color: var(--vscode-descriptionForeground);
}
.unpin-button {
color: var(--vscode-icon-foreground);
cursor: pointer;
padding: 2px;
line-height: 1;
}
.pinned-message {
color: var(--vscode-descriptionForeground);
}
================================================
FILE: apps/vscode-extension/webview/vendor/codicon.css
================================================
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
@font-face {
font-family: "codicon";
font-display: block;
src: url("./codicon.ttf?eb10abb8b9291201e2c39eced5bd6993") format("truetype");
}
.codicon[class*="codicon-"] {
font: normal normal normal 12px/1 codicon;
display: inline-block;
text-decoration: none;
text-rendering: auto;
text-align: center;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
/*---------------------
* Modifiers
*-------------------*/
@keyframes codicon-spin {
100% {
transform: rotate(360deg);
}
}
.codicon-sync.codicon-modifier-spin,
.codicon-loading.codicon-modifier-spin,
.codicon-gear.codicon-modifier-spin {
/* Use steps to throttle FPS to reduce CPU usage */
animation: codicon-spin 1.5s steps(30) infinite;
}
.codicon-modifier-disabled {
opacity: 0.5;
}
.codicon-modifier-hidden {
opacity: 0;
}
/* custom speed & easing for loading icon */
.codicon-loading {
animation-duration: 1s !important;
animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67) !important;
}
/*---------------------
* Icons
*-------------------*/
.codicon-link-external:before {
content: "\eb14";
}
.codicon-globe:before {
content: "\eb01";
}
.codicon-layout-sidebar-left-dock:before {
content: "\ec4a";
}
.codicon-copy:before {
content: "\ebcc";
}
.codicon-go-to-file:before {
content: "\ea94";
}
.codicon-indent:before {
content: "\ebf9";
}
.codicon-debug:before {
content: "\ead8";
}
.codicon-close:before {
content: "\ea76";
}
.codicon-pinned:before {
content: "\eba0";
}
================================================
FILE: docs/hide-original-errors.md
================================================
To hide the original errors, display only the prettified ones, and make type blocks copyable, you can use the following hack:
## The Hack
1. Install the [Custom CSS and JS Loader](https://marketplace.visualstudio.com/items?itemName=be5invis.vscode-custom-css) extension from the VSCode marketplace.
2. Follow the installation instructions provided by the extension, and use [this CSS file](./pretty-ts-errors-hack.css).
## Why Do We Need This Hack?
### Hiding Original Errors
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.
For updates on this feature, follow [this issue](https://github.com/yoavbls/pretty-ts-errors/issues/3).
### Making Type Blocks Copyable
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.
Until this change is approved, you can use the hack described above as a workaround.
================================================
FILE: docs/pretty-ts-errors-hack.css
================================================
/* Allow copying */
.codicon-none {
user-select: text !important;
-webkit-user-select: text !important;
}
/* Hide errors */
div.monaco-hover-content:has(.codicon-none) > .hover-row:first-child {
display: none !important;
}
div.monaco-hover-content:has([style="color:#f96363;"])
> .hover-row:first-child {
display: none !important;
}
/* Change order */
.monaco-hover .monaco-hover-content {
display: flex;
flex-direction: column;
}
.monaco-hover .monaco-hover-content .hover-row {
order: 2;
}
.monaco-hover .monaco-hover-content .hover-row:has(.codicon-none) {
order: 1;
}
.monaco-hover .monaco-hover-content .hover-row:has([style="color:#f96363;"]) {
order: 1;
}
================================================
FILE: docs/vscode-logs.md
================================================
# Instructions to find and export the VS Code logs for Pretty TypeScript Errors
1. Open the output window to the `Pretty TypeScript Errors` channel

2. Set the log level of the `Pretty TypeScript Errors` output channel to `Trace`

3. **Reproduce your bug or error**, this should generate verbose logging output.
4. Either copy the output by selecting it or use one of the options in the menu shown:

5. Either paste the output or add the logfile to the GitHub issue

================================================
FILE: eslint.config.mjs
================================================
// @ts-check
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import globals from "globals";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
{
ignores: [
"apps/*/scripts/*",
"apps/*/dist/*",
"packages/*/scripts/*",
"packages/*/dist/*",
"examples/*",
".vscode-test/*",
],
},
{
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
globals: {
...globals.node,
/**
* @see https://www.npmjs.com/package/@types/vscode-webview
*/
acquireVsCodeApi: false,
window: false,
},
},
plugins: {
"@typescript-eslint": tseslint.plugin,
},
rules: {
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-non-null-assertion": "off",
curly: "warn",
eqeqeq: ["warn", { smart: true }],
"no-throw-literal": "warn",
semi: "off",
},
}
);
================================================
FILE: examples/errors.js
================================================
// @ts-check
/**
* @typedef {Object} Person
* @property {string} name
* @property {number} age
* @property {Object} address
* @property {string} address.street
* @property {string} address.city
* @property {string} address.country
*/
/**
* @type {Person}
*/
const john = {
name: "John Doe",
age: 30,
address: {
street: "123 Main St",
city: "New York",
},
};
/**
* @typedef {Function} GetUserFunction
* @returns {{ user: { name: string, email: string, age: number } }}
*/
const getPerson = () => ({
person: {
username: "usr",
email: "usr@usr.io",
},
});
/**
* @typedef {Object} JSAnimal
* @property {string} name
* @property {number} age
*/
/**
* @template {JSAnimal} T
* @param {T} animal
* @returns
*/
function run(animal) {
return animal;
}
run({ firstName: "John", weight: 20 });
/**
* @typedef {Object} MyError
* @property {number} code
*/
try {
// ...
} catch (/** @type {MyError} */ error) {
console.log(error.code);
}
export {};
================================================
FILE: examples/errors.ts
================================================
interface Person {
name: string;
age: number;
address: {
street: string;
city: string;
country: string;
};
}
const john: Person = {
name: "John Doe",
age: 30,
address: {
street: "123 Main St",
city: "New York",
},
};
type GetUserFunction = () => {
user: {
name: string;
email: `${string}@${string}.${string}`;
age: number;
};
};
const getPerson: GetUserFunction = () => ({
person: {
username: "usr",
email: "usr@usr.io",
},
});
interface Animal {
name: string;
age: number;
}
function run<T extends Animal>(animal: T) {
return animal;
}
run({ firstName: "John", weight: 20 });
type MyError = {
code: number;
};
try {
// ...
} catch (error: MyError) {
console.log(error.code);
}
export {};
================================================
FILE: examples/errors.vue
================================================
<script setup lang="ts">
import { RouterView } from "vue-router";
const x = [1, 2, 3, "4"];
function y(param: number[]) {}
y(x);
</script>
<template>
<RouterView />
</template>
================================================
FILE: examples/examples.type
================================================
{
fistName: string;
lastName: string;
age: number;
} | undefined | null;
{
x: string;
y: number;
}[]
================================================
FILE: package.json
================================================
{
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,js,mjs,cjs,json,md,yml,yaml,css,html}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,mjs,cjs,json,md,yml,yaml,css,html}\""
},
"engines": {
"node": ">=20"
},
"devDependencies": {
"@eslint/js": "^9.15.0",
"@types/node": "^20.19.21",
"eslint": "^9.15.0",
"globals": "^16.4.0",
"prettier": "^3.6.2",
"turbo": "^2.1.3",
"typescript": "^5.7.2",
"typescript-eslint": "^8.15.0"
},
"name": "pretty-ts-errors-mono",
"packageManager": "npm@10.0.0",
"workspaces": [
"packages/*",
"apps/*"
]
}
================================================
FILE: packages/formatter/README.md
================================================
# Pretty TypeScript Error formatter
The formatting package of [pretty-ts-errors](https://github.com/yoavbls/pretty-ts-errors)
# Usage
```typescript
import { createErrorMessagePrettifier } from "@pretty-ts-errors/formatter";
function codeBlock(code: string, language?: string, multiLine?: boolean) {
return `\`\`\`${language}
${code}
\`\`\`
`;
}
const prettifyErrorMessage = createErrorMessagePrettifier(codeBlock);
prettifyErrorMessage(`Type 'string' is not assignable to type 'number'.`);
```
================================================
FILE: packages/formatter/package.json
================================================
{
"name": "@pretty-ts-errors/formatter",
"version": "0.1.8",
"description": "Pretty TypeScript Errors Formatter",
"files": [
"src/**",
"dist/**"
],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsdown src/index.ts --format cjs,esm --dts",
"dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
"lint": "tsc -p . --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"publish": "npm run build && npm publish --access public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yoavbls/pretty-ts-errors.git"
},
"keywords": [
"typescript",
"errors"
],
"author": "yoavbls",
"license": "MIT",
"bugs": {
"url": "https://github.com/yoavbls/pretty-ts-errors/issues"
},
"homepage": "https://github.com/yoavbls/pretty-ts-errors#readme",
"devDependencies": {
"tsdown": "^0.15.6",
"vitest": "^3.2.4"
},
"dependencies": {
"@pretty-ts-errors/utils": "*",
"prettier": "^3.6.2"
}
}
================================================
FILE: packages/formatter/src/addMissingParentheses.ts
================================================
import { has, invert, objectKeys } from "@pretty-ts-errors/utils";
const parentheses = {
"(": ")",
"{": "}",
"[": "]",
} as const;
const openParentheses = objectKeys(parentheses);
const closeParentheses = Object.values(parentheses);
export function addMissingParentheses(type: string): string {
const openStack: (typeof openParentheses)[number][] = [];
const missingClosingChars: string[] = [];
for (const char of type) {
if (has(openParentheses, char)) {
openStack.push(char);
} else if (has(closeParentheses, char)) {
const lastOpen = openStack[openStack.length - 1];
if (lastOpen === undefined || parentheses[lastOpen] !== 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.push(closingChar);
}
let validType = type;
// Close the last string if it's not closed
const quoteMatches = validType.match(/['"]/g);
if (quoteMatches) {
const lastQuote = quoteMatches[quoteMatches.length - 1];
if (quoteMatches.length % 2 === 1) {
validType += `...${lastQuote}`;
}
}
if (validType.endsWith(":")) {
validType += "...";
}
validType = (validType + "\n..." + missingClosingChars.join("")).replace(
// Change (param: ...) to (param) => __RETURN_TYPE__ if needed
/(\([a-zA-Z0-9]*:[^)]*\))/,
(p1) => `${p1} => ...`
);
return validType;
}
================================================
FILE: packages/formatter/src/errorMessagePrettifier.ts
================================================
import { formatTypeBlock } from "./formatTypeBlock";
export type CodeBlockFn = (
code: string,
language?: string,
multiLine?: boolean
) => string;
export function createErrorMessagePrettifier(
codeBlock: CodeBlockFn
): (message: string) => Promise<string> {
return async (message: string) => {
const rules = await getRules(codeBlock);
let output = message;
for (const { pattern, replacer } of rules) {
let result = "";
let lastIndex = 0;
for (const match of output.matchAll(pattern)) {
const [fullMatch, ...captures] = match;
const matchIndex = match.index ?? 0;
result += output.slice(lastIndex, matchIndex);
result += await replacer(...captures);
lastIndex = matchIndex + fullMatch.length;
}
result += output.slice(lastIndex);
output = result;
}
return output;
};
}
type Rule = {
pattern: RegExp;
replacer: (...args: any[]) => string | Promise<string>;
};
async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {
const formatTypeScriptBlock = (code: string) => codeBlock(code, "typescript");
const formatSimpleTypeBlock = (code: string) => codeBlock(code, "type");
const formatTypeOrModuleBlock = (prefix: string, code: string) =>
formatTypeBlock(
prefix,
["module", "file", "file name"].includes(prefix.toLowerCase())
? `"${code}"`
: code,
codeBlock
);
return [
{
pattern: /(?:\s)'"(.*?)(?<!\\)"'(?:\s|:|.|$)/g,
replacer: async (p1: string) => formatTypeBlock("", `"${p1}"`, codeBlock),
},
{
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,
replacer: async (pre: string, type: string, post: string) => {
const formattedType = await formatTypeBlock("", type, codeBlock);
const list = post
.split(", ")
.filter(Boolean)
.map((prop: string) => `<li>${prop}</li>`)
.join("");
return `${pre}${formattedType}: <ul>${list}</ul>`;
},
},
{
pattern: /(types) ['“](.*?)['”] and ['“](.*?)['”][.]?/gi,
replacer: async (p1: string, p2: string, p3: string) => {
const [left, right] = await Promise.all([
formatTypeBlock(p1, p2, codeBlock),
formatTypeBlock("", p3, codeBlock),
]);
return `${left} and ${right}`;
},
},
{
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([
formatTypeBlock(p1, p2, codeBlock),
formatTypeBlock("", p3, codeBlock),
]);
return `${left} or ${right}`;
}
const [left, right] = await Promise.all([
formatTypeBlock("", p1, codeBlock),
formatTypeBlock("", p2, codeBlock),
]);
return `${left} or ${right}`;
},
},
{
pattern: /(Overload \d of \d), ['“](.*?)['”], /gi,
replacer: async (p1: string, p2: string) =>
`${p1}${await formatTypeBlock("", p2, codeBlock)}`,
},
{
pattern: /^['“]"[^"]*"['”]$/g,
replacer: formatTypeScriptBlock,
},
{
pattern: /(module )'([^"]*?)'/gi,
replacer: (p1: string, p2: string) => `${p1}"${p2}"`,
},
{
pattern:
/(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,
replacer: (p1: string, p2: string) => formatTypeOrModuleBlock(p1, p2),
},
{
pattern:
/(.*)['“]([^>]*)['”] (type|interface|return type|file|module|is (not )?assignable)/gi,
replacer: async (p1: string, p2: string, p3: string) =>
`${p1}${await formatTypeOrModuleBlock("", p2)} ${p3}`,
},
{
pattern:
/['“]((void|null|undefined|any|boolean|string|number|bigint|symbol)(\[\])?)['”]/g,
replacer: formatSimpleTypeBlock,
},
{
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}`),
},
{
pattern: /(return|operator) ['“](.*?)['”]/gi,
replacer: (p1: string, p2: string) =>
`${p1} ${formatTypeScriptBlock(p2)}`,
},
{
pattern: /(?<!\w)'((?:(?!["]).)*?)'(?!\w)/g,
replacer: (p1: string) => ` ${codeBlock(p1)} `,
},
];
}
================================================
FILE: packages/formatter/src/formatTypeBlock.ts
================================================
import { addMissingParentheses } from "./addMissingParentheses";
import { formatTypeWithPrettier } from "./formatTypeWithPrettier";
export async function formatTypeBlock(
prefix: string,
type: string,
codeBlock: (code: string, language?: string, multiLine?: boolean) => string
) {
// Return a simple code block if it's just a parenthesis
if (type.match(/^(\[\]|\{\})$/)) {
return `${prefix} ${codeBlock(type)}`;
}
if (
// Skip formatting if it's a simple type
type.match(
/^((void|null|undefined|any|number|string|bigint|symbol|readonly|typeof)(\[\])?)$/
)
) {
return `${prefix} ${codeBlock(type, "type")}`;
}
const formattedType = await formatType(type);
if (formattedType.includes("\n")) {
return `${prefix}: ${codeBlock(formattedType, "type", true)}`;
} else {
return `${prefix} ${codeBlock(formattedType, "type")}`;
}
}
/**
* Try to format type with prettier
*/
export async function formatType(
type: string,
options?: { throwOnError?: boolean }
) {
try {
// Wrap type with valid statement, format it and extract the type back
return convertToOriginalType(
await formatTypeWithPrettier(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(/'([^']+)'(?=\s*:)/g, '"$1"')
.replaceAll(/... (\d{0,}) more .../g, (_, p1) => `/* ${p1} more */`) // ... x more ... not shown sell
// .replaceAll(/\(param\: \/\* (\{ .* \}) \*\//g, (_, p1) => `(${p1}: `)
.replace(/^type x =[ ]?([\s\S]*?);?$/g, "$1")
.trim();
================================================
FILE: packages/formatter/src/formatTypeWithPrettier.ts
================================================
import { format } from "prettier/standalone";
import * as parserEstree from "prettier/plugins/estree";
import * as parserTypescript from "prettier/plugins/typescript";
export async function formatTypeWithPrettier(text: string) {
return format(text, {
plugins: [parserTypescript, parserEstree],
parser: "typescript",
printWidth: 60,
arrowParens: "avoid",
semi: false,
singleQuote: false,
});
}
================================================
FILE: packages/formatter/src/index.ts
================================================
export {
createErrorMessagePrettifier,
type CodeBlockFn,
} from "./errorMessagePrettifier";
================================================
FILE: packages/formatter/test/errorMessageMocks.ts
================================================
/**
* This file contains mocks of error messages, only some of them
* are used in tests but all of them can be used to test and debug
* the formatting visually, you can try to select them on debug and check the hover.
*/
import { d } from "@pretty-ts-errors/utils";
export const errorWithSpecialCharsInObjectKeys = d`
Type 'string' is not assignable to type '{ 'abc*bc': string; }'.
`;
export const errorWithDashInObjectKeys = d`
Type '{ person: { 'first-name': string; }; }' is not assignable to type 'string'.
`;
/**
* Formatting error from this issue: https://github.com/yoavbls/pretty-ts-errors/issues/20
*/
export const errorWithMethodsWordInIt = d`
The 'this' context of type 'ElementHandle<Node>' is not assignable to method's 'this' of type 'ElementHandle<Element>'.
Type 'Node' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 114 more.
`;
export const errorWithParamsDestructuring = d`
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'.
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'.
`;
export const errorWithLongType = d`
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 ... | { ...; }; }; }'.
`;
export const errorWithTruncatedType2 = d`
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.
`;
export const variableNotUsedEror = d`
'a' is declared but its value is never read.
`;
export const errorWithSimpleIndentations = d`
Type '(newIds: number[]) => void' is not assignable to type '(selectedId: string[]) => void'.
Types of parameters 'newIds' and 'selectedId' are incompatible.
Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'.
`;
export const errorWithComma = d`
Argument of type '{ filters: Filters; } & T' is not assignable to parameter of type 'T & F'.
Type '{ filters: Filters; } & T' is not assignable to type 'F'.
'{ filters: Filters; } & T' is assignable to the constraint of type 'F', but 'F' could be instantiated with a different subtype of constraint '{ filters: Filters; }'.
`;
export const missingPropertyError =
"\
Property 'user' is missing in type '{ person: { username: string; email: string; }; }' but required in type '{ user: { name: string; email: `${string}@${string}.${string}`; age: number; }; }'.\
";
export const missingReactPropsError = d`
Type '{ style: { backgroundColor: string; }; }' is not assignable to type 'DropDownPickerProps<Object>'.
Type '{ style: { backgroundColor: string; }; }' is not assignable to type 'DropDownPickerMultipleProps<Object> & DropDownPickerBaseProps<Object›'.
Type '{ style: { backgroundColor: string; }; }' is missing the following properties from type 'DropDownPickerMultipleProps<Object>': multiple, setValue, value
`;
export const leftSideAritmeticError = d`
The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
`;
export const theosError = d`
Exported variable 'uploadRouter' has or is using name 'Uploader' from external module "/Users/theo/Code/Work/filething/packages/uploadthing/dist/types-dbaf1b46" but cannot be named.
`;
export const ts1378Error = d`
Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.
`;
export const ts2304Error = d`
Cannot find name 'varname'.
`;
export const ts2305Error = d`
Module '"@pretty-ts-errors/formatter"' has no exported member 'values'.
`;
export const ts2307Error = d`
Cannot find module 'events' or its corresponding type declarations.
`;
export const ts1360Error = d`
Property 'a' is missing in type '{ b: { name: string; icon: undefined; }; c: { name: string; icon: undefined; }; d: { name: string; icon: undefined; }; e: { name: string; icon: undefined; }; f: { ...; }; g: { ...; }; h:...' but required in type '{a: {name: string; icon: undefined}}'.
`;
export const errorWithStringChars = d`
Type '"' 'Oh no"' is not assignable to type '"' 'Oh n\"o\"' "'.
`;
export const ts2322ErrorWithPrivateProperty = d`
Type 'Ref<{ name: string; readonly type: "json"; mm: <T extends Convertible = Convertible>(px: T) => T; px: <T extends Convertible = Convertible>(mm: T) => T; ... 18 more ...; toJson: () => string; }>' is not assignable to type 'Ref<MpcdiConfiguration>'.
Type '{ name: string; readonly type: "json"; mm: <T extends Convertible = Convertible>(px: T) => T; px: <T extends Convertible = Convertible>(mm: T) => T; ... 18 more ...; toJson: () => string; }' is missing the following properties from type 'MpcdiConfiguration': ratio, #overlaps, download
`;
export const ts4113Error = d`
This member cannot have an 'override' modifier because it is not declared in the base class 'A<T["nested"]>'.
`;
export const ts4117Error = d`
This member cannot have an 'override' modifier because it is not declared in the base class 'A<T["nested"]>'. Did you mean 'testA'?
`;
================================================
FILE: packages/formatter/test/formatter.vitest.ts
================================================
import { describe, it, expect } from "vitest";
import {
createErrorMessagePrettifier,
type CodeBlockFn,
} from "../src/errorMessagePrettifier";
import { addMissingParentheses } from "../src/addMissingParentheses";
import { formatType } from "../src/formatTypeBlock";
import { d } from "@pretty-ts-errors/utils";
import {
errorWithDashInObjectKeys,
errorWithSpecialCharsInObjectKeys,
} from "./errorMessageMocks";
import * as errorMessageMocks from "./errorMessageMocks";
// Simple stub that marks code blocks without any rendering logic
const stubCodeBlock: CodeBlockFn = (code, language, multiLine) => {
if (multiLine) return `\n\`\`\`${language}\n${code}\n\`\`\`\n`;
return `\`${code}\``;
};
const prettifyErrorMessage = createErrorMessagePrettifier(stubCodeBlock);
describe("formatter", () => {
it("adds missing parentheses", () => {
expect(addMissingParentheses("Hello, {world! [This] is a (test.")).toBe(
"Hello, {world! [This] is a (test.\n...)}"
);
});
it("formats Special characters in object keys", async () => {
expect(await prettifyErrorMessage(errorWithSpecialCharsInObjectKeys)).toBe(
'Type `string` is not assignable to type `{ "abc*bc": string }`.'
);
});
it("formats method's word in the error", async () => {
expect(await prettifyErrorMessage(errorWithDashInObjectKeys)).toBe(
'Type `{ person: { "first-name": string } }` is not assignable to type `string`.'
);
});
it("prettifies type with params destructuring", async () => {
await expect(
formatType(
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<...>; }
`,
{ throwOnError: true }
)
).resolves.toBeTypeOf("string");
});
it("prettifies truncated type", async () => {
await expect(
formatType(
d` { b: { name: string; icon: undefined; }; c: { name: string; icon: undefined; }; d: { name: string; icon: undefined; }; e: { name: string; icon: undefined; }; f: { ...; }; g: { ...; }; h:...`,
{ throwOnError: true }
)
).resolves.toBeTypeOf("string");
});
it.each(Object.entries(errorMessageMocks))(
"prettifies mock error message: %s",
async (_name, message) => {
await expect(prettifyErrorMessage(message)).resolves.toBeTypeOf("string");
}
);
});
================================================
FILE: packages/formatter/tsconfig.json
================================================
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": false,
"outDir": "dist",
"rootDir": "src",
"composite": true,
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"references": [{ "path": "../utils" }]
}
================================================
FILE: packages/formatter/vitest.config.ts
================================================
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["test/**/*.vitest.{ts,tsx}", "test/**/*.spec.{ts,tsx}"],
},
});
================================================
FILE: packages/utils/package.json
================================================
{
"private": true,
"name": "@pretty-ts-errors/utils",
"files": [
"dist/**"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -p ."
},
"devDependencies": {},
"dependencies": {
"ts-dedent": "^2.2.0"
}
}
================================================
FILE: packages/utils/src/index.ts
================================================
import dedent from "ts-dedent";
export function objectKeys<T extends Record<string, unknown>>(
obj: T
): (keyof T & string)[] {
return Object.keys(obj) as (keyof T & string)[];
}
export function invert<T extends Record<string, string>>(
obj: T
): Partial<{
[K in T[keyof T]]: { [P in keyof T]: K extends T[P] ? P : never }[keyof T];
}> {
const result: Partial<{
[K in T[keyof T]]: { [P in keyof T]: K extends T[P] ? P : never }[keyof T];
}> = {};
for (const key in obj) {
const value = obj[key];
if (value !== undefined) {
result[value] = key;
}
}
return result;
}
/**
* d stands for dedent.
* it allow us to indent html in template literals without affecting the output
*/
export const d = dedent;
/**
* Check if an array contains a string.
* Type guard the string if it does.
*/
export const has = (
array: unknown[],
item: string
): item is Extract<(typeof array)[number], string> => array.includes(item);
================================================
FILE: packages/utils/tsconfig.json
================================================
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": false,
"outDir": "dist",
"rootDir": "src",
"composite": true,
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"]
}
================================================
FILE: packages/vscode-formatter/README.md
================================================
# Pretty TypeScript Errors - Formatter for VSCode hovers
================================================
FILE: packages/vscode-formatter/package.json
================================================
{
"name": "@pretty-ts-errors/vscode-formatter",
"version": "0.1.0",
"description": "Pretty TypeScript Errors Formatter for VSCode hovers",
"files": [
"src/**",
"dist/**"
],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsdown src/index.ts --format cjs,esm --dts",
"dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
"lint": "tsc -p . --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"publish": "npm run build && npm publish --access public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yoavbls/pretty-ts-errors.git"
},
"keywords": [
"typescript",
"errors"
],
"author": "yoavbls",
"license": "MIT",
"bugs": {
"url": "https://github.com/yoavbls/pretty-ts-errors/issues"
},
"homepage": "https://github.com/yoavbls/pretty-ts-errors#readme",
"devDependencies": {
"tsdown": "^0.15.6",
"vitest": "^3.2.4"
},
"dependencies": {
"@pretty-ts-errors/formatter": "*",
"@pretty-ts-errors/utils": "*",
"lz-string": "^1.5.0",
"vscode-languageserver-types": "^3.17.5",
"vscode-uri": "^3.1.0"
}
}
================================================
FILE: packages/vscode-formatter/src/components/actions.ts
================================================
import { compressToEncodedURIComponent } from "lz-string";
import { Diagnostic, Range } from "vscode-languageserver-types";
import { d } from "@pretty-ts-errors/utils";
export const divider = `<span class="divider">|</span>`;
export const errorCodeExplanationLink = (errorCode: Diagnostic["code"]) =>
d /*html*/ `
<a title="See detailed explanation" href="https://typescript.tv/errors/ts${errorCode}">
<span class="codicon codicon-link-external">
</span>
</a>`;
export const showErrorInSidebarLink = (range: Range, message?: string) => {
const args = encodeURIComponent(
JSON.stringify(message != null ? [range, message] : [range])
);
return d /*html*/ `
<a title="Show error in sidebar" href="command:prettyTsErrors.showErrorInSidebar?${args}">
<span class="codicon codicon-layout-sidebar-left-dock">
</span>
</a>`;
};
export const pinErrorLink = (range: Range, message?: string) => {
const args = encodeURIComponent(
JSON.stringify(message != null ? [range, message] : [range])
);
return d /*html*/ `
<a title="Pin error" href="command:prettyTsErrors.pinError?${args}">
<span class="codicon codicon-pinned">
</span>
</a>`;
};
export const copyErrorLink = (message: Diagnostic["message"]) => {
const args = encodeURIComponent(JSON.stringify(message));
return d /*html*/ `
<a title="Copy error to clipboard" href="command:prettyTsErrors.copyError?${args}">
<span class="codicon codicon-copy">
</span>
</a>`;
};
export const errorMessageTranslationLink = (message: Diagnostic["message"]) => {
const encodedMessage = compressToEncodedURIComponent(message);
return d /*html*/ `
<a title="See translation" href="https://ts-error-translator.vercel.app/?error=${encodedMessage}">
<span class="codicon codicon-globe">
</span>
</a>`;
};
================================================
FILE: packages/vscode-formatter/src/components/errorTitle.ts
================================================
import { d } from "@pretty-ts-errors/utils";
import { Diagnostic } from "vscode-languageserver-types";
export const errorTitle = (
code: Diagnostic["code"],
actions: string,
suffix = ""
) => d /*html*/ `
<span style="color:#f96363;">⚠ Error </span>${
typeof code === "number"
? d /*html*/ `
<span style="color:#5f5f5f;">
(TS${code})
<span class="title-actions">
${actions}
</span>
</span>
`
: ""
}
<br>
${suffix}
`;
================================================
FILE: packages/vscode-formatter/src/components/hoverCodeBlock.ts
================================================
import { d } from "@pretty-ts-errors/utils";
import { miniLine } from "./miniLine";
import { spanBreak } from "./spanBreak";
import { CodeBlockFn } from "@pretty-ts-errors/formatter";
import { plainCodeBlock } from "./plainCodeBlock";
/**
* @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>
`);
const inlineHoverCodeBlock = (code: string, language: string) =>
codeBlock(` ${code} `, language);
const multiLineHoverCodeBlock = (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}
`;
};
export const hoverCodeBlock: CodeBlockFn = (code, language, multiLine) => {
if (!language) {
return plainCodeBlock(code);
}
if (multiLine) {
return multiLineHoverCodeBlock(code, language);
}
return inlineHoverCodeBlock(code, language);
};
================================================
FILE: packages/vscode-formatter/src/components/htmlCodeBlock.ts
================================================
import { CodeBlockFn } from "@pretty-ts-errors/formatter";
interface Highlighter {
codeToHtml(code: string, options: { lang: string }): string;
}
let highlighter: Highlighter | null = null;
export function initHighlighter(h: Highlighter) {
highlighter = h;
}
export const htmlCodeBlock: CodeBlockFn = (code, language, multiLine) => {
if (!language) {
return `<code>${code}</code>`;
}
if (!highlighter) {
throw new Error(
"htmlCodeBlock not initialized. Call initHighlighter() first."
);
}
const highlighted = highlighter.codeToHtml(code.trim(), {
lang: language,
});
if (multiLine) {
return (
`<p></p>` +
`<div class="code-container">` +
`<button class="copy-button" data-copy-content><span class="codicon codicon-copy" title="Copy type to clipboard"><span></button>` +
highlighted +
`</div>` +
`<p></p>`
);
}
// Inline: strip <pre> wrapper, keep as inline element
const inlineHtml = highlighted
.replace(/^<pre[^>]*><code[^>]*>/, "")
.replace(/<\/code><\/pre>$/, "");
return `<code style="background-color:var(--vscode-textCodeBlock-background);padding:4px 8px;border-radius:4px;">${inlineHtml}</code>`;
};
================================================
FILE: packages/vscode-formatter/src/components/miniLine.ts
================================================
import { spanBreak } from "./spanBreak";
/** May be useful for line separations */
export const miniLine = spanBreak(/*html*/ `<p></p>`);
================================================
FILE: packages/vscode-formatter/src/components/plainCodeBlock.ts
================================================
import { d } from "@pretty-ts-errors/utils";
/**
* Code block without syntax highlighting like.
* For syntax highlighting, use {@link inlineCodeBlock} or {@link multiLineCodeBlock}
*/
export const plainCodeBlock = (content: string) => d /*html*/ `
<code>${content}</code>
`;
================================================
FILE: packages/vscode-formatter/src/components/spanBreak.ts
================================================
import { d } from "@pretty-ts-errors/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>
`;
================================================
FILE: packages/vscode-formatter/src/format/embedSymbolLinks.ts
================================================
import { Diagnostic } from "vscode-languageserver-types";
import { URI } from "vscode-uri";
export function embedSymbolLinks(diagnostic: Diagnostic): Diagnostic {
if (
!diagnostic?.relatedInformation?.[0]?.message?.includes("is declared here")
) {
return diagnostic;
}
const ref = diagnostic.relatedInformation[0];
const symbol = ref?.message.match(/(?<symbol>'[^']*') is declared here./)
?.groups?.["symbol"];
if (!symbol) {
return diagnostic;
}
const args = [URI.parse(ref.location.uri), ref.location.range];
const href = URI.parse(
`command:prettyTsErrors.revealSelection?${encodeURIComponent(
JSON.stringify(args)
)}`
);
return {
...diagnostic,
message: diagnostic.message.replaceAll(
symbol,
`${symbol} <a href="${href}" title="Go to symbol"><span class="codicon codicon-go-to-file" ></span></a> `
),
};
}
================================================
FILE: packages/vscode-formatter/src/format/identSentences.ts
================================================
import { d } from "@pretty-ts-errors/utils";
export const identSentences = (message: string): string =>
message
.split("\n")
.map((line) => {
let whiteSpacesCount = line.search(/\S/);
if (whiteSpacesCount === -1) {
whiteSpacesCount = 0;
}
if (whiteSpacesCount === 0) {
return line;
}
if (whiteSpacesCount >= 2) {
whiteSpacesCount -= 2;
}
return d /*html*/ `
</span>
<p></p>
<span>
<table>
<tr>
<td>
${" ".repeat(3).repeat(whiteSpacesCount)}
<span class="codicon codicon-indent"></span>
</td>
<td>${line}</td>
</tr>
</table>
`;
})
.join("");
================================================
FILE: packages/vscode-formatter/src/format/prettifyDiagnosticForHover.ts
================================================
import { createErrorMessagePrettifier } from "@pretty-ts-errors/formatter";
import { Diagnostic } from "vscode-languageserver-types";
import {
divider,
showErrorInSidebarLink,
pinErrorLink,
copyErrorLink,
errorCodeExplanationLink,
} from "../components/actions";
import { errorTitle } from "../components/errorTitle";
import { miniLine } from "../components/miniLine";
import { d } from "@pretty-ts-errors/utils";
import { embedSymbolLinks } from "./embedSymbolLinks";
import { identSentences } from "./identSentences";
import { hoverCodeBlock } from "../components/hoverCodeBlock";
const prettifyErrorMessageForHover =
createErrorMessagePrettifier(hoverCodeBlock);
/**
* Prettify a diagnostic for display in hover tooltips.
* Uses markdown fenced code blocks (required by VS Code's MarkdownString).
*/
export async function prettifyDiagnosticForHover(
diagnostic: Diagnostic
): Promise<string> {
const newDiagnostic = embedSymbolLinks(diagnostic);
const identedSentences = identSentences(newDiagnostic.message);
const prettifiedMessage =
await prettifyErrorMessageForHover(identedSentences);
return d /*html*/ `
${errorTitle(
newDiagnostic.code,
d`${showErrorInSidebarLink(newDiagnostic.range, diagnostic.message)} ${divider}
${pinErrorLink(newDiagnostic.range, diagnostic.message)} ${divider}
${copyErrorLink(newDiagnostic.message)} ${divider}
${errorCodeExplanationLink(newDiagnostic.code)}`,
miniLine
)}
<span>
${prettifiedMessage}
</span>
`;
}
================================================
FILE: packages/vscode-formatter/src/format/prettifyDiagnosticForSidebar.ts
================================================
import { createErrorMessagePrettifier } from "@pretty-ts-errors/formatter";
import { Diagnostic } from "vscode-languageserver-types";
import { htmlCodeBlock } from "../components/htmlCodeBlock";
import {
divider,
pinErrorLink,
copyErrorLink,
errorMessageTranslationLink,
errorCodeExplanationLink,
} from "../components/actions";
import { errorTitle } from "../components/errorTitle";
import { d } from "@pretty-ts-errors/utils";
import { embedSymbolLinks } from "./embedSymbolLinks";
import { identSentences } from "./identSentences";
const prettifyErrorMessageForSidebar =
createErrorMessagePrettifier(htmlCodeBlock);
/**
* Prettify a diagnostic for display in the sidebar webview.
* Uses shiki HTML code blocks (must call initHtmlCodeBlock before first use).
*/
export async function prettifyDiagnosticForSidebar(
diagnostic: Diagnostic
): Promise<string> {
const newDiagnostic = embedSymbolLinks(diagnostic);
const identedSentences = identSentences(newDiagnostic.message);
const prettifiedMessage =
await prettifyErrorMessageForSidebar(identedSentences);
return d /*html*/ `
${errorTitle(
newDiagnostic.code,
d`${pinErrorLink(newDiagnostic.range, diagnostic.message)} ${divider}
${copyErrorLink(newDiagnostic.message)} ${divider}
${errorMessageTranslationLink(newDiagnostic.message)} ${divider}
${errorCodeExplanationLink(newDiagnostic.code)}`
)}
<div style="line-height:2; padding-top: 8px;">
${prettifiedMessage}
</div>
`;
}
================================================
FILE: packages/vscode-formatter/src/index.ts
================================================
export { prettifyDiagnosticForHover } from "./format/prettifyDiagnosticForHover";
export { prettifyDiagnosticForSidebar } from "./format/prettifyDiagnosticForSidebar";
export { initHighlighter } from "./components/htmlCodeBlock";
================================================
FILE: packages/vscode-formatter/test/vscode-formatter.vitest.ts
================================================
import { describe, it, expect } from "vitest";
import { createErrorMessagePrettifier } from "@pretty-ts-errors/formatter";
import { hoverCodeBlock } from "../src/components/hoverCodeBlock";
import { plainCodeBlock } from "../src/components/plainCodeBlock";
import {
errorWithSpecialCharsInObjectKeys,
errorWithDashInObjectKeys,
} from "../../formatter/test/errorMessageMocks";
const prettifyErrorMessage = createErrorMessagePrettifier(hoverCodeBlock);
describe("hoverCodeBlock", () => {
it("renders inline code with language", () => {
const result = hoverCodeBlock("string", "type", false);
expect(result).toContain("```type");
expect(result).toContain("string");
expect(result).toContain("```");
});
it("renders multiline code with padding", () => {
const code = "{\n name: string\n}";
const result = hoverCodeBlock(code, "type", true);
expect(result).toContain("```type");
expect(result).toContain("<p></p>");
});
it("falls back to plainCodeBlock when no language", () => {
const result = hoverCodeBlock("someCode", undefined, false);
expect(result).toContain("<code>");
expect(result).toContain("someCode");
});
});
describe("plainCodeBlock", () => {
it("wraps content in a code tag", () => {
const result = plainCodeBlock("hello");
expect(result).toContain("<code>hello</code>");
});
});
describe("formatDiagnosticMessage with hoverCodeBlock", () => {
it("formats special characters in object keys", async () => {
const result = await prettifyErrorMessage(
errorWithSpecialCharsInObjectKeys
);
expect(result).toContain("```type");
expect(result).toContain("string");
expect(result).toContain("abc*bc");
});
it("formats dash in object keys", async () => {
const result = await prettifyErrorMessage(errorWithDashInObjectKeys);
expect(result).toContain("```type");
expect(result).toContain("first-name");
expect(result).toContain("string");
});
});
================================================
FILE: packages/vscode-formatter/tsconfig.json
================================================
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": false,
"outDir": "dist",
"rootDir": "src",
"composite": true,
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"references": [
{
"path": "../formatter"
},
{
"path": "../utils"
}
]
}
================================================
FILE: packages/vscode-formatter/vitest.config.ts
================================================
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["test/**/*.vitest.{ts,tsx}", "test/**/*.spec.{ts,tsx}"],
},
});
================================================
FILE: tsconfig.base.json
================================================
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"lib": ["ES2022", "ES2021.String"],
"baseUrl": ".",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"useDefineForClassFields": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true,
"allowJs": true,
"checkJs": true,
"moduleDetection": "force",
"declaration": true,
"declarationMap": true,
"composite": true
}
}
================================================
FILE: tsconfig.json
================================================
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"files": [],
"references": [
{ "path": "packages/utils" },
{ "path": "packages/formatter" },
{ "path": "packages/vscode-formatter" },
{ "path": "apps/vscode-extension" }
]
}
================================================
FILE: tsdown.config.mjs
================================================
import { defineConfig } from "tsdown";
export default defineConfig({
ignoreWatch: [".turbo/"],
});
================================================
FILE: turbo.json
================================================
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"tasks": {
"build": {
"outputs": ["dist/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
gitextract_npj6yo6v/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ └── pr-ci.yml ├── .gitignore ├── .prettierrc ├── .vscode/ │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── apps/ │ └── vscode-extension/ │ ├── .gitignore │ ├── LICENSE │ ├── package.json │ ├── scripts/ │ │ ├── build.js │ │ └── process-shim.js │ ├── src/ │ │ ├── commands/ │ │ │ ├── copyError.ts │ │ │ ├── execute.ts │ │ │ ├── pinError.ts │ │ │ ├── revealSelection.ts │ │ │ ├── showErrorInSidebar.ts │ │ │ ├── unpinError.ts │ │ │ └── validate.ts │ │ ├── diagnostics.ts │ │ ├── extension.ts │ │ ├── formattedDiagnosticsStore.ts │ │ ├── globals.d.ts │ │ ├── logger.ts │ │ ├── provider/ │ │ │ ├── hoverProvider.ts │ │ │ ├── markdownWebviewProvider.ts │ │ │ ├── selectedTextHoverProvider.ts │ │ │ └── webviewViewProvider.ts │ │ ├── supportedLanguageIds.ts │ │ └── test/ │ │ ├── runTest.ts │ │ └── suite/ │ │ ├── extension.test.ts │ │ └── index.ts │ ├── syntaxes/ │ │ └── type.tmGrammar.json │ ├── tsconfig.json │ └── webview/ │ ├── index.html │ ├── index.js │ ├── style.css │ └── vendor/ │ └── codicon.css ├── docs/ │ ├── hide-original-errors.md │ ├── pretty-ts-errors-hack.css │ └── vscode-logs.md ├── eslint.config.mjs ├── examples/ │ ├── errors.js │ ├── errors.ts │ ├── errors.vue │ └── examples.type ├── package.json ├── packages/ │ ├── formatter/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── addMissingParentheses.ts │ │ │ ├── errorMessagePrettifier.ts │ │ │ ├── formatTypeBlock.ts │ │ │ ├── formatTypeWithPrettier.ts │ │ │ └── index.ts │ │ ├── test/ │ │ │ ├── errorMessageMocks.ts │ │ │ └── formatter.vitest.ts │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ ├── utils/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── vscode-formatter/ │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── components/ │ │ │ ├── actions.ts │ │ │ ├── errorTitle.ts │ │ │ ├── hoverCodeBlock.ts │ │ │ ├── htmlCodeBlock.ts │ │ │ ├── miniLine.ts │ │ │ ├── plainCodeBlock.ts │ │ │ └── spanBreak.ts │ │ ├── format/ │ │ │ ├── embedSymbolLinks.ts │ │ │ ├── identSentences.ts │ │ │ ├── prettifyDiagnosticForHover.ts │ │ │ └── prettifyDiagnosticForSidebar.ts │ │ └── index.ts │ ├── test/ │ │ └── vscode-formatter.vitest.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── tsconfig.base.json ├── tsconfig.json ├── tsdown.config.mjs └── turbo.json
SYMBOL INDEX (103 symbols across 31 files)
FILE: apps/vscode-extension/scripts/build.js
function main (line 11) | async function main() {
method setup (line 41) | setup(build) {
method setup (line 64) | setup(build) {
FILE: apps/vscode-extension/src/commands/copyError.ts
constant COMMAND_ID (line 4) | const COMMAND_ID = "prettyTsErrors.copyError";
function registerCopyError (line 6) | function registerCopyError(context: ExtensionContext) {
FILE: apps/vscode-extension/src/commands/execute.ts
function execute (line 7) | async function execute(
FILE: apps/vscode-extension/src/commands/pinError.ts
constant COMMAND_ID (line 6) | const COMMAND_ID = "prettyTsErrors.pinError";
function registerPinError (line 8) | function registerPinError(context: ExtensionContext) {
FILE: apps/vscode-extension/src/commands/revealSelection.ts
constant COMMAND_ID (line 5) | const COMMAND_ID = "prettyTsErrors.revealSelection";
function registerRevealSelection (line 7) | function registerRevealSelection(context: ExtensionContext) {
FILE: apps/vscode-extension/src/commands/showErrorInSidebar.ts
constant COMMAND_ID (line 6) | const COMMAND_ID = "prettyTsErrors.showErrorInSidebar";
function registerShowErrorInSidebar (line 8) | function registerShowErrorInSidebar(context: ExtensionContext) {
FILE: apps/vscode-extension/src/commands/unpinError.ts
constant COMMAND_ID (line 5) | const COMMAND_ID = "prettyTsErrors.unpinError";
function registerUnpinError (line 7) | function registerUnpinError(context: ExtensionContext) {
FILE: apps/vscode-extension/src/commands/validate.ts
function tryEnsureUri (line 3) | function tryEnsureUri(
type UriLike (line 22) | type UriLike = Parameters<typeof Uri.from>[0];
function isUriLike (line 24) | function isUriLike(value: unknown): value is UriLike {
function tryEnsureRange (line 33) | function tryEnsureRange(
type RangeLike (line 55) | type RangeLike = { start: PositionLike; end: PositionLike };
function isRangeLike (line 57) | function isRangeLike(value: unknown): value is RangeLike {
type PositionLike (line 68) | type PositionLike = { line: number; character: number };
function isPositionLike (line 70) | function isPositionLike(value: unknown): value is PositionLike {
FILE: apps/vscode-extension/src/diagnostics.ts
function registerOnDidChangeDiagnostics (line 33) | function registerOnDidChangeDiagnostics(context: ExtensionContext) {
constant CACHE_SIZE_MAX (line 76) | const CACHE_SIZE_MAX = 100;
function getFormattedDiagnostic (line 91) | async function getFormattedDiagnostic(
function ensureHoverProviderIsRegistered (line 131) | function ensureHoverProviderIsRegistered(uri: Uri, context: ExtensionCon...
FILE: apps/vscode-extension/src/extension.ts
function activate (line 13) | function activate(context: ExtensionContext) {
function deactivate (line 40) | function deactivate() {
FILE: apps/vscode-extension/src/formattedDiagnosticsStore.ts
type StoreKey (line 4) | type StoreKey = Uri["fsPath"];
type FormattedDiagnostic (line 6) | interface FormattedDiagnostic {
FILE: apps/vscode-extension/src/logger.ts
function getLogger (line 11) | function getLogger(): LogOutputChannel {
function info (line 21) | function info(...args: Parameters<LogOutputChannel["info"]>) {
function trace (line 25) | function trace(...args: Parameters<LogOutputChannel["trace"]>) {
function debug (line 29) | function debug(...args: Parameters<LogOutputChannel["debug"]>) {
function warn (line 32) | function warn(...args: Parameters<LogOutputChannel["warn"]>) {
function error (line 36) | function error(...args: Parameters<LogOutputChannel["error"]>) {
type LogLevel (line 40) | type LogLevel = "info" | "trace" | "debug" | "warn" | "error";
type LogLevelThresholds (line 41) | type LogLevelThresholds = Record<LogLevel, number>;
type SortedLogLevelThresholds (line 42) | type SortedLogLevelThresholds = [LogLevel, number][];
function measure (line 73) | function measure<T = unknown>(
function logMeasuredDuration (line 103) | function logMeasuredDuration(
function normalizeThresholds (line 113) | function normalizeThresholds(
function findLogLevel (line 124) | function findLogLevel(
function dispose (line 135) | function dispose() {
function isPromiseLike (line 142) | function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
function register (line 151) | function register(context: ExtensionContext) {
FILE: apps/vscode-extension/src/provider/hoverProvider.ts
method provideHover (line 5) | provideHover(document, position, _token) {
FILE: apps/vscode-extension/src/provider/markdownWebviewProvider.ts
class MarkdownWebviewProvider (line 6) | class MarkdownWebviewProvider {
method constructor (line 10) | constructor(private readonly context: vscode.ExtensionContext) {
method loadWebviewHtmlTemplate (line 18) | private async loadWebviewHtmlTemplate(): Promise<string> {
method getWebviewOptions (line 29) | getWebviewOptions(): vscode.WebviewOptions {
method createOnDidReceiveMessage (line 43) | createOnDidReceiveMessage() {
method getWebviewContent (line 58) | async getWebviewContent(
method patchCspSafeAttrs (line 71) | private patchCspSafeAttrs(html: string, webview: vscode.Webview) {
FILE: apps/vscode-extension/src/provider/selectedTextHoverProvider.ts
function registerSelectedTextHoverProvider (line 17) | function registerSelectedTextHoverProvider(context: ExtensionContext) {
FILE: apps/vscode-extension/src/provider/webviewViewProvider.ts
constant NO_DIAGNOSTICS_MESSAGE (line 18) | const NO_DIAGNOSTICS_MESSAGE =
type ViewMode (line 21) | type ViewMode = "cursor" | "locked";
type DiagnosticItem (line 23) | interface DiagnosticItem {
type PinnedError (line 28) | interface PinnedError {
function getViewProvider (line 34) | function getViewProvider() {
function updateHasErrorsContext (line 38) | function updateHasErrorsContext() {
function registerWebviewViewProvider (line 57) | function registerWebviewViewProvider(context: ExtensionContext) {
function diagnosticToItem (line 72) | async function diagnosticToItem(
class MarkdownWebviewViewProvider (line 82) | class MarkdownWebviewViewProvider implements vscode.WebviewViewProvider {
method constructor (line 93) | constructor(private readonly provider: MarkdownWebviewProvider) {}
method ensureInitialized (line 95) | private async ensureInitialized() {
method lockToDiagnostic (line 113) | async lockToDiagnostic(range: vscode.Range, message?: string) {
method pinDiagnostic (line 137) | async pinDiagnostic(range: vscode.Range, message?: string) {
method unpinDiagnostic (line 165) | unpinDiagnostic() {
method resolveWebviewView (line 172) | async resolveWebviewView(
method ensureDisposables (line 242) | private ensureDisposables(webviewView: vscode.WebviewView) {
method getActiveContentHtml (line 251) | private async getActiveContentHtml(): Promise<string> {
method getActiveDiagnosticItems (line 257) | private async getActiveDiagnosticItems(): Promise<DiagnosticItem[]> {
method getCursorDiagnosticItems (line 267) | private async getCursorDiagnosticItems(): Promise<DiagnosticItem[]> {
method refresh (line 280) | async refresh(webview: vscode.Webview) {
FILE: apps/vscode-extension/src/supportedLanguageIds.ts
constant SUPPORTED_LANGUAGE_IDS (line 1) | const SUPPORTED_LANGUAGE_IDS = [
FILE: apps/vscode-extension/src/test/runTest.ts
function main (line 5) | async function main() {
FILE: apps/vscode-extension/src/test/suite/index.ts
function run (line 5) | function run(
FILE: apps/vscode-extension/webview/index.js
method postMessage (line 14) | postMessage(message) {
method notify (line 23) | notify(text) {
function handleCopyContentEvent (line 56) | function handleCopyContentEvent(element) {
function copyToClipboard (line 72) | async function copyToClipboard(text) {
FILE: examples/errors.js
function run (line 48) | function run(animal) {
FILE: examples/errors.ts
type Person (line 1) | interface Person {
type GetUserFunction (line 20) | type GetUserFunction = () => {
type Animal (line 35) | interface Animal {
function run (line 40) | function run<T extends Animal>(animal: T) {
type MyError (line 46) | type MyError = {
FILE: packages/formatter/src/addMissingParentheses.ts
function addMissingParentheses (line 12) | function addMissingParentheses(type: string): string {
FILE: packages/formatter/src/errorMessagePrettifier.ts
type CodeBlockFn (line 3) | type CodeBlockFn = (
function createErrorMessagePrettifier (line 9) | function createErrorMessagePrettifier(
type Rule (line 33) | type Rule = {
function getRules (line 38) | async function getRules(codeBlock: CodeBlockFn): Promise<Rule[]> {
FILE: packages/formatter/src/formatTypeBlock.ts
function formatTypeBlock (line 4) | async function formatTypeBlock(
function formatType (line 34) | async function formatType(
FILE: packages/formatter/src/formatTypeWithPrettier.ts
function formatTypeWithPrettier (line 5) | async function formatTypeWithPrettier(text: string) {
FILE: packages/utils/src/index.ts
function objectKeys (line 3) | function objectKeys<T extends Record<string, unknown>>(
function invert (line 9) | function invert<T extends Record<string, string>>(
FILE: packages/vscode-formatter/src/components/htmlCodeBlock.ts
type Highlighter (line 3) | interface Highlighter {
function initHighlighter (line 9) | function initHighlighter(h: Highlighter) {
FILE: packages/vscode-formatter/src/format/embedSymbolLinks.ts
function embedSymbolLinks (line 4) | function embedSymbolLinks(diagnostic: Diagnostic): Diagnostic {
FILE: packages/vscode-formatter/src/format/prettifyDiagnosticForHover.ts
function prettifyDiagnosticForHover (line 24) | async function prettifyDiagnosticForHover(
FILE: packages/vscode-formatter/src/format/prettifyDiagnosticForSidebar.ts
function prettifyDiagnosticForSidebar (line 23) | async function prettifyDiagnosticForSidebar(
Condensed preview — 87 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (129K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 33,
"preview": "github: [yoavbls, kevinramharak]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 975,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: bug\nassignees: \"\"\n---\n\n### **Describe t"
},
{
"path": ".github/workflows/pr-ci.yml",
"chars": 1154,
"preview": "name: PR CI\n\non:\n workflow_dispatch:\n pull_request:\n types: [opened, synchronize, reopened, ready_for_review]\n\nperm"
},
{
"path": ".gitignore",
"chars": 97,
"preview": "out\ndist\nnode_modules\n.vscode-test/\n*.vsix\n.turbo\n.vercel\n.idea\n**/.DS_Store\ntsconfig.tsbuildinfo"
},
{
"path": ".prettierrc",
"chars": 86,
"preview": "{\n \"printWidth\": 80,\n \"tabWidth\": 2,\n \"useTabs\": false,\n \"trailingComma\": \"es5\"\n}\n"
},
{
"path": ".vscode/extensions.json",
"chars": 218,
"preview": "{\n // See http://go.microsoft.com/fwlink/?LinkId=827846\n // for the documentation about the extensions.json format\n \""
},
{
"path": ".vscode/launch.json",
"chars": 1726,
"preview": "// A launch configuration that compiles the extension and then opens it inside a new window\n// Use IntelliSense to learn"
},
{
"path": ".vscode/settings.json",
"chars": 666,
"preview": "// Place your settings in this file to overwrite default and user settings.\n{\n \"files.exclude\": {\n \"out\": false, // "
},
{
"path": ".vscode/tasks.json",
"chars": 1541,
"preview": "// See https://go.microsoft.com/fwlink/?LinkId=733558\n// for the documentation about the tasks.json format\n{\n \"version\""
},
{
"path": "CONTRIBUTING.md",
"chars": 189,
"preview": "Contribute good stuff\n\nIf you're looking for ideas, check out our [board](https://github.com/users/yoavbls/projects/3) a"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2023 Yoav Balasiano\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 8859,
"preview": "<a href=\"https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors\" style=\"display: none;\">\n <pictur"
},
{
"path": "apps/vscode-extension/.gitignore",
"chars": 9,
"preview": "README.md"
},
{
"path": "apps/vscode-extension/LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2023 Yoav Balasiano\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "apps/vscode-extension/package.json",
"chars": 4397,
"preview": "{\n \"name\": \"pretty-ts-errors\",\n \"displayName\": \"Pretty TypeScript Errors\",\n \"publisher\": \"YoavBls\",\n \"description\": "
},
{
"path": "apps/vscode-extension/scripts/build.js",
"chars": 2593,
"preview": "const process = require(\"node:process\");\nconst console = require(\"node:console\");\nconst fs = require(\"node:fs\");\nconst p"
},
{
"path": "apps/vscode-extension/scripts/process-shim.js",
"chars": 153,
"preview": "// https://esbuild.github.io/api/#inject\n\nlet _cwd = \"/\";\n\nexport let process = {\n cwd: () => _cwd,\n chdir: (newCwd) ="
},
{
"path": "apps/vscode-extension/src/commands/copyError.ts",
"chars": 695,
"preview": "import { commands, env, window, type ExtensionContext } from \"vscode\";\nimport { execute } from \"./execute\";\n\nconst COMMA"
},
{
"path": "apps/vscode-extension/src/commands/execute.ts",
"chars": 663,
"preview": "import { window } from \"vscode\";\nimport { logger } from \"../logger\";\n\n/**\n * A wrapper function to execute command tasks"
},
{
"path": "apps/vscode-extension/src/commands/pinError.ts",
"chars": 1094,
"preview": "import { commands, type ExtensionContext } from \"vscode\";\nimport { execute } from \"./execute\";\nimport { tryEnsureRange }"
},
{
"path": "apps/vscode-extension/src/commands/revealSelection.ts",
"chars": 1056,
"preview": "import { type ExtensionContext, commands } from \"vscode\";\nimport { tryEnsureRange, tryEnsureUri } from \"./validate\";\nimp"
},
{
"path": "apps/vscode-extension/src/commands/showErrorInSidebar.ts",
"chars": 1117,
"preview": "import { commands, type ExtensionContext } from \"vscode\";\nimport { execute } from \"./execute\";\nimport { tryEnsureRange }"
},
{
"path": "apps/vscode-extension/src/commands/unpinError.ts",
"chars": 510,
"preview": "import { commands, type ExtensionContext } from \"vscode\";\nimport { execute } from \"./execute\";\nimport { getViewProvider "
},
{
"path": "apps/vscode-extension/src/commands/validate.ts",
"chars": 2011,
"preview": "import { Range, Uri } from \"vscode\";\n\nexport function tryEnsureUri(\n maybeUriLike: unknown\n): { isValidUri: true; uri: "
},
{
"path": "apps/vscode-extension/src/diagnostics.ts",
"chars": 5087,
"preview": "import { has } from \"@pretty-ts-errors/utils\";\nimport { prettifyDiagnosticForHover } from \"@pretty-ts-errors/vscode-form"
},
{
"path": "apps/vscode-extension/src/extension.ts",
"chars": 1451,
"preview": "import { ExtensionContext, commands } from \"vscode\";\nimport { registerOnDidChangeDiagnostics } from \"./diagnostics\";\nimp"
},
{
"path": "apps/vscode-extension/src/formattedDiagnosticsStore.ts",
"chars": 749,
"preview": "import { MarkdownString, Range, Uri } from \"vscode\";\nimport type { Diagnostic } from \"vscode-languageserver-types\";\n\ntyp"
},
{
"path": "apps/vscode-extension/src/globals.d.ts",
"chars": 324,
"preview": "// \"@types/node\": \"^16.11.68\" misses a bunch of global declarations, this file fixes the one's we use.\n\ndeclare global {"
},
{
"path": "apps/vscode-extension/src/logger.ts",
"chars": 4391,
"preview": "import {\n ExtensionMode,\n LogOutputChannel,\n window,\n type ExtensionContext,\n LogLevel as VSLogLevel,\n} from \"vscod"
},
{
"path": "apps/vscode-extension/src/provider/hoverProvider.ts",
"chars": 656,
"preview": "import { HoverProvider } from \"vscode\";\nimport { formattedDiagnosticsStore } from \"../formattedDiagnosticsStore\";\n\nexpor"
},
{
"path": "apps/vscode-extension/src/provider/markdownWebviewProvider.ts",
"chars": 3896,
"preview": "import * as vscode from \"vscode\";\n\n/**\n * @see https://github.com/microsoft/vscode-extension-samples/blob/main/webview-s"
},
{
"path": "apps/vscode-extension/src/provider/selectedTextHoverProvider.ts",
"chars": 2131,
"preview": "import { d } from \"@pretty-ts-errors/utils\";\nimport { prettifyDiagnosticForHover } from \"@pretty-ts-errors/vscode-format"
},
{
"path": "apps/vscode-extension/src/provider/webviewViewProvider.ts",
"chars": 10511,
"preview": "import type { ExtensionContext } from \"vscode\";\nimport * as vscode from \"vscode\";\nimport { getUserLangs, getUserTheme } "
},
{
"path": "apps/vscode-extension/src/supportedLanguageIds.ts",
"chars": 190,
"preview": "export const SUPPORTED_LANGUAGE_IDS = [\n \"typescript\",\n \"typescriptreact\",\n \"javascript\",\n \"javascriptreact\",\n \"ast"
},
{
"path": "apps/vscode-extension/src/test/runTest.ts",
"chars": 756,
"preview": "import * as path from \"path\";\n\nimport { runTests } from \"@vscode/test-electron\";\n\nasync function main() {\n try {\n //"
},
{
"path": "apps/vscode-extension/src/test/suite/extension.test.ts",
"chars": 370,
"preview": "// You can import and use all API from the 'vscode' module\n// as well as import your extension to test it\nimport * as vs"
},
{
"path": "apps/vscode-extension/src/test/suite/index.ts",
"chars": 720,
"preview": "import * as path from \"path\";\nimport Mocha from \"mocha\";\nimport { glob } from \"glob\";\n\nexport function run(\n testsRoot:"
},
{
"path": "apps/vscode-extension/syntaxes/type.tmGrammar.json",
"chars": 407,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/RedCMD/TmLanguage-Syntax-Highlighter/main/vscode.tmLanguage.schema.jso"
},
{
"path": "apps/vscode-extension/tsconfig.json",
"chars": 343,
"preview": "{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"module\": \"nodenext\",\n \"target\": \"ES2022\",\n "
},
{
"path": "apps/vscode-extension/webview/index.html",
"chars": 1147,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "apps/vscode-extension/webview/index.js",
"chars": 1891,
"preview": "// @ts-check\n\n// wrap this in IIFE because `vscode` should **NEVER** be leaked into the global scope\n// @see https://cod"
},
{
"path": "apps/vscode-extension/webview/style.css",
"chars": 2566,
"preview": "body {\n font-family:\n -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", sans-serif;\n line-height: 1.6;\n max"
},
{
"path": "apps/vscode-extension/webview/vendor/codicon.css",
"chars": 1963,
"preview": "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Micros"
},
{
"path": "docs/hide-original-errors.md",
"chars": 1252,
"preview": "To hide the original errors, display only the prettified ones, and make type blocks copyable, you can use the following "
},
{
"path": "docs/pretty-ts-errors-hack.css",
"chars": 685,
"preview": "/* Allow copying */\n.codicon-none {\n user-select: text !important;\n -webkit-user-select: text !important;\n}\n\n/* Hide e"
},
{
"path": "docs/vscode-logs.md",
"chars": 788,
"preview": "# Instructions to find and export the VS Code logs for Pretty TypeScript Errors\n\n1. Open the output window to the `Prett"
},
{
"path": "eslint.config.mjs",
"chars": 1100,
"preview": "// @ts-check\n\nimport eslint from \"@eslint/js\";\nimport tseslint from \"typescript-eslint\";\nimport globals from \"globals\";\n"
},
{
"path": "examples/errors.js",
"chars": 1004,
"preview": "// @ts-check\n\n/**\n * @typedef {Object} Person\n * @property {string} name\n * @property {number} age\n * @property {Object}"
},
{
"path": "examples/errors.ts",
"chars": 774,
"preview": "interface Person {\n name: string;\n age: number;\n address: {\n street: string;\n city: string;\n country: string"
},
{
"path": "examples/errors.vue",
"chars": 182,
"preview": "<script setup lang=\"ts\">\nimport { RouterView } from \"vue-router\";\n\nconst x = [1, 2, 3, \"4\"];\n\nfunction y(param: number[]"
},
{
"path": "examples/examples.type",
"chars": 113,
"preview": "{\n fistName: string;\n lastName: string;\n age: number; \n} | undefined | null;\n\n{\n x: string;\n y: number;\n}[]"
},
{
"path": "package.json",
"chars": 729,
"preview": "{\n \"private\": true,\n \"scripts\": {\n \"build\": \"turbo run build\",\n \"dev\": \"turbo run dev\",\n \"lint\": \"turbo run l"
},
{
"path": "packages/formatter/README.md",
"chars": 502,
"preview": "# Pretty TypeScript Error formatter\n\nThe formatting package of [pretty-ts-errors](https://github.com/yoavbls/pretty-ts-e"
},
{
"path": "packages/formatter/package.json",
"chars": 1034,
"preview": "{\n \"name\": \"@pretty-ts-errors/formatter\",\n \"version\": \"0.1.8\",\n \"description\": \"Pretty TypeScript Errors Formatter\",\n"
},
{
"path": "packages/formatter/src/addMissingParentheses.ts",
"chars": 1685,
"preview": "import { has, invert, objectKeys } from \"@pretty-ts-errors/utils\";\n\nconst parentheses = {\n \"(\": \")\",\n \"{\": \"}\",\n \"[\":"
},
{
"path": "packages/formatter/src/errorMessagePrettifier.ts",
"chars": 4947,
"preview": "import { formatTypeBlock } from \"./formatTypeBlock\";\n\nexport type CodeBlockFn = (\n code: string,\n language?: string,\n "
},
{
"path": "packages/formatter/src/formatTypeBlock.ts",
"chars": 2612,
"preview": "import { addMissingParentheses } from \"./addMissingParentheses\";\nimport { formatTypeWithPrettier } from \"./formatTypeWit"
},
{
"path": "packages/formatter/src/formatTypeWithPrettier.ts",
"chars": 422,
"preview": "import { format } from \"prettier/standalone\";\nimport * as parserEstree from \"prettier/plugins/estree\";\nimport * as parse"
},
{
"path": "packages/formatter/src/index.ts",
"chars": 96,
"preview": "export {\n createErrorMessagePrettifier,\n type CodeBlockFn,\n} from \"./errorMessagePrettifier\";\n"
},
{
"path": "packages/formatter/test/errorMessageMocks.ts",
"chars": 6351,
"preview": "/**\n * This file contains mocks of error messages, only some of them\n * are used in tests but all of them can be used to"
},
{
"path": "packages/formatter/test/formatter.vitest.ts",
"chars": 2548,
"preview": "import { describe, it, expect } from \"vitest\";\nimport {\n createErrorMessagePrettifier,\n type CodeBlockFn,\n} from \"../s"
},
{
"path": "packages/formatter/tsconfig.json",
"chars": 281,
"preview": "{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"noEmit\": false,\n \"outDir\": \"dist\",\n \"rootDi"
},
{
"path": "packages/formatter/vitest.config.ts",
"chars": 211,
"preview": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n test: {\n environment: \"node\",\n glob"
},
{
"path": "packages/utils/package.json",
"chars": 268,
"preview": "{\n \"private\": true,\n \"name\": \"@pretty-ts-errors/utils\",\n \"files\": [\n \"dist/**\"\n ],\n \"main\": \"dist/index.js\",\n \""
},
{
"path": "packages/utils/src/index.ts",
"chars": 963,
"preview": "import dedent from \"ts-dedent\";\n\nexport function objectKeys<T extends Record<string, unknown>>(\n obj: T\n): (keyof T & s"
},
{
"path": "packages/utils/tsconfig.json",
"chars": 239,
"preview": "{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"noEmit\": false,\n \"outDir\": \"dist\",\n \"rootDi"
},
{
"path": "packages/vscode-formatter/README.md",
"chars": 57,
"preview": "# Pretty TypeScript Errors - Formatter for VSCode hovers\n"
},
{
"path": "packages/vscode-formatter/package.json",
"chars": 1175,
"preview": "{\n \"name\": \"@pretty-ts-errors/vscode-formatter\",\n \"version\": \"0.1.0\",\n \"description\": \"Pretty TypeScript Errors Forma"
},
{
"path": "packages/vscode-formatter/src/components/actions.ts",
"chars": 1865,
"preview": "import { compressToEncodedURIComponent } from \"lz-string\";\nimport { Diagnostic, Range } from \"vscode-languageserver-type"
},
{
"path": "packages/vscode-formatter/src/components/errorTitle.ts",
"chars": 543,
"preview": "import { d } from \"@pretty-ts-errors/utils\";\nimport { Diagnostic } from \"vscode-languageserver-types\";\n\nexport const err"
},
{
"path": "packages/vscode-formatter/src/components/hoverCodeBlock.ts",
"chars": 1795,
"preview": "import { d } from \"@pretty-ts-errors/utils\";\nimport { miniLine } from \"./miniLine\";\nimport { spanBreak } from \"./spanBre"
},
{
"path": "packages/vscode-formatter/src/components/htmlCodeBlock.ts",
"chars": 1216,
"preview": "import { CodeBlockFn } from \"@pretty-ts-errors/formatter\";\n\ninterface Highlighter {\n codeToHtml(code: string, options: "
},
{
"path": "packages/vscode-formatter/src/components/miniLine.ts",
"chars": 139,
"preview": "import { spanBreak } from \"./spanBreak\";\n\n/** May be useful for line separations */\nexport const miniLine = spanBreak(/*"
},
{
"path": "packages/vscode-formatter/src/components/plainCodeBlock.ts",
"chars": 281,
"preview": "import { d } from \"@pretty-ts-errors/utils\";\n\n/**\n * Code block without syntax highlighting like.\n * For syntax highligh"
},
{
"path": "packages/vscode-formatter/src/components/spanBreak.ts",
"chars": 324,
"preview": "import { d } from \"@pretty-ts-errors/utils\";\n\n/**\n * Since every thing in the extension hover split into spans,\n * we ne"
},
{
"path": "packages/vscode-formatter/src/format/embedSymbolLinks.ts",
"chars": 898,
"preview": "import { Diagnostic } from \"vscode-languageserver-types\";\nimport { URI } from \"vscode-uri\";\n\nexport function embedSymbol"
},
{
"path": "packages/vscode-formatter/src/format/identSentences.ts",
"chars": 759,
"preview": "import { d } from \"@pretty-ts-errors/utils\";\n\nexport const identSentences = (message: string): string =>\n message\n ."
},
{
"path": "packages/vscode-formatter/src/format/prettifyDiagnosticForHover.ts",
"chars": 1545,
"preview": "import { createErrorMessagePrettifier } from \"@pretty-ts-errors/formatter\";\nimport { Diagnostic } from \"vscode-languages"
},
{
"path": "packages/vscode-formatter/src/format/prettifyDiagnosticForSidebar.ts",
"chars": 1520,
"preview": "import { createErrorMessagePrettifier } from \"@pretty-ts-errors/formatter\";\nimport { Diagnostic } from \"vscode-languages"
},
{
"path": "packages/vscode-formatter/src/index.ts",
"chars": 230,
"preview": "export { prettifyDiagnosticForHover } from \"./format/prettifyDiagnosticForHover\";\nexport { prettifyDiagnosticForSidebar "
},
{
"path": "packages/vscode-formatter/test/vscode-formatter.vitest.ts",
"chars": 1980,
"preview": "import { describe, it, expect } from \"vitest\";\nimport { createErrorMessagePrettifier } from \"@pretty-ts-errors/formatter"
},
{
"path": "packages/vscode-formatter/tsconfig.json",
"chars": 341,
"preview": "{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"noEmit\": false,\n \"outDir\": \"dist\",\n \"rootDi"
},
{
"path": "packages/vscode-formatter/vitest.config.ts",
"chars": 211,
"preview": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n test: {\n environment: \"node\",\n glob"
},
{
"path": "tsconfig.base.json",
"chars": 823,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"CommonJS\",\n \"moduleResolution\": \"Node\",\n \"lib\": [\""
},
{
"path": "tsconfig.json",
"chars": 289,
"preview": "{\n \"$schema\": \"https://json.schemastore.org/tsconfig\",\n \"extends\": \"./tsconfig.base.json\",\n \"files\": [],\n \"reference"
},
{
"path": "tsdown.config.mjs",
"chars": 102,
"preview": "import { defineConfig } from \"tsdown\";\n\nexport default defineConfig({\n ignoreWatch: [\".turbo/\"],\n});\n"
},
{
"path": "turbo.json",
"chars": 246,
"preview": "{\n \"$schema\": \"https://turbo.build/schema.json\",\n \"globalDependencies\": [\"**/.env.*local\"],\n \"tasks\": {\n \"build\": "
}
]
About this extraction
This page contains the full source code of the yoavbls/pretty-ts-errors GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 87 files (113.9 KB), approximately 32.4k tokens, and a symbol index with 103 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.