Skip to content

Instantly share code, notes, and snippets.

@romainl
Last active November 9, 2024 21:02
Show Gist options
  • Save romainl/ce55ce6fdc1659c5fbc0f4224fd6ad29 to your computer and use it in GitHub Desktop.
Save romainl/ce55ce6fdc1659c5fbc0f4224fd6ad29 to your computer and use it in GitHub Desktop.
Linting your code, the vanilla way

Linting your code, the vanilla way

You may want a linter plugin to lint your code in Vim but you probably don't need it. At least try the built-in way before jumping on the plugin bandwagon.

Defining makeprg

autocmd FileType <filetype> setlocal makeprg=<external command>

This autocommand tells Vim to use <external command> when invoking :make % in a <filetype> buffer. You can add as many similar lines as needed for other languages.

Example:

autocmd FileType python setlocal makeprg=pylint\ --output-format=parseable
autocmd FileType javascript setlocal makeprg=eslint\ --format\ compact

Defining errorformat (optional)

The whole point of defining makeprg is for Vim to parse the output of the linter and display eventual errors.

The most sensibly designed compilers/linters default to or can be told to use a simple, machine-readable, output formatting that looks more or less like this:

path/to/file:123:4 Error message

That format (and a few variants) is supported by default so there is no need to do anything but some compilers/linters are not as sensibly designed and you might have to handle silly formatting with ASCII arrows, multi-line messages, etc. In such a case, writing a custom errorformat is pretty much an obligation. See :help errorformat for the nitty-gritty.

"Compiler"

Chances are your linter is already natively supported via the "Compiler" feature, in which case you don't have to define either makeprg or errorformat. You simply need to call the :compiler command with the name of your linter as argument and you are set:

:compiler eslint

Of course, calling that command manually for every file you edit is not an exciting prospect but you can use an autocommand like the ones above to make it all automatic:

autocmd FileType python compiler pylint
autocmd FileType javascript compiler eslint

There are currently 96 supported "compilers" so it might be a good idea to :view $VIMRUNTIME/compiler (and consider adding it to the default distribution if it is not ;-)).

Automatic execution on :write

autocmd BufWritePost <pattern> silent make! <afile> | silent redraw!

This autocommand tells Vim to run :make on the current file matching <pattern> whenever you :write it. You can add patterns if you want that to happen with other filetypes.

Example:

autocmd BufWritePost *.py,*.js silent make! <afile> | silent redraw!

Automatic opening of the quickfix window

This autocommand tells Vim to open the quickfix window whenever a quickfix command is executed (like :make) AND there are valid errors to display.

autocmd QuickFixCmdPost [^l]* cwindow

My Vim-related gists.

augroup Linting
autocmd!
autocmd FileType python compiler pylint
autocmd BufWritePost *.py silent make! <afile> | silent redraw!
autocmd QuickFixCmdPost [^l]* cwindow
augroup END
@w0rp
Copy link

w0rp commented Nov 15, 2017

Now try using this to check your code with tsserver or flow.

@lewis6991
Copy link

Any tips for populating the signs column without a plugin?

@iovis
Copy link

iovis commented Dec 12, 2017

TIL about [^l]*, very interesting gist!

@alcarney
Copy link

alcarney commented Dec 19, 2017

Using this approach how would you go about using multiple linting programs per file type, say flake8 and mypy for python for example?

@romainl
Copy link
Author

romainl commented Jan 5, 2018

@lewis6991, the "interface" for managing signs is way too messy. Defining, placing, and removing a sign is not particularly hard but you'll have to manage them and sync them with the content of the quickfix list and/or the content of any location list. You really need some non-trivial scripting for getting it to work.

See :help sign-support.

@romainl
Copy link
Author

romainl commented Jan 5, 2018

@alcarney, I would offset as much of the responsibility as possible to external tools. In this case, I would write a short shell script that aggregates the output of those tools and point Vim at it.

@bfrg
Copy link

bfrg commented Feb 17, 2018

@lewis6991, maybe you can use this as a starting point: https://github.com/mh21/errormarker.vim
It's a very simple plugin under 200 lines.

@Konfekt
Copy link

Konfekt commented Jun 25, 2018

@alcarney, see :help compiler. You add for every compiler cmp a configuration file ~/.vim/compilers/cmp.vim, and invoke them by :Compiler cmp.

@Konfekt
Copy link

Konfekt commented Apr 5, 2020

This collection sets up some common compilers to be used with Vim's :help compiler feature.

@romainl
Copy link
Author

romainl commented Apr 5, 2020

@Konfekt, that is a useful resource, thank you for sharing it. I've noticed a few errors in the README:

  • %USERPROFILE is missing a closing %.
  • :lN is synonymous with :lp so :lN should be :ln.
  • :cN is synonymous with :cp so :cN should be :cn.

@Konfekt
Copy link

Konfekt commented Apr 5, 2020

Thank you, fixed!

@KonnorRogers
Copy link

KonnorRogers commented May 7, 2020

I've been hitting issues with redraw! it doesn't consistently work.

It could be plugin related, I haven't had the chance to fully test it. However, when using edit I don't run into any issues.

Are there any downsides to using edit instead of redraw!

Example:

autocmd BufWritePost *.py silent make! % | edit

The other option I found was also:

set autoread

@romainl
Copy link
Author

romainl commented May 7, 2020

@ParamagicDev, I never thought about using edit in this context. That's pretty clever.

As for downsides I must admit I don't know. There are side-effects, that's for sure, but whether they are blockers or not is yours to decide, I guess.

Off the top of my head:

  • :edit triggers the whole filetype mechanism from ftdetect to ftplugin via custom autocommands in your vimrc, which may or may not be acceptable.
  • :edit also triggers the BufRead event and possibly others, which may or may not end up executing stuff you don't want.

:help 'autoread' is generally used in conjunction with :help :checktime to react on an external write, which may be relevant if your linter also does some fixing. See this other gist of mine for an example involving eslint --fix.

@romainl
Copy link
Author

romainl commented Jul 21, 2021

This needs an update with :help :compiler. (done)

@pbnj
Copy link

pbnj commented Sep 3, 2021

@lewis6991, the "interface" for managing signs is way too messy. Defining, placing, and removing a sign is not particularly hard but you'll have to manage them and sync them with the content of the quickfix list and/or the content of any location list. You really need some non-trivial scripting for getting it to work.

See :help sign-support.

This is how I do it:

function! QF_signs() abort
	" define the signs used by quickfix
	call sign_define('QFError',{'text':'>>','texthl':'NONE','linehl':'NONE'})
	" remove any existing signs
	call sign_unplace('*')
	" get the quickfix list
	let s:qfl = getqflist()
	" loop over quickfix list to place signs
	for item in s:qfl
		call sign_place(0, '', 'QFError', item.bufnr, {'lnum': item.lnum})
	endfor
endfunction

augroup quickfix
	autocmd!
	autocmd QuickFixCmdPost [^l]* cwindow | call QF_signs()
	autocmd QuickFixCmdPost l* lwindow | call QF_signs()
augroup END

It's not the most eloquent and there may be definitely are some buggy edge-cases, but it works Good Enough for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment