diff options
Diffstat (limited to 'ale_linters')
-rw-r--r-- | ale_linters/d/dmd.vim | 60 | ||||
-rw-r--r-- | ale_linters/go/gobuild.vim | 213 | ||||
-rw-r--r-- | ale_linters/python/flake8.vim | 58 | ||||
-rw-r--r-- | ale_linters/python/mypy.vim | 4 | ||||
-rw-r--r-- | ale_linters/ruby/rubocop.vim | 14 | ||||
-rw-r--r-- | ale_linters/rust/cargo.vim | 7 | ||||
-rw-r--r-- | ale_linters/rust/rustc.vim | 74 | ||||
-rw-r--r-- | ale_linters/vim/vint.vim | 2 |
8 files changed, 324 insertions, 108 deletions
diff --git a/ale_linters/d/dmd.vim b/ale_linters/d/dmd.vim index 0a6e9bf3..0c665d71 100644 --- a/ale_linters/d/dmd.vim +++ b/ale_linters/d/dmd.vim @@ -1,24 +1,54 @@ " Author: w0rp <devw0rp@gmail.com> " Description: "dmd for D files" -" A function for finding the dmd-wrapper script in the Vim runtime paths -function! s:FindWrapperScript() abort - for l:parent in split(&runtimepath, ',') - " Expand the path to deal with ~ issues. - let l:path = expand(l:parent . '/' . 'dmd-wrapper') - - if filereadable(l:path) - return l:path +function! s:FindDUBConfig(buffer) abort + " Find a DUB configuration file in ancestor paths. + " The most DUB-specific names will be tried first. + for l:possible_filename in ['dub.sdl', 'dub.json', 'package.json'] + let l:dub_file = ale#util#FindNearestFile(a:buffer, l:possible_filename) + + if !empty(l:dub_file) + return l:dub_file endif endfor + + return '' endfunction -function! ale_linters#d#dmd#GetCommand(buffer) abort - let l:wrapper_script = s:FindWrapperScript() +function! ale_linters#d#dmd#DUBCommand(buffer) abort + " If we can't run dub, then skip this command. + if !executable('dub') + " Returning an empty string skips to the DMD command. + return '' + endif + + let l:dub_file = s:FindDUBConfig(a:buffer) - let l:command = l:wrapper_script . ' -o- -vcolumns -c' + if empty(l:dub_file) + return '' + endif + + " To support older dub versions, we just change the directory to + " the directory where we found the dub config, and then run `dub describe` + " from that directory. + return 'cd ' . fnameescape(fnamemodify(l:dub_file, ':h')) + \ . ' && dub describe --import-paths' +endfunction + +function! ale_linters#d#dmd#DMDCommand(buffer, dub_output) abort + let l:import_list = [] + + " Build a list of import paths generated from DUB, if available. + for l:line in a:dub_output + if !empty(l:line) + " The arguments must be '-Ifilename', not '-I filename' + call add(l:import_list, '-I' . fnameescape(l:line)) + endif + endfor - return l:command + return g:ale#util#stdin_wrapper . ' .d dmd ' + \ . join(l:import_list) + \ . ' -o- -vcolumns -c' endfunction function! ale_linters#d#dmd#Handle(buffer, lines) abort @@ -57,8 +87,10 @@ endfunction call ale#linter#Define('d', { \ 'name': 'dmd', -\ 'output_stream': 'stderr', \ 'executable': 'dmd', -\ 'command_callback': 'ale_linters#d#dmd#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale_linters#d#dmd#DUBCommand', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#d#dmd#DMDCommand', 'output_stream': 'stderr'}, +\ ], \ 'callback': 'ale_linters#d#dmd#Handle', \}) diff --git a/ale_linters/go/gobuild.vim b/ale_linters/go/gobuild.vim index 4abf1987..832669f2 100644 --- a/ale_linters/go/gobuild.vim +++ b/ale_linters/go/gobuild.vim @@ -1,17 +1,214 @@ -" Author: dzhou121 <dzhou121@gmail.com> +" Author: Joshua Rubin <joshua@rubixconsulting.com> " Description: go build for Go files -function! s:FindGobuildScript() abort - return g:ale#util#stdin_wrapper . ' .go go build -o /dev/null' +" inspired by work from dzhou121 <dzhou121@gmail.com> + +function! ale_linters#go#gobuild#GoEnv(buffer) abort + if exists('s:go_env') + return '' + endif + + return 'go env GOPATH GOROOT' +endfunction + +let s:SplitChar = has('unix') ? ':' : ':' + +" get a list of all source directories from $GOPATH and $GOROOT +function! s:SrcDirs() abort + let l:paths = split(s:go_env.GOPATH, s:SplitChar) + call add(l:paths, s:go_env.GOROOT) + + return l:paths +endfunction + +" figure out from a directory like `/home/user/go/src/some/package` that the +" import for that path is simply `some/package` +function! s:PackageImportPath(buffer) abort + let l:bufname = resolve(bufname(a:buffer)) + let l:pkgdir = fnamemodify(l:bufname, ':p:h') + + for l:path in s:SrcDirs() + let l:path = l:path . '/src/' + + if stridx(l:pkgdir, l:path) == 0 + return l:pkgdir[strlen(l:path):] + endif + endfor + + return '' +endfunction + +" get the package info data structure using `go list` +function! ale_linters#go#gobuild#GoList(buffer, goenv_output) abort + if !empty(a:goenv_output) + let s:go_env = { + \ 'GOPATH': a:goenv_output[0], + \ 'GOROOT': a:goenv_output[1], + \} + endif + + return 'go list -json ' . shellescape(s:PackageImportPath(a:buffer)) +endfunction + +let s:filekeys = [ +\ 'GoFiles', +\ 'CgoFiles', +\ 'CFiles', +\ 'CXXFiles', +\ 'MFiles', +\ 'HFiles', +\ 'FFiles', +\ 'SFiles', +\ 'SwigFiles', +\ 'SwigCXXFiles', +\ 'SysoFiles', +\ 'TestGoFiles', +\ 'XTestGoFiles', +\] + +" get the go and test go files from the package +" will return empty list if the package has any cgo or other invalid files +function! s:PkgFiles(pkginfo) abort + let l:files = [] + + for l:key in s:filekeys + if has_key(a:pkginfo, l:key) + call extend(l:files, a:pkginfo[l:key]) + endif + endfor + + " resolve the path of the file relative to the window directory + return map(l:files, 'shellescape(fnamemodify(resolve(a:pkginfo.Dir . ''/'' . v:val), '':p''))') +endfunction + +function! ale_linters#go#gobuild#CopyFiles(buffer, golist_output) abort + let l:tempdir = tempname() + let l:temppkgdir = l:tempdir . '/src/' . s:PackageImportPath(a:buffer) + call mkdir(l:temppkgdir, 'p', 0700) + + if empty(a:golist_output) + return 'echo ' . shellescape(l:tempdir) + endif + + " parse the output + let l:pkginfo = json_decode(join(a:golist_output, "\n")) + + " get all files for the package + let l:files = s:PkgFiles(l:pkginfo) + + " copy the files to a temp directory with $GOPATH structure + return 'cp ' . join(l:files, ' ') . ' ' . shellescape(l:temppkgdir) . ' && echo ' . shellescape(l:tempdir) +endfunction + +function! ale_linters#go#gobuild#GetCommand(buffer, copy_output) abort + let l:tempdir = a:copy_output[0] + let l:importpath = s:PackageImportPath(a:buffer) + + " write the a:buffer and any modified buffers from the package to the tempdir + for l:bufnum in range(1, bufnr('$')) + " ignore unloaded buffers (can't be a:buffer or a modified buffer) + if !bufloaded(l:bufnum) + continue + endif + + " ignore non-Go buffers + if getbufvar(l:bufnum, '&ft') !=# 'go' + continue + endif + + " only consider buffers other than a:buffer if they have the same import + " path as a:buffer and are modified + if l:bufnum != a:buffer + if s:PackageImportPath(l:bufnum) !=# l:importpath + continue + endif + + if !getbufvar(l:bufnum, '&mod') + continue + endif + endif + + call writefile(getbufline(l:bufnum, 1, '$'), l:tempdir . '/src/' . s:PkgFile(l:bufnum)) + endfor + + let l:gopaths = [ l:tempdir ] + call extend(l:gopaths, split(s:go_env.GOPATH, s:SplitChar)) + + return 'GOPATH=' . shellescape(join(l:gopaths, s:SplitChar)) . ' go test -c -o /dev/null ' . shellescape(l:importpath) +endfunction + +function! s:PkgFile(buffer) abort + let l:bufname = resolve(bufname(a:buffer)) + let l:importpath = s:PackageImportPath(a:buffer) + let l:fname = fnamemodify(l:bufname, ':t') + + return l:importpath . '/' . l:fname +endfunction + +function! s:FindBuffer(file) abort + for l:buffer in range(1, bufnr('$')) + if !buflisted(l:buffer) + continue + endif + + let l:pkgfile = s:PkgFile(l:buffer) + + if a:file =~ '/' . l:pkgfile . '$' + return l:buffer + endif + endfor + + return -1 endfunction -let g:ale#util#gobuild_script = -\ get(g:, 'ale_go_gobuild_script', s:FindGobuildScript()) +let s:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+' +let s:handler_pattern = '^\(' . s:path_pattern . '\):\(\d\+\):\?\(\d\+\)\?: \(.\+\)$' + +let s:multibuffer = 0 + +function! ale_linters#go#gobuild#Handler(buffer, lines) abort + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, s:handler_pattern) + + if len(l:match) == 0 + continue + endif + + let l:buffer = s:FindBuffer(l:match[1]) + + if l:buffer == -1 + continue + endif + + if !s:multibuffer && l:buffer != a:buffer + " strip lines from other buffers + continue + endif + + call add(l:output, { + \ 'bufnr': l:buffer, + \ 'lnum': l:match[2] + 0, + \ 'vcol': 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \ 'nr': -1, + \}) + endfor + + return l:output +endfunction call ale#linter#Define('go', { \ 'name': 'go build', -\ 'output_stream': 'stderr', \ 'executable': 'go', -\ 'command': g:ale#util#gobuild_script, -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'command_chain': [ +\ {'callback': 'ale_linters#go#gobuild#GoEnv', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#go#gobuild#GoList', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#go#gobuild#CopyFiles', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#go#gobuild#GetCommand', 'output_stream': 'stderr'}, +\ ], +\ 'callback': 'ale_linters#go#gobuild#Handler', \}) diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index 42b8ca78..bd136b27 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -7,18 +7,70 @@ let g:ale_python_flake8_executable = let g:ale_python_flake8_args = \ get(g:, 'ale_python_flake8_args', '') +" A map from Python executable paths to semver strings parsed for those +" executables, so we don't have to look up the version number constantly. +let s:version_cache = {} + function! ale_linters#python#flake8#GetExecutable(buffer) abort return g:ale_python_flake8_executable endfunction -function! ale_linters#python#flake8#GetCommand(buffer) abort +function! ale_linters#python#flake8#VersionCheck(buffer) abort + let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) + + " If we have previously stored the version number in a cache, then + " don't look it up again. + if has_key(s:version_cache, l:executable) + " Returning an empty string skips this command. + return '' + endif + + return ale_linters#python#flake8#GetExecutable(a:buffer) . ' --version' +endfunction + +" Get the flake8 version from the output, or the cache. +function! s:GetVersion(buffer, version_output) abort + let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) + let l:version = [] + + " Get the version from the cache. + if has_key(s:version_cache, l:executable) + return s:version_cache[l:executable] + endif + + if !empty(a:version_output) + " Parse the version string, and store it in the cache. + let l:version = ale#semver#Parse(a:version_output[0]) + let s:version_cache[l:executable] = l:version + endif + + return l:version +endfunction + +" flake8 versions 3 and up support the --stdin-display-name argument. +function! s:SupportsDisplayName(version) abort + return !empty(a:version) && ale#semver#GreaterOrEqual(a:version, [3, 0, 0]) +endfunction + +function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort + let l:version = s:GetVersion(a:buffer, a:version_output) + + " Only include the --stdin-display-name argument if we can parse the + " flake8 version, and it is recent enough to support it. + let l:display_name_args = s:SupportsDisplayName(l:version) + \ ? '--stdin-display-name %s' + \ : '' + return ale_linters#python#flake8#GetExecutable(a:buffer) - \ . ' ' . g:ale_python_flake8_args . ' --stdin-display-name %s -' + \ . ' ' . g:ale_python_flake8_args . ' ' . l:display_name_args . ' -' endfunction call ale#linter#Define('python', { \ 'name': 'flake8', \ 'executable_callback': 'ale_linters#python#flake8#GetExecutable', -\ 'command_callback': 'ale_linters#python#flake8#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale_linters#python#flake8#VersionCheck'}, +\ {'callback': 'ale_linters#python#flake8#GetCommand'}, +\ ], \ 'callback': 'ale#handlers#HandlePEP8Format', \}) diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim index 934961b1..1509d9e6 100644 --- a/ale_linters/python/mypy.vim +++ b/ale_linters/python/mypy.vim @@ -16,6 +16,8 @@ function! g:ale_linters#python#mypy#GetCommand(buffer) abort \ . g:ale_python_mypy_options endfunction +let s:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+' + function! g:ale_linters#python#mypy#Handle(buffer, lines) abort " Look for lines like the following: " @@ -24,7 +26,7 @@ function! g:ale_linters#python#mypy#Handle(buffer, lines) abort " Lines like these should be ignored below: " " file.py:4: note: (Stub files are from https://github.com/python/typeshed) - let l:pattern = '^.\+:\(\d\+\):\?\(\d\+\)\?: \([^:]\+\): \(.\+\)$' + let l:pattern = '^' . s:path_pattern . ':\(\d\+\):\?\(\d\+\)\?: \([^:]\+\): \(.\+\)$' let l:output = [] for l:line in a:lines diff --git a/ale_linters/ruby/rubocop.vim b/ale_linters/ruby/rubocop.vim index 69d26d33..87fc7b79 100644 --- a/ale_linters/ruby/rubocop.vim +++ b/ale_linters/ruby/rubocop.vim @@ -4,9 +4,9 @@ function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort " Matches patterns line the following: " - " <path>/_:47:14: 83:29: C: Prefer single-quoted strings when you don't + " <path>:83:29: C: Prefer single-quoted strings when you don't " need string interpolation or special symbols. - let l:pattern = '\v_:(\d+):(\d+): (.): (.+)' + let l:pattern = '\v:(\d+):(\d+): (.): (.+)' let l:output = [] for l:line in a:lines @@ -34,6 +34,12 @@ function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort return l:output endfunction +function! ale_linters#ruby#rubocop#GetCommand(buffer) abort + return 'rubocop --format emacs --force-exclusion ' . + \ g:ale_ruby_rubocop_options . + \ ' --stdin ' . bufname(a:buffer) +endfunction + " Set this option to change Rubocop options. if !exists('g:ale_ruby_rubocop_options') " let g:ale_ruby_rubocop_options = '--lint' @@ -43,8 +49,6 @@ endif call ale#linter#Define('ruby', { \ 'name': 'rubocop', \ 'executable': 'rubocop', -\ 'command': 'rubocop --format emacs --force-exclusion --stdin ' -\ . g:ale_ruby_rubocop_options -\ . ' %s', +\ 'command_callback': 'ale_linters#ruby#rubocop#GetCommand', \ 'callback': 'ale_linters#ruby#rubocop#Handle', \}) diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim index 738821d7..7f821d2c 100644 --- a/ale_linters/rust/cargo.vim +++ b/ale_linters/rust/cargo.vim @@ -1,7 +1,6 @@ " Author: Daniel Schemala <istjanichtzufassen@gmail.com> " Description: rustc invoked by cargo for rust files - function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort if ale#util#FindNearestFile(a:bufnr, 'Cargo.toml') !=# '' return 'cargo' @@ -15,7 +14,7 @@ endfunction call ale#linter#Define('rust', { \ 'name': 'cargo', \ 'executable_callback': 'ale_linters#rust#cargo#GetCargoExecutable', -\ 'command': 'cargo rustc -- --error-format=json -Z no-trans', -\ 'callback': 'ale_linters#rust#rustc#HandleRustcErrors', -\ 'output_stream': 'stderr', +\ 'command': 'cargo build --message-format=json -q', +\ 'callback': 'ale#handlers#rust#HandleRustErrors', +\ 'output_stream': 'stdout', \}) diff --git a/ale_linters/rust/rustc.vim b/ale_linters/rust/rustc.vim index 3eeede66..1d080b98 100644 --- a/ale_linters/rust/rustc.vim +++ b/ale_linters/rust/rustc.vim @@ -1,77 +1,6 @@ " Author: Daniel Schemala <istjanichtzufassen@gmail.com> " Description: rustc for rust files -if !exists('g:ale_rust_ignore_error_codes') - let g:ale_rust_ignore_error_codes = [] -endif - - -function! ale_linters#rust#rustc#HandleRustcErrors(buffer_number, errorlines) abort - let l:file_name = fnamemodify(bufname(a:buffer_number), ':t') - let l:output = [] - - for l:errorline in a:errorlines - " ignore everything that is not Json - if l:errorline !~# '^{' - continue - endif - - let l:error = json_decode(l:errorline) - - if !empty(l:error.code) && index(g:ale_rust_ignore_error_codes, l:error.code.code) > -1 - continue - endif - - for l:span in l:error.spans - if l:span.is_primary && - \ (l:span.file_name ==# l:file_name || l:span.file_name ==# '<anon>') - call add(l:output, { - \ 'bufnr': a:buffer_number, - \ 'lnum': l:span.line_start, - \ 'vcol': 0, - \ 'col': l:span.byte_start, - \ 'nr': -1, - \ 'text': l:error.message, - \ 'type': toupper(l:error.level[0]), - \}) - else - " when the error is caused in the expansion of a macro, we have - " to bury deeper - let l:root_cause = s:FindErrorInExpansion(l:span, l:file_name) - - if !empty(l:root_cause) - call add(l:output, { - \ 'bufnr': a:buffer_number, - \ 'lnum': l:root_cause[0], - \ 'vcol': 0, - \ 'col': l:root_cause[1], - \ 'nr': -1, - \ 'text': l:error.message, - \ 'type': toupper(l:error.level[0]), - \}) - endif - endif - endfor - endfor - - return l:output -endfunction - - -" returns: a list [lnum, col] with the location of the error or [] -function! s:FindErrorInExpansion(span, file_name) abort - if a:span.file_name ==# a:file_name - return [a:span.line_start, a:span.byte_start] - endif - - if !empty(a:span.expansion) - return s:FindErrorInExpansion(a:span.expansion.span, a:file_name) - endif - - return [] -endfunction - - function! ale_linters#rust#rustc#RustcCommand(buffer_number) abort " Try to guess the library search path. If the project is managed by cargo, " it's usually <project root>/target/debug/deps/ or @@ -89,11 +18,10 @@ function! ale_linters#rust#rustc#RustcCommand(buffer_number) abort return 'rustc --error-format=json -Z no-trans ' . l:dependencies . ' -' endfunction - call ale#linter#Define('rust', { \ 'name': 'rustc', \ 'executable': 'rustc', \ 'command_callback': 'ale_linters#rust#rustc#RustcCommand', -\ 'callback': 'ale_linters#rust#rustc#HandleRustcErrors', +\ 'callback': 'ale#handlers#rust#HandleRustErrors', \ 'output_stream': 'stderr', \}) diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim index c25d899c..bc6ea460 100644 --- a/ale_linters/vim/vint.vim +++ b/ale_linters/vim/vint.vim @@ -6,6 +6,7 @@ let g:ale_vim_vint_show_style_issues = \ get(g:, 'ale_vim_vint_show_style_issues', 1) let s:warning_flag = g:ale_vim_vint_show_style_issues ? '-s' : '-w' +let s:enable_neovim = has('nvim') ? ' --enable-neovim ' : '' let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})"' call ale#linter#Define('vim', { @@ -15,6 +16,7 @@ call ale#linter#Define('vim', { \ . ' .vim vint ' \ . s:warning_flag \ . ' --no-color ' +\ . s:enable_neovim \ . s:format, \ 'callback': 'ale#handlers#HandleGCCFormat', \}) |