Editor setup

Type-checked JavaScript in Vim with ALE

  • Vim with ALE
  • ALE plugin

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

EditorVim with ALE
What to installALE plugin
What checks the codeALE's tsserver linter, which talks to TypeScript's own server directly
Where its TypeScript comes fromThe tsserver in the project's node_modules/.bin, which ALE looks for first; a global tsserver otherwise
Readsjsconfig.json, tsconfig.json
How this page was checkedNot 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.

~/.vim/vimrc
" 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.json that 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 pure extends. Setting g:ale_root leaves the project untouched.
  • Per ALE's help, an LSP linter without a project root does not run, so a project with only a jsconfig.json and no g:ale_root gets no TypeScript errors from ALE at all, which looks like a clean file.

Sources

Last reviewed 20 October 2026.