ALE (Asynchronous Lint Engine) checks the buffers you edit and can talk to TypeScript's server, tsserver, as a JavaScript linter named tsserver. VS Code uses the same server, and it runs the same checker as tsc, so its errors in a .js file are tsc's errors.
One detail decides whether it runs at all in a JavaScript project. Per ALE's source, its tsserver linter finds the project root by looking for the nearest tsconfig.json, and its help says an LSP linter with no project root does not run. A project with only a jsconfig.json therefore needs the root set through g:ale_root.
At a glance
| Editor | Vim with ALE |
|---|---|
| What to install | ALE plugin |
| What checks the code | ALE's tsserver linter, which talks to TypeScript's own server directly |
| Where its TypeScript comes from | The tsserver in the project's node_modules/.bin, which ALE looks for first; a global tsserver otherwise |
| Reads | jsconfig.json, tsconfig.json |
| How this page was checked | Not run in Vim for this page. The settings are from ALE's documentation and source linked below (its help file, ale-typescript, and the tsserver linter and handler); what tsserver reports comes from TypeScript's language service, which the site's tests run. |
Turn on the tsserver linter and give it a root
List tsserver among the JavaScript linters, and map it to the folder that holds jsconfig.json. g:ale_root takes a dictionary from linter name to a path or a function of the buffer number; the function below uses ALE's own helper for finding the nearest file.
" run TypeScript's server on JavaScript files, next to ESLint if you use it
let g:ale_linters = {'javascript': ['tsserver', 'eslint']}
" ALE looks for tsconfig.json to find the project root; point it at jsconfig.json
let g:ale_root = {
\ 'tsserver': {buffer -> fnamemodify(ale#path#FindNearestFile(buffer, 'jsconfig.json'), ':h')},
\} Which TypeScript it runs
ALE looks for node_modules/.bin/tsserver above the file and uses it, falling back to a tsserver on your PATH (the g:ale_javascript_tsserver_executable setting). Keep typescript in the project's dev dependencies so that the local copy exists and matches the version npm run lint runs; g:ale_javascript_tsserver_use_global would make ALE skip it.
What differs from tsc
ALE checks buffers, so errors in files you have not opened are not listed. It shows problems with signs, in the location list and, if you enable it, as virtual text; :ALEDetail prints the full message, including the indented lines of a long TypeScript error. :ALEGoToDefinition and :ALEHover use the same server, and completion works when g:ale_completion_enabled is set before ALE loads.
Make sure it matches tsc
Run npm run lint (or npx tsc -p jsconfig.json --pretty false) in a terminal, open a file it reports, and compare. :ALEInfo shows which linters ran for the buffer and the command ALE used, which answers most "why is nothing reported" questions.
Gotchas
- Adding a
tsconfig.jsonthat only says"extends": "./jsconfig.json"is the other way to give ALE a root; TypeScript's server then reads that file for those folders, so keep it a pureextends. Settingg:ale_rootleaves the project untouched. - Per ALE's help, an LSP linter without a project root does not run, so a project with only a
jsconfig.jsonand nog:ale_rootgets no TypeScript errors from ALE at all, which looks like a clean file.
Sources
- ALE help (doc/ale.txt): g:ale_root, g:ale_linters · ALE on GitHub
- ALE: the JavaScript tsserver linter · ALE on GitHub
- ALE: tsserver project root handler · ALE on GitHub
- ALE help (doc/ale-typescript.txt) · ALE on GitHub
Last reviewed 20 October 2026.