diff options
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | ale_linters/coffee/coffee.vim | 17 | ||||
-rw-r--r-- | ale_linters/coffee/coffeelint.vim | 17 | ||||
-rw-r--r-- | ale_linters/go/gobuild.vim | 213 | ||||
-rw-r--r-- | ale_linters/javascript/xo.vim | 41 | ||||
-rw-r--r-- | ale_linters/php/phpmd.vim | 41 | ||||
-rw-r--r-- | ale_linters/python/flake8.vim | 58 | ||||
-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-- | autoload/ale/engine.vim | 66 | ||||
-rw-r--r-- | autoload/ale/handlers/rust.vim | 90 | ||||
-rw-r--r-- | autoload/ale/loclist_jumping.vim | 16 | ||||
-rw-r--r-- | autoload/ale/semver.vim | 29 | ||||
-rw-r--r-- | autoload/ale/statusline.vim | 4 | ||||
-rw-r--r-- | autoload/ale/util.vim | 2 | ||||
-rw-r--r-- | doc/ale.txt | 89 | ||||
-rw-r--r-- | plugin/ale.vim | 81 | ||||
-rw-r--r-- | stdin-wrapper.bat | 22 | ||||
-rw-r--r-- | stdin-wrapper.exe | bin | 0 -> 534016 bytes | |||
-rw-r--r-- | stdin_wrapper.d | 84 | ||||
-rw-r--r-- | test/test_rust_handler.vader | 28 | ||||
-rw-r--r-- | test/test_semver_utils.vader | 16 | ||||
-rw-r--r-- | test/test_sign_placement.vader | 68 |
24 files changed, 902 insertions, 182 deletions
@@ -59,7 +59,7 @@ name. That seems to be the fairest way to arrange this table. | CoffeeScript | [coffee](http://coffeescript.org/), [coffeelint](https://www.npmjs.com/package/coffeelint) | | CSS | [csslint](http://csslint.net/), [stylelint](https://github.com/stylelint/stylelint) | | Cython (pyrex filetype) | [cython](http://cython.org/) | -| D | [dmd](https://dlang.org/dmd-linux.html)^ | +| D | [dmd](https://dlang.org/dmd-linux.html) | | Dockerfile | [hadolint](https://github.com/lukasmartinelli/hadolint) | | Elixir | [credo](https://github.com/rrrene/credo) | | Elm | [elm-make](https://github.com/elm-lang/elm-make) | @@ -76,7 +76,7 @@ name. That seems to be the fairest way to arrange this table. | MATLAB | [mlint](https://www.mathworks.com/help/matlab/ref/mlint.html) | | OCaml | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-integration-ocaml-merlin` for configuration instructions | Perl | [perl -c](https://perl.org/), [perl-critic](https://metacpan.org/pod/Perl::Critic) | -| PHP | [hack](http://hacklang.org/), [php -l](https://secure.php.net/), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer) | +| PHP | [hack](http://hacklang.org/), [php -l](https://secure.php.net/), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer), [phpmd](https://phpmd.org) | | Pug | [pug-lint](https://github.com/pugjs/pug-lint) | | Puppet | [puppet](https://puppet.com), [puppet-lint](https://puppet-lint.com) | | Python | [flake8](http://flake8.pycqa.org/en/latest/), [mypy](http://mypy-lang.org/), [pylint](https://www.pylint.org/) | @@ -93,14 +93,13 @@ name. That seems to be the fairest way to arrange this table. | Vim | [vint](https://github.com/Kuniwak/vint) | | YAML | [yamllint](https://yamllint.readthedocs.io/) | -* *^ Supported only on Unix machines via a wrapper script.* * *^^ No text linters are enabled by default.* If you would like to see support for more languages and tools, please [create an issue](https://github.com/w0rp/ale/issues) or [create a pull request](https://github.com/w0rp/ale/pulls). If your tool can read from stdin or you have code to suggest which is good, -support can be happily added for more tools. +support can be happily added for it. <a name="usage"></a> diff --git a/ale_linters/coffee/coffee.vim b/ale_linters/coffee/coffee.vim index 243fec6b..ac9ef79e 100644 --- a/ale_linters/coffee/coffee.vim +++ b/ale_linters/coffee/coffee.vim @@ -1,10 +1,23 @@ " Author: KabbAmine - https://github.com/KabbAmine " Description: Coffee for checking coffee files +function! ale_linters#coffee#coffee#GetExecutable(buffer) abort + return ale#util#ResolveLocalPath( + \ a:buffer, + \ 'node_modules/.bin/coffee', + \ 'coffee' + \) +endfunction + +function! ale_linters#coffee#coffee#GetCommand(buffer) abort + return ale_linters#coffee#coffee#GetExecutable(a:buffer) + \ . ' -cp -s' +endfunction + call ale#linter#Define('coffee', { \ 'name': 'coffee', -\ 'executable': 'coffee', -\ 'command': 'coffee -cp -s', +\ 'executable_callback': 'ale_linters#coffee#coffee#GetExecutable', +\ 'command_callback': 'ale_linters#coffee#coffee#GetCommand', \ 'output_stream': 'stderr', \ 'callback': 'ale#handlers#HandleGCCFormat', \}) diff --git a/ale_linters/coffee/coffeelint.vim b/ale_linters/coffee/coffeelint.vim index 96b8c59d..8b1c713e 100644 --- a/ale_linters/coffee/coffeelint.vim +++ b/ale_linters/coffee/coffeelint.vim @@ -1,6 +1,19 @@ " Author: Prashanth Chandra https://github.com/prashcr " Description: coffeelint linter for coffeescript files +function! ale_linters#coffee#coffeelint#GetExecutable(buffer) abort + return ale#util#ResolveLocalPath( + \ a:buffer, + \ 'node_modules/.bin/coffeelint', + \ 'coffeelint' + \) +endfunction + +function! ale_linters#coffee#coffeelint#GetCommand(buffer) abort + return ale_linters#coffee#coffeelint#GetExecutable(a:buffer) + \ . ' --stdin --reporter csv' +endfunction + function! ale_linters#coffee#coffeelint#Handle(buffer, lines) abort " Matches patterns like the following: " @@ -40,7 +53,7 @@ endfunction call ale#linter#Define('coffee', { \ 'name': 'coffeelint', -\ 'executable': 'coffeelint', -\ 'command': 'coffeelint --stdin --reporter csv', +\ 'executable_callback': 'ale_linters#coffee#coffeelint#GetExecutable', +\ 'command_callback': 'ale_linters#coffee#coffeelint#GetCommand', \ 'callback': 'ale_linters#coffee#coffeelint#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/javascript/xo.vim b/ale_linters/javascript/xo.vim new file mode 100644 index 00000000..e27f6f33 --- /dev/null +++ b/ale_linters/javascript/xo.vim @@ -0,0 +1,41 @@ +" Author: Daniel Lupu <lupu.daniel.f@gmail.com> +" Description: xo for JavaScript files + +let g:ale_javascript_xo_executable = +\ get(g:, 'ale_javascript_xo_executable', 'xo') + +let g:ale_javascript_xo_options = +\ get(g:, 'ale_javascript_xo_options', '') + +let g:ale_javascript_xo_use_global = +\ get(g:, 'ale_javascript_xo_use_global', 0) + +function! ale_linters#javascript#xo#GetExecutable(buffer) abort + if g:ale_javascript_xo_use_global + return g:ale_javascript_xo_executable + endif + + return ale#util#ResolveLocalPath( + \ a:buffer, + \ 'node_modules/.bin/xo', + \ g:ale_javascript_xo_executable + \) +endfunction + +function! ale_linters#javascript#xo#GetCommand(buffer) abort + return ale_linters#javascript#xo#GetExecutable(a:buffer) + \ . ' ' . g:ale_javascript_xo_options + \ . ' --reporter unix --stdin --stdin-filename %s' +endfunction + +function! ale_linters#javascript#xo#Handle(buffer, lines) abort + " xo uses eslint and the output format is the same + return ale_linters#javascript#eslint#Handle(a:buffer, a:lines) +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'xo', +\ 'executable_callback': 'ale_linters#javascript#xo#GetExecutable', +\ 'command_callback': 'ale_linters#javascript#xo#GetCommand', +\ 'callback': 'ale_linters#javascript#xo#Handle', +\}) diff --git a/ale_linters/php/phpmd.vim b/ale_linters/php/phpmd.vim new file mode 100644 index 00000000..73432538 --- /dev/null +++ b/ale_linters/php/phpmd.vim @@ -0,0 +1,41 @@ +" Author: medains <https://github.com/medains> +" Description: phpmd for PHP files + +" Set to change the ruleset +let g:ale_php_phpmd_ruleset = get(g:, 'ale_php_phpmd_ruleset', 'cleancode,codesize,controversial,design,naming,unusedcode') + +function! ale_linters#php#phpmd#Handle(buffer, lines) abort + " Matches against lines like the following: + " + " /path/to/some-filename.php:18 message + let l:pattern = '^.*:\(\d\+\)\t\(.\+\)$' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + " vcol is Needed to indicate that the column is a character. + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'vcol': 0, + \ 'col': 0, + \ 'text': l:match[2], + \ 'type': 'W', + \ 'nr': -1, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phpmd', +\ 'executable': 'phpmd', +\ 'command': g:ale#util#stdin_wrapper . ' .php phpmd %s text ' . g:ale_php_phpmd_ruleset . ' --ignore-violations-on-exit', +\ 'callback': 'ale_linters#php#phpmd#Handle', +\}) 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/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/autoload/ale/engine.vim b/autoload/ale/engine.vim index 25be0457..34e5ad95 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -166,24 +166,28 @@ function! s:HandleExit(job) abort let g:ale_buffer_info[l:buffer].loclist = g:ale_buffer_info[l:buffer].new_loclist let g:ale_buffer_info[l:buffer].new_loclist = [] + call ale#engine#SetResults(l:buffer, g:ale_buffer_info[l:buffer].loclist) + + " Call user autocommands. This allows users to hook into ALE's lint cycle. + silent doautocmd User ALELint + + " Mark line 200, column 17 with a squiggly line or something + " matchadd('ALEError', '\%200l\%17v') +endfunction + +function! ale#engine#SetResults(buffer, loclist) abort if g:ale_set_quickfix || g:ale_set_loclist - call ale#list#SetLists(g:ale_buffer_info[l:buffer].loclist) + call ale#list#SetLists(a:loclist) endif if g:ale_set_signs - call ale#sign#SetSigns(l:buffer, g:ale_buffer_info[l:buffer].loclist) + call ale#sign#SetSigns(a:buffer, a:loclist) endif if exists('*ale#statusline#Update') " Don't load/run if not already loaded. - call ale#statusline#Update(l:buffer, g:ale_buffer_info[l:buffer].loclist) + call ale#statusline#Update(a:buffer, a:loclist) endif - - " Call user autocommands. This allows users to hook into ALE's lint cycle. - silent doautocmd User ALELint - - " Mark line 200, column 17 with a squiggly line or something - " matchadd('ALEError', '\%200l\%17v') endfunction function! s:HandleExitNeoVim(job, data, event) abort @@ -215,6 +219,7 @@ function! s:RunJob(command, generic_job_options) abort let l:linter = a:generic_job_options.linter let l:output_stream = a:generic_job_options.output_stream let l:next_chain_index = a:generic_job_options.next_chain_index + let l:read_buffer = a:generic_job_options.read_buffer let l:command = a:command if l:command =~# '%s' @@ -270,10 +275,12 @@ function! s:RunJob(command, generic_job_options) abort " Execute the command with the shell, to fix escaping issues. let l:command = split(&shell) + split(&shellcmdflag) + [l:command] - " On Unix machines, we can send the Vim buffer directly. - " This is faster than reading the lines ourselves. - let l:job_options.in_io = 'buffer' - let l:job_options.in_buf = l:buffer + if l:read_buffer + " On Unix machines, we can send the Vim buffer directly. + " This is faster than reading the lines ourselves. + let l:job_options.in_io = 'buffer' + let l:job_options.in_buf = l:buffer + endif endif " Vim 8 will read the stdin from the file's buffer. @@ -293,20 +300,22 @@ function! s:RunJob(command, generic_job_options) abort \ 'next_chain_index': l:next_chain_index, \} - if has('nvim') - " In NeoVim, we have to send the buffer lines ourselves. - let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n" - - call jobsend(l:job, l:input) - call jobclose(l:job, 'stdin') - elseif has('win32') - " On some Vim versions, we have to send the buffer data ourselves. - let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n" - let l:channel = job_getchannel(l:job) - - if ch_status(l:channel) ==# 'open' - call ch_sendraw(l:channel, l:input) - call ch_close_in(l:channel) + if l:read_buffer + if has('nvim') + " In NeoVim, we have to send the buffer lines ourselves. + let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n" + + call jobsend(l:job, l:input) + call jobclose(l:job, 'stdin') + elseif has('win32') + " On some Vim versions, we have to send the buffer data ourselves. + let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n" + let l:channel = job_getchannel(l:job) + + if ch_status(l:channel) ==# 'open' + call ch_sendraw(l:channel, l:input) + call ch_close_in(l:channel) + endif endif endif endif @@ -364,11 +373,14 @@ function! s:InvokeChain(buffer, linter, chain_index, input) abort let l:command = a:linter.command endif + let l:is_last_job = l:chain_index >= len(get(a:linter, 'command_chain', [])) - 1 + call s:RunJob(l:command, { \ 'buffer': a:buffer, \ 'linter': a:linter, \ 'output_stream': l:output_stream, \ 'next_chain_index': l:chain_index + 1, + \ 'read_buffer': l:is_last_job, \}) endfunction diff --git a/autoload/ale/handlers/rust.vim b/autoload/ale/handlers/rust.vim new file mode 100644 index 00000000..c00c2276 --- /dev/null +++ b/autoload/ale/handlers/rust.vim @@ -0,0 +1,90 @@ +" Author: Daniel Schemala <istjanichtzufassen@gmail.com>, +" w0rp <devw0rp@gmail.com> +" +" Description: This file implements handlers specific to Rust. + +if !exists('g:ale_rust_ignore_error_codes') + let g:ale_rust_ignore_error_codes = [] +endif + +" 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 + +" A handler function which accepts a file name, to make unit testing easier. +function! ale#handlers#rust#HandleRustErrorsForFile(buffer, full_filename, lines) abort + let l:filename = fnamemodify(a:full_filename, ':t') + let l:output = [] + + for l:errorline in a:lines + " ignore everything that is not Json + if l:errorline !~# '^{' + continue + endif + + let l:error = json_decode(l:errorline) + + if has_key(l:error, 'message') && type(l:error.message) == type({}) + let l:error = l:error.message + endif + + if !has_key(l:error, 'code') + continue + endif + + 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 + let l:span_filename = fnamemodify(l:span.file_name, ':t') + + if ( + \ l:span.is_primary + \ && (l:span_filename ==# l:filename || l:span_filename ==# '<anon>') + \) + call add(l:output, { + \ 'bufnr': a:buffer, + \ '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:filename) + + if !empty(l:root_cause) + call add(l:output, { + \ 'bufnr': a:buffer, + \ '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 + +" A handler for output for Rust linters. +function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort + return ale#handlers#rust#HandleRustErrorsForFile(a:buffer, bufname(a:buffer), a:lines) +endfunction diff --git a/autoload/ale/loclist_jumping.vim b/autoload/ale/loclist_jumping.vim index b9ea9d13..b4d1993f 100644 --- a/autoload/ale/loclist_jumping.vim +++ b/autoload/ale/loclist_jumping.vim @@ -1,11 +1,23 @@ " Author: w0rp <devw0rp@gmail.com> " Description: This file implements functions for jumping around in a file -" based on errors and warnings in the loclist. +" based on errors and warnings in the loclist or quickfix list. + +function! s:GetCurrentList() abort + if g:ale_set_loclist + return getloclist(winnr()) + elseif g:ale_set_quickfix + let l:buffer = bufnr('%') + + return filter(getqflist(), 'get(v:val, ''bufnr'', -1) == ' . l:buffer) + endif + + return [] +endfunction function! s:GetSortedLoclist() abort let l:loclist = [] - for l:item in getloclist(winnr()) + for l:item in s:GetCurrentList() if l:item.lnum < 1 " Remove items we can't even jump to. continue diff --git a/autoload/ale/semver.vim b/autoload/ale/semver.vim new file mode 100644 index 00000000..b153dd1d --- /dev/null +++ b/autoload/ale/semver.vim @@ -0,0 +1,29 @@ +" Given some text, parse a semantic versioning string from the text +" into a triple of integeers [major, minor, patch]. +" +" If no match can be performed, then an empty List will be returned instead. +function! ale#semver#Parse(text) abort + let l:match = matchlist(a:text, '^ *\(\d\+\)\.\(\d\+\)\.\(\d\+\)') + + if empty(l:match) + return [] + endif + + return [l:match[1] + 0, l:match[2] + 0, l:match[3] + 0] +endfunction + +" Given two triples of integers [major, minor, patch], compare the triples +" and return 1 if the lhs is greater than or equal to the rhs. +function! ale#semver#GreaterOrEqual(lhs, rhs) abort + if a:lhs[0] > a:rhs[0] + return 1 + elseif a:lhs[0] == a:rhs[0] + if a:lhs[1] > a:rhs[1] + return 1 + elseif a:lhs[1] == a:rhs[1] + return a:lhs[2] >= a:rhs[2] + endif + endif + + return 0 +endfunction diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim index c01dd34d..7269ddca 100644 --- a/autoload/ale/statusline.vim +++ b/autoload/ale/statusline.vim @@ -3,6 +3,10 @@ " Update the buffer error/warning count with data from loclist. function! ale#statusline#Update(buffer, loclist) abort + if !has_key(g:ale_buffer_info, a:buffer) + return + endif + let l:errors = 0 let l:warnings = 0 diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index 27517ef7..6c6f20b0 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -8,7 +8,7 @@ function! s:FindWrapperScript() abort if filereadable(l:path) if has('win32') - return l:path . '.bat' + return l:path . '.exe' endif return l:path diff --git a/doc/ale.txt b/doc/ale.txt index 4bed2e02..348be809 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -35,6 +35,8 @@ CONTENTS *ale-contents* 4.23. python-mypy.....................|ale-linter-options-python-mypy| 4.24. python-pylint...................|ale-linter-options-python-pylint| 4.25. erlang..........................|ale-linter-options-erlang| + 4.26. phpmd...........................|ale-linter-options-phpmd| + 4.27. xo..............................|ale-linter-options-xo| 5. Linter Integration Notes.............|ale-linter-integration| 5.1. merlin..........................|ale-linter-integration-ocaml-merlin| 5.2. rust.............................|ale-integration-rust| @@ -85,7 +87,7 @@ The following languages and tools are supported. * Go: 'gofmt -e', 'go vet', 'golint', 'go build' * Haskell: 'ghc', 'hlint' * HTML: 'HTMLHint', 'tidy' -* JavaScript: 'eslint', 'jscs', 'jshint', 'flow' +* JavaScript: 'eslint', 'jscs', 'jshint', 'flow', 'xo' * JSON: 'jsonlint' * LaTeX: 'chktex', 'lacheck' * Lua: 'luacheck' @@ -93,7 +95,7 @@ The following languages and tools are supported. * MATLAB: 'mlint' * OCaml: 'merlin' (see |ale-linter-integration-ocaml-merlin|) * Perl: 'perl' (-c flag), 'perlcritic' -* PHP: 'hack', 'php' (-l flag), 'phpcs' +* PHP: 'hack', 'php' (-l flag), 'phpcs', 'phpmd' * Pug: 'pug-lint' * Puppet: 'puppet', 'puppet-lint' * Python: 'flake8', 'mypy', 'pylint' @@ -880,6 +882,52 @@ g:ale_erlang_erlc_options *g:ale_erlang_erlc_options* This variable controls additional parameters passed to `erlc`, such as `-I` or `-pa`. +------------------------------------------------------------------------------ +4.26. phpmd *ale-linter-options-phpmd* + +g:ale_php_phpmd_ruleset *g:ale_php_phpmd_ruleset* + + Type: |String| + Default: 'cleancode,codesize,controversial,design,naming,unusedcode' + + This variable controls the ruleset used by phpmd. Default is to use all of + the available phpmd rulesets + +------------------------------------------------------------------------------ +4.27. xo *ale-linter-options-xo* + +g:ale_javascript_xo_executable *g:ale_javascript_xo_executable* + + Type: |String| + Default: `'xo'` + + ALE will first discover the xo path in an ancestor node_modules + directory. If no such path exists, this variable will be used instead. + + This variable can be set to change the path to xo. + + If you wish to use only a globally installed version of xo, set + |g:ale_javascript_xo_use_global| to `1`. + + +g:ale_javascript_xo_options *g:ale_javascript_xo_options* + + Type: |String| + Default: `''` + + This variable can be set to pass additional options to xo. + + +g:ale_javascript_xo_use_global *g:ale_javascript_xo_use_global* + + Type: |String| + Default: `0` + + This variable controls whether or not ALE will search for a local path for + xo first. If this variable is set to `1`, then ALE will always use the + global version of xo, in preference to locally installed versions of + xo in node_modules. + =============================================================================== 5. Linter Integration Notes *ale-linter-integration* @@ -1005,15 +1053,40 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* check, and will produce the lines of output given to the `callback`. - Either this or the `command_callback` argument must - be provided. - `command_callback` A |String| or |Funcref| for a callback function accepting a buffer number. A |String| should be returned for a command to run. This can be used in place of `command` when more complicated processing is needed. + `command_chain` A |List| of |Dictionary| items defining a series + of commands to be run. At least one |Dictionary| + should be provided. Each Dictionary must contain the + key `callback`, defining a |String| or |Funcref| for + a function returning a |String| for a command to run. + + The callback functions for each command after the + first command in in the chain should accept two + arguments `(buffer, output)`, a buffer number and a + |List| of lines of output from the previous command + in the chain. + + The first callback function in a chain accepts only + a `(buffer)` argument, as there are no previous + commands to run which return `output`. + + Commands in the chain will all use the + `output_stream` value provided in the root + |Dictionary|. Each command in the chain can also + provide an `output_stream` key to override this value. + See the `output_stream` description for more + information. + + The Vim buffer being checked for linter will only + be sent to the final command in the chain. Previous + commands in the chain will receive no input from + stdin. + `output_stream` A |String| for the output stream the lines of output should be read from for the command which is run. The accepted values are `'stdout'`, `'stderr'`, and @@ -1023,6 +1096,12 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* instead of stdout. The option `'both'` will read from both stder and stdout at the same time. + Only one of `command`, `command_callback`, or `command_chain` should be + specified. `command_callback` is generally recommended when a command string + needs to be generated dynamically, or any global options are used. + `command_chain` is recommended where any system calls need to be made to + retrieve some kind of information before running the final command. + Some programs for checking for errors are not capable of receiving input from stdin, as is required by ALE. To remedy this, a wrapper script is provided named in the variable |g:ale#util#stdin_wrapper|. This variable diff --git a/plugin/ale.vim b/plugin/ale.vim index 5c319870..780d37e0 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -49,30 +49,16 @@ let g:ale_lint_delay = get(g:, 'ale_lint_delay', 200) " This flag can be set to 0 to disable linting when text is changed. let g:ale_lint_on_text_changed = get(g:, 'ale_lint_on_text_changed', 1) -if g:ale_lint_on_text_changed - augroup ALERunOnTextChangedGroup - autocmd! - autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay) - augroup END -endif " This flag can be set to 0 to disable linting when the buffer is entered. let g:ale_lint_on_enter = get(g:, 'ale_lint_on_enter', 1) -if g:ale_lint_on_enter - augroup ALERunOnEnterGroup - autocmd! - autocmd BufEnter,BufRead * call ale#Queue(300) - augroup END -endif " This flag can be set to 1 to enable linting when a buffer is written. let g:ale_lint_on_save = get(g:, 'ale_lint_on_save', 0) -if g:ale_lint_on_save - augroup ALERunOnSaveGroup - autocmd! - autocmd BufWrite * call ale#Queue(0) - augroup END -endif + +" This flag may be set to 0 to disable ale. After ale is loaded, :ALEToggle +" should be used instead. +let g:ale_enabled = get(g:, 'ale_enabled', 1) " These flags dictates if ale uses the quickfix or the loclist (loclist is the " default, quickfix overrides loclist). @@ -112,12 +98,6 @@ let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') " This flag can be set to 0 to disable echoing when the cursor moves. let g:ale_echo_cursor = get(g:, 'ale_echo_cursor', 1) -if g:ale_echo_cursor - augroup ALECursorGroup - autocmd! - autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() - augroup END -endif " String format for statusline " Its a list where: @@ -132,12 +112,64 @@ let g:ale_statusline_format = get(g:, 'ale_statusline_format', let g:ale_warn_about_trailing_whitespace = \ get(g:, 'ale_warn_about_trailing_whitespace', 1) +function! s:ALEInitAuGroups() abort + augroup ALERunOnTextChangedGroup + autocmd! + if g:ale_enabled && g:ale_lint_on_text_changed + autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay) + endif + augroup END + + augroup ALERunOnEnterGroup + autocmd! + if g:ale_enabled && g:ale_lint_on_enter + autocmd BufEnter,BufRead * call ale#Queue(300) + endif + augroup END + + augroup ALERunOnSaveGroup + autocmd! + if g:ale_enabled && g:ale_lint_on_save + autocmd BufWrite * call ale#Queue(0) + endif + augroup END + + augroup ALECursorGroup + autocmd! + if g:ale_enabled && g:ale_echo_cursor + autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() + endif + augroup END +endfunction + +function! s:ALEToggle() abort + let g:ale_enabled = !get(g:, 'ale_enabled') + + if g:ale_enabled + " Lint immediately + call ale#Queue(0) + else + for l:buffer in keys(g:ale_buffer_info) + " Stop jobs and delete stored buffer data + call ale#cleanup#Buffer(l:buffer) + " Clear signs, loclist, quicklist + call ale#engine#SetResults(l:buffer, []) + endfor + endif + + call s:ALEInitAuGroups() +endfunction + +call s:ALEInitAuGroups() + " Define commands for moving through warnings and errors. command! ALEPrevious :call ale#loclist_jumping#Jump('before', 0) command! ALEPreviousWrap :call ale#loclist_jumping#Jump('before', 1) command! ALENext :call ale#loclist_jumping#Jump('after', 0) command! ALENextWrap :call ale#loclist_jumping#Jump('after', 1) +command! ALEToggle :call s:ALEToggle() + " Define command to get information about current filetype. command! ALEInfo :call ale#linter#Info() @@ -146,6 +178,7 @@ nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return> nnoremap <silent> <Plug>(ale_previous_wrap) :ALEPreviousWrap<Return> nnoremap <silent> <Plug>(ale_next) :ALENext<Return> nnoremap <silent> <Plug>(ale_next_wrap) :ALENextWrap<Return> +nnoremap <silent> <Plug>(ale_toggle) :ALEToggle<Return> " Housekeeping diff --git a/stdin-wrapper.bat b/stdin-wrapper.bat deleted file mode 100644 index eca8d717..00000000 --- a/stdin-wrapper.bat +++ /dev/null @@ -1,22 +0,0 @@ -@echo off - -REM Get a unique directory name in the temporary directory -:loop -set "directory=%tmp%\ale_%RANDOM%" -if exist "%directory%" goto :loop - -REM Use a filename with the same file extension -mkdir "%directory%" -set filename="%directory%\file%1" - -REM Get all arguments after the first to run as a command -for /f "tokens=1,* delims= " %%a in ("%*") do set command_args=%%b - -REM Read all stdin data to the filename -more > "%filename%" - -REM Run the command on the file -%command_args% "%filename%" - -REM Delete the temporary directory -rmdir "%directory%" /s /q diff --git a/stdin-wrapper.exe b/stdin-wrapper.exe Binary files differnew file mode 100644 index 00000000..d79f6785 --- /dev/null +++ b/stdin-wrapper.exe diff --git a/stdin_wrapper.d b/stdin_wrapper.d new file mode 100644 index 00000000..8714bc2a --- /dev/null +++ b/stdin_wrapper.d @@ -0,0 +1,84 @@ +// Author: w0rp <devw0rp@gmail.com> +// Description: This file provides a D program for implementing +// the stdin-wrapper on Windows. + +import std.algorithm; +import std.array; +import std.file; +import std.process; +import std.stdio; +import std.path; + +@safe +auto createTemporaryFilename(string fileExtension) { + import std.uuid; + + string filename; + + do { + const randomPart = randomUUID().toString.replace("-", "_"); + + filename = buildPath(tempDir(), "ale_" ~ randomPart ~ fileExtension); + } while (exists(filename)); + + return filename; +} + +@trusted +void readStdinToFile(ref File tempFile) { + stdin.byChunk(4096).copy(tempFile.lockingTextWriter()); +} + +// Expand program names like "csslint" to "csslint.cmd" +// D is not able to perform this kind of expanstion in spawnProcess +@safe +string expandedProgramName(string name) { + auto extArray = environment["PATHEXT"].split(";"); + + foreach(pathDir; environment["PATH"].split(";")) { + foreach(extension; extArray) { + const candidate = buildPath(pathDir, name ~ extension); + + if (exists(candidate)) { + return candidate; + } + } + } + + // We were given a full path for a program name, so use that. + if (exists(name)) { + return name; + } + + return ""; +} + +@trusted +int runLinterProgram(string[] args) { + const expandedName = expandedProgramName(args[0]); + + writeln(expandedName); + + if (expandedName) { + return wait(spawnProcess([expandedName] ~ args[1..$])); + } + + return 1; +} + +@safe +int main(string[] args) { + const filename = createTemporaryFilename(args[1]); + + auto tempFile = File(filename, "w"); + + scope(exit) { + tempFile.close(); + remove(filename); + } + + readStdinToFile(tempFile); + tempFile.close(); + + return runLinterProgram(args[2..$] ~ [filename]); +} diff --git a/test/test_rust_handler.vader b/test/test_rust_handler.vader new file mode 100644 index 00000000..d4d54d37 --- /dev/null +++ b/test/test_rust_handler.vader @@ -0,0 +1,28 @@ +Execute(The Rust handler should handle rustc output): + AssertEqual + \ [ + \ {'lnum': 15, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 418, 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`'}, + \ {'lnum': 13, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 407, 'text': 'no method named `wat` found for type `std::string::String` in the current scope'}, + \ ], + \ ale#handlers#rust#HandleRustErrorsForFile(347, 'src/playpen.rs', [ + \ '', + \ 'ignore this', + \ '{"message":"expected one of `.`, `;`, `?`, `}`, or an operator, found `for`","code":null,"level":"error","spans":[{"file_name":"<anon>","byte_start":418,"byte_end":421,"line_start":15,"line_end":15,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" for chr in source.trim().chars() {","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"expansion":null}],"children":[],"rendered":null}', + \ '{"message":"main function not found","code":null,"level":"error","spans":[],"children":[],"rendered":null}', + \ '{"message":"no method named `wat` found for type `std::string::String` in the current scope","code":null,"level":"error","spans":[{"file_name":"<anon>","byte_start":407,"byte_end":410,"line_start":13,"line_end":13,"column_start":7,"column_end":10,"is_primary":true,"text":[{"text":" s.wat()","highlight_start":7,"highlight_end":10}],"label":null,"suggested_replacement":null,"expansion":null}],"children":[],"rendered":null}', + \ '{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":null}', + \ ]) + +Execute(The Rust handler should handle cargo output): + AssertEqual + \ [ + \ {'lnum': 15, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 11505, 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`'}, + \ {'lnum': 13, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 11494, 'text': 'no method named `wat` found for type `std::string::String` in the current scope'}, + \ ], + \ ale#handlers#rust#HandleRustErrorsForFile(347, 'src/playpen.rs', [ + \ '', + \ 'ignore this', + \ '{"message":{"children":[],"code":null,"level":"error","message":"expected one of `.`, `;`, `?`, `}`, or an operator, found `for`","rendered":null,"spans":[{"byte_end":11508,"byte_start":11505,"column_end":8,"column_start":5,"expansion":null,"file_name":"src/playpen.rs","is_primary":true,"label":null,"line_end":15,"line_start":15,"suggested_replacement":null,"text":[{"highlight_end":8,"highlight_start":5,"text":" for chr in source.trim().chars() {"}]}]},"package_id":"update 0.0.1 (path+file:///home/w0rp/Downloads/rust-by-example)","reason":"compiler-message","target":{"kind":["bin"],"name":"update","src_path":"/home/w0rp/Downloads/rust-by-example/src/main.rs"}}', + \ '{"message":{"children":[],"code":null,"level":"error","message":"no method named `wat` found for type `std::string::String` in the current scope","rendered":null,"spans":[{"byte_end":11497,"byte_start":11494,"column_end":10,"column_start":7,"expansion":null,"file_name":"src/playpen.rs","is_primary":true,"label":null,"line_end":13,"line_start":13,"suggested_replacement":null,"text":[{"highlight_end":10,"highlight_start":7,"text":" s.wat()"}]}]},"package_id":"update 0.0.1 (path+file:///home/w0rp/Downloads/rust-by-example)","reason":"compiler-message","target":{"kind":["bin"],"name":"update","src_path":"/home/w0rp/Downloads/rust-by-example/src/main.rs"}}', + \ '{"message":{"children":[],"code":null,"level":"error","message":"aborting due to previous error","rendered":null,"spans":[]},"package_id":"update 0.0.1 (path+file:///home/w0rp/Downloads/rust-by-example)","reason":"compiler-message","target":{"kind":["bin"],"name":"update","src_path":"/home/w0rp/Downloads/rust-by-example/src/main.rs"}}', + \ ]) diff --git a/test/test_semver_utils.vader b/test/test_semver_utils.vader new file mode 100644 index 00000000..9730b74b --- /dev/null +++ b/test/test_semver_utils.vader @@ -0,0 +1,16 @@ +Execute(ParseSemver should return the correct results): + " We should be able to parse the semver string from flake8 + AssertEqual [3, 0, 4], ale#semver#Parse('3.0.4 (mccabe: 0.5.2, pyflakes: 1.2.3, pycodestyle: 2.0.0) CPython 2.7.12 on Linux') + +Execute(GreaterOrEqual should compare triples correctly): + Assert ale#semver#GreaterOrEqual([3, 0, 4], [3, 0, 0]) + Assert ale#semver#GreaterOrEqual([3, 0, 0], [3, 0, 0]) + Assert ale#semver#GreaterOrEqual([3, 0, 0], [2, 0, 0]) + Assert ale#semver#GreaterOrEqual([3, 1, 0], [3, 1, 0]) + Assert ale#semver#GreaterOrEqual([3, 2, 0], [3, 1, 0]) + Assert ale#semver#GreaterOrEqual([3, 2, 2], [3, 1, 6]) + Assert ale#semver#GreaterOrEqual([3, 2, 5], [3, 2, 5]) + Assert ale#semver#GreaterOrEqual([3, 2, 6], [3, 2, 5]) + Assert !ale#semver#GreaterOrEqual([2, 9, 1], [3, 0, 0]) + Assert !ale#semver#GreaterOrEqual([3, 2, 3], [3, 3, 3]) + Assert !ale#semver#GreaterOrEqual([3, 3, 2], [3, 3, 3]) diff --git a/test/test_sign_placement.vader b/test/test_sign_placement.vader new file mode 100644 index 00000000..dbec27ef --- /dev/null +++ b/test/test_sign_placement.vader @@ -0,0 +1,68 @@ +Before: + function! GenerateResults(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'foo', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'bar', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'baz', + \ }, + \] + endfunction + + call ale#linter#Define('testft', { + \ 'name': 'x', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'callback': 'GenerateResults', + \}) + +After: + call ale#linter#Reset() + delfunction GenerateResults + unlet! g:output + +Given testft(A Javscript file with warnings/errors): + foo + bar + baz + +Execute: + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + redir => g:output + :sign place + redir END + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEErrorSign'], + \ ['2', '1000002', 'ALEWarningSign'], + \ ['3', '1000003', 'ALEErrorSign'], + \ ], + \ map( + \ split(g:output, '\n')[2:], + \ 'matchlist(v:val, "[^=]*=\\(\\d\\+\\)[^=]*=\\(\\d\\+\\).*\\(ALE.*\\)$")[1:3]' + \ ) |