summaryrefslogtreecommitdiff
path: root/ale_linters
diff options
context:
space:
mode:
Diffstat (limited to 'ale_linters')
-rw-r--r--ale_linters/d/dmd.vim60
-rw-r--r--ale_linters/go/gobuild.vim213
-rw-r--r--ale_linters/python/flake8.vim58
-rw-r--r--ale_linters/python/mypy.vim4
-rw-r--r--ale_linters/ruby/rubocop.vim14
-rw-r--r--ale_linters/rust/cargo.vim7
-rw-r--r--ale_linters/rust/rustc.vim74
-rw-r--r--ale_linters/vim/vint.vim2
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',
\})