diff options
177 files changed, 4679 insertions, 1091 deletions
diff --git a/.gitattributes b/.gitattributes index 4da669b7..27cbcff5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,6 +6,7 @@ /Makefile export-ignore /PULL_REQUEST_TEMPLATE.md export-ignore /README.md export-ignore +/check-supported-tools-tables export-ignore /custom-checks export-ignore /img export-ignore /run-tests export-ignore diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index d51bdecd..94116532 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,9 @@ READ THIS: Before creating a pull request, please consider the following first. * The most important thing you can do is write tests. Code without tests - probably doesn't work, and will almost certainly stop working later on. + probably doesn't work, and will almost certainly stop working later on. Pull + requests without tests probably won't be accepted, although there are some + exceptions. * Read the Contributing guide linked above first. * If you are adding a new linter, remember to update the README.md file and doc/ale.txt first. @@ -15,13 +15,17 @@ back to a filesystem. In other words, this plugin allows you to lint while you type. -ALE also supports fixing problems with files by running commands in the -background with a command `ALEFix`. +In addition to linting support, ALE offers some support for fixing code with +formatting tools, and completion via Language Server Protocol servers, or +servers with similar enough protocols, like `tsserver`. ## Table of Contents 1. [Supported Languages and Tools](#supported-languages) 2. [Usage](#usage) + 1. [Linting](#usage-linting) + 2. [Fixing](#usage-fixing) + 3. [Completion](#usage-completion) 3. [Installation](#installation) 1. [Installation with Vim package management](#standard-installation) 2. [Installation with Pathogen](#installation-with-pathogen) @@ -51,65 +55,75 @@ tools will be run in combination, so they can be complementary. Keep the table rows sorted alphabetically by the language name, and the tools in the tools column sorted alphabetically by the tool name. That seems to be the fairest way to arrange this table. + +Remember to also update doc/ale.txt, which has a similar list with different +formatting. --> +**Notes:** + +* *^ No linters for text or Vim help filetypes are enabled by default.* +* *!! These linters check only files on disk. See `:help ale-lint-file-linters`* + | Language | Tools | | -------- | ----- | | ASM | [gcc](https://gcc.gnu.org) | | Ansible | [ansible-lint](https://github.com/willthames/ansible-lint) | -| AsciiDoc | [proselint](http://proselint.com/)| +| AsciiDoc | [proselint](http://proselint.com/) | | Awk | [gawk](https://www.gnu.org/software/gawk/)| -| Bash | [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/) | -| Bourne Shell | [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/) | -| C | [cppcheck](http://cppcheck.sourceforge.net), [gcc](https://gcc.gnu.org/), [clang](http://clang.llvm.org/), [clangtidy](http://clang.llvm.org/extra/clang-tidy/)| -| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html), [clangtidy](http://clang.llvm.org/extra/clang-tidy/), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [gcc](https://gcc.gnu.org/)| +| Bash | shell [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/) | +| Bourne Shell | shell [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/) | +| C | [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [gcc](https://gcc.gnu.org/), [clang](http://clang.llvm.org/), [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html)| +| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) !!, [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) !!, [gcc](https://gcc.gnu.org/), [clang-format](https://clang.llvm.org/docs/ClangFormat.html)| | C# | [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) | | Chef | [foodcritic](http://www.foodcritic.io/) | | CMake | [cmakelint](https://github.com/richq/cmake-lint) | | CoffeeScript | [coffee](http://coffeescript.org/), [coffeelint](https://www.npmjs.com/package/coffeelint) | -| Crystal | [crystal](https://crystal-lang.org/) | +| Crystal | [crystal](https://crystal-lang.org/) !! | | 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) | | Dart | [dartanalyzer](https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli) | | Dockerfile | [hadolint](https://github.com/lukasmartinelli/hadolint) | -| Elixir | [credo](https://github.com/rrrene/credo), [dogma](https://github.com/lpil/dogma) | +| Elixir | [credo](https://github.com/rrrene/credo), [dogma](https://github.com/lpil/dogma) !! | | Elm | [elm-make](https://github.com/elm-lang/elm-make) | -| Erb | [erb](https://github.com/jeremyevans/erubi) | +| Erb | [erb](https://github.com/jeremyevans/erubi), [erubis](https://github.com/kwatch/erubis) | | Erlang | [erlc](http://erlang.org/doc/man/erlc.html), [SyntaxErl](https://github.com/ten0s/syntaxerl) | | Fortran | [gcc](https://gcc.gnu.org/) | | FusionScript | [fusion-lint](https://github.com/RyanSquared/fusionscript) | -| Go | [gofmt -e](https://golang.org/cmd/gofmt/), [go vet](https://golang.org/cmd/vet/), [golint](https://godoc.org/github.com/golang/lint), [gometalinter](https://github.com/alecthomas/gometalinter), [go build](https://golang.org/cmd/go/), [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple), [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) | +| Go | [gofmt](https://golang.org/cmd/gofmt/), [go vet](https://golang.org/cmd/vet/), [golint](https://godoc.org/github.com/golang/lint), [gometalinter](https://github.com/alecthomas/gometalinter) !!, [go build](https://golang.org/cmd/go/) !!, [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple), [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) | +| GraphQL | [gqlint](https://github.com/happylinks/gqlint) | | Haml | [haml-lint](https://github.com/brigade/haml-lint) | Handlebars | [ember-template-lint](https://github.com/rwjblue/ember-template-lint) | -| Haskell | [ghc](https://www.haskell.org/ghc/), [stack-ghc](https://haskellstack.org/), [stack-build](https://haskellstack.org/), [ghc-mod](https://github.com/DanielG/ghc-mod), [stack-ghc-mod](https://github.com/DanielG/ghc-mod), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools) | +| Haskell | [ghc](https://www.haskell.org/ghc/), [stack-ghc](https://haskellstack.org/), [stack-build](https://haskellstack.org/) !!, [ghc-mod](https://github.com/DanielG/ghc-mod), [stack-ghc-mod](https://github.com/DanielG/ghc-mod), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools) | | HTML | [HTMLHint](http://htmlhint.com/), [proselint](http://proselint.com/), [tidy](http://www.html-tidy.org/) | +| Idris | [idris](http://www.idris-lang.org/) | | Java | [checkstyle](http://checkstyle.sourceforge.net), [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html) | -| JavaScript | [eslint](http://eslint.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [flow](https://flowtype.org/), [standard](http://standardjs.com/), [prettier](https://github.com/prettier/prettier) (and `prettier-eslint`, `prettier-standard`), [xo](https://github.com/sindresorhus/xo) +| JavaScript | [eslint](http://eslint.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [flow](https://flowtype.org/), [prettier](https://github.com/prettier/prettier), prettier-eslint, prettier-standard, [standard](http://standardjs.com/), [xo](https://github.com/sindresorhus/xo) | JSON | [jsonlint](http://zaa.ch/jsonlint/) | -| Kotlin | [kotlinc](https://kotlinlang.org), [ktlint](https://ktlint.github.io) see `:help ale-integration-kotlin` for configuration instructions +| Kotlin | [kotlinc](https://kotlinlang.org) !!, [ktlint](https://ktlint.github.io) !! see `:help ale-integration-kotlin` for configuration instructions | LaTeX | [chktex](http://www.nongnu.org/chktex/), [lacheck](https://www.ctan.org/pkg/lacheck), [proselint](http://proselint.com/) | | Lua | [luacheck](https://github.com/mpeterv/luacheck) | | Markdown | [mdl](https://github.com/mivok/markdownlint), [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale) | | MATLAB | [mlint](https://www.mathworks.com/help/matlab/ref/mlint.html) | -| Nim | [nim](https://nim-lang.org/docs/nimc.html) | +| Nim | [nim check](https://nim-lang.org/docs/nimc.html) !! | | nix | [nix-instantiate](http://nixos.org/nix/manual/#sec-nix-instantiate) | | nroff | [proselint](http://proselint.com/)| | Objective-C | [clang](http://clang.llvm.org/) | | Objective-C++ | [clang](http://clang.llvm.org/) | | 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), [phpmd](https://phpmd.org), [phpstan](https://github.com/phpstan/phpstan) | +| PHP | [hack](http://hacklang.org/), [langserver](https://github.com/felixfbecker/php-language-server), [php -l](https://secure.php.net/), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer), [phpmd](https://phpmd.org), [phpstan](https://github.com/phpstan/phpstan), [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer) | | Pod | [proselint](http://proselint.com/)| | Pug | [pug-lint](https://github.com/pugjs/pug-lint) | | Puppet | [puppet](https://puppet.com), [puppet-lint](https://puppet-lint.com) | -| Python | [autopep8](https://github.com/hhatto/autopep8), [flake8](http://flake8.pycqa.org/en/latest/), [isort](https://github.com/timothycrosley/isort), [mypy](http://mypy-lang.org/), [pylint](https://www.pylint.org/), [yapf](https://github.com/google/yapf) | +| Python | [autopep8](https://github.com/hhatto/autopep8), [flake8](http://flake8.pycqa.org/en/latest/), [isort](https://github.com/timothycrosley/isort), [mypy](http://mypy-lang.org/), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pylint](https://www.pylint.org/) !!, [yapf](https://github.com/google/yapf) | | R | [lintr](https://github.com/jimhester/lintr) | | ReasonML | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-integration-reason-merlin` for configuration instructions -| reStructuredText | [proselint](http://proselint.com/)| +| reStructuredText | [proselint](http://proselint.com/) | | RPM spec | [rpmlint](https://github.com/rpm-software-management/rpmlint) (disabled by default; see `:help ale-integration-spec`) | -| Ruby | [brakeman](http://brakemanscanner.org/), [rails_best_practices](https://github.com/flyerhzm/rails_best_practices), [reek](https://github.com/troessner/reek), [rubocop](https://github.com/bbatsov/rubocop), [ruby](https://www.ruby-lang.org) | -| Rust | [rustc](https://www.rust-lang.org/), cargo (see `:help ale-integration-rust` for configuration instructions) | +| Ruby | [brakeman](http://brakemanscanner.org/) !!, [rails_best_practices](https://github.com/flyerhzm/rails_best_practices) !!, [reek](https://github.com/troessner/reek), [rubocop](https://github.com/bbatsov/rubocop), [ruby](https://www.ruby-lang.org) | +| Rust | cargo !! (see `:help ale-integration-rust` for configuration instructions), [rls](https://github.com/rust-lang-nursery/rls), [rustc](https://www.rust-lang.org/) | | SASS | [sass-lint](https://www.npmjs.com/package/sass-lint), [stylelint](https://github.com/stylelint/stylelint) | | SCSS | [sass-lint](https://www.npmjs.com/package/sass-lint), [scss-lint](https://github.com/brigade/scss-lint), [stylelint](https://github.com/stylelint/stylelint) | | Scala | [scalac](http://scala-lang.org), [scalastyle](http://www.scalastyle.org) | @@ -117,10 +131,11 @@ name. That seems to be the fairest way to arrange this table. | SML | [smlnj](http://www.smlnj.org/) | | Stylus | [stylelint](https://github.com/stylelint/stylelint) | | SQL | [sqlint](https://github.com/purcell/sqlint) | -| Swift | [swiftlint](https://swift.org/) | -| Tcl | [nagelfar](http://nagelfar.sourceforge.net)| +| Swift | [swiftlint](https://github.com/realm/SwiftLint), [swiftformat](https://github.com/nicklockwood/SwiftFormat) | +| Tcl | [nagelfar](http://nagelfar.sourceforge.net) !! | | Texinfo | [proselint](http://proselint.com/)| | Text^ | [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale) | +| Thrift | [thrift](http://thrift.apache.org/) | | TypeScript | [eslint](http://eslint.org/), [tslint](https://github.com/palantir/tslint), tsserver, typecheck | | Verilog | [iverilog](https://github.com/steveicarus/iverilog), [verilator](http://www.veripool.org/projects/verilator/wiki/Intro) | | Vim | [vint](https://github.com/Kuniwak/vint) | @@ -129,12 +144,14 @@ name. That seems to be the fairest way to arrange this table. | XML | [xmllint](http://xmlsoft.org/xmllint.html/)| | YAML | [swaglint](https://github.com/byCedric/swaglint), [yamllint](https://yamllint.readthedocs.io/) | -* *^ No linters for text or Vim help filetypes are enabled by default.* - <a name="usage"></a> ## 2. Usage +<a name="usage-linting"></a> + +### 2.i Linting + Once this plugin is installed, while editing your files in supported languages and tools which have been correctly installed, this plugin will send the contents of your text buffers to a variety of @@ -147,8 +164,46 @@ documented in [the Vim help file](doc/ale.txt). For more information on the options ALE offers, consult `:help ale-options` for global options and `:help ale-linter-options` for options specified to particular linters. +<a name="usage-fixing"></a> + +### 2.ii Fixing + ALE can fix files with the `ALEFix` command. Functions need to be configured -for different filetypes with the `g:ale_fixers` variable. See `:help ale-fix`. +for different filetypes with the `g:ale_fixers` variable. For example, the +following code can be used to fix JavaScript code with ESLint: + +```vim +" Put this in vimrc or a plugin file of your own. +" After this is configured, :ALEFix will try and fix your JS code with ESLint. +let g:ale_fixers = { +\ 'javascript': ['eslint'], +\} + +" Set this setting in vimrc if you want to fix files automatically on save. +" This is off by default. +let g:ale_fix_on_save = 1 +``` + +The `:ALEFixSuggest` command will suggest some supported tools for fixing code, +but fixers can be also implemented with functions, including lambda functions +too. See `:help ale-fix` for detailed information. + +<a name="usage-completion"></a> + +### 2.iii Completion + +ALE offers some support for completion via hijacking of omnicompletion while you +type. All of ALE's completion information must come from Language Server +Protocol linters, or similar protocols. At the moment, completion is only +supported for TypeScript code with `tsserver`, when `tsserver` is enabled. You +can enable completion like so: + +```vim +" Enable completion where available. +let g:ale_completion_enabled = 1 +``` + +See `:help ale-completion` for more information. <a name="installation"></a> @@ -189,7 +244,7 @@ mkdir -p ~/vimfiles/pack/git-plugins/start git clone https://github.com/w0rp/ale.git ~/vimfiles/pack/git-plugins/start/ale ``` -#### Generating documentation +#### Generating Vim help files You can add the following line to your vimrc files to generate documentation tags automatically, if you don't have something similar already, so you can use diff --git a/ale_linters/ansible/ansible_lint.vim b/ale_linters/ansible/ansible_lint.vim index 192e65b9..7d68cde3 100644 --- a/ale_linters/ansible/ansible_lint.vim +++ b/ale_linters/ansible/ansible_lint.vim @@ -21,8 +21,8 @@ function! ale_linters#ansible#ansible_lint#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:code = l:match[4] - if (l:code ==# 'EANSIBLE002') - \ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + if l:code is# 'EANSIBLE002' + \&& !ale#Var(a:buffer, 'warn_about_trailing_whitespace') " Skip warnings for trailing whitespace if the option is off. continue endif @@ -32,7 +32,7 @@ function! ale_linters#ansible#ansible_lint#Handle(buffer, lines) abort \ 'lnum': l:match[2] + 0, \ 'col': l:match[3] + 0, \ 'text': l:code . ': ' . l:match[5], - \ 'type': l:code[:0] ==# 'E' ? 'E' : 'W', + \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', \}) endif endfor diff --git a/ale_linters/coffee/coffeelint.vim b/ale_linters/coffee/coffeelint.vim index 9db3399c..6d3df353 100644 --- a/ale_linters/coffee/coffeelint.vim +++ b/ale_linters/coffee/coffeelint.vim @@ -27,7 +27,7 @@ function! ale_linters#coffee#coffeelint#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { \ 'lnum': str2nr(l:match[1]), - \ 'type': l:match[3] ==# 'error' ? 'E' : 'W', + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', \ 'text': l:match[4], \}) endfor diff --git a/ale_linters/d/dmd.vim b/ale_linters/d/dmd.vim index bd3f9d55..b91238ae 100644 --- a/ale_linters/d/dmd.vim +++ b/ale_linters/d/dmd.vim @@ -60,7 +60,7 @@ function! ale_linters#d#dmd#Handle(buffer, lines) abort call add(l:output, { \ 'lnum': l:match[1], \ 'col': l:match[2], - \ 'type': l:match[3] ==# 'Warning' ? 'W' : 'E', + \ 'type': l:match[3] is# 'Warning' ? 'W' : 'E', \ 'text': l:match[4], \}) endfor diff --git a/ale_linters/dart/dartanalyzer.vim b/ale_linters/dart/dartanalyzer.vim index 8f440571..f7b82c4b 100644 --- a/ale_linters/dart/dartanalyzer.vim +++ b/ale_linters/dart/dartanalyzer.vim @@ -22,7 +22,7 @@ function! ale_linters#dart#dartanalyzer#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'type': l:match[1] ==# 'error' ? 'E' : 'W', + \ 'type': l:match[1] is# 'error' ? 'E' : 'W', \ 'text': l:match[6] . ': ' . l:match[2], \ 'lnum': str2nr(l:match[4]), \ 'col': str2nr(l:match[5]), diff --git a/ale_linters/dockerfile/hadolint.vim b/ale_linters/dockerfile/hadolint.vim index 4063bf1b..5550d698 100644 --- a/ale_linters/dockerfile/hadolint.vim +++ b/ale_linters/dockerfile/hadolint.vim @@ -14,7 +14,7 @@ function! ale_linters#dockerfile#hadolint#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:lnum = 0 - if l:match[1] !=# '' + if l:match[1] isnot# '' let l:lnum = l:match[1] + 0 endif @@ -45,9 +45,9 @@ function! ale_linters#dockerfile#hadolint#GetExecutable(buffer) abort let l:use_docker = ale#Var(a:buffer, 'dockerfile_hadolint_use_docker') " check for mandatory directives - if l:use_docker ==# 'never' + if l:use_docker is# 'never' return 'hadolint' - elseif l:use_docker ==# 'always' + elseif l:use_docker is# 'always' return 'docker' endif @@ -62,7 +62,7 @@ endfunction function! ale_linters#dockerfile#hadolint#GetCommand(buffer) abort let l:command = ale_linters#dockerfile#hadolint#GetExecutable(a:buffer) - if l:command ==# 'docker' + if l:command is# 'docker' return 'docker run --rm -i ' . ale#Var(a:buffer, 'dockerfile_hadolint_docker_image') endif return 'hadolint -' diff --git a/ale_linters/elixir/credo.vim b/ale_linters/elixir/credo.vim index 46f75457..3699dd23 100644 --- a/ale_linters/elixir/credo.vim +++ b/ale_linters/elixir/credo.vim @@ -11,9 +11,9 @@ function! ale_linters#elixir#credo#Handle(buffer, lines) abort let l:type = l:match[3] let l:text = l:match[4] - if l:type ==# 'C' + if l:type is# 'C' let l:type = 'E' - elseif l:type ==# 'R' + elseif l:type is# 'R' let l:type = 'W' endif diff --git a/ale_linters/elixir/dogma.vim b/ale_linters/elixir/dogma.vim index e3b24711..b4f32b04 100644 --- a/ale_linters/elixir/dogma.vim +++ b/ale_linters/elixir/dogma.vim @@ -11,9 +11,9 @@ function! ale_linters#elixir#dogma#Handle(buffer, lines) abort let l:type = l:match[3] let l:text = l:match[4] - if l:type ==# 'C' + if l:type is# 'C' let l:type = 'E' - elseif l:type ==# 'R' + elseif l:type is# 'R' let l:type = 'W' endif diff --git a/ale_linters/elm/make.vim b/ale_linters/elm/make.vim index 0a570ff6..04563a4d 100644 --- a/ale_linters/elm/make.vim +++ b/ale_linters/elm/make.vim @@ -7,16 +7,16 @@ function! ale_linters#elm#make#Handle(buffer, lines) abort let l:temp_dir = l:is_windows ? $TMP : $TMPDIR let l:unparsed_lines = [] for l:line in a:lines - if l:line[0] ==# '[' + if l:line[0] is# '[' let l:errors = json_decode(l:line) for l:error in l:errors " Check if file is from the temp directory. " Filters out any errors not related to the buffer. if l:is_windows - let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] ==? l:temp_dir + let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] is? l:temp_dir else - let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] ==# l:temp_dir + let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] is# l:temp_dir endif if l:file_is_buffer @@ -25,13 +25,13 @@ function! ale_linters#elm#make#Handle(buffer, lines) abort \ 'col': l:error.region.start.column, \ 'end_lnum': l:error.region.end.line, \ 'end_col': l:error.region.end.column, - \ 'type': (l:error.type ==? 'error') ? 'E' : 'W', + \ 'type': (l:error.type is? 'error') ? 'E' : 'W', \ 'text': l:error.overview, \ 'detail': l:error.overview . "\n\n" . l:error.details \}) endif endfor - elseif l:line !=# 'Successfully generated /dev/null' + elseif l:line isnot# 'Successfully generated /dev/null' call add(l:unparsed_lines, l:line) endif endfor diff --git a/ale_linters/erlang/erlc.vim b/ale_linters/erlang/erlc.vim index 162c7ec3..559dc679 100644 --- a/ale_linters/erlang/erlc.vim +++ b/ale_linters/erlang/erlc.vim @@ -27,7 +27,7 @@ function! ale_linters#erlang#erlc#Handle(buffer, lines) abort let l:pattern_no_module_definition = '\v(no module definition)$' let l:pattern_unused = '\v(.* is unused)$' - let l:is_hrl = fnamemodify(bufname(a:buffer), ':e') ==# 'hrl' + let l:is_hrl = fnamemodify(bufname(a:buffer), ':e') is# 'hrl' for l:line in a:lines let l:match = matchlist(l:line, l:pattern) diff --git a/ale_linters/erlang/syntaxerl.vim b/ale_linters/erlang/syntaxerl.vim index dd53d8e8..46ecdcb7 100644 --- a/ale_linters/erlang/syntaxerl.vim +++ b/ale_linters/erlang/syntaxerl.vim @@ -9,8 +9,15 @@ function! ale_linters#erlang#syntaxerl#GetExecutable(buffer) abort endfunction -function! ale_linters#erlang#syntaxerl#GetCommand(buffer) abort - return ale_linters#erlang#syntaxerl#GetExecutable(a:buffer) . ' %t' +function! ale_linters#erlang#syntaxerl#FeatureCheck(buffer) abort + return s:GetEscapedExecutable(a:buffer) . ' -h' +endfunction + + +function! ale_linters#erlang#syntaxerl#GetCommand(buffer, output) abort + let l:use_b_option = match(a:output, '\C\V-b, --base\>') > -1 + + return s:GetEscapedExecutable(a:buffer) . (l:use_b_option ? ' -b %s %t' : ' %t') endfunction @@ -30,9 +37,17 @@ function! ale_linters#erlang#syntaxerl#Handle(buffer, lines) abort endfunction +function! s:GetEscapedExecutable(buffer) abort + return ale#Escape(ale_linters#erlang#syntaxerl#GetExecutable(a:buffer)) +endfunction + + call ale#linter#Define('erlang', { \ 'name': 'syntaxerl', \ 'executable_callback': 'ale_linters#erlang#syntaxerl#GetExecutable', -\ 'command_callback': 'ale_linters#erlang#syntaxerl#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale_linters#erlang#syntaxerl#FeatureCheck'}, +\ {'callback': 'ale_linters#erlang#syntaxerl#GetCommand'}, +\ ], \ 'callback': 'ale_linters#erlang#syntaxerl#Handle', \}) diff --git a/ale_linters/eruby/erubis.vim b/ale_linters/eruby/erubis.vim new file mode 100644 index 00000000..be9332df --- /dev/null +++ b/ale_linters/eruby/erubis.vim @@ -0,0 +1,11 @@ +" Author: Jake Zimmerman <jake@zimmerman.io> +" Description: eruby checker using `erubis`, instead of `erb` + +call ale#linter#Define('eruby', { +\ 'name': 'erubis', +\ 'executable': 'erubis', +\ 'output_stream': 'stderr', +\ 'command': 'erubis -x %t | ruby -c', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) + diff --git a/ale_linters/fortran/gcc.vim b/ale_linters/fortran/gcc.vim index a59c6564..5f2ac018 100644 --- a/ale_linters/fortran/gcc.vim +++ b/ale_linters/fortran/gcc.vim @@ -44,7 +44,7 @@ function! ale_linters#fortran#gcc#Handle(buffer, lines) abort " Now we have the text, we can set it and add the error. let l:last_loclist_obj.text = l:match[2] - let l:last_loclist_obj.type = l:match[1] ==# 'Warning' ? 'W' : 'E' + let l:last_loclist_obj.type = l:match[1] is# 'Warning' ? 'W' : 'E' call add(l:output, l:last_loclist_obj) else let l:last_loclist_obj = { diff --git a/ale_linters/go/gometalinter.vim b/ale_linters/go/gometalinter.vim index 6d10871d..f1abfc83 100644 --- a/ale_linters/go/gometalinter.vim +++ b/ale_linters/go/gometalinter.vim @@ -32,7 +32,7 @@ function! ale_linters#go#gometalinter#Handler(buffer, lines) abort call add(l:output, { \ 'lnum': l:match[2] + 0, \ 'col': l:match[3] + 0, - \ 'type': tolower(l:match[4]) ==# 'warning' ? 'W' : 'E', + \ 'type': tolower(l:match[4]) is# 'warning' ? 'W' : 'E', \ 'text': l:match[5], \}) endfor diff --git a/ale_linters/graphql/gqlint.vim b/ale_linters/graphql/gqlint.vim new file mode 100644 index 00000000..882cc697 --- /dev/null +++ b/ale_linters/graphql/gqlint.vim @@ -0,0 +1,9 @@ +" Author: Michiel Westerbeek <happylinks@gmail.com> +" Description: Linter for GraphQL Schemas + +call ale#linter#Define('graphql', { +\ 'name': 'gqlint', +\ 'executable': 'gqlint', +\ 'command': 'gqlint --reporter=simple %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/haskell/ghc-mod.vim b/ale_linters/haskell/ghc-mod.vim index d3d23649..1b15d8c2 100644 --- a/ale_linters/haskell/ghc-mod.vim +++ b/ale_linters/haskell/ghc-mod.vim @@ -4,13 +4,13 @@ call ale#linter#Define('haskell', { \ 'name': 'ghc-mod', \ 'executable': 'ghc-mod', -\ 'command': 'ghc-mod check %t', +\ 'command': 'ghc-mod --map-file %s=%t check %s', \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) call ale#linter#Define('haskell', { \ 'name': 'stack-ghc-mod', \ 'executable': 'stack', -\ 'command': 'stack exec ghc-mod check %t', +\ 'command': 'stack exec ghc-mod -- --map-file %s=%t check %s', \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) diff --git a/ale_linters/haskell/hlint.vim b/ale_linters/haskell/hlint.vim index 91e65123..be40d92c 100644 --- a/ale_linters/haskell/hlint.vim +++ b/ale_linters/haskell/hlint.vim @@ -5,9 +5,9 @@ function! ale_linters#haskell#hlint#Handle(buffer, lines) abort let l:output = [] for l:error in ale#util#FuzzyJSONDecode(a:lines, []) - if l:error.severity ==# 'Error' + if l:error.severity is# 'Error' let l:type = 'E' - elseif l:error.severity ==# 'Suggestion' + elseif l:error.severity is# 'Suggestion' let l:type = 'I' else let l:type = 'W' diff --git a/ale_linters/html/tidy.vim b/ale_linters/html/tidy.vim index c9fdbc7c..4a55d62f 100644 --- a/ale_linters/html/tidy.vim +++ b/ale_linters/html/tidy.vim @@ -46,7 +46,7 @@ function! ale_linters#html#tidy#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[1] + 0 let l:col = l:match[2] + 0 - let l:type = l:match[3] ==# 'Error' ? 'E' : 'W' + let l:type = l:match[3] is# 'Error' ? 'E' : 'W' let l:text = l:match[4] call add(l:output, { diff --git a/ale_linters/idris/idris.vim b/ale_linters/idris/idris.vim new file mode 100644 index 00000000..115d04fc --- /dev/null +++ b/ale_linters/idris/idris.vim @@ -0,0 +1,87 @@ +" Author: Scott Bonds <scott@ggr.com> +" Description: default Idris compiler + +call ale#Set('idris_idris_executable', 'idris') +call ale#Set('idris_idris_options', '--total --warnpartial --warnreach --warnipkg') + +function! ale_linters#idris#idris#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'idris_idris_executable') +endfunction + +function! ale_linters#idris#idris#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'idris_idris_options') + + return ale#Escape(ale_linters#idris#idris#GetExecutable(a:buffer)) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --check %s' +endfunction + +function! ale_linters#idris#idris#Handle(buffer, lines) abort + " This was copied almost verbatim from ale#handlers#haskell#HandleGHCFormat + + " Look for lines like the following: + " foo.idr:2:6:When checking right hand side of main with expected type + " bar.idr:11:11-13: + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)(-\d+)?:(.*)?$' + let l:output = [] + + let l:corrected_lines = [] + + for l:line in a:lines + if len(matchlist(l:line, l:pattern)) > 0 + call add(l:corrected_lines, l:line) + elseif len(l:corrected_lines) > 0 + if l:line is# '' + let l:corrected_lines[-1] .= ' ' " turn a blank line into a space + else + let l:corrected_lines[-1] .= l:line + endif + let l:corrected_lines[-1] = substitute(l:corrected_lines[-1], '\s\+', ' ', 'g') + endif + endfor + + for l:line in l:corrected_lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + if !ale#path#IsBufferPath(a:buffer, l:match[1]) + continue + endif + + let l:errors = matchlist(l:match[5], '\v([wW]arning|[eE]rror) - ?(.*)') + + if len(l:errors) > 0 + let l:ghc_type = l:errors[1] + let l:text = l:errors[2] + else + let l:ghc_type = '' + let l:text = l:match[5][:0] is# ' ' ? l:match[5][1:] : l:match[5] + endif + + if l:ghc_type is? 'Warning' + let l:type = 'W' + else + let l:type = 'E' + endif + + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('idris', { +\ 'name': 'idris', +\ 'executable_callback': 'ale_linters#idris#idris#GetExecutable', +\ 'command_callback': 'ale_linters#idris#idris#GetCommand', +\ 'callback': 'ale_linters#idris#idris#Handle', +\}) + diff --git a/ale_linters/java/javac.vim b/ale_linters/java/javac.vim index 97530882..d83da182 100644 --- a/ale_linters/java/javac.vim +++ b/ale_linters/java/javac.vim @@ -14,6 +14,11 @@ function! ale_linters#java#javac#GetImportPaths(buffer) abort \ . 'mvn dependency:build-classpath' endif + let l:classpath_command = ale#gradle#BuildClasspathCommand(a:buffer) + if !empty(l:classpath_command) + return l:classpath_command + endif + return '' endfunction @@ -68,14 +73,14 @@ function! ale_linters#java#javac#Handle(buffer, lines) abort let l:output[-1].col = len(l:match[1]) elseif empty(l:match[3]) " Add symbols to 'cannot find symbol' errors. - if l:output[-1].text ==# 'error: cannot find symbol' + if l:output[-1].text is# 'error: cannot find symbol' let l:output[-1].text .= ': ' . l:match[2] endif else call add(l:output, { \ 'lnum': l:match[1] + 0, \ 'text': l:match[2] . ':' . l:match[3], - \ 'type': l:match[2] ==# 'error' ? 'E' : 'W', + \ 'type': l:match[2] is# 'error' ? 'E' : 'W', \}) endif endfor diff --git a/ale_linters/javascript/flow.vim b/ale_linters/javascript/flow.vim index f3e5deff..0dd64535 100644 --- a/ale_linters/javascript/flow.vim +++ b/ale_linters/javascript/flow.vim @@ -46,7 +46,7 @@ function! s:GetJSONLines(lines) abort let l:start_index = 0 for l:line in a:lines - if l:line[:0] ==# '{' + if l:line[:0] is# '{' break endif @@ -77,13 +77,13 @@ function! ale_linters#javascript#flow#Handle(buffer, lines) abort " In certain cases, `l:message.loc.source` points to a different path " than the buffer one, thus we skip this loc information too. if has_key(l:message, 'loc') - \&& l:line ==# 0 + \&& l:line is# 0 \&& ale#path#IsBufferPath(a:buffer, l:message.loc.source) let l:line = l:message.loc.start.line + 0 let l:col = l:message.loc.start.column + 0 endif - if l:text ==# '' + if l:text is# '' let l:text = l:message.descr . ':' else let l:text = l:text . ' ' . l:message.descr @@ -98,7 +98,7 @@ function! ale_linters#javascript#flow#Handle(buffer, lines) abort \ 'lnum': l:line, \ 'col': l:col, \ 'text': l:text, - \ 'type': l:error.level ==# 'error' ? 'E' : 'W', + \ 'type': l:error.level is# 'error' ? 'E' : 'W', \}) endfor diff --git a/ale_linters/javascript/standard.vim b/ale_linters/javascript/standard.vim index 15e6d78f..fc534ebb 100644 --- a/ale_linters/javascript/standard.vim +++ b/ale_linters/javascript/standard.vim @@ -7,13 +7,25 @@ call ale#Set('javascript_standard_options', '') function! ale_linters#javascript#standard#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_standard', [ + \ 'node_modules/standard/bin/cmd.js', \ 'node_modules/.bin/standard', \]) endfunction function! ale_linters#javascript#standard#GetCommand(buffer) abort - return ale#Escape(ale_linters#javascript#standard#GetExecutable(a:buffer)) - \ . ' ' . ale#Var(a:buffer, 'javascript_standard_options') + let l:executable = ale_linters#javascript#standard#GetExecutable(a:buffer) + + if ale#Has('win32') && l:executable =~? '\.js$' + " .js files have to be executed with Node on Windows. + let l:head = 'node ' . ale#Escape(l:executable) + else + let l:head = ale#Escape(l:executable) + endif + + let l:options = ale#Var(a:buffer, 'javascript_standard_options') + + return l:head + \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --stdin %s' endfunction diff --git a/ale_linters/kotlin/kotlinc.vim b/ale_linters/kotlin/kotlinc.vim index ebaf4456..00f94be5 100644 --- a/ale_linters/kotlin/kotlinc.vim +++ b/ale_linters/kotlin/kotlinc.vim @@ -13,7 +13,7 @@ let s:classpath_sep = has('unix') ? ':' : ';' function! ale_linters#kotlin#kotlinc#GetImportPaths(buffer) abort " exec maven/gradle only if classpath is not set - if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') !=# '' + if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') isnot# '' return '' else let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') @@ -70,7 +70,7 @@ function! ale_linters#kotlin#kotlinc#GetCommand(buffer, import_paths) abort endif " We only get here if not using module or the module file not readable - if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') !=# '' + if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') isnot# '' let l:kotlinc_opts .= ' -cp ' . ale#Var(a:buffer, 'kotlin_kotlinc_classpath') else " get classpath from maven/gradle @@ -78,7 +78,7 @@ function! ale_linters#kotlin#kotlinc#GetCommand(buffer, import_paths) abort endif let l:fname = '' - if ale#Var(a:buffer, 'kotlin_kotlinc_sourcepath') !=# '' + if ale#Var(a:buffer, 'kotlin_kotlinc_sourcepath') isnot# '' let l:fname .= expand(ale#Var(a:buffer, 'kotlin_kotlinc_sourcepath'), 1) . ' ' else " Find the src directory for files in this project. @@ -121,10 +121,10 @@ function! ale_linters#kotlin#kotlinc#Handle(buffer, lines) abort let l:curbuf_abspath = expand('#' . a:buffer . ':p') " Skip if file is not loaded - if l:buf_abspath !=# l:curbuf_abspath + if l:buf_abspath isnot# l:curbuf_abspath continue endif - let l:type_marker_str = l:type ==# 'warning' ? 'W' : 'E' + let l:type_marker_str = l:type is# 'warning' ? 'W' : 'E' call add(l:output, { \ 'lnum': l:line, @@ -145,7 +145,7 @@ function! ale_linters#kotlin#kotlinc#Handle(buffer, lines) abort let l:type = l:match[1] let l:text = l:match[2] - let l:type_marker_str = l:type ==# 'warning' || l:type ==# 'info' ? 'W' : 'E' + let l:type_marker_str = l:type is# 'warning' || l:type is# 'info' ? 'W' : 'E' call add(l:output, { \ 'lnum': 1, diff --git a/ale_linters/matlab/mlint.vim b/ale_linters/matlab/mlint.vim index 851e398a..32766334 100644 --- a/ale_linters/matlab/mlint.vim +++ b/ale_linters/matlab/mlint.vim @@ -30,7 +30,7 @@ function! ale_linters#matlab#mlint#Handle(buffer, lines) abort " Suppress erroneous waring about filename " TODO: Enable this error when copying filename is supported - if l:code ==# 'FNDEF' + if l:code is# 'FNDEF' continue endif diff --git a/ale_linters/nim/nimcheck.vim b/ale_linters/nim/nimcheck.vim index 19e67303..cdd8c564 100644 --- a/ale_linters/nim/nimcheck.vim +++ b/ale_linters/nim/nimcheck.vim @@ -12,7 +12,7 @@ function! ale_linters#nim#nimcheck#Handle(buffer, lines) abort " module names. let l:temp_buffer_filename = fnamemodify(l:match[1], ':p:t') - if l:buffer_filename !=# '' && l:temp_buffer_filename !=# l:buffer_filename + if l:buffer_filename isnot# '' && l:temp_buffer_filename isnot# l:buffer_filename continue endif @@ -26,7 +26,7 @@ function! ale_linters#nim#nimcheck#Handle(buffer, lines) abort if len(l:textmatch) > 0 let l:errortype = l:textmatch[1] - if l:errortype ==# 'Error' + if l:errortype is# 'Error' let l:type = 'E' endif endif diff --git a/ale_linters/perl/perl.vim b/ale_linters/perl/perl.vim index 087d03eb..33288061 100644 --- a/ale_linters/perl/perl.vim +++ b/ale_linters/perl/perl.vim @@ -34,7 +34,7 @@ function! ale_linters#perl#perl#Handle(buffer, lines) abort if ale#path#IsBufferPath(a:buffer, l:match[2]) \ && ( - \ l:text !=# 'BEGIN failed--compilation aborted' + \ l:text isnot# 'BEGIN failed--compilation aborted' \ || empty(l:output) \ || match(l:output[-1].text, s:begin_failed_skip_pattern) < 0 \ ) diff --git a/ale_linters/perl/perlcritic.vim b/ale_linters/perl/perlcritic.vim index a9e8f117..df2f8b23 100644 --- a/ale_linters/perl/perlcritic.vim +++ b/ale_linters/perl/perlcritic.vim @@ -21,7 +21,7 @@ function! ale_linters#perl#perlcritic#GetProfile(buffer) abort " first see if we've been overridden let l:profile = ale#Var(a:buffer, 'perl_perlcritic_profile') - if l:profile ==? '' + if l:profile is? '' return '' endif @@ -41,10 +41,10 @@ function! ale_linters#perl#perlcritic#GetCommand(buffer) abort let l:command = ale#Escape(ale_linters#perl#perlcritic#GetExecutable(a:buffer)) \ . " --verbose '". l:critic_verbosity . "' --nocolor" - if l:profile !=? '' + if l:profile isnot? '' let l:command .= ' --profile ' . ale#Escape(l:profile) endif - if l:options !=? '' + if l:options isnot? '' let l:command .= ' ' . l:options endif diff --git a/ale_linters/php/langserver.vim b/ale_linters/php/langserver.vim index d772e90d..be2d6ef1 100644 --- a/ale_linters/php/langserver.vim +++ b/ale_linters/php/langserver.vim @@ -2,7 +2,6 @@ " Description: PHP Language server integration for ALE call ale#Set('php_langserver_executable', 'php-language-server.php') -call ale#Set('php_langserver_config_path', '') call ale#Set('php_langserver_use_global', 0) function! ale_linters#php#langserver#GetExecutable(buffer) abort diff --git a/ale_linters/php/phpcs.vim b/ale_linters/php/phpcs.vim index a8eae4e6..ddaf171e 100644 --- a/ale_linters/php/phpcs.vim +++ b/ale_linters/php/phpcs.vim @@ -40,7 +40,7 @@ function! ale_linters#php#phpcs#Handle(buffer, lines) abort \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 0, \ 'text': l:text, - \ 'type': l:type ==# 'error' ? 'E' : 'W', + \ 'type': l:type is# 'error' ? 'E' : 'W', \}) endfor diff --git a/ale_linters/php/phpstan.vim b/ale_linters/php/phpstan.vim index 69173ef4..b99e4f58 100644 --- a/ale_linters/php/phpstan.vim +++ b/ale_linters/php/phpstan.vim @@ -5,8 +5,14 @@ let g:ale_php_phpstan_executable = get(g:, 'ale_php_phpstan_executable', 'phpstan') let g:ale_php_phpstan_level = get(g:, 'ale_php_phpstan_level', '4') -function! ale_linters#php#phpstan#GetCommand(buffer) abort +function! ale_linters#php#phpstan#GetExecutable(buffer) abort return ale#Var(a:buffer, 'php_phpstan_executable') +endfunction + +function! ale_linters#php#phpstan#GetCommand(buffer) abort + let l:executable = ale_linters#php#phpstan#GetExecutable(a:buffer) + + return ale#Escape(l:executable) \ . ' analyze -l' \ . ale#Var(a:buffer, 'php_phpstan_level') \ . ' --errorFormat raw' @@ -34,7 +40,7 @@ endfunction call ale#linter#Define('php', { \ 'name': 'phpstan', -\ 'executable': 'phpstan', +\ 'executable_callback': 'ale_linters#php#phpstan#GetExecutable', \ 'command_callback': 'ale_linters#php#phpstan#GetCommand', \ 'callback': 'ale_linters#php#phpstan#Handle', \}) diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index bf7b3027..8aa4c4df 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -83,6 +83,7 @@ function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort return ale#Escape(ale_linters#python#flake8#GetExecutable(a:buffer)) \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --format=default' \ . l:display_name_args . ' -' endfunction @@ -115,7 +116,7 @@ function! ale_linters#python#flake8#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:code = l:match[3] - if (l:code ==# 'W291' || l:code ==# 'W293') + if (l:code is# 'W291' || l:code is# 'W293') \ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') " Skip warnings for trailing whitespace if the option is off. continue @@ -128,12 +129,12 @@ function! ale_linters#python#flake8#Handle(buffer, lines) abort \ 'type': 'W', \} - if l:code[:0] ==# 'F' || l:code ==# 'E999' + if l:code[:0] is# 'F' || l:code is# 'E999' let l:item.type = 'E' - elseif l:code[:0] ==# 'E' + elseif l:code[:0] is# 'E' let l:item.type = 'E' let l:item.sub_type = 'style' - elseif l:code[:0] ==# 'W' + elseif l:code[:0] is# 'W' let l:item.sub_type = 'style' endif diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim index f71365a6..6884a9ac 100644 --- a/ale_linters/python/mypy.vim +++ b/ale_linters/python/mypy.vim @@ -10,14 +10,22 @@ function! ale_linters#python#mypy#GetExecutable(buffer) abort return ale#python#FindExecutable(a:buffer, 'python_mypy', ['mypy']) endfunction -function! ale_linters#python#mypy#GetCommand(buffer) abort +" The directory to change to before running mypy +function! s:GetDir(buffer) abort let l:project_root = ale#python#FindProjectRoot(a:buffer) - let l:cd_command = !empty(l:project_root) - \ ? ale#path#CdString(l:project_root) - \ : '' + + return !empty(l:project_root) + \ ? l:project_root + \ : expand('#' . a:buffer . ':p:h') +endfunction + +function! ale_linters#python#mypy#GetCommand(buffer) abort + let l:dir = s:GetDir(a:buffer) let l:executable = ale_linters#python#mypy#GetExecutable(a:buffer) - return l:cd_command + " We have to always switch to an explicit directory for a command so + " we can know with certainty the base path for the 'filename' keys below. + return ale#path#CdString(l:dir) \ . ale#Escape(l:executable) \ . ' --show-column-numbers ' \ . ale#Var(a:buffer, 'python_mypy_options') @@ -25,6 +33,7 @@ function! ale_linters#python#mypy#GetCommand(buffer) abort endfunction function! ale_linters#python#mypy#Handle(buffer, lines) abort + let l:dir = s:GetDir(a:buffer) " Look for lines like the following: " " file.py:4: error: No library stub file for module 'django.db' @@ -34,17 +43,13 @@ function! ale_linters#python#mypy#Handle(buffer, lines) abort " file.py:4: note: (Stub files are from https://github.com/python/typeshed) let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?: (error|warning): (.+)$' let l:output = [] - let l:buffer_filename = expand('#' . a:buffer . ':p') for l:match in ale#util#GetMatches(a:lines, l:pattern) - if l:buffer_filename[-len(l:match[1]):] !=# l:match[1] - continue - endif - call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), \ 'lnum': l:match[2] + 0, \ 'col': l:match[3] + 0, - \ 'type': l:match[4] =~# 'error' ? 'E' : 'W', + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', \ 'text': l:match[5], \}) endfor diff --git a/ale_linters/python/pycodestyle.vim b/ale_linters/python/pycodestyle.vim new file mode 100644 index 00000000..ad895999 --- /dev/null +++ b/ale_linters/python/pycodestyle.vim @@ -0,0 +1,42 @@ +" Author: Michael Thiesen <micthiesen@gmail.com> +" Description: pycodestyle linting for python files + +call ale#Set('python_pycodestyle_executable', 'pycodestyle') +call ale#Set('python_pycodestyle_options', '') +call ale#Set('python_pycodestyle_use_global', 0) + +function! ale_linters#python#pycodestyle#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'python_pycodestyle', ['pycodestyle']) +endfunction + +function! ale_linters#python#pycodestyle#GetCommand(buffer) abort + return ale#Escape(ale_linters#python#pycodestyle#GetExecutable(a:buffer)) + \ . ' ' + \ . ale#Var(a:buffer, 'python_pycodestyle_options') + \ . ' -' +endfunction + +function! ale_linters#python#pycodestyle#Handle(buffer, lines) abort + let l:pattern = '\v^(\S*):(\d*):(\d*): ((([EW])\d+) .*)$' + let l:output = [] + + " lines are formatted as follows: + " file.py:21:26: W291 trailing whitespace + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:match[6], + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pycodestyle', +\ 'executable_callback': 'ale_linters#python#pycodestyle#GetExecutable', +\ 'command_callback': 'ale_linters#python#pycodestyle#GetCommand', +\ 'callback': 'ale_linters#python#pycodestyle#Handle', +\}) diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim index 6f95776b..befc51a5 100644 --- a/ale_linters/python/pylint.vim +++ b/ale_linters/python/pylint.vim @@ -31,13 +31,13 @@ function! ale_linters#python#pylint#Handle(buffer, lines) abort "let l:failed = append(0, l:match) let l:code = l:match[3] - if (l:code ==# 'C0303') + if (l:code is# 'C0303') \ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') " Skip warnings for trailing whitespace if the option is off. continue endif - if l:code ==# 'I0011' + if l:code is# 'I0011' " Skip 'Locally disabling' message continue endif @@ -46,7 +46,7 @@ function! ale_linters#python#pylint#Handle(buffer, lines) abort \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 1, \ 'text': l:code . ': ' . l:match[5] . ' (' . l:match[4] . ')', - \ 'type': l:code[:0] ==# 'E' ? 'E' : 'W', + \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', \}) endfor diff --git a/ale_linters/ruby/brakeman.vim b/ale_linters/ruby/brakeman.vim index 0070c15c..790eb563 100644 --- a/ale_linters/ruby/brakeman.vim +++ b/ale_linters/ruby/brakeman.vim @@ -33,7 +33,7 @@ endfunction function! ale_linters#ruby#brakeman#GetCommand(buffer) abort let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) - if l:rails_root ==? '' + if l:rails_root is? '' return '' endif diff --git a/ale_linters/ruby/rails_best_practices.vim b/ale_linters/ruby/rails_best_practices.vim index 503a115c..107753c3 100644 --- a/ale_linters/ruby/rails_best_practices.vim +++ b/ale_linters/ruby/rails_best_practices.vim @@ -30,7 +30,7 @@ function! ale_linters#ruby#rails_best_practices#GetCommand(buffer) abort let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) - if l:rails_root ==? '' + if l:rails_root is? '' return '' endif diff --git a/ale_linters/ruby/rubocop.vim b/ale_linters/ruby/rubocop.vim index 3d7b64d1..2a4388f2 100644 --- a/ale_linters/ruby/rubocop.vim +++ b/ale_linters/ruby/rubocop.vim @@ -43,9 +43,9 @@ function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort endfunction function! ale_linters#ruby#rubocop#GetType(severity) abort - if a:severity ==? 'convention' - \|| a:severity ==? 'warning' - \|| a:severity ==? 'refactor' + if a:severity is? 'convention' + \|| a:severity is? 'warning' + \|| a:severity is? 'refactor' return 'W' endif diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim index 32c09a58..f19061ad 100644 --- a/ale_linters/rust/cargo.vim +++ b/ale_linters/rust/cargo.vim @@ -4,7 +4,7 @@ let g:ale_rust_cargo_use_check = get(g:, 'ale_rust_cargo_use_check', 0) function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort - if ale#path#FindNearestFile(a:bufnr, 'Cargo.toml') !=# '' + if ale#path#FindNearestFile(a:bufnr, 'Cargo.toml') isnot# '' return 'cargo' else " if there is no Cargo.toml file, we don't use cargo even if it exists, diff --git a/ale_linters/rust/rustc.vim b/ale_linters/rust/rustc.vim index 73c99cd9..e792faa7 100644 --- a/ale_linters/rust/rustc.vim +++ b/ale_linters/rust/rustc.vim @@ -7,7 +7,7 @@ function! ale_linters#rust#rustc#RustcCommand(buffer_number) abort " <project root>/target/release/deps/ let l:cargo_file = ale#path#FindNearestFile(a:buffer_number, 'Cargo.toml') - if l:cargo_file !=# '' + if l:cargo_file isnot# '' let l:project_root = fnamemodify(l:cargo_file, ':h') let l:dependencies = '-L ' . l:project_root . '/target/debug/deps -L ' . \ l:project_root . '/target/release/deps' diff --git a/ale_linters/scala/scalac.vim b/ale_linters/scala/scalac.vim index 9262e625..584aee74 100644 --- a/ale_linters/scala/scalac.vim +++ b/ale_linters/scala/scalac.vim @@ -38,7 +38,7 @@ function! ale_linters#scala#scalac#Handle(buffer, lines) abort endif let l:text = l:match[3] - let l:type = l:match[2] ==# 'error' ? 'E' : 'W' + let l:type = l:match[2] is# 'error' ? 'E' : 'W' let l:col = 0 if l:ln + 1 < len(a:lines) diff --git a/ale_linters/scss/scsslint.vim b/ale_linters/scss/scsslint.vim index 2331ac16..7ce57241 100644 --- a/ale_linters/scss/scsslint.vim +++ b/ale_linters/scss/scsslint.vim @@ -19,7 +19,7 @@ function! ale_linters#scss#scsslint#Handle(buffer, lines) abort \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 0, \ 'text': l:match[4], - \ 'type': l:match[3] ==# 'E' ? 'E' : 'W', + \ 'type': l:match[3] is# 'E' ? 'E' : 'W', \}) endfor diff --git a/ale_linters/sh/shell.vim b/ale_linters/sh/shell.vim index 1539e8b3..cf5e4e6c 100644 --- a/ale_linters/sh/shell.vim +++ b/ale_linters/sh/shell.vim @@ -11,24 +11,16 @@ endif if !exists('g:ale_sh_shell_default_shell') let g:ale_sh_shell_default_shell = fnamemodify($SHELL, ':t') - if g:ale_sh_shell_default_shell ==# '' || g:ale_sh_shell_default_shell ==# 'fish' + if g:ale_sh_shell_default_shell is# '' || g:ale_sh_shell_default_shell is# 'fish' let g:ale_sh_shell_default_shell = 'bash' endif endif function! ale_linters#sh#shell#GetExecutable(buffer) abort - let l:banglines = getbufline(a:buffer, 1) + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) - " Take the shell executable from the hashbang, if we can. - if len(l:banglines) == 1 && l:banglines[0] =~# '^#!' - " Remove options like -e, etc. - let l:line = substitute(l:banglines[0], '--\?[a-zA-Z0-9]\+', '', 'g') - - for l:possible_shell in ['bash', 'tcsh', 'csh', 'zsh', 'sh'] - if l:line =~# l:possible_shell . '\s*$' - return l:possible_shell - endif - endfor + if !empty(l:shell_type) + return l:shell_type endif return ale#Var(a:buffer, 'sh_shell_default_shell') diff --git a/ale_linters/sh/shellcheck.vim b/ale_linters/sh/shellcheck.vim index 5353683d..3a2d33f2 100644 --- a/ale_linters/sh/shellcheck.vim +++ b/ale_linters/sh/shellcheck.vim @@ -19,25 +19,35 @@ function! ale_linters#sh#shellcheck#GetExecutable(buffer) abort return ale#Var(a:buffer, 'sh_shellcheck_executable') endfunction -function! s:GetDialectArgument() abort - if exists('b:is_bash') && b:is_bash - return '-s bash' - elseif exists('b:is_sh') && b:is_sh - return '-s sh' - elseif exists('b:is_kornshell') && b:is_kornshell - return '-s ksh' +function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) + + if !empty(l:shell_type) + return l:shell_type + endif + + " If there's no hashbang, try using Vim's buffer variables. + if get(b:, 'is_bash') + return 'bash' + elseif get(b:, 'is_sh') + return 'sh' + elseif get(b:, 'is_kornshell') + return 'ksh' endif return '' endfunction function! ale_linters#sh#shellcheck#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'sh_shellcheck_options') let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions') + let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer) return ale_linters#sh#shellcheck#GetExecutable(a:buffer) - \ . ' ' . ale#Var(a:buffer, 'sh_shellcheck_options') - \ . ' ' . (!empty(l:exclude_option) ? '-e ' . l:exclude_option : '') - \ . ' ' . s:GetDialectArgument() . ' -f gcc -' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '') + \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '') + \ . ' -f gcc -' endfunction call ale#linter#Define('sh', { diff --git a/ale_linters/sml/smlnj.vim b/ale_linters/sml/smlnj.vim index fda1d038..4acfc9e6 100644 --- a/ale_linters/sml/smlnj.vim +++ b/ale_linters/sml/smlnj.vim @@ -29,7 +29,7 @@ function! ale_linters#sml#smlnj#Handle(buffer, lines) abort \ 'bufnr': a:buffer, \ 'lnum': l:match[1] + 0, \ 'text': l:match[2] . ': ' . l:match[3], - \ 'type': l:match[2] ==# 'error' ? 'E' : 'W', + \ 'type': l:match[2] is# 'error' ? 'E' : 'W', \}) continue endif diff --git a/ale_linters/tcl/nagelfar.vim b/ale_linters/tcl/nagelfar.vim index 8b29f9ee..13b7a549 100644 --- a/ale_linters/tcl/nagelfar.vim +++ b/ale_linters/tcl/nagelfar.vim @@ -28,7 +28,7 @@ function! ale_linters#tcl#nagelfar#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { \ 'lnum': l:match[1] + 0, - \ 'type': l:match[2] ==# 'N' ? 'W' : l:match[2], + \ 'type': l:match[2] is# 'N' ? 'W' : l:match[2], \ 'text': l:match[3], \}) endfor diff --git a/ale_linters/thrift/thrift.vim b/ale_linters/thrift/thrift.vim new file mode 100644 index 00000000..2f62570a --- /dev/null +++ b/ale_linters/thrift/thrift.vim @@ -0,0 +1,91 @@ +" Author: Jon Parise <jon@indelible.org> + +call ale#Set('thrift_thrift_executable', 'thrift') +call ale#Set('thrift_thrift_generators', ['cpp']) +call ale#Set('thrift_thrift_includes', []) +call ale#Set('thrift_thrift_options', '-strict') + +function! ale_linters#thrift#thrift#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'thrift_thrift_executable') +endfunction + +function! ale_linters#thrift#thrift#GetCommand(buffer) abort + let l:generators = ale#Var(a:buffer, 'thrift_thrift_generators') + let l:includes = ale#Var(a:buffer, 'thrift_thrift_includes') + + " The thrift compiler requires at least one generator. If none are set, + " fall back to our default value to avoid silently failing. We could also + " `throw` here, but that seems even less helpful. + if empty(l:generators) + let l:generators = ['cpp'] + endif + + let l:output_dir = tempname() + call mkdir(l:output_dir) + call ale#engine#ManageDirectory(a:buffer, l:output_dir) + + return ale#Escape(ale_linters#thrift#thrift#GetExecutable(a:buffer)) + \ . ' ' . join(map(copy(l:generators), "'--gen ' . v:val")) + \ . ' ' . join(map(copy(l:includes), "'-I ' . v:val")) + \ . ' ' . ale#Var(a:buffer, 'thrift_thrift_options') + \ . ' -out ' . ale#Escape(l:output_dir) + \ . ' %t' +endfunction + +function! ale_linters#thrift#thrift#Handle(buffer, lines) abort + " Matches lines like the following: + " + " [SEVERITY:/path/filename.thrift:31] Message text + " [ERROR:/path/filename.thrift:31] (last token was ';') + let l:pattern = '\v^\[(\u+):(.*):(\d+)\] (.*)$' + + let l:index = 0 + let l:output = [] + + " Roll our own output-matching loop instead of using ale#util#GetMatches + " because we need to support error messages that span multiple lines. + while l:index < len(a:lines) + let l:line = a:lines[l:index] + + let l:match = matchlist(l:line, l:pattern) + if empty(l:match) + let l:index += 1 + continue + endif + + let l:severity = l:match[1] + if l:severity is# 'WARNING' + let l:type = 'W' + else + let l:type = 'E' + endif + + " If our text looks like "(last token was ';')", the *next* line + " should contain a more descriptive error message. + let l:text = l:match[4] + if l:text =~# '\(last token was .*\)' + let l:index += 1 + let l:text = get(a:lines, l:index, 'Unknown error ' . l:text) + endif + + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + + let l:index += 1 + endwhile + + return l:output +endfunction + +call ale#linter#Define('thrift', { +\ 'name': 'thrift', +\ 'executable': 'thrift', +\ 'output_stream': 'both', +\ 'executable_callback': 'ale_linters#thrift#thrift#GetExecutable', +\ 'command_callback': 'ale_linters#thrift#thrift#GetCommand', +\ 'callback': 'ale_linters#thrift#thrift#Handle', +\}) diff --git a/ale_linters/typescript/tslint.vim b/ale_linters/typescript/tslint.vim index 7cd1cb4b..18f9e08c 100644 --- a/ale_linters/typescript/tslint.vim +++ b/ale_linters/typescript/tslint.vim @@ -1,8 +1,9 @@ -" Author: Prashanth Chandra https://github.com/prashcr +" Author: Prashanth Chandra <https://github.com/prashcr>, Jonathan Clem <https://jclem.net> " Description: tslint for TypeScript files call ale#Set('typescript_tslint_executable', 'tslint') call ale#Set('typescript_tslint_config_path', '') +call ale#Set('typescript_tslint_rules_dir', '') call ale#Set('typescript_tslint_use_global', 0) function! ale_linters#typescript#tslint#GetExecutable(buffer) abort @@ -12,44 +13,52 @@ function! ale_linters#typescript#tslint#GetExecutable(buffer) abort endfunction function! ale_linters#typescript#tslint#Handle(buffer, lines) abort + let l:dir = expand('#' . a:buffer . ':p:h') let l:output = [] for l:error in ale#util#FuzzyJSONDecode(a:lines, []) - if ale#path#IsBufferPath(a:buffer, l:error.name) - call add(l:output, { - \ 'type': (get(l:error, 'ruleSeverity', '') ==# 'WARNING' ? 'W' : 'E'), - \ 'text': l:error.failure, - \ 'lnum': l:error.startPosition.line + 1, - \ 'col': l:error.startPosition.character + 1, - \ 'end_lnum': l:error.endPosition.line + 1, - \ 'end_col': l:error.endPosition.character + 1, - \}) - endif + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:error.name), + \ 'type': (get(l:error, 'ruleSeverity', '') is# 'WARNING' ? 'W' : 'E'), + \ 'text': has_key(l:error, 'ruleName') + \ ? l:error.ruleName . ': ' . l:error.failure + \ : l:error.failure, + \ 'lnum': l:error.startPosition.line + 1, + \ 'col': l:error.startPosition.character + 1, + \ 'end_lnum': l:error.endPosition.line + 1, + \ 'end_col': l:error.endPosition.character + 1, + \}) endfor return l:output endfunction -function! ale_linters#typescript#tslint#BuildLintCommand(buffer) abort +function! ale_linters#typescript#tslint#GetCommand(buffer) abort let l:tslint_config_path = ale#path#ResolveLocalPath( \ a:buffer, \ 'tslint.json', \ ale#Var(a:buffer, 'typescript_tslint_config_path') \) - let l:tslint_config_option = !empty(l:tslint_config_path) \ ? ' -c ' . ale#Escape(l:tslint_config_path) \ : '' - return ale_linters#typescript#tslint#GetExecutable(a:buffer) + let l:tslint_rules_dir = ale#Var(a:buffer, 'typescript_tslint_rules_dir') + let l:tslint_rules_option = !empty(l:tslint_rules_dir) + \ ? ' -r ' . ale#Escape(l:tslint_rules_dir) + \ : '' + + return ale#path#BufferCdString(a:buffer) + \ . ale_linters#typescript#tslint#GetExecutable(a:buffer) \ . ' --format json' \ . l:tslint_config_option + \ . l:tslint_rules_option \ . ' %t' endfunction call ale#linter#Define('typescript', { \ 'name': 'tslint', \ 'executable_callback': 'ale_linters#typescript#tslint#GetExecutable', -\ 'command_callback': 'ale_linters#typescript#tslint#BuildLintCommand', +\ 'command_callback': 'ale_linters#typescript#tslint#GetCommand', \ 'callback': 'ale_linters#typescript#tslint#Handle', \}) diff --git a/ale_linters/verilog/iverilog.vim b/ale_linters/verilog/iverilog.vim index 0f4cd7b3..18769d56 100644 --- a/ale_linters/verilog/iverilog.vim +++ b/ale_linters/verilog/iverilog.vim @@ -14,7 +14,7 @@ function! ale_linters#verilog#iverilog#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[1] + 0 let l:type = l:match[2] =~# 'error' ? 'E' : 'W' - let l:text = l:match[2] ==# 'syntax error' ? 'syntax error' : l:match[4] + let l:text = l:match[2] is# 'syntax error' ? 'syntax error' : l:match[4] call add(l:output, { \ 'lnum': l:line, diff --git a/ale_linters/verilog/verilator.vim b/ale_linters/verilog/verilator.vim index aa5e7047..6053da09 100644 --- a/ale_linters/verilog/verilator.vim +++ b/ale_linters/verilog/verilator.vim @@ -11,7 +11,8 @@ function! ale_linters#verilog#verilator#GetCommand(buffer) abort " Create a special filename, so we can detect it in the handler. call ale#engine#ManageFile(a:buffer, l:filename) - call writefile(getbufline(a:buffer, 1, '$'), l:filename) + let l:lines = getbufline(a:buffer, 1, '$') + call ale#util#Writefile(a:buffer, l:lines, l:filename) return 'verilator --lint-only -Wall -Wno-DECLFILENAME ' \ . ale#Var(a:buffer, 'verilog_verilator_options') .' ' @@ -32,7 +33,7 @@ function! ale_linters#verilog#verilator#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[3] + 0 - let l:type = l:match[1] ==# 'Error' ? 'E' : 'W' + let l:type = l:match[1] is# 'Error' ? 'E' : 'W' let l:text = l:match[4] let l:file = l:match[2] diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim index 18ae2e4c..adf2b4ab 100644 --- a/ale_linters/vim/vint.vim +++ b/ale_linters/vim/vint.vim @@ -36,6 +36,32 @@ function! ale_linters#vim#vint#GetCommand(buffer, version_output) abort \ . ' %t' endfunction +let s:word_regex_list = [ +\ '\v^Undefined variable: ([^ ]+)', +\ '\v^Make the scope explicit like ...([^ ]+). ', +\ '\v^.*start with a capital or contain a colon: ([^ ]+)', +\ '\v.*instead of .(\=[=~]).', +\] + +function! ale_linters#vim#vint#Handle(buffer, lines) abort + let l:loclist = ale#handlers#gcc#HandleGCCFormat(a:buffer, a:lines) + + for l:item in l:loclist + let l:match = [] + + for l:regex in s:word_regex_list + let l:match = matchlist(l:item.text, l:regex) + + if !empty(l:match) + let l:item.end_col = l:item.col + len(l:match[1]) - 1 + break + endif + endfor + endfor + + return l:loclist +endfunction + call ale#linter#Define('vim', { \ 'name': 'vint', \ 'executable': 'vint', @@ -43,5 +69,5 @@ call ale#linter#Define('vim', { \ {'callback': 'ale_linters#vim#vint#VersionCommand', 'output_stream': 'stderr'}, \ {'callback': 'ale_linters#vim#vint#GetCommand', 'output_stream': 'stdout'}, \ ], -\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\ 'callback': 'ale_linters#vim#vint#Handle', \}) diff --git a/ale_linters/yaml/swaglint.vim b/ale_linters/yaml/swaglint.vim index 1368d7b7..454cad05 100644 --- a/ale_linters/yaml/swaglint.vim +++ b/ale_linters/yaml/swaglint.vim @@ -21,7 +21,7 @@ function! ale_linters#yaml#swaglint#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:obj = { - \ 'type': l:match[1] ==# 'error' ? 'E' : 'W', + \ 'type': l:match[1] is# 'error' ? 'E' : 'W', \ 'lnum': l:match[2] + 0, \ 'col': l:match[3] + 0, \ 'text': l:match[4], diff --git a/ale_linters/yaml/yamllint.vim b/ale_linters/yaml/yamllint.vim index a0eb2a0a..731f8012 100644 --- a/ale_linters/yaml/yamllint.vim +++ b/ale_linters/yaml/yamllint.vim @@ -33,7 +33,7 @@ function! ale_linters#yaml#yamllint#Handle(buffer, lines) abort \ 'lnum': l:line, \ 'col': l:col, \ 'text': l:text, - \ 'type': l:type ==# 'error' ? 'E' : 'W', + \ 'type': l:type is# 'error' ? 'E' : 'W', \}) endfor diff --git a/autoload/ale.vim b/autoload/ale.vim index aba3fda1..9defbd82 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -5,6 +5,31 @@ let s:lint_timer = -1 let s:queued_buffer_number = -1 let s:should_lint_file_for_buffer = {} +let s:error_delay_ms = 1000 * 60 * 2 + +let s:timestamp_map = {} + +" Given a key for a script variable for tracking the time to wait until +" a given function should be called, a funcref for a function to call, and +" a List of arguments, call the function and return whatever value it returns. +" +" If the function throws an exception, then the function will not be called +" for a while, and 0 will be returned instead. +function! ale#CallWithCooldown(timestamp_key, func, arglist) abort + let l:now = ale#util#ClockMilliseconds() + + if l:now < get(s:timestamp_map, a:timestamp_key, -1) + return 0 + endif + + let s:timestamp_map[a:timestamp_key] = l:now + s:error_delay_ms + + let l:return_value = call(a:func, a:arglist) + + let s:timestamp_map[a:timestamp_key] = -1 + + return l:return_value +endfunction " Return 1 if a file is too large for ALE to handle. function! ale#FileTooLarge() abort @@ -33,24 +58,31 @@ function! ale#Queue(delay, ...) abort " Default linting_flag to '' let l:linting_flag = get(a:000, 0, '') + let l:buffer = get(a:000, 1, bufnr('')) + + return ale#CallWithCooldown( + \ 'dont_queue_until', + \ function('s:ALEQueueImpl'), + \ [a:delay, l:linting_flag, l:buffer], + \) +endfunction - if l:linting_flag !=# '' && l:linting_flag !=# 'lint_file' +function! s:ALEQueueImpl(delay, linting_flag, buffer) abort + if a:linting_flag isnot# '' && a:linting_flag isnot# 'lint_file' throw "linting_flag must be either '' or 'lint_file'" endif - let l:buffer = get(a:000, 1, bufnr('')) - - if type(l:buffer) != type(0) + if type(a:buffer) != type(0) throw 'buffer_number must be a Number' endif - if ale#ShouldDoNothing(l:buffer) + if ale#ShouldDoNothing(a:buffer) return endif " Remember that we want to check files for this buffer. " We will remember this until we finally run the linters, via any event. - if l:linting_flag ==# 'lint_file' + if a:linting_flag is# 'lint_file' let s:should_lint_file_for_buffer[bufnr('%')] = 1 endif @@ -59,24 +91,24 @@ function! ale#Queue(delay, ...) abort let s:lint_timer = -1 endif - let l:linters = ale#linter#Get(getbufvar(l:buffer, '&filetype')) + let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype')) " Don't set up buffer data and so on if there are no linters to run. if empty(l:linters) " If we have some previous buffer data, then stop any jobs currently " running and clear everything. - if has_key(g:ale_buffer_info, l:buffer) - call ale#engine#RunLinters(l:buffer, [], 1) + if has_key(g:ale_buffer_info, a:buffer) + call ale#engine#RunLinters(a:buffer, [], 1) endif return endif if a:delay > 0 - let s:queued_buffer_number = l:buffer + let s:queued_buffer_number = a:buffer let s:lint_timer = timer_start(a:delay, function('ale#Lint')) else - call ale#Lint(-1, l:buffer) + call ale#Lint(-1, a:buffer) endif endfunction @@ -92,22 +124,30 @@ function! ale#Lint(...) abort let l:buffer = bufnr('') endif - if ale#ShouldDoNothing(l:buffer) + return ale#CallWithCooldown( + \ 'dont_lint_until', + \ function('s:ALELintImpl'), + \ [l:buffer], + \) +endfunction + +function! s:ALELintImpl(buffer) abort + if ale#ShouldDoNothing(a:buffer) return endif " Use the filetype from the buffer - let l:linters = ale#linter#Get(getbufvar(l:buffer, '&filetype')) + let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype')) let l:should_lint_file = 0 " Check if we previously requested checking the file. - if has_key(s:should_lint_file_for_buffer, l:buffer) - unlet s:should_lint_file_for_buffer[l:buffer] + if has_key(s:should_lint_file_for_buffer, a:buffer) + unlet s:should_lint_file_for_buffer[a:buffer] " Lint files if they exist. - let l:should_lint_file = filereadable(expand('#' . l:buffer . ':p')) + let l:should_lint_file = filereadable(expand('#' . a:buffer . ':p')) endif - call ale#engine#RunLinters(l:buffer, l:linters, l:should_lint_file) + call ale#engine#RunLinters(a:buffer, l:linters, l:should_lint_file) endfunction " Reset flags indicating that files should be checked for all buffers. @@ -115,6 +155,10 @@ function! ale#ResetLintFileMarkers() abort let s:should_lint_file_for_buffer = {} endfunction +function! ale#ResetErrorDelays() abort + let s:timestamp_map = {} +endfunction + let g:ale_has_override = get(g:, 'ale_has_override', {}) " Call has(), but check a global Dictionary so we can force flags on or off @@ -157,7 +201,7 @@ endfunction " Escape a string suitably for each platform. " shellescape does not work on Windows. function! ale#Escape(str) abort - if fnamemodify(&shell, ':t') ==? 'cmd.exe' + if fnamemodify(&shell, ':t') is? 'cmd.exe' " If the string contains spaces, it will be surrounded by quotes. " Otherwise, special characters will be escaped with carets (^). return substitute( diff --git a/autoload/ale/balloon.vim b/autoload/ale/balloon.vim index 3d179a0d..41fa95fa 100644 --- a/autoload/ale/balloon.vim +++ b/autoload/ale/balloon.vim @@ -3,7 +3,7 @@ function! ale#balloon#MessageForPos(bufnr, lnum, col) abort let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist - let l:index = ale#util#BinarySearch(l:loclist, a:lnum, a:col) + let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col) return l:index >= 0 ? l:loclist[l:index].text : '' endfunction diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim index 1703da78..b9f94399 100644 --- a/autoload/ale/c.vim +++ b/autoload/ale/c.vim @@ -9,7 +9,7 @@ function! ale#c#FindProjectRoot(buffer) abort let l:path = fnamemodify(l:full_path, ':h') " Correct .git path detection. - if fnamemodify(l:path, ':t') ==# '.git' + if fnamemodify(l:path, ':t') is# '.git' let l:path = fnamemodify(l:path, ':h') endif diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index eac6e6fc..9f4e3c28 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -39,12 +39,12 @@ let s:omni_start_map = { \ 'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$', \} -function! ale#completion#FilterSuggestionsByPrefix(suggestions, prefix) abort +function! ale#completion#Filter(suggestions, prefix) abort " For completing... " foo. " ^ " We need to include all of the given suggestions. - if a:prefix ==# '.' + if a:prefix is# '.' return a:suggestions endif @@ -53,20 +53,31 @@ function! ale#completion#FilterSuggestionsByPrefix(suggestions, prefix) abort " Filter suggestions down to those starting with the prefix we used for " finding suggestions in the first place. " - " Some completion tools will - " include suggestions which don't even start with the characters we have - " already typed. - for l:suggestion in a:suggestions + " Some completion tools will include suggestions which don't even start + " with the characters we have already typed. + for l:item in a:suggestions + " A List of String values or a List of completion item Dictionaries + " is accepted here. + let l:word = type(l:item) == type('') ? l:item : l:item.word + " Add suggestions if the suggestion starts with a case-insensitive " match for the prefix. - if l:suggestion.word[: len(a:prefix) - 1] ==? a:prefix - call add(l:filtered_suggestions, l:suggestion) + if l:word[: len(a:prefix) - 1] is? a:prefix + call add(l:filtered_suggestions, l:item) endif endfor return l:filtered_suggestions endfunction +function! s:ReplaceCompleteopt() abort + if !exists('b:ale_old_completopt') + let b:ale_old_completopt = &l:completeopt + endif + + let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' +endfunction + function! ale#completion#OmniFunc(findstart, base) abort if a:findstart let l:line = b:ale_completion_info.line @@ -86,14 +97,11 @@ function! ale#completion#OmniFunc(findstart, base) abort unlet b:ale_completion_response unlet b:ale_completion_parser - let l:prefix = b:ale_completion_info.prefix - - let b:ale_completion_result = ale#completion#FilterSuggestionsByPrefix( - \ function(l:parser)(l:response), - \ l:prefix - \)[: g:ale_completion_max_suggestions] + let b:ale_completion_result = function(l:parser)(l:response) endif + call s:ReplaceCompleteopt() + return get(b:, 'ale_completion_result', []) endif endfunction @@ -111,7 +119,8 @@ function! ale#completion#Show(response, completion_parser) abort let b:ale_completion_response = a:response let b:ale_completion_parser = a:completion_parser let &l:omnifunc = 'ale#completion#OmniFunc' - call feedkeys("\<C-x>\<C-o>", 'n') + call s:ReplaceCompleteopt() + call ale#util#FeedKeys("\<C-x>\<C-o>", 'n') endfunction function! s:CompletionStillValid(request_id) abort @@ -150,9 +159,9 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort call add(l:documentationParts, l:part.text) endfor - if l:suggestion.kind ==# 'clasName' + if l:suggestion.kind is# 'clasName' let l:kind = 'f' - elseif l:suggestion.kind ==# 'parameterName' + elseif l:suggestion.kind is# 'parameterName' let l:kind = 'f' else let l:kind = 'v' @@ -171,7 +180,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort return l:results endfunction -function! s:HandleTSServerLSPResponse(conn_id, response) abort +function! ale#completion#HandleTSServerLSPResponse(conn_id, response) abort if !s:CompletionStillValid(get(a:response, 'request_seq')) return endif @@ -182,8 +191,11 @@ function! s:HandleTSServerLSPResponse(conn_id, response) abort let l:command = get(a:response, 'command', '') - if l:command ==# 'completions' - let l:names = ale#completion#ParseTSServerCompletions(a:response) + if l:command is# 'completions' + let l:names = ale#completion#Filter( + \ ale#completion#ParseTSServerCompletions(a:response), + \ b:ale_completion_info.prefix, + \)[: g:ale_completion_max_suggestions - 1] if !empty(l:names) let b:ale_completion_info.request_id = ale#lsp#Send( @@ -196,7 +208,7 @@ function! s:HandleTSServerLSPResponse(conn_id, response) abort \ ), \) endif - elseif l:command ==# 'completionEntryDetails' + elseif l:command is# 'completionEntryDetails' call ale#completion#Show( \ a:response, \ 'ale#completion#ParseTSServerCompletionEntryDetails', @@ -209,7 +221,7 @@ function! s:GetLSPCompletions(linter) abort let l:lsp_details = ale#linter#StartLSP( \ l:buffer, \ a:linter, - \ function('s:HandleTSServerLSPResponse'), + \ function('ale#completion#HandleTSServerLSPResponse'), \) if empty(l:lsp_details) @@ -237,10 +249,6 @@ endfunction function! ale#completion#GetCompletions() abort let [l:line, l:column] = getcurpos()[1:2] - if s:timer_pos != [l:line, l:column] - return - endif - let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) if empty(l:prefix) @@ -256,7 +264,7 @@ function! ale#completion#GetCompletions() abort \} for l:linter in ale#linter#Get(&filetype) - if l:linter.lsp ==# 'tsserver' + if l:linter.lsp is# 'tsserver' call s:GetLSPCompletions(l:linter) endif endfor @@ -265,7 +273,13 @@ endfunction function! s:TimerHandler(...) abort let s:timer_id = -1 - call ale#completion#GetCompletions() + let [l:line, l:column] = getcurpos()[1:2] + + " When running the timer callback, we have to be sure that the cursor + " hasn't moved from where it was when we requested completions by typing. + if s:timer_pos == [l:line, l:column] + call ale#completion#GetCompletions() + endif endfunction function! ale#completion#Queue() abort @@ -292,6 +306,11 @@ function! ale#completion#Done() abort let &l:omnifunc = b:ale_old_omnifunc unlet b:ale_old_omnifunc endif + + if exists('b:ale_old_completopt') + let &l:completeopt = b:ale_old_completopt + unlet b:ale_old_completopt + endif endfunction function! s:Setup(enabled) abort diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim index 0c6a8634..340432f7 100644 --- a/autoload/ale/cursor.vim +++ b/autoload/ale/cursor.vim @@ -1,10 +1,22 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Echoes lint message for the current line, if any +let s:cursor_timer = -1 +let s:last_pos = [0, 0, 0] +let s:error_delay_ms = 1000 * 60 * 2 + +if !exists('s:dont_queue_until') + let s:dont_queue_until = -1 +endif + +if !exists('s:dont_echo_until') + let s:dont_echo_until = -1 +endif + " Return a formatted message according to g:ale_echo_msg_format variable function! s:GetMessage(linter, type, text) abort let l:msg = g:ale_echo_msg_format - let l:type = a:type ==# 'E' + let l:type = a:type is# 'E' \ ? g:ale_echo_msg_error_str \ : g:ale_echo_msg_warning_str @@ -22,12 +34,12 @@ function! s:EchoWithShortMess(setting, message) abort try " Turn shortmess on or off. - if a:setting ==# 'on' + if a:setting is# 'on' setlocal shortmess+=T " echomsg is needed for the message to get truncated and appear in " the message history. exec "norm! :echomsg a:message\n" - elseif a:setting ==# 'off' + elseif a:setting is# 'off' setlocal shortmess-=T " Regular echo is needed for printing newline characters. echo a:message @@ -50,10 +62,12 @@ function! ale#cursor#TruncatedEcho(message) abort endfunction function! s:FindItemAtCursor() abort - let l:info = get(g:ale_buffer_info, bufnr('%'), {'loclist': []}) + let l:buf = bufnr('') + let l:info = get(g:ale_buffer_info, l:buf, {}) + let l:loclist = get(l:info, 'loclist', []) let l:pos = getcurpos() - let l:index = ale#util#BinarySearch(l:info.loclist, l:pos[1], l:pos[2]) - let l:loc = l:index >= 0 ? l:info.loclist[l:index] : {} + let l:index = ale#util#BinarySearch(l:loclist, l:buf, l:pos[1], l:pos[2]) + let l:loc = l:index >= 0 ? l:loclist[l:index] : {} return [l:info, l:loc] endfunction @@ -66,12 +80,16 @@ function! s:StopCursorTimer() abort endfunction function! ale#cursor#EchoCursorWarning(...) abort + return ale#CallWithCooldown('dont_echo_until', function('s:EchoImpl'), []) +endfunction + +function! s:EchoImpl() abort if ale#ShouldDoNothing(bufnr('')) return endif " Only echo the warnings in normal mode, otherwise we will get problems. - if mode() !=# 'n' + if mode() isnot# 'n' return endif @@ -89,10 +107,15 @@ function! ale#cursor#EchoCursorWarning(...) abort endif endfunction -let s:cursor_timer = -1 -let s:last_pos = [0, 0, 0] - function! ale#cursor#EchoCursorWarningWithDelay() abort + return ale#CallWithCooldown( + \ 'dont_echo_with_delay_until', + \ function('s:EchoWithDelayImpl'), + \ [], + \) +endfunction + +function! s:EchoWithDelayImpl() abort if ale#ShouldDoNothing(bufnr('')) return endif @@ -117,7 +140,7 @@ function! ale#cursor#ShowCursorDetail() abort endif " Only echo the warnings in normal mode, otherwise we will get problems. - if mode() !=# 'n' + if mode() isnot# 'n' return endif diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index 8136fbee..7454bb1a 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -70,39 +70,53 @@ function! s:EchoGlobalVariables() abort endfor endfunction -function! s:EchoCommandHistory() abort - let l:buffer = bufnr('%') +" Echo a command that was run. +function! s:EchoCommand(item) abort + let l:status_message = a:item.status - if !has_key(g:ale_buffer_info, l:buffer) - return + " Include the exit code in output if we have it. + if a:item.status is# 'finished' + let l:status_message .= ' - exit code ' . a:item.exit_code endif - for l:item in g:ale_buffer_info[l:buffer].history - let l:status_message = l:item.status + echom '(' . l:status_message . ') ' . string(a:item.command) - " Include the exit code in output if we have it. - if l:item.status ==# 'finished' - let l:status_message .= ' - exit code ' . l:item.exit_code - endif + if g:ale_history_log_output && has_key(a:item, 'output') + if empty(a:item.output) + echom '' + echom '<<<NO OUTPUT RETURNED>>>' + echom '' + else + echom '' + echom '<<<OUTPUT STARTS>>>' - echom '(' . l:status_message . ') ' . string(l:item.command) + for l:line in a:item.output + echom l:line + endfor - if g:ale_history_log_output && has_key(l:item, 'output') - if empty(l:item.output) - echom '' - echom '<<<NO OUTPUT RETURNED>>>' - echom '' - else - echom '' - echom '<<<OUTPUT STARTS>>>' + echom '<<<OUTPUT ENDS>>>' + echom '' + endif + endif +endfunction - for l:line in l:item.output - echom l:line - endfor +" Echo the results of an executable check. +function! s:EchoExecutable(item) abort + echom printf( + \ '(executable check - %s) %s', + \ a:item.status ? 'success' : 'failure', + \ a:item.command, + \) +endfunction - echom '<<<OUTPUT ENDS>>>' - echom '' - endif +function! s:EchoCommandHistory() abort + let l:buffer = bufnr('%') + + for l:item in ale#history#Get(l:buffer) + if l:item.job_id is# 'executable' + call s:EchoExecutable(l:item) + else + call s:EchoCommand(l:item) endif endfor endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index a5a5f524..74ae0d9c 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -16,22 +16,34 @@ if !has_key(s:, 'lsp_linter_map') let s:lsp_linter_map = {} endif -let s:executable_cache_map = {} +if !has_key(s:, 'executable_cache_map') + let s:executable_cache_map = {} +endif + +function! ale#engine#ResetExecutableCache() abort + let s:executable_cache_map = {} +endfunction " Check if files are executable, and if they are, remember that they are " for subsequent calls. We'll keep checking until programs can be executed. -function! s:IsExecutable(executable) abort +function! ale#engine#IsExecutable(buffer, executable) abort if has_key(s:executable_cache_map, a:executable) return 1 endif + let l:result = 0 + if executable(a:executable) let s:executable_cache_map[a:executable] = 1 - return 1 + let l:result = 1 endif - return 0 + if g:ale_history_enabled + call ale#history#Add(a:buffer, l:result, 'executable', a:executable) + endif + + return l:result endfunction function! ale#engine#InitBufferInfo(buffer) abort @@ -41,16 +53,18 @@ function! ale#engine#InitBufferInfo(buffer) abort " loclist holds the loclist items after all jobs have completed. " temporary_file_list holds temporary files to be cleaned up " temporary_directory_list holds temporary directories to be cleaned up - " history holds a list of previously run commands for this buffer let g:ale_buffer_info[a:buffer] = { \ 'job_list': [], \ 'active_linter_list': [], \ 'loclist': [], \ 'temporary_file_list': [], \ 'temporary_directory_list': [], - \ 'history': [], \} + + return 1 endif + + return 0 endfunction " Return 1 if ALE is busy checking a given buffer @@ -83,7 +97,7 @@ function! ale#engine#CreateDirectory(buffer) abort endfunction function! ale#engine#RemoveManagedFiles(buffer) abort - let l:info = get(g:ale_buffer_info, a:buffer) + let l:info = get(g:ale_buffer_info, a:buffer, {}) " We can't delete anything in a sandbox, so wait until we escape from " it to delete temporary files and directories. @@ -128,14 +142,14 @@ function! s:HandleLoclist(linter_name, buffer, loclist) abort " Remove this linter from the list of active linters. " This may have already been done when the job exits. - call filter(l:buffer_info.active_linter_list, 'v:val !=# a:linter_name') + call filter(l:buffer_info.active_linter_list, 'v:val isnot# a:linter_name') " Make some adjustments to the loclists to fix common problems, and also " to set default values for loclist items. let l:linter_loclist = ale#engine#FixLocList(a:buffer, a:linter_name, a:loclist) " Remove previous items for this linter. - call filter(g:ale_buffer_info[a:buffer].loclist, 'v:val.linter_name !=# a:linter_name') + call filter(g:ale_buffer_info[a:buffer].loclist, 'v:val.linter_name isnot# a:linter_name') " Add the new items. call extend(g:ale_buffer_info[a:buffer].loclist, l:linter_loclist) @@ -169,8 +183,8 @@ function! s:HandleExit(job_id, exit_code) abort " Remove this job from the list. call ale#job#Stop(a:job_id) call remove(s:job_info_map, a:job_id) - call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val !=# a:job_id') - call filter(g:ale_buffer_info[l:buffer].active_linter_list, 'v:val !=# l:linter.name') + call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val isnot# a:job_id') + call filter(g:ale_buffer_info[l:buffer].active_linter_list, 'v:val isnot# l:linter.name') " Stop here if we land in the handle for a job completing if we're in " a sandbox. @@ -224,7 +238,7 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort " tsserver sends syntax and semantic errors in separate messages, so we " have to collect the messages separately for each buffer and join them " back together again. - if a:error_type ==# 'syntax' + if a:error_type is# 'syntax' let l:info.syntax_loclist = l:thislist else let l:info.semantic_loclist = l:thislist @@ -247,16 +261,16 @@ endfunction function! ale#engine#HandleLSPResponse(conn_id, response) abort let l:method = get(a:response, 'method', '') - if get(a:response, 'jsonrpc', '') ==# '2.0' && has_key(a:response, 'error') + if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error') " Uncomment this line to print LSP error messages. " call s:HandleLSPErrorMessage(a:response.error.message) - elseif l:method ==# 'textDocument/publishDiagnostics' + elseif l:method is# 'textDocument/publishDiagnostics' call s:HandleLSPDiagnostics(a:conn_id, a:response) - elseif get(a:response, 'type', '') ==# 'event' - \&& get(a:response, 'event', '') ==# 'semanticDiag' + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'semanticDiag' call s:HandleTSServerDiagnostics(a:response, 'semantic') - elseif get(a:response, 'type', '') ==# 'event' - \&& get(a:response, 'event', '') ==# 'syntaxDiag' + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'syntaxDiag' call s:HandleTSServerDiagnostics(a:response, 'syntax') endif endfunction @@ -268,10 +282,6 @@ function! ale#engine#SetResults(buffer, loclist) abort " The List could be sorted again here by SetSigns. if g:ale_set_signs call ale#sign#SetSigns(a:buffer, a:loclist) - - if l:linting_is_done - call ale#sign#RemoveDummySignIfNeeded(a:buffer) - endif endif if g:ale_set_quickfix || g:ale_set_loclist @@ -298,6 +308,9 @@ function! ale#engine#SetResults(buffer, loclist) abort endif if l:linting_is_done + " Reset the save event marker, used for opening windows, etc. + call setbufvar(a:buffer, 'ale_save_event_fired', 0) + " Automatically remove all managed temporary files and directories " now that all jobs have completed. call ale#engine#RemoveManagedFiles(a:buffer) @@ -310,17 +323,17 @@ endfunction function! s:RemapItemTypes(type_map, loclist) abort for l:item in a:loclist let l:key = l:item.type - \ . (get(l:item, 'sub_type', '') ==# 'style' ? 'S' : '') + \ . (get(l:item, 'sub_type', '') is# 'style' ? 'S' : '') let l:new_key = get(a:type_map, l:key, '') - if l:new_key ==# 'E' - \|| l:new_key ==# 'ES' - \|| l:new_key ==# 'W' - \|| l:new_key ==# 'WS' - \|| l:new_key ==# 'I' + if l:new_key is# 'E' + \|| l:new_key is# 'ES' + \|| l:new_key is# 'W' + \|| l:new_key is# 'WS' + \|| l:new_key is# 'I' let l:item.type = l:new_key[0] - if l:new_key ==# 'ES' || l:new_key ==# 'WS' + if l:new_key is# 'ES' || l:new_key is# 'WS' let l:item.sub_type = 'style' elseif has_key(l:item, 'sub_type') call remove(l:item, 'sub_type') @@ -329,7 +342,11 @@ function! s:RemapItemTypes(type_map, loclist) abort endfor endfunction +" Save the temporary directory so we can figure out if files are in it. +let s:temp_dir = fnamemodify(tempname(), ':h') + function! ale#engine#FixLocList(buffer, linter_name, loclist) abort + let l:bufnr_map = {} let l:new_loclist = [] " Some errors have line numbers beyond the end of the file, @@ -351,16 +368,42 @@ function! ale#engine#FixLocList(buffer, linter_name, loclist) abort " The linter_name will be set on the errors so it can be used in " output, filtering, etc.. let l:item = { + \ 'bufnr': a:buffer, \ 'text': l:old_item.text, \ 'lnum': str2nr(l:old_item.lnum), \ 'col': str2nr(get(l:old_item, 'col', 0)), - \ 'bufnr': get(l:old_item, 'bufnr', a:buffer), \ 'vcol': get(l:old_item, 'vcol', 0), \ 'type': get(l:old_item, 'type', 'E'), \ 'nr': get(l:old_item, 'nr', -1), \ 'linter_name': a:linter_name, \} + if has_key(l:old_item, 'filename') + \&& l:old_item.filename[:len(s:temp_dir) - 1] isnot# s:temp_dir + " Use the filename given. + " Temporary files are assumed to be for this buffer, + " and the filename is not included then, because it looks bad + " in the loclist window. + let l:filename = l:old_item.filename + let l:item.filename = l:filename + + if has_key(l:old_item, 'bufnr') + " If a buffer number is also given, include that too. + " If Vim detects that he buffer number is valid, it will + " be used instead of the filename. + let l:item.bufnr = l:old_item.bufnr + elseif has_key(l:bufnr_map, l:filename) + " Get the buffer number from the map, which can be faster. + let l:item.bufnr = l:bufnr_map[l:filename] + else + " Look up the buffer number. + let l:item.bufnr = bufnr(l:filename) + let l:bufnr_map[l:filename] = l:item.bufnr + endif + elseif has_key(l:old_item, 'bufnr') + let l:item.bufnr = l:old_item.bufnr + endif + if has_key(l:old_item, 'detail') let l:item.detail = l:old_item.detail endif @@ -381,8 +424,9 @@ function! ale#engine#FixLocList(buffer, linter_name, loclist) abort if l:item.lnum < 1 " When errors appear before line 1, put them at line 1. let l:item.lnum = 1 - elseif l:item.lnum > l:last_line_number + elseif l:item.bufnr == a:buffer && l:item.lnum > l:last_line_number " When errors go beyond the end of the file, put them at the end. + " This is only done for the current buffer. let l:item.lnum = l:last_line_number endif @@ -417,7 +461,8 @@ function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort " Automatically delete the directory later. call ale#engine#ManageDirectory(a:buffer, l:temporary_directory) " Write the buffer out to a file. - call writefile(getbufline(a:buffer, 1, '$'), a:temporary_file) + let l:lines = getbufline(a:buffer, 1, '$') + call ale#util#Writefile(a:buffer, l:lines, a:temporary_file) return 1 endfunction @@ -432,6 +477,7 @@ function! s:RunJob(options) abort let l:output_stream = a:options.output_stream let l:next_chain_index = a:options.next_chain_index let l:read_buffer = a:options.read_buffer + let l:info = g:ale_buffer_info[l:buffer] if empty(l:command) return 0 @@ -461,9 +507,9 @@ function! s:RunJob(options) abort \ 'exit_cb': function('s:HandleExit'), \} - if l:output_stream ==# 'stderr' + if l:output_stream is# 'stderr' let l:job_options.err_cb = function('s:GatherOutput') - elseif l:output_stream ==# 'both' + elseif l:output_stream is# 'both' let l:job_options.out_cb = function('s:GatherOutput') let l:job_options.err_cb = function('s:GatherOutput') else @@ -487,8 +533,11 @@ function! s:RunJob(options) abort " Only proceed if the job is being run. if l:job_id " Add the job to the list of jobs, so we can track them. - call add(g:ale_buffer_info[l:buffer].job_list, l:job_id) - call add(g:ale_buffer_info[l:buffer].active_linter_list, l:linter.name) + call add(l:info.job_list, l:job_id) + + if index(l:info.active_linter_list, l:linter.name) < 0 + call add(l:info.active_linter_list, l:linter.name) + endif let l:status = 'started' " Store the ID for the job in the map to read back again. @@ -502,8 +551,6 @@ function! s:RunJob(options) abort if g:ale_history_enabled call ale#history#Add(l:buffer, l:status, l:job_id, l:command) - else - let g:ale_buffer_info[l:buffer].history = [] endif if get(g:, 'ale_run_synchronously') == 1 @@ -596,6 +643,7 @@ endfunction function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) let l:new_job_list = [] + let l:new_active_linter_list = [] for l:job_id in get(l:info, 'job_list', []) let l:job_info = get(s:job_info_map, l:job_id, {}) @@ -606,15 +654,23 @@ function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort call remove(s:job_info_map, l:job_id) else call add(l:new_job_list, l:job_id) + " Linters with jobs still running are still active. + call add(l:new_active_linter_list, l:job_info.linter.name) endif endif endfor + " Remove duplicates from the active linter list. + call uniq(sort(l:new_active_linter_list)) + " Update the List, so it includes only the jobs we still need. let l:info.job_list = l:new_job_list + " Update the active linter list, clearing out anything not running. + let l:info.active_linter_list = l:new_active_linter_list endfunction function! s:CheckWithLSP(buffer, linter) abort + let l:info = g:ale_buffer_info[a:buffer] let l:lsp_details = ale#linter#StartLSP( \ a:buffer, \ a:linter, @@ -631,13 +687,15 @@ function! s:CheckWithLSP(buffer, linter) abort " Remember the linter this connection is for. let s:lsp_linter_map[l:id] = a:linter.name - let l:change_message = a:linter.lsp ==# 'tsserver' + let l:change_message = a:linter.lsp is# 'tsserver' \ ? ale#lsp#tsserver_message#Geterr(a:buffer) \ : ale#lsp#message#DidChange(a:buffer) let l:request_id = ale#lsp#Send(l:id, l:change_message, l:root) if l:request_id != 0 - call add(g:ale_buffer_info[a:buffer].active_linter_list, a:linter.name) + if index(l:info.active_linter_list, a:linter.name) < 0 + call add(l:info.active_linter_list, a:linter.name) + endif endif return l:request_id != 0 @@ -654,10 +712,44 @@ function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort call filter( \ get(g:ale_buffer_info[a:buffer], 'loclist', []), - \ 'get(l:name_map, v:val.linter_name)', + \ 'get(l:name_map, get(v:val, ''linter_name''))', \) endfunction +function! s:AddProblemsFromOtherBuffers(buffer, linters) abort + let l:filename = expand('#' . a:buffer . ':p') + let l:loclist = [] + let l:name_map = {} + + " Build a map of the active linters. + for l:linter in a:linters + let l:name_map[l:linter.name] = 1 + endfor + + " Find the items from other buffers, for the linters that are enabled. + for l:info in values(g:ale_buffer_info) + for l:item in l:info.loclist + if has_key(l:item, 'filename') + \&& l:item.filename is# l:filename + \&& has_key(l:name_map, l:item.linter_name) + " Copy the items and set the buffer numbers to this one. + let l:new_item = copy(l:item) + let l:new_item.bufnr = a:buffer + call add(l:loclist, l:new_item) + endif + endfor + endfor + + if !empty(l:loclist) + call sort(l:loclist, function('ale#util#LocItemCompareWithText')) + call uniq(l:loclist, function('ale#util#LocItemCompareWithText')) + + " Set the loclist variable, used by some parts of ALE. + let g:ale_buffer_info[a:buffer].loclist = l:loclist + call ale#engine#SetResults(a:buffer, l:loclist) + endif +endfunction + " Run a linter for a buffer. " " Returns 1 if the linter was successfully run. @@ -667,7 +759,7 @@ function! s:RunLinter(buffer, linter) abort else let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) - if s:IsExecutable(l:executable) + if ale#engine#IsExecutable(a:buffer, l:executable) return s:InvokeChain(a:buffer, a:linter, 0, []) endif endif @@ -677,7 +769,7 @@ endfunction function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort " Initialise the buffer information if needed. - call ale#engine#InitBufferInfo(a:buffer) + let l:new_buffer = ale#engine#InitBufferInfo(a:buffer) call s:StopCurrentJobs(a:buffer, a:should_lint_file) call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters) @@ -702,6 +794,8 @@ function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort " disabled, or ALE itself is disabled. if l:can_clear_results call ale#engine#SetResults(a:buffer, []) + elseif l:new_buffer + call s:AddProblemsFromOtherBuffers(a:buffer, a:linters) endif endfunction diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim index 4722afa9..a3b74677 100644 --- a/autoload/ale/events.vim +++ b/autoload/ale/events.vim @@ -1,6 +1,7 @@ " Author: w0rp <devw0rp@gmail.com> function! ale#events#SaveEvent(buffer) abort + call setbufvar(a:buffer, 'ale_save_event_fired', 1) let l:should_lint = ale#Var(a:buffer, 'enabled') && g:ale_lint_on_save if g:ale_fix_on_save @@ -23,9 +24,20 @@ function! s:LintOnEnter(buffer) abort endfunction function! ale#events#EnterEvent(buffer) abort + let l:filetype = getbufvar(a:buffer, '&filetype') + call setbufvar(a:buffer, 'ale_original_filetype', l:filetype) + call s:LintOnEnter(a:buffer) endfunction +function! ale#events#FileTypeEvent(buffer, new_filetype) abort + let l:filetype = getbufvar(a:buffer, 'ale_original_filetype', '') + + if a:new_filetype isnot# l:filetype + call ale#Queue(300, 'lint_file', a:buffer) + endif +endfunction + function! ale#events#FileChangedEvent(buffer) abort call setbufvar(a:buffer, 'ale_file_changed', 1) diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index 45855a50..80f46c27 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -43,7 +43,6 @@ function! ale#fix#ApplyQueuedFixes() abort if empty(&buftype) noautocmd :w! else - call writefile(l:data.output, 'fix_test_file') set nomodified endif endif @@ -155,7 +154,7 @@ function! s:CreateTemporaryFileForJob(buffer, temporary_file, input) abort " Automatically delete the directory later. call ale#fix#ManageDirectory(a:buffer, l:temporary_directory) " Write the buffer out to a file. - call writefile(a:input, a:temporary_file) + call ale#util#Writefile(a:buffer, a:input, a:temporary_file) return 1 endfunction @@ -187,9 +186,9 @@ function! s:RunJob(options) abort if l:read_temporary_file " TODO: Check that a temporary file is set here. let l:job_info.file_to_read = l:temporary_file - elseif l:output_stream ==# 'stderr' + elseif l:output_stream is# 'stderr' let l:job_options.err_cb = function('s:GatherOutput') - elseif l:output_stream ==# 'both' + elseif l:output_stream is# 'both' let l:job_options.out_cb = function('s:GatherOutput') let l:job_options.err_cb = function('s:GatherOutput') else @@ -321,7 +320,7 @@ function! ale#fix#InitBufferData(buffer, fixing_flag) abort \ 'lines_before': getbufline(a:buffer, 1, '$'), \ 'filename': expand('#' . a:buffer . ':p'), \ 'done': 0, - \ 'should_save': a:fixing_flag ==# 'save_file', + \ 'should_save': a:fixing_flag is# 'save_file', \ 'temporary_directory_list': [], \} endfunction @@ -336,14 +335,14 @@ function! ale#fix#Fix(...) abort let l:fixing_flag = get(a:000, 0, '') - if l:fixing_flag !=# '' && l:fixing_flag !=# 'save_file' + if l:fixing_flag isnot# '' && l:fixing_flag isnot# 'save_file' throw "fixing_flag must be either '' or 'save_file'" endif let l:callback_list = s:GetCallbacks() if empty(l:callback_list) - if l:fixing_flag ==# '' + if l:fixing_flag is# '' echoerr 'No fixers have been defined. Try :ALEFixSuggest' endif diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim index ce30c36e..b77ac031 100644 --- a/autoload/ale/fix/registry.vim +++ b/autoload/ale/fix/registry.vim @@ -72,6 +72,21 @@ let s:default_registry = { \ 'suggested_filetypes': ['css', 'sass', 'scss', 'stylus'], \ 'description': 'Fix stylesheet files using stylelint --fix.', \ }, +\ 'swiftformat': { +\ 'function': 'ale#fixers#swiftformat#Fix', +\ 'suggested_filetypes': ['swift'], +\ 'description': 'Apply SwiftFormat to a file.', +\ }, +\ 'phpcbf': { +\ 'function': 'ale#fixers#phpcbf#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix PHP files with phpcbf.', +\ }, +\ 'clang-format': { +\ 'function': 'ale#fixers#clangformat#Fix', +\ 'suggested_filetypes': ['c', 'cpp'], +\ 'description': 'Fix C/C++ files with clang-format.', +\ }, \} " Reset the function registry to the default entries. diff --git a/autoload/ale/fixers/clangformat.vim b/autoload/ale/fixers/clangformat.vim new file mode 100644 index 00000000..b50b7047 --- /dev/null +++ b/autoload/ale/fixers/clangformat.vim @@ -0,0 +1,22 @@ +scriptencoding utf-8 +" Author: Peter Renström <renstrom.peter@gmail.com> +" Description: Fixing C/C++ files with clang-format. + +call ale#Set('c_clangformat_executable', 'clang-format') +call ale#Set('c_clangformat_use_global', 0) +call ale#Set('c_clangformat_options', '') + +function! ale#fixers#clangformat#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'c_clangformat', [ + \ 'clang-format', + \]) +endfunction + +function! ale#fixers#clangformat#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'c_clangformat_options') + + return { + \ 'command': ale#Escape(ale#fixers#clangformat#GetExecutable(a:buffer)) + \ . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/generic_python.vim b/autoload/ale/fixers/generic_python.vim index 8bdde505..124146be 100644 --- a/autoload/ale/fixers/generic_python.vim +++ b/autoload/ale/fixers/generic_python.vim @@ -23,3 +23,38 @@ function! ale#fixers#generic_python#AddLinesBeforeControlStatements(buffer, line return l:new_lines endfunction + +" This function breaks up long lines so that autopep8 or other tools can +" fix the badly-indented code which is produced as a result. +function! ale#fixers#generic_python#BreakUpLongLines(buffer, lines) abort + " Default to a maximum line length of 79 + let l:max_line_length = 79 + let l:conf = ale#path#FindNearestFile(a:buffer, 'setup.cfg') + + " Read the maximum line length from setup.cfg + if !empty(l:conf) + for l:match in ale#util#GetMatches( + \ readfile(l:conf), + \ '\v^ *max-line-length *\= *(\d+)', + \) + let l:max_line_length = str2nr(l:match[1]) + endfor + endif + + let l:new_list = [] + + for l:line in a:lines + if len(l:line) > l:max_line_length && l:line !~# '# *noqa' + let l:line = substitute(l:line, '\v([(,])([^)])', '\1\n\2', 'g') + let l:line = substitute(l:line, '\v([^(])([)])', '\1,\n\2', 'g') + + for l:split_line in split(l:line, "\n") + call add(l:new_list, l:split_line) + endfor + else + call add(l:new_list, l:line) + endif + endfor + + return l:new_list +endfunction diff --git a/autoload/ale/fixers/phpcbf.vim b/autoload/ale/fixers/phpcbf.vim new file mode 100644 index 00000000..9bff7412 --- /dev/null +++ b/autoload/ale/fixers/phpcbf.vim @@ -0,0 +1,24 @@ +" Author: notomo <notomo.motono@gmail.com> +" Description: Fixing files with phpcbf. + +call ale#Set('php_phpcbf_standard', '') +call ale#Set('php_phpcbf_executable', 'phpcbf') +call ale#Set('php_phpcbf_use_global', 0) + +function! ale#fixers#phpcbf#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'php_phpcbf', [ + \ 'vendor/bin/phpcbf', + \ 'phpcbf' + \]) +endfunction + +function! ale#fixers#phpcbf#Fix(buffer) abort + let l:executable = ale#fixers#phpcbf#GetExecutable(a:buffer) + let l:standard = ale#Var(a:buffer, 'php_phpcbf_standard') + let l:standard_option = !empty(l:standard) + \ ? '--standard=' . l:standard + \ : '' + return { + \ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option + \} +endfunction diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim index ae370ac6..581536e6 100644 --- a/autoload/ale/fixers/prettier.vim +++ b/autoload/ale/fixers/prettier.vim @@ -1,13 +1,33 @@ " Author: tunnckoCore (Charlike Mike Reagent) <mameto2011@gmail.com>, -" w0rp <devw0rp@gmail.com> +" w0rp <devw0rp@gmail.com>, morhetz (Pavel Pertsev) <morhetz@gmail.com> " Description: Integration of Prettier with ALE. call ale#Set('javascript_prettier_executable', 'prettier') call ale#Set('javascript_prettier_use_global', 0) +call ale#Set('javascript_prettier_use_local_config', 0) call ale#Set('javascript_prettier_options', '') +function! s:FindConfig(buffer) abort + for l:filename in [ + \ '.prettierrc', + \ 'prettier.config.js', + \ 'package.json', + \ ] + + let l:config = ale#path#FindNearestFile(a:buffer, l:filename) + + if !empty(l:config) + return l:config + endif + endfor + + return '' +endfunction + + function! ale#fixers#prettier#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_prettier', [ + \ 'node_modules/.bin/prettier_d', \ 'node_modules/prettier-cli/index.js', \ 'node_modules/.bin/prettier', \]) @@ -15,11 +35,15 @@ endfunction function! ale#fixers#prettier#Fix(buffer) abort let l:options = ale#Var(a:buffer, 'javascript_prettier_options') + let l:config = s:FindConfig(a:buffer) + let l:use_config = ale#Var(a:buffer, 'javascript_prettier_use_local_config') + \ && !empty(l:config) return { \ 'command': ale#Escape(ale#fixers#prettier#GetExecutable(a:buffer)) \ . ' %t' - \ . ' ' . l:options + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (l:use_config ? ' --config ' . ale#Escape(l:config) : '') \ . ' --write', \ 'read_temporary_file': 1, \} diff --git a/autoload/ale/fixers/rubocop.vim b/autoload/ale/fixers/rubocop.vim index 88dc1c43..35569b19 100644 --- a/autoload/ale/fixers/rubocop.vim +++ b/autoload/ale/fixers/rubocop.vim @@ -4,9 +4,11 @@ function! ale#fixers#rubocop#GetCommand(buffer) abort \ ? ' exec rubocop' \ : '' let l:config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml') + let l:options = ale#Var(a:buffer, 'ruby_rubocop_options') return ale#Escape(l:executable) . l:exec_args \ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --auto-correct %t' endfunction diff --git a/autoload/ale/fixers/swiftformat.vim b/autoload/ale/fixers/swiftformat.vim new file mode 100644 index 00000000..dcc204b1 --- /dev/null +++ b/autoload/ale/fixers/swiftformat.vim @@ -0,0 +1,25 @@ +" Author: gfontenot (Gordon Fontenot) <gordon@fonten.io> +" Description: Integration of SwiftFormat with ALE. + +call ale#Set('swift_swiftformat_executable', 'swiftformat') +call ale#Set('swift_swiftformat_use_global', 0) +call ale#Set('swift_swiftformat_options', '') + +function! ale#fixers#swiftformat#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'swift_swiftformat', [ + \ 'Pods/SwiftFormat/CommandLineTool/swiftformat', + \ 'ios/Pods/SwiftFormat/CommandLineTool/swiftformat', + \ 'swiftformat', + \]) +endfunction + +function! ale#fixers#swiftformat#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'swift_swiftformat_options') + + return { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(ale#fixers#swiftformat#GetExecutable(a:buffer)) + \ . ' %t' + \ . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/handlers/cppcheck.vim b/autoload/ale/handlers/cppcheck.vim index b365c794..dc56cd0b 100644 --- a/autoload/ale/handlers/cppcheck.vim +++ b/autoload/ale/handlers/cppcheck.vim @@ -11,7 +11,7 @@ function! ale#handlers#cppcheck#HandleCppCheckFormat(buffer, lines) abort if ale#path#IsBufferPath(a:buffer, l:match[1]) call add(l:output, { \ 'lnum': str2nr(l:match[2]), - \ 'type': l:match[3] ==# 'error' ? 'E' : 'W', + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', \ 'text': l:match[4], \}) endif diff --git a/autoload/ale/handlers/css.vim b/autoload/ale/handlers/css.vim index 71eaf2ce..4c1b81cb 100644 --- a/autoload/ale/handlers/css.vim +++ b/autoload/ale/handlers/css.vim @@ -29,7 +29,7 @@ function! ale#handlers#css#HandleCSSLintFormat(buffer, lines) abort \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 0, \ 'text': l:text, - \ 'type': l:type ==# 'Warning' ? 'W' : 'E', + \ 'type': l:type is# 'Warning' ? 'W' : 'E', \}) endfor @@ -61,7 +61,7 @@ function! ale#handlers#css#HandleStyleLintFormat(buffer, lines) abort call add(l:output, { \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 0, - \ 'type': l:match[3] ==# '✖' ? 'E' : 'W', + \ 'type': l:match[3] is# '✖' ? 'E' : 'W', \ 'text': l:match[4] . ' [' . l:match[5] . ']', \}) endfor diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim index a7ff0ae2..6c5d75c0 100644 --- a/autoload/ale/handlers/eslint.vim +++ b/autoload/ale/handlers/eslint.vim @@ -4,6 +4,7 @@ call ale#Set('javascript_eslint_options', '') call ale#Set('javascript_eslint_executable', 'eslint') call ale#Set('javascript_eslint_use_global', 0) +call ale#Set('javascript_eslint_suppress_eslintignore', 0) function! ale#handlers#eslint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_eslint', [ @@ -82,6 +83,12 @@ function! ale#handlers#eslint#Handle(buffer, lines) abort let l:type = 'Error' let l:text = l:match[3] + if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore') + if l:text is# 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.' + continue + endif + endif + " Take the error type from the output if available. if !empty(l:match[4]) let l:type = split(l:match[4], '/')[0] @@ -92,7 +99,7 @@ function! ale#handlers#eslint#Handle(buffer, lines) abort \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 0, \ 'text': l:text, - \ 'type': l:type ==# 'Warning' ? 'W' : 'E', + \ 'type': l:type is# 'Warning' ? 'W' : 'E', \} for l:col_match in ale#util#GetMatches(l:text, s:col_end_patterns) diff --git a/autoload/ale/handlers/gcc.vim b/autoload/ale/handlers/gcc.vim index aaac5281..ad5cab39 100644 --- a/autoload/ale/handlers/gcc.vim +++ b/autoload/ale/handlers/gcc.vim @@ -81,7 +81,7 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort let l:included_filename = '' endif elseif l:include_lnum > 0 - \&& (empty(l:included_filename) || l:included_filename ==# l:match[1]) + \&& (empty(l:included_filename) || l:included_filename is# l:match[1]) " If we hit the first error after an include header, or the " errors below have the same name as the first filename we see, " then include these lines, and remember what that filename was. @@ -96,7 +96,7 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort let l:included_filename = '' if s:IsHeaderFile(bufname(bufnr(''))) - \&& l:match[5][:len(s:pragma_error) - 1] ==# s:pragma_error + \&& l:match[5][:len(s:pragma_error) - 1] is# s:pragma_error continue endif diff --git a/autoload/ale/handlers/haskell.vim b/autoload/ale/handlers/haskell.vim index 5d417c89..bac5f4ae 100644 --- a/autoload/ale/handlers/haskell.vim +++ b/autoload/ale/handlers/haskell.vim @@ -14,7 +14,7 @@ function! ale#handlers#haskell#HandleGHCFormat(buffer, lines) abort for l:line in a:lines if len(matchlist(l:line, l:pattern)) > 0 call add(l:corrected_lines, l:line) - elseif l:line ==# '' + elseif l:line is# '' call add(l:corrected_lines, l:line) else if len(l:corrected_lines) > 0 @@ -42,10 +42,10 @@ function! ale#handlers#haskell#HandleGHCFormat(buffer, lines) abort let l:text = l:errors[2] else let l:ghc_type = '' - let l:text = l:match[4][:0] ==# ' ' ? l:match[4][1:] : l:match[4] + let l:text = l:match[4][:0] is# ' ' ? l:match[4][1:] : l:match[4] endif - if l:ghc_type ==? 'Warning' + if l:ghc_type is? 'Warning' let l:type = 'W' else let l:type = 'E' diff --git a/autoload/ale/handlers/rust.vim b/autoload/ale/handlers/rust.vim index 3f9083a5..12a5a16a 100644 --- a/autoload/ale/handlers/rust.vim +++ b/autoload/ale/handlers/rust.vim @@ -47,7 +47,7 @@ function! ale#handlers#rust#HandleRustErrorsForFile(buffer, full_filename, lines for l:span in l:error.spans if ( \ l:span.is_primary - \ && (ale#path#IsBufferPath(a:buffer, l:span.file_name) || l:span.file_name ==# '<anon>') + \ && (ale#path#IsBufferPath(a:buffer, l:span.file_name) || l:span.file_name is# '<anon>') \) call add(l:output, { \ 'lnum': l:span.line_start, diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim new file mode 100644 index 00000000..894879ee --- /dev/null +++ b/autoload/ale/handlers/sh.vim @@ -0,0 +1,20 @@ +" Author: w0rp <devw0rp@gmail.com> + +" Get the shell type for a buffer, based on the hashbang line. +function! ale#handlers#sh#GetShellType(buffer) abort + let l:bang_line = get(getbufline(a:buffer, 1), 0, '') + + " Take the shell executable from the hashbang, if we can. + if l:bang_line[:1] is# '#!' + " Remove options like -e, etc. + let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g') + + for l:possible_shell in ['bash', 'tcsh', 'csh', 'zsh', 'sh'] + if l:command =~# l:possible_shell . '\s*$' + return l:possible_shell + endif + endfor + endif + + return '' +endfunction diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim index a109a265..5c01e7a9 100644 --- a/autoload/ale/highlight.vim +++ b/autoload/ale/highlight.vim @@ -65,15 +65,15 @@ function! ale#highlight#UpdateHighlights() abort call ale#highlight#RemoveHighlights() for l:item in l:item_list - if l:item.type ==# 'W' - if get(l:item, 'sub_type', '') ==# 'style' + if l:item.type is# 'W' + if get(l:item, 'sub_type', '') is# 'style' let l:group = 'ALEStyleWarning' else let l:group = 'ALEWarning' endif - elseif l:item.type ==# 'I' + elseif l:item.type is# 'I' let l:group = 'ALEInfo' - elseif get(l:item, 'sub_type', '') ==# 'style' + elseif get(l:item, 'sub_type', '') is# 'style' let l:group = 'ALEStyleError' else let l:group = 'ALEError' diff --git a/autoload/ale/history.vim b/autoload/ale/history.vim index 0356c022..a6282ea5 100644 --- a/autoload/ale/history.vim +++ b/autoload/ale/history.vim @@ -1,15 +1,20 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Tools for managing command history -" + +" Return a shallow copy of the command history for a given buffer number. +function! ale#history#Get(buffer) abort + return copy(getbufvar(a:buffer, 'ale_history', [])) +endfunction + function! ale#history#Add(buffer, status, job_id, command) abort if g:ale_max_buffer_history_size <= 0 " Don't save anything if the history isn't a positive number. - let g:ale_buffer_info[a:buffer].history = [] + call setbufvar(a:buffer, 'ale_history', []) return endif - let l:history = g:ale_buffer_info[a:buffer].history + let l:history = getbufvar(a:buffer, 'ale_history', []) " Remove the first item if we hit the max history size. if len(l:history) >= g:ale_max_buffer_history_size @@ -22,18 +27,13 @@ function! ale#history#Add(buffer, status, job_id, command) abort \ 'command': a:command, \}) - let g:ale_buffer_info[a:buffer].history = l:history + call setbufvar(a:buffer, 'ale_history', l:history) endfunction function! s:FindHistoryItem(buffer, job_id) abort - " Stop immediately if there's nothing set up for the buffer. - if !has_key(g:ale_buffer_info, a:buffer) - return {} - endif - " Search backwards to find a matching job ID. IDs might be recycled, " so finding the last one should be good enough. - for l:obj in reverse(g:ale_buffer_info[a:buffer].history[:]) + for l:obj in reverse(ale#history#Get(a:buffer)) if l:obj.job_id == a:job_id return l:obj endif @@ -46,18 +46,14 @@ endfunction function! ale#history#SetExitCode(buffer, job_id, exit_code) abort let l:obj = s:FindHistoryItem(a:buffer, a:job_id) - if !empty(l:obj) - " If we find a match, then set the code and status. - let l:obj.exit_code = a:exit_code - let l:obj.status = 'finished' - endif + " If we find a match, then set the code and status. + let l:obj.exit_code = a:exit_code + let l:obj.status = 'finished' endfunction " Set the output for a command which finished. function! ale#history#RememberOutput(buffer, job_id, output) abort let l:obj = s:FindHistoryItem(a:buffer, a:job_id) - if !empty(l:obj) - let l:obj.output = a:output - endif + let l:obj.output = a:output endfunction diff --git a/autoload/ale/job.vim b/autoload/ale/job.vim index 93f28824..1d8b6760 100644 --- a/autoload/ale/job.vim +++ b/autoload/ale/job.vim @@ -34,7 +34,7 @@ function! ale#job#JoinNeovimOutput(job, last_line, data, mode, callback) abort let l:new_last_line = a:last_line . a:data[0] endif - if a:mode ==# 'raw' + if a:mode is# 'raw' if !empty(l:lines) call a:callback(a:job, join(l:lines, "\n") . "\n") endif @@ -50,7 +50,7 @@ endfunction function! s:NeoVimCallback(job, data, event) abort let l:info = s:job_map[a:job] - if a:event ==# 'stdout' + if a:event is# 'stdout' let l:info.out_cb_line = ale#job#JoinNeovimOutput( \ a:job, \ l:info.out_cb_line, @@ -58,7 +58,7 @@ function! s:NeoVimCallback(job, data, event) abort \ l:info.mode, \ ale#util#GetFunction(l:info.out_cb), \) - elseif a:event ==# 'stderr' + elseif a:event is# 'stderr' let l:info.err_cb_line = ale#job#JoinNeovimOutput( \ a:job, \ l:info.err_cb_line, @@ -117,7 +117,7 @@ function! s:VimCloseCallback(channel) abort " job_status() can trigger the exit handler. " The channel can close before the job has exited. - if job_status(l:job) ==# 'dead' + if job_status(l:job) is# 'dead' try if !empty(l:info) && has_key(l:info, 'exit_cb') call ale#util#GetFunction(l:info.exit_cb)(l:job_id, l:info.exit_code) @@ -142,7 +142,7 @@ function! s:VimExitCallback(job, exit_code) abort let l:info.exit_code = a:exit_code " The program can exit before the data has finished being read. - if ch_status(job_getchannel(a:job)) ==# 'closed' + if ch_status(job_getchannel(a:job)) is# 'closed' try if !empty(l:info) && has_key(l:info, 'exit_cb') call ale#util#GetFunction(l:info.exit_cb)(l:job_id, a:exit_code) @@ -161,7 +161,7 @@ function! ale#job#ParseVim8ProcessID(job_string) abort endfunction function! ale#job#ValidateArguments(command, options) abort - if a:options.mode !=# 'nl' && a:options.mode !=# 'raw' + if a:options.mode isnot# 'nl' && a:options.mode isnot# 'raw' throw 'Invalid mode: ' . a:options.mode endif endfunction @@ -244,7 +244,7 @@ function! ale#job#Start(command, options) abort let l:job_id = ale#job#ParseVim8ProcessID(string(l:job_info.job)) endif - if l:job_id + if l:job_id > 0 " Store the job in the map for later only if we can get the ID. let s:job_map[l:job_id] = l:job_info endif @@ -273,7 +273,7 @@ function! ale#job#IsRunning(job_id) abort endtry elseif has_key(s:job_map, a:job_id) let l:job = s:job_map[a:job_id].job - return job_status(l:job) ==# 'run' + return job_status(l:job) is# 'run' endif return 0 @@ -296,7 +296,7 @@ function! ale#job#Stop(job_id) abort " We must close the channel for reading the buffer if it is open " when stopping a job. Otherwise, we will get errors in the status line. - if ch_status(job_getchannel(l:job)) ==# 'open' + if ch_status(job_getchannel(l:job)) is# 'open' call ch_close_in(job_getchannel(l:job)) endif diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 8fc68510..2cd773f1 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -25,6 +25,7 @@ let s:default_ale_linters = { \ 'csh': ['shell'], \ 'go': ['gofmt', 'golint', 'go vet'], \ 'help': [], +\ 'python': ['flake8', 'mypy', 'pylint'], \ 'rust': ['cargo'], \ 'spec': [], \ 'text': [], @@ -59,9 +60,9 @@ function! ale#linter#PreProcess(linter) abort throw '`name` must be defined to name the linter' endif - let l:needs_address = l:obj.lsp ==# 'socket' - let l:needs_executable = l:obj.lsp !=# 'socket' - let l:needs_command = l:obj.lsp !=# 'socket' + let l:needs_address = l:obj.lsp is# 'socket' + let l:needs_executable = l:obj.lsp isnot# 'socket' + let l:needs_command = l:obj.lsp isnot# 'socket' let l:needs_lsp_details = !empty(l:obj.lsp) if empty(l:obj.lsp) @@ -311,7 +312,7 @@ function! ale#linter#Get(original_filetypes) abort let l:all_linters = ale#linter#GetAll(l:filetype) let l:filetype_linters = [] - if type(l:linter_names) == type('') && l:linter_names ==# 'all' + if type(l:linter_names) == type('') && l:linter_names is# 'all' let l:filetype_linters = l:all_linters elseif type(l:linter_names) == type([]) " Select only the linters we or the user has specified. @@ -377,13 +378,13 @@ function! ale#linter#StartLSP(buffer, linter, callback) abort let l:address = '' let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) - if empty(l:root) && a:linter.lsp !=# 'tsserver' + if empty(l:root) && a:linter.lsp isnot# 'tsserver' " If there's no project root, then we can't check files with LSP, " unless we are using tsserver, which doesn't use project roots. return {} endif - if a:linter.lsp ==# 'socket' + if a:linter.lsp is# 'socket' let l:address = ale#linter#GetAddress(a:buffer, a:linter) let l:conn_id = ale#lsp#ConnectToAddress( \ l:address, @@ -425,7 +426,7 @@ function! ale#linter#StartLSP(buffer, linter, callback) abort endif " The change message needs to be sent for tsserver before doing anything. - if a:linter.lsp ==# 'tsserver' + if a:linter.lsp is# 'tsserver' call ale#lsp#Send(l:conn_id, ale#lsp#tsserver_message#Change(a:buffer)) endif diff --git a/autoload/ale/list.vim b/autoload/ale/list.vim index 9fe35bf3..7b2bf2cb 100644 --- a/autoload/ale/list.vim +++ b/autoload/ale/list.vim @@ -1,24 +1,71 @@ " Author: Bjorn Neergaard <bjorn@neersighted.com>, modified by Yann fery <yann@fery.me> " Description: Manages the loclist and quickfix lists +if !exists('s:timer_args') + let s:timer_args = {} +endif + " Return 1 if there is a buffer with buftype == 'quickfix' in bufffer list function! ale#list#IsQuickfixOpen() abort for l:buf in range(1, bufnr('$')) - if getbufvar(l:buf, '&buftype') ==# 'quickfix' + if getbufvar(l:buf, '&buftype') is# 'quickfix' return 1 endif endfor return 0 endfunction -function! ale#list#SetLists(buffer, loclist) abort +" Check if we should open the list, based on the save event being fired, and +" that setting being on, or the setting just being set to `1`. +function! s:ShouldOpen(buffer) abort + let l:val = ale#Var(a:buffer, 'open_list') + let l:saved = getbufvar(a:buffer, 'ale_save_event_fired', 0) + + return l:val is 1 || (l:val is# 'on_save' && l:saved) +endfunction + +function! ale#list#GetCombinedList() abort + let l:list = [] + + for l:info in values(g:ale_buffer_info) + call extend(l:list, l:info.loclist) + endfor + + call sort(l:list, function('ale#util#LocItemCompareWithText')) + call uniq(l:list, function('ale#util#LocItemCompareWithText')) + + return l:list +endfunction + +function! s:FixList(list) abort + let l:new_list = [] + + for l:item in a:list + if l:item.bufnr == -1 + " If the buffer number is invalid, remove it. + let l:fixed_item = copy(l:item) + call remove(l:fixed_item, 'bufnr') + else + " Don't copy the Dictionary if we do not need to. + let l:fixed_item = l:item + endif + + call add(l:new_list, l:fixed_item) + endfor + + return l:new_list +endfunction + +function! s:SetListsImpl(timer_id, buffer, loclist) abort let l:title = expand('#' . a:buffer . ':p') if g:ale_set_quickfix + let l:quickfix_list = ale#list#GetCombinedList() + if has('nvim') - call setqflist(a:loclist, ' ', l:title) + call setqflist(s:FixList(l:quickfix_list), ' ', l:title) else - call setqflist(a:loclist) + call setqflist(s:FixList(l:quickfix_list)) call setqflist([], 'r', {'title': l:title}) endif elseif g:ale_set_loclist @@ -28,16 +75,24 @@ function! ale#list#SetLists(buffer, loclist) abort let l:win_id = exists('*bufwinid') ? bufwinid(str2nr(a:buffer)) : 0 if has('nvim') - call setloclist(l:win_id, a:loclist, ' ', l:title) + call setloclist(l:win_id, s:FixList(a:loclist), ' ', l:title) else - call setloclist(l:win_id, a:loclist) + call setloclist(l:win_id, s:FixList(a:loclist)) call setloclist(l:win_id, [], 'r', {'title': l:title}) endif endif - " If we have errors in our list, open the list. Only if it isn't already open - if (g:ale_open_list && !empty(a:loclist)) || g:ale_keep_list_window_open + let l:keep_open = ale#Var(a:buffer, 'keep_list_window_open') + + " Open a window to show the problems if we need to. + " + " We'll check if the current buffer's List is not empty here, so the + " window will only be opened if the current buffer has problems. + if s:ShouldOpen(a:buffer) && (l:keep_open || !empty(a:loclist)) let l:winnr = winnr() + let l:mode = mode() + let l:reset_visual_selection = l:mode is? 'v' || l:mode is# "\<c-v>" + let l:reset_character_selection = l:mode is? 's' || l:mode is# "\<c-s>" if g:ale_set_quickfix if !ale#list#IsQuickfixOpen() @@ -48,14 +103,36 @@ function! ale#list#SetLists(buffer, loclist) abort endif " If focus changed, restore it (jump to the last window). - if l:winnr !=# winnr() + if l:winnr isnot# winnr() wincmd p endif + + if l:reset_visual_selection || l:reset_character_selection + " If we were in a selection mode before, select the last selection. + normal! gv + + if l:reset_character_selection + " Switch back to Select mode, if we were in that. + normal! "\<c-g>" + endif + endif endif endfunction -function! ale#list#CloseWindowIfNeeded(buffer) abort - if g:ale_keep_list_window_open || !g:ale_open_list +function! ale#list#SetLists(buffer, loclist) abort + if get(g:, 'ale_set_lists_synchronously') == 1 + call s:SetListsImpl(-1, a:buffer, a:loclist) + else + call ale#util#StartPartialTimer( + \ 0, + \ function('s:SetListsImpl'), + \ [a:buffer, a:loclist], + \) + endif +endfunction + +function! s:CloseWindowIfNeededImpl(timer_id, buffer) abort + if ale#Var(a:buffer, 'keep_list_window_open') || !s:ShouldOpen(a:buffer) return endif @@ -73,3 +150,15 @@ function! ale#list#CloseWindowIfNeeded(buffer) abort catch /E444/ endtry endfunction + +function! ale#list#CloseWindowIfNeeded(buffer) abort + if get(g:, 'ale_set_lists_synchronously') == 1 + call s:CloseWindowIfNeededImpl(-1, a:buffer) + else + call ale#util#StartPartialTimer( + \ 0, + \ function('s:CloseWindowIfNeededImpl'), + \ [a:buffer], + \) + endif +endfunction diff --git a/autoload/ale/loclist_jumping.vim b/autoload/ale/loclist_jumping.vim index 88ed4c97..7ed9e6ba 100644 --- a/autoload/ale/loclist_jumping.vim +++ b/autoload/ale/loclist_jumping.vim @@ -10,15 +10,16 @@ " List will be returned, otherwise a pair of [line_number, column_number] will " be returned. function! ale#loclist_jumping#FindNearest(direction, wrap) abort + let l:buffer = bufnr('') let l:pos = getcurpos() let l:info = get(g:ale_buffer_info, bufnr('%'), {'loclist': []}) - " This list will have already been sorted. - let l:loclist = l:info.loclist - let l:search_item = {'lnum': l:pos[1], 'col': l:pos[2]} + " Copy the list and filter to only the items in this buffer. + let l:loclist = filter(copy(l:info.loclist), 'v:val.bufnr == l:buffer') + let l:search_item = {'bufnr': l:buffer, 'lnum': l:pos[1], 'col': l:pos[2]} " When searching backwards, so we can find the next smallest match. - if a:direction ==# 'before' - let l:loclist = reverse(copy(l:loclist)) + if a:direction is# 'before' + call reverse(l:loclist) endif " Look for items before or after the current position. @@ -30,17 +31,21 @@ function! ale#loclist_jumping#FindNearest(direction, wrap) abort " cursor to a line without changing the column, in some cases. let l:cmp_value = ale#util#LocItemCompare( \ { + \ 'bufnr': l:buffer, \ 'lnum': l:item.lnum, - \ 'col': min([max([l:item.col, 1]), len(getline(l:item.lnum))]), + \ 'col': min([ + \ max([l:item.col, 1]), + \ max([len(getline(l:item.lnum)), 1]), + \ ]), \ }, \ l:search_item \) - if a:direction ==# 'before' && l:cmp_value < 0 + if a:direction is# 'before' && l:cmp_value < 0 return [l:item.lnum, l:item.col] endif - if a:direction ==# 'after' && l:cmp_value > 0 + if a:direction is# 'after' && l:cmp_value > 0 return [l:item.lnum, l:item.col] endif endfor @@ -66,13 +71,16 @@ function! ale#loclist_jumping#Jump(direction, wrap) abort endfunction function! ale#loclist_jumping#JumpToIndex(index) abort - let l:info = get(g:ale_buffer_info, bufnr('%'), {'loclist': []}) - let l:loclist = l:info.loclist + let l:buffer = bufnr('') + let l:info = get(g:ale_buffer_info, l:buffer, {'loclist': []}) + let l:loclist = filter(copy(l:info.loclist), 'v:val.bufnr == l:buffer') + if empty(l:loclist) return endif let l:item = l:loclist[a:index] + if !empty(l:item) call cursor([l:item.lnum, l:item.col]) endif diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index bb9a24f2..b5525c98 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -190,17 +190,17 @@ function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort return endif - if get(a:response, 'method', '') ==# '' + if get(a:response, 'method', '') is# '' if has_key(get(a:response, 'result', {}), 'capabilities') for [l:dir, l:project] in l:uninitialized_projects call s:MarkProjectAsInitialized(a:conn, l:project) endfor endif - elseif get(a:response, 'method', '') ==# 'textDocument/publishDiagnostics' + elseif get(a:response, 'method', '') is# 'textDocument/publishDiagnostics' let l:filename = ale#path#FromURI(a:response.params.uri) for [l:dir, l:project] in l:uninitialized_projects - if l:filename[:len(l:dir) - 1] ==# l:dir + if l:filename[:len(l:dir) - 1] is# l:dir call s:MarkProjectAsInitialized(a:conn, l:project) endif endfor @@ -215,7 +215,7 @@ function! ale#lsp#HandleMessage(conn, message) abort " Call our callbacks. for l:response in l:response_list - if get(l:response, 'method', '') ==# 'initialize' + if get(l:response, 'method', '') is# 'initialize' call s:HandleInitializeResponse(a:conn, l:response) else call ale#lsp#HandleOtherInitializeResponses(a:conn, l:response) @@ -296,7 +296,7 @@ function! ale#lsp#ConnectToAddress(address, project_root, callback) abort " Get the current connection or a new one. let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() - if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) !=# 'open' + if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) isnot# 'open' let l:conn.channnel = ch_open(a:address, { \ 'mode': 'raw', \ 'waittime': 0, @@ -304,7 +304,7 @@ function! ale#lsp#ConnectToAddress(address, project_root, callback) abort \}) endif - if ch_status(l:conn.channnel) ==# 'fail' + if ch_status(l:conn.channnel) is# 'fail' return 0 endif @@ -319,7 +319,7 @@ endfunction function! s:SendMessageData(conn, data) abort if has_key(a:conn, 'executable') call ale#job#SendRaw(a:conn.id, a:data) - elseif has_key(a:conn, 'channel') && ch_status(a:conn.channnel) ==# 'open' + elseif has_key(a:conn, 'channel') && ch_status(a:conn.channnel) is# 'open' " Send the message to the server call ch_sendraw(a:conn.channel, a:data) else diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim index c68114ae..bc026cc2 100644 --- a/autoload/ale/path.vim +++ b/autoload/ale/path.vim @@ -65,7 +65,7 @@ endfunction " Return 1 if a path is an absolute path. function! ale#path#IsAbsolute(filename) abort " Check for /foo and C:\foo, etc. - return a:filename[:0] ==# '/' || a:filename[1:2] ==# ':\' + return a:filename[:0] is# '/' || a:filename[1:2] is# ':\' endfunction " Given a filename, return 1 if the file represents some temporary file @@ -78,7 +78,7 @@ function! ale#path#IsTempName(filename) abort \] for l:prefix in l:prefix_list - if a:filename[:len(l:prefix) - 1] ==# l:prefix + if a:filename[:len(l:prefix) - 1] is# l:prefix return 1 endif endfor @@ -86,23 +86,36 @@ function! ale#path#IsTempName(filename) abort return 0 endfunction +" Given a base directory, which must not have a trailing slash, and a +" filename, which may have an absolute path a path relative to the base +" directory, return the absolute path to the file. +function! ale#path#GetAbsPath(base_directory, filename) abort + if ale#path#IsAbsolute(a:filename) + return a:filename + endif + + let l:sep = has('win32') ? '\' : '/' + + return ale#path#Simplify(a:base_directory . l:sep . a:filename) +endfunction + " Given a buffer number and a relative or absolute path, return 1 if the " two paths represent the same file on disk. function! ale#path#IsBufferPath(buffer, complex_filename) abort " If the path is one of many different names for stdin, we have a match. - if a:complex_filename ==# '-' - \|| a:complex_filename ==# 'stdin' - \|| a:complex_filename[:0] ==# '<' + if a:complex_filename is# '-' + \|| a:complex_filename is# 'stdin' + \|| a:complex_filename[:0] is# '<' return 1 endif let l:test_filename = ale#path#Simplify(a:complex_filename) - if l:test_filename[:1] ==# './' + if l:test_filename[:1] is# './' let l:test_filename = l:test_filename[2:] endif - if l:test_filename[:1] ==# '..' + if l:test_filename[:1] is# '..' " Remove ../../ etc. from the front of the path. let l:test_filename = substitute(l:test_filename, '\v^(\.\.[/\\])+', '/', '') endif @@ -114,8 +127,8 @@ function! ale#path#IsBufferPath(buffer, complex_filename) abort let l:buffer_filename = expand('#' . a:buffer . ':p') - return l:buffer_filename ==# l:test_filename - \ || l:buffer_filename[-len(l:test_filename):] ==# l:test_filename + return l:buffer_filename is# l:test_filename + \ || l:buffer_filename[-len(l:test_filename):] is# l:test_filename endfunction " Given a path, return every component of the path, moving upwards. @@ -133,7 +146,7 @@ function! ale#path#Upwards(path) abort if ale#Has('win32') && a:path =~# '^[a-zA-z]:\' " Add \ to C: for C:\, etc. let l:path_list[-1] .= '\' - elseif a:path[0] ==# '/' + elseif a:path[0] is# '/' " If the path starts with /, even on Windows, add / and / to all paths. call map(l:path_list, '''/'' . v:val') call add(l:path_list, '/') @@ -146,10 +159,10 @@ endfunction " relatives paths will not be prefixed with the protocol. " For Windows paths, the `:` in C:\ etc. will not be percent-encoded. function! ale#path#ToURI(path) abort - let l:has_drive_letter = a:path[1:2] ==# ':\' + let l:has_drive_letter = a:path[1:2] is# ':\' return substitute( - \ ((l:has_drive_letter || a:path[:0] ==# '/') ? 'file://' : '') + \ ((l:has_drive_letter || a:path[:0] is# '/') ? 'file://' : '') \ . (l:has_drive_letter ? '/' . a:path[:2] : '') \ . ale#uri#Encode(l:has_drive_letter ? a:path[3:] : a:path), \ '\\', @@ -160,7 +173,7 @@ endfunction function! ale#path#FromURI(uri) abort let l:i = len('file://') - let l:encoded_path = a:uri[: l:i - 1] ==# 'file://' ? a:uri[l:i :] : a:uri + let l:encoded_path = a:uri[: l:i - 1] is# 'file://' ? a:uri[l:i :] : a:uri return ale#uri#Decode(l:encoded_path) endfunction diff --git a/autoload/ale/pattern_options.vim b/autoload/ale/pattern_options.vim index 03c9146a..a603c980 100644 --- a/autoload/ale/pattern_options.vim +++ b/autoload/ale/pattern_options.vim @@ -13,7 +13,7 @@ function! ale#pattern_options#SetOptions() abort endfor for l:key in keys(l:options) - if l:key[:0] ==# '&' + if l:key[:0] is# '&' call setbufvar(bufnr(''), l:key, l:options[l:key]) else let b:[l:key] = l:options[l:key] diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim index 503ff1db..b981ded6 100644 --- a/autoload/ale/ruby.vim +++ b/autoload/ale/ruby.vim @@ -10,7 +10,7 @@ function! ale#ruby#FindRailsRoot(buffer) abort \ ':h:h' \) - if l:dir !=# '.' + if l:dir isnot# '.' \&& isdirectory(l:dir . '/app') \&& isdirectory(l:dir . '/config') \&& isdirectory(l:dir . '/db') diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim index 37105f9f..7ba83647 100644 --- a/autoload/ale/sign.vim +++ b/autoload/ale/sign.vim @@ -2,8 +2,6 @@ scriptencoding utf8 " Author: w0rp <devw0rp@gmail.com> " Description: Draws error and warning signs into signcolumn -let b:dummy_sign_set_map = {} - if !hlexists('ALEErrorSign') highlight link ALEErrorSign error endif @@ -40,7 +38,7 @@ if !hlexists('ALESignColumnWithoutErrors') if !empty(l:match) execute 'highlight link ALESignColumnWithoutErrors ' . l:match[1] - elseif l:highlight_syntax !=# 'cleared' + elseif l:highlight_syntax isnot# 'cleared' execute 'highlight ALESignColumnWithoutErrors ' . l:highlight_syntax endif endfunction @@ -62,6 +60,62 @@ execute 'sign define ALEInfoSign text=' . g:ale_sign_info \ . ' texthl=ALEInfoSign linehl=ALEInfoLine' sign define ALEDummySign +let s:error_priority = 1 +let s:warning_priority = 2 +let s:info_priority = 3 +let s:style_error_priority = 4 +let s:style_warning_priority = 5 + +function! ale#sign#GetSignName(sublist) abort + let l:priority = s:style_warning_priority + + " Determine the highest priority item for the line. + for l:item in a:sublist + if l:item.type is# 'I' + let l:item_priority = s:info_priority + elseif l:item.type is# 'W' + if get(l:item, 'sub_type', '') is# 'style' + let l:item_priority = s:style_warning_priority + else + let l:item_priority = s:warning_priority + endif + else + if get(l:item, 'sub_type', '') is# 'style' + let l:item_priority = s:style_error_priority + else + let l:item_priority = s:error_priority + endif + endif + + if l:item_priority < l:priority + let l:priority = l:item_priority + endif + endfor + + if l:priority is# s:error_priority + return 'ALEErrorSign' + endif + + if l:priority is# s:warning_priority + return 'ALEWarningSign' + endif + + if l:priority is# s:style_error_priority + return 'ALEStyleErrorSign' + endif + + if l:priority is# s:style_warning_priority + return 'ALEStyleWarningSign' + endif + + if l:priority is# s:info_priority + return 'ALEInfoSign' + endif + + " Use the error sign for invalid severities. + return 'ALEErrorSign' +endfunction + " Read sign data for a buffer to a list of lines. function! ale#sign#ReadSigns(buffer) abort redir => l:output @@ -71,7 +125,7 @@ function! ale#sign#ReadSigns(buffer) abort return split(l:output, "\n") endfunction -" Given a list of lines for sign output, return a List of pairs [line, id] +" Given a list of lines for sign output, return a List of [line, id, group] function! ale#sign#ParseSigns(line_list) abort " Matches output like : " line=4 id=1 name=ALEErrorSign @@ -81,20 +135,25 @@ function! ale#sign#ParseSigns(line_list) abort " riga=1 id=1000001, nome=ALEWarningSign let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)' let l:result = [] + let l:is_dummy_sign_set = 0 for l:line in a:line_list let l:match = matchlist(l:line, l:pattern) if len(l:match) > 0 - call add(l:result, [ - \ str2nr(l:match[1]), - \ str2nr(l:match[2]), - \ l:match[3], - \]) + if l:match[3] is# 'ALEDummySign' + let l:is_dummy_sign_set = 1 + else + call add(l:result, [ + \ str2nr(l:match[1]), + \ str2nr(l:match[2]), + \ l:match[3], + \]) + endif endif endfor - return l:result + return [l:is_dummy_sign_set, l:result] endfunction function! ale#sign#FindCurrentSigns(buffer) abort @@ -104,11 +163,15 @@ function! ale#sign#FindCurrentSigns(buffer) abort endfunction " Given a loclist, group the List into with one List per line. -function! s:GroupLoclistItems(loclist) abort +function! s:GroupLoclistItems(buffer, loclist) abort let l:grouped_items = [] let l:last_lnum = -1 for l:obj in a:loclist + if l:obj.bufnr != a:buffer + continue + endif + " Create a new sub-List when we hit a new line. if l:obj.lnum != l:last_lnum call add(l:grouped_items, []) @@ -118,148 +181,143 @@ function! s:GroupLoclistItems(loclist) abort let l:last_lnum = l:obj.lnum endfor - " Now we've gathered the items in groups, filter the groups down to - " the groups containing at least one new item. - let l:new_grouped_items = [] + return l:grouped_items +endfunction - for l:group in l:grouped_items - for l:obj in l:group - if !has_key(l:obj, 'sign_id') - call add(l:new_grouped_items, l:group) - break - endif - endfor - endfor +function! ale#sign#SetSignColumnHighlight(has_problems) abort + highlight clear SignColumn - return l:new_grouped_items + if a:has_problems + highlight link SignColumn ALESignColumnWithErrors + else + highlight link SignColumn ALESignColumnWithoutErrors + endif endfunction -function! s:IsDummySignSet(current_id_list) abort - for [l:line, l:id, l:name] in a:current_id_list - if l:id == g:ale_sign_offset - return 1 - endif +function! s:UpdateLineNumbers(buffer, current_sign_list, loclist) abort + let l:line_map = {} + let l:line_numbers_changed = 0 - if l:line > 1 - return 0 - endif + for [l:line, l:sign_id, l:name] in a:current_sign_list + let l:line_map[l:sign_id] = l:line endfor - return 0 -endfunction - -function! s:SetDummySignIfNeeded(buffer, current_sign_list, new_signs) abort - let l:is_dummy_sign_set = s:IsDummySignSet(a:current_sign_list) + for l:item in a:loclist + if l:item.bufnr == a:buffer + let l:lnum = get(l:line_map, get(l:item, 'sign_id', 0), 0) - " If we haven't already set a dummy sign, and we have some previous signs - " or always want a dummy sign, then set one, to keep the sign column open. - if !l:is_dummy_sign_set && (a:new_signs || g:ale_sign_column_always) - silent! execute 'sign place ' . g:ale_sign_offset - \ . ' line=1 name=ALEDummySign buffer=' - \ . a:buffer + if l:lnum && l:item.lnum != l:lnum + let l:item.lnum = l:lnum + let l:line_numbers_changed = 1 + endif + endif + endfor - let l:is_dummy_sign_set = 1 + " When the line numbers change, sort the list again + if l:line_numbers_changed + call sort(a:loclist, 'ale#util#LocItemCompare') endif - - return l:is_dummy_sign_set endfunction -function! ale#sign#GetSignType(sublist) abort - let l:highest_level = 100 +function! s:BuildSignMap(current_sign_list, grouped_items) abort + let l:sign_map = {} + let l:sign_offset = g:ale_sign_offset - for l:item in a:sublist - let l:level = (l:item.type ==# 'I' ? 2 : l:item.type ==# 'W') - - if get(l:item, 'sub_type', '') ==# 'style' - let l:level += 10 + for [l:line, l:sign_id, l:name] in a:current_sign_list + let l:sign_map[l:line] = { + \ 'current_id': l:sign_id, + \ 'current_name': l:name, + \ 'new_id': 0, + \ 'new_name': '', + \ 'items': [], + \} + + if l:sign_id > l:sign_offset + let l:sign_offset = l:sign_id endif + endfor - if l:level < l:highest_level - let l:highest_level = l:level + for l:group in a:grouped_items + let l:line = l:group[0].lnum + let l:sign_info = get(l:sign_map, l:line, { + \ 'current_id': 0, + \ 'current_name': '', + \ 'new_id': 0, + \ 'new_name': '', + \ 'items': [], + \}) + + let l:sign_info.new_name = ale#sign#GetSignName(l:group) + let l:sign_info.items = l:group + + if l:sign_info.current_name isnot# l:sign_info.new_name + let l:sign_info.new_id = l:sign_offset + 1 + let l:sign_offset += 1 + else + let l:sign_info.new_id = l:sign_info.current_id endif - endfor - if l:highest_level == 10 - return 'ALEStyleErrorSign' - elseif l:highest_level == 11 - return 'ALEStyleWarningSign' - elseif l:highest_level == 2 - return 'ALEInfoSign' - elseif l:highest_level == 1 - return 'ALEWarningSign' - endif + let l:sign_map[l:line] = l:sign_info + endfor - return 'ALEErrorSign' + return l:sign_map endfunction -function! ale#sign#SetSignColumnHighlight(has_problems) abort - highlight clear SignColumn - - if a:has_problems - highlight link SignColumn ALESignColumnWithErrors - else - highlight link SignColumn ALESignColumnWithoutErrors - endif -endfunction +function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort + let l:command_list = [] + let l:is_dummy_sign_set = a:was_sign_set -function! s:PlaceNewSigns(buffer, grouped_items, current_sign_offset) abort - if g:ale_change_sign_column_color - call ale#sign#SetSignColumnHighlight(!empty(a:grouped_items)) + " Set the dummy sign if we need to. + " The dummy sign is needed to keep the sign column open while we add + " and remove signs. + if !l:is_dummy_sign_set && (!empty(a:sign_map) || g:ale_sign_column_always) + call add(l:command_list, 'sign place ' + \ . g:ale_sign_offset + \ . ' line=1 name=ALEDummySign buffer=' + \ . a:buffer + \) + let l:is_dummy_sign_set = 1 endif - let l:offset = a:current_sign_offset > 0 - \ ? a:current_sign_offset - \ : g:ale_sign_offset - - " Add the new signs, - for l:index in range(0, len(a:grouped_items) - 1) - let l:sign_id = l:offset + l:index + 1 - let l:sublist = a:grouped_items[l:index] - let l:type = ale#sign#GetSignType(a:grouped_items[l:index]) - - " Save the sign IDs we are setting back on our loclist objects. - " These IDs will be used to preserve items which are set many times. - for l:obj in l:sublist - let l:obj.sign_id = l:sign_id - endfor - - silent! execute 'sign place ' . l:sign_id - \ . ' line=' . l:sublist[0].lnum - \ . ' name=' . l:type - \ . ' buffer=' . a:buffer - endfor -endfunction - -" Get items grouped by any current sign IDs they might have. -function! s:GetItemsWithSignIDs(loclist) abort - let l:items_by_sign_id = {} - - for l:item in a:loclist - if has_key(l:item, 'sign_id') - if !has_key(l:items_by_sign_id, l:item.sign_id) - let l:items_by_sign_id[l:item.sign_id] = [] + " Place new items first. + for [l:line_str, l:info] in items(a:sign_map) + if l:info.new_id + " Save the sign IDs we are setting back on our loclist objects. + " These IDs will be used to preserve items which are set many times. + for l:item in l:info.items + let l:item.sign_id = l:info.new_id + endfor + + if l:info.new_id isnot l:info.current_id + call add(l:command_list, 'sign place ' + \ . (l:info.new_id) + \ . ' line=' . l:line_str + \ . ' name=' . (l:info.new_name) + \ . ' buffer=' . a:buffer + \) endif - - call add(l:items_by_sign_id[l:item.sign_id], l:item) endif endfor - return l:items_by_sign_id -endfunction + " Remove signs without new IDs. + for l:info in values(a:sign_map) + if l:info.current_id && l:info.current_id isnot l:info.new_id + call add(l:command_list, 'sign unplace ' + \ . (l:info.current_id) + \ . ' buffer=' . a:buffer + \) + endif + endfor -" Given some current signs and a loclist, look for items with sign IDs, -" and change the line numbers for loclist items to match the signs. -function! s:UpdateLineNumbers(current_sign_list, items_by_sign_id) abort - " Do nothing if there's nothing to work with. - if empty(a:items_by_sign_id) - return + " Remove the dummy sign to close the sign column if we need to. + if l:is_dummy_sign_set && !g:ale_sign_column_always + call add(l:command_list, 'sign unplace ' + \ . g:ale_sign_offset + \ . ' buffer=' . a:buffer + \) endif - for [l:line, l:sign_id, l:name] in a:current_sign_list - for l:obj in get(a:items_by_sign_id, l:sign_id, []) - let l:obj.lnum = l:line - endfor - endfor + return l:command_list endfunction " This function will set the signs which show up on the left. @@ -271,42 +329,25 @@ function! ale#sign#SetSigns(buffer, loclist) abort endif " Find the current markers - let l:current_sign_list = ale#sign#FindCurrentSigns(a:buffer) - " Get a mapping from sign IDs to current loclist items which have them. - let l:items_by_sign_id = s:GetItemsWithSignIDs(a:loclist) + let [l:is_dummy_sign_set, l:current_sign_list] = + \ ale#sign#FindCurrentSigns(a:buffer) - " Use sign information to update the line numbers for the loclist items. - call s:UpdateLineNumbers(l:current_sign_list, l:items_by_sign_id) - " Sort items again, as the line numbers could have changed. - call sort(a:loclist, 'ale#util#LocItemCompare') + " Update the line numbers for items from before which may have moved. + call s:UpdateLineNumbers(a:buffer, l:current_sign_list, a:loclist) - let l:grouped_items = s:GroupLoclistItems(a:loclist) + " Group items after updating the line numbers. + let l:grouped_items = s:GroupLoclistItems(a:buffer, a:loclist) - " Set the dummy sign if we need to. - " This keeps the sign gutter open while we remove things, etc. - let l:is_dummy_sign_set = s:SetDummySignIfNeeded( + " Build a map of current and new signs, with the lines as the keys. + let l:sign_map = s:BuildSignMap(l:current_sign_list, l:grouped_items) + + let l:command_list = ale#sign#GetSignCommands( \ a:buffer, - \ l:current_sign_list, - \ !empty(l:grouped_items), + \ l:is_dummy_sign_set, + \ l:sign_map, \) - " Now remove the previous signs. The dummy will hold the column open - " while we add the new signs, if we had signs before. - for [l:line, l:sign_id, l:name] in l:current_sign_list - if l:sign_id != g:ale_sign_offset - \&& !has_key(l:items_by_sign_id, l:sign_id) - execute 'sign unplace ' . l:sign_id . ' buffer=' . a:buffer - endif + for l:command in l:command_list + silent! execute l:command endfor - - " Compute a sign ID offset so we don't hit the same sign IDs again. - let l:current_sign_offset = max(map(keys(l:items_by_sign_id), 'str2nr(v:val)')) - - call s:PlaceNewSigns(a:buffer, l:grouped_items, l:current_sign_offset) -endfunction - -function! ale#sign#RemoveDummySignIfNeeded(buffer) abort - if !g:ale_sign_column_always - execute 'sign unplace ' . g:ale_sign_offset . ' buffer=' . a:buffer - endif endfunction diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim index afc27b3b..a073f7a0 100644 --- a/autoload/ale/statusline.vim +++ b/autoload/ale/statusline.vim @@ -22,19 +22,20 @@ function! ale#statusline#Update(buffer, loclist) abort return endif + let l:loclist = filter(copy(a:loclist), 'v:val.bufnr == a:buffer') let l:count = s:CreateCountDict() - let l:count.total = len(a:loclist) + let l:count.total = len(l:loclist) - for l:entry in a:loclist - if l:entry.type ==# 'W' - if get(l:entry, 'sub_type', '') ==# 'style' + for l:entry in l:loclist + if l:entry.type is# 'W' + if get(l:entry, 'sub_type', '') is# 'style' let l:count.style_warning += 1 else let l:count.warning += 1 endif - elseif l:entry.type ==# 'I' + elseif l:entry.type is# 'I' let l:count.info += 1 - elseif get(l:entry, 'sub_type', '') ==# 'style' + elseif get(l:entry, 'sub_type', '') is# 'style' let l:count.style_error += 1 else let l:count.error += 1 diff --git a/autoload/ale/test.vim b/autoload/ale/test.vim index 204b7115..c0458053 100644 --- a/autoload/ale/test.vim +++ b/autoload/ale/test.vim @@ -12,7 +12,7 @@ " " This function should be run in a Vader Before: block. function! ale#test#SetDirectory(docker_path) abort - if a:docker_path[:len('/testplugin/') - 1] !=# '/testplugin/' + if a:docker_path[:len('/testplugin/') - 1] isnot# '/testplugin/' throw 'docker_path must start with /testplugin/!' endif diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index f3146151..cf8d5bec 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -1,11 +1,23 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Contains miscellaneous functions -" A null file for sending output to nothing. -let g:ale#util#nul_file = '/dev/null' +" A wrapper function for mode() so we can test calls for it. +function! ale#util#Mode(...) abort + return call('mode', a:000) +endfunction + +" A wrapper function for feedkeys so we can test calls for it. +function! ale#util#FeedKeys(...) abort + return call('feedkeys', a:000) +endfunction -if has('win32') - let g:ale#util#nul_file = 'nul' +if !exists('g:ale#util#nul_file') + " A null file for sending output to nothing. + let g:ale#util#nul_file = '/dev/null' + + if has('win32') + let g:ale#util#nul_file = 'nul' + endif endif " Return the number of lines for a given buffer. @@ -21,57 +33,113 @@ function! ale#util#GetFunction(string_or_ref) abort return a:string_or_ref endfunction +" Compare two loclist items for ALE, sorted by their buffers, filenames, and +" line numbers and column numbers. function! ale#util#LocItemCompare(left, right) abort - if a:left['lnum'] < a:right['lnum'] + if a:left.bufnr < a:right.bufnr + return -1 + endif + + if a:left.bufnr > a:right.bufnr + return 1 + endif + + if a:left.bufnr == -1 + if a:left.filename < a:right.filename + return -1 + endif + + if a:left.filename > a:right.filename + return 1 + endif + endif + + if a:left.lnum < a:right.lnum return -1 endif - if a:left['lnum'] > a:right['lnum'] + if a:left.lnum > a:right.lnum return 1 endif - if a:left['col'] < a:right['col'] + if a:left.col < a:right.col return -1 endif - if a:left['col'] > a:right['col'] + if a:left.col > a:right.col return 1 endif return 0 endfunction -" This function will perform a binary search to find a message from the -" loclist to echo when the cursor moves. -function! ale#util#BinarySearch(loclist, line, column) abort +" Compare two loclist items, including the text for the items. +" +" This function can be used for de-duplicating lists. +function! ale#util#LocItemCompareWithText(left, right) abort + let l:cmp_value = ale#util#LocItemCompare(a:left, a:right) + + if l:cmp_value + return l:cmp_value + endif + + if a:left.text < a:right.text + return -1 + endif + + if a:left.text > a:right.text + return 1 + endif + + return 0 +endfunction + +" This function will perform a binary search and a small sequential search +" on the list to find the last problem in the buffer and line which is +" on or before the column. The index of the problem will be returned. +" +" -1 will be returned if nothing can be found. +function! ale#util#BinarySearch(loclist, buffer, line, column) abort let l:min = 0 let l:max = len(a:loclist) - 1 - let l:last_column_match = -1 while 1 if l:max < l:min - return l:last_column_match + return -1 endif let l:mid = (l:min + l:max) / 2 - let l:obj = a:loclist[l:mid] + let l:item = a:loclist[l:mid] - " Binary search to get on the same line - if a:loclist[l:mid]['lnum'] < a:line + " Binary search for equal buffers, equal lines, then near columns. + if l:item.bufnr < a:buffer let l:min = l:mid + 1 - elseif a:loclist[l:mid]['lnum'] > a:line + elseif l:item.bufnr > a:buffer + let l:max = l:mid - 1 + elseif l:item.lnum < a:line + let l:min = l:mid + 1 + elseif l:item.lnum > a:line let l:max = l:mid - 1 else - let l:last_column_match = l:mid - - " Binary search to get the same column, or near it - if a:loclist[l:mid]['col'] < a:column - let l:min = l:mid + 1 - elseif a:loclist[l:mid]['col'] > a:column - let l:max = l:mid - 1 - else - return l:mid - endif + " This part is a small sequential search. + let l:index = l:mid + + " Search backwards to find the first problem on the line. + while l:index > 0 + \&& a:loclist[l:index - 1].bufnr == a:buffer + \&& a:loclist[l:index - 1].lnum == a:line + let l:index -= 1 + endwhile + + " Find the last problem on or before this column. + while l:index < l:max + \&& a:loclist[l:index + 1].bufnr == a:buffer + \&& a:loclist[l:index + 1].lnum == a:line + \&& a:loclist[l:index + 1].col <= a:column + let l:index += 1 + endwhile + + return l:index endif endwhile endfunction @@ -136,7 +204,7 @@ function! s:LoadArgCount(function) abort endif let l:match = matchstr(split(l:output, "\n")[0], '\v\([^)]+\)')[1:-2] - let l:arg_list = filter(split(l:match, ', '), 'v:val !=# ''...''') + let l:arg_list = filter(split(l:match, ', '), 'v:val isnot# ''...''') return len(l:arg_list) endfunction @@ -187,3 +255,45 @@ function! ale#util#FuzzyJSONDecode(data, default) abort return a:default endtry endfunction + +" Write a file, including carriage return characters for DOS files. +" +" The buffer number is required for determining the fileformat setting for +" the buffer. +function! ale#util#Writefile(buffer, lines, filename) abort + let l:corrected_lines = getbufvar(a:buffer, '&fileformat') is# 'dos' + \ ? map(copy(a:lines), 'v:val . "\r"') + \ : a:lines + + call writefile(l:corrected_lines, a:filename) " no-custom-checks +endfunction + +if !exists('s:patial_timers') + let s:partial_timers = {} +endif + +function! s:ApplyPartialTimer(timer_id) abort + let [l:Callback, l:args] = remove(s:partial_timers, a:timer_id) + call call(l:Callback, [a:timer_id] + l:args) +endfunction + +" Given a delay, a callback, a List of arguments, start a timer with +" timer_start() and call the callback provided with [timer_id] + args. +" +" The timer must not be stopped with timer_stop(). +" Use ale#util#StopPartialTimer() instead, which can stop any timer, and will +" clear any arguments saved for executing callbacks later. +function! ale#util#StartPartialTimer(delay, callback, args) abort + let l:timer_id = timer_start(a:delay, function('s:ApplyPartialTimer')) + let s:partial_timers[l:timer_id] = [a:callback, a:args] + + return l:timer_id +endfunction + +function! ale#util#StopPartialTimer(timer_id) abort + call timer_stop(a:timer_id) + + if has_key(s:partial_timers, a:timer_id) + call remove(s:partial_timers, a:timer_id) + endif +endfunction diff --git a/check-supported-tools-tables b/check-supported-tools-tables new file mode 100755 index 00000000..842d431b --- /dev/null +++ b/check-supported-tools-tables @@ -0,0 +1,68 @@ +#!/bin/bash -eu + +# This script compares the table of supported tools in both the README file +# and the doc/ale.txt file, so we can complain if they don't match up. + +# Find the start and end lines for the help section. +ale_help_start_line="$( \ + grep -m1 -n '^[0-9][0-9]*\. *Supported Languages' doc/ale.txt \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +ale_help_section_size="$( \ + tail -n +"$ale_help_start_line" doc/ale.txt \ + | grep -m1 -n '================' \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +# -- shellcheck complains about expr, but it works better. +# shellcheck disable=SC2003 +ale_help_end_line="$(expr "$ale_help_start_line" + "$ale_help_section_size")" + +# Find the start and end lines for the same section in the README. +readme_start_line="$( \ + grep -m1 -n '^.*[0-9][0-9]*\. *Supported Languages' README.md \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +readme_section_size="$( \ + tail -n +"$readme_start_line" README.md \ + | grep -m1 -n '^##.*Usage' \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +# shellcheck disable=SC2003 +readme_end_line="$(expr "$readme_start_line" + "$readme_section_size")" + +doc_file="$(mktemp)" +readme_file="$(mktemp)" + +sed -n "$ale_help_start_line,$ale_help_end_line"p doc/ale.txt \ + | grep '\* .*: ' \ + | sed 's/^*//' \ + | sed 's/[`!^]\|([^)]*)//g' \ + | sed 's/ *\([,:]\)/\1/g' \ + | sed 's/ */ /g' \ + | sed 's/^ *\| *$//g' \ + | sed 's/^/ /' \ + > "$doc_file" + +sed -n "$readme_start_line,$readme_end_line"p README.md \ + | grep '| .* |' \ + | sed '/^| Language\|^| ---/d' \ + | sed 's/^|//' \ + | sed 's/ \?|/:/' \ + | sed 's/[`!^|]\|([^)]*)//g' \ + | sed 's/\[\|\]//g' \ + | sed 's/see.*\(,\|$\)/\1/g' \ + | sed 's/ *\([,:]\)/\1/g' \ + | sed 's/ */ /g' \ + | sed 's/^ *\| *$//g' \ + | sed 's/^/ /' \ + | sed 's/ *-n flag//g' \ + > "$readme_file" + +exit_code=0 + +diff -U0 "$readme_file" "$doc_file" || exit_code=$? + +rm "$doc_file" +rm "$readme_file" + +exit "$exit_code" diff --git a/custom-checks b/custom-checks index 3bb60cda..aad16c9c 100755 --- a/custom-checks +++ b/custom-checks @@ -65,6 +65,10 @@ if (( FIX_ERRORS )); then for directory in "${directories[@]}"; do sed -i "s/^\(function.*)\) *$/\1 abort/" "$directory"/**/*.vim sed -i "s/shellescape(/ale#Escape(/" "$directory"/**/*.vim + sed -i 's/==#/is#/g' "$directory"/**/*.vim + sed -i 's/==?/is?/g' "$directory"/**/*.vim + sed -i 's/!=#/isnot#/g' "$directory"/**/*.vim + sed -i 's/!=?/isnot?/g' "$directory"/**/*.vim done fi @@ -80,5 +84,9 @@ check_errors 'let g:ale_\w\+_\w\+_args =' 'Name your option g:ale_<filetype>_<li check_errors 'shellescape(' 'Use ale#Escape instead of shellescape' check_errors 'simplify(' 'Use ale#path#Simplify instead of simplify' check_errors "expand(['\"]%" "Use expand('#' . a:buffer . '...') instead. You might get a filename for the wrong buffer." +check_errors '==#' "Use 'is#' instead of '==#'. 0 ==# 'foobar' is true" +check_errors '==?' "Use 'is?' instead of '==?'. 0 ==? 'foobar' is true" +check_errors '!=#' "Use 'isnot#' instead of '!=#'. 0 !=# 'foobar' is false" +check_errors '!=?' "Use 'isnot?' instead of '!=?'. 0 !=? 'foobar' is false" exit $RETURN_CODE diff --git a/doc/ale-awk.txt b/doc/ale-awk.txt index d3f23ac7..b9c5c34e 100644 --- a/doc/ale-awk.txt +++ b/doc/ale-awk.txt @@ -1,20 +1,20 @@ =============================================================================== -ALE Awk Integration *ale-awk-options* +ALE Awk Integration *ale-awk-options* =============================================================================== -gawk *ale-awk-gawk* +gawk *ale-awk-gawk* -g:ale_awk_gawk_executable *g:ale_awk_gawk_executable* - *b:ale_awk_gawk_executable* +g:ale_awk_gawk_executable *g:ale_awk_gawk_executable* + *b:ale_awk_gawk_executable* Type: |String| Default: `'gawk'` This variable sets executable used for gawk. -g:ale_awk_gawk_options *g:ale_awk_gawk_options* - *b:ale_awk_gawk_options* +g:ale_awk_gawk_options *g:ale_awk_gawk_options* + *b:ale_awk_gawk_options* Type: |String| Default: `''` diff --git a/doc/ale-c.txt b/doc/ale-c.txt index 29208bc8..0c4f8dc9 100644 --- a/doc/ale-c.txt +++ b/doc/ale-c.txt @@ -144,4 +144,23 @@ g:ale_c_gcc_options *g:ale_c_gcc_options* =============================================================================== +clang-format *ale-c-clangformat* + +g:ale_c_clangformat_executable *g:ale_c_clangformat_executable* + *b:ale_c_clangformat_executable* + Type: |String| + Default: `'clang-format'` + + This variable can be changed to use a different executable for clang-format. + + +g:ale_c_clangformat_options *g:ale_c_clangformat_options* + *b:ale_c_clangformat_options* + Type: |String| + Default: `''` + + This variable can be change to modify flags given to clang-format. + + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cpp.txt b/doc/ale-cpp.txt index 854e9b4e..685bb8dc 100644 --- a/doc/ale-cpp.txt +++ b/doc/ale-cpp.txt @@ -5,33 +5,9 @@ ALE C++ Integration *ale-cpp-options* =============================================================================== Global Options -g:ale_c_build_dir_names *g:ale_c_build_dir_names* - *b:ale_c_build_dir_names* +The |g:ale_c_build_dir_names| and |g:ale_c_build_dir| also apply to some C++ +linters too. - Type: |List| - Default: `['build', 'bin']` - - A list of directory names to be used when searching upwards from cpp - files to discover compilation databases with. For directory named `'foo'`, - ALE will search for `'foo/compile_commands.json'` in all directories on and above - the directory containing the cpp file to find path to compilation database. - This feature is useful for the clang tools wrapped around LibTooling (namely - here, clang-tidy) - - -g:ale_c_build_dir *g:ale_c_build_dir* - *b:ale_c_build_dir* - - Type: |String| - Default: `''` - - A path to the directory containing the `compile_commands.json` file to use - with c-family linters. Usually setting this option to a non-empty string - will override the |g:ale_c_build_dir_names| option to impose a compilation - database (it can be useful if multiple builds are in multiple build - subdirectories in the project tree). - This feature is also most useful for the clang tools linters, wrapped - aroung LibTooling (namely clang-tidy here) =============================================================================== clang *ale-cpp-clang* @@ -190,4 +166,11 @@ g:ale_cpp_gcc_options *g:ale_cpp_gcc_options* =============================================================================== +clang-format *ale-cpp-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for C++. + + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-css.txt b/doc/ale-css.txt index effa52ad..b1ab8eb8 100644 --- a/doc/ale-css.txt +++ b/doc/ale-css.txt @@ -13,8 +13,8 @@ g:ale_css_stylelint_executable *g:ale_css_stylelint_executable* See |ale-integrations-local-executables| -g:ale_css_stylelint_options *g:ale_css_stylelint_options* - *b:ale_css_stylelint_options* +g:ale_css_stylelint_options *g:ale_css_stylelint_options* + *b:ale_css_stylelint_options* Type: |String| Default: `''` diff --git a/doc/ale-eruby.txt b/doc/ale-eruby.txt new file mode 100644 index 00000000..b9cd3cbf --- /dev/null +++ b/doc/ale-eruby.txt @@ -0,0 +1,17 @@ +=============================================================================== +ALE Eruby Integration *ale-eruby-options* + +There are two linters for `eruby` files: + +- `erubylint` +- `erubis` + +If you don't know which one your project uses, it's probably `erb`. +To selectively enable one or the other, see |g:ale_linters|. + +(Note that ALE already disables linters if the executable for that linter is +not found; thus, there's probably no need to disable one of these if you're +using the other one.) + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-graphql.txt b/doc/ale-graphql.txt new file mode 100644 index 00000000..5ceb5ca7 --- /dev/null +++ b/doc/ale-graphql.txt @@ -0,0 +1,9 @@ +=============================================================================== +ALE GraphQL Integration *ale-graphql-options* + + +=============================================================================== +gqlint *ale-graphql-gqlint* + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-idris.txt b/doc/ale-idris.txt new file mode 100644 index 00000000..c7500b0d --- /dev/null +++ b/doc/ale-idris.txt @@ -0,0 +1,23 @@ +=============================================================================== +ALE Idris Integration *ale-idris-options* + +=============================================================================== +idris *ale-idris-idris* + +g:ale_idris_idris_executable *g:ale_idris_idris_executable* + *b:ale_idris_idris_executable* + Type: |String| + Default: `'idris'` + + This variable can be changed to change the path to idris. + + +g:ale_idris_idris_options *g:ale_idris_idris_options* + *b:ale_idris_idris_options* + Type: |String| + Default: `'--total --warnpartial --warnreach --warnipkg'` + + This variable can be changed to modify flags given to idris. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-java.txt b/doc/ale-java.txt index 36d606de..13decb41 100644 --- a/doc/ale-java.txt +++ b/doc/ale-java.txt @@ -3,10 +3,10 @@ ALE Java Integration *ale-java-options* =============================================================================== -checkstyle ale-java-checkstyle +checkstyle *ale-java-checkstyle* -g:ale_java_checkstyle_options g:ale_java_checkstyle_options - b:ale_java_checkstyle_options +g:ale_java_checkstyle_options *g:ale_java_checkstyle_options* + *b:ale_java_checkstyle_options* Type: String Default: '-c /google_checks.xml' diff --git a/doc/ale-javascript.txt b/doc/ale-javascript.txt index 4abc6298..95d2504a 100644 --- a/doc/ale-javascript.txt +++ b/doc/ale-javascript.txt @@ -50,6 +50,16 @@ g:ale_javascript_eslint_use_global *g:ale_javascript_eslint_use_global* See |ale-integrations-local-executables| +g:ale_javascript_eslint_suppress_eslintignore + *g:ale_javascript_eslint_suppress_eslintignore* + *b:ale_javascript_eslint_suppress_eslintignore* + Type: |Number| + Default: `0` + + This variable can be set to disable the warning that linting is disabled on + the current file due to being covered by `.eslintignore`. + + =============================================================================== prettier *ale-javascript-prettier* @@ -76,6 +86,12 @@ g:ale_javascript_prettier_use_global *g:ale_javascript_prettier_use_global* See |ale-integrations-local-executables| +g:ale_javascript_prettier_use_local_config *g:ale_javascript_prettier_use_local_config* + *b:ale_javascript_prettier_use_local_config* + Type: |Number| + Default: `0` + + This variable can be set to use the local prettier configuration file. =============================================================================== prettier-eslint *ale-javascript-prettier-eslint* diff --git a/doc/ale-php.txt b/doc/ale-php.txt index 4109673a..e2b0de6f 100644 --- a/doc/ale-php.txt +++ b/doc/ale-php.txt @@ -3,6 +3,38 @@ ALE PHP Integration *ale-php-options* =============================================================================== +hack *ale-php-hack* + +There are no options for this linter. + + +=============================================================================== +langserver *ale-php-langserver* + +g:ale_php_langserver_executable *g:ale_php_langserver_executable* + *b:ale_php_langserver_executable* + Type: |String| + Default: `'php-language-server.php'` + + The variable can be set to configure the executable that will be used for + running the PHP language server. `vendor` directory executables will be + preferred instead of this setting if |g:ale_php_langserver_use_global| is `0`. + + See: |ale-integrations-local-executables| + + +g:ale_php_langserver_use_global *g:ale_php_langserver_use_global* + *b:ale_php_langserver_use_global* + Type: |Number| + Default: `0` + + This variable can be set to `1` to force the language server to be run with + the executable set for |g:ale_php_langserver_executable|. + + See: |ale-integrations-local-executables| + + +=============================================================================== phpcs *ale-php-phpcs* g:ale_php_phpcs_executable *g:ale_php_phpcs_executable* @@ -31,7 +63,7 @@ g:ale_php_phpcs_use_global *g:ale_php_phpcs_use_global* See |ale-integrations-local-executables| ------------------------------------------------------------------------------- +------------------------------------------------------------------------------- phpmd *ale-php-phpmd* g:ale_php_phpmd_ruleset *g:ale_php_phpmd_ruleset* @@ -43,8 +75,8 @@ g:ale_php_phpmd_ruleset *g:ale_php_phpmd_ruleset* the available phpmd rulesets ------------------------------------------------------------------------------- -phpstan *ale-php-stan* +------------------------------------------------------------------------------- +phpstan *ale-php-phpstan* g:ale_php_phpstan_executable *g:ale_php_phpstan_executable* *b:ale_php_phpstan_executable* @@ -62,5 +94,35 @@ g:ale_php_phpstan_level *g:ale_php_phpstan_level* This variable controls the rule levels. 0 is the loosest and 4 is the strictest. + +------------------------------------------------------------------------------- +phpcbf *ale-php-phpcbf* + +g:ale_php_phpcbf_executable *g:ale_php_phpcbf_executable* + *b:ale_php_phpcbf_executable* + Type: |String| + Default: `'phpcbf'` + + See |ale-integrations-local-executables| + + +g:ale_php_phpcbf_standard *g:ale_php_phpcbf_standard* + *b:ale_php_phpcbf_standard* + Type: |String| + Default: `''` + + This variable can be set to specify the coding standard used by phpcbf. If no + coding standard is specified, phpcbf will default to fixing against the + PEAR coding standard, or the standard you have set as the default. + + +g:ale_php_phpcbf_use_global *g:ale_php_phpcbf_use_global* + *b:ale_php_phpcbf_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-python.txt b/doc/ale-python.txt index dabc573c..a8d033e2 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -123,6 +123,35 @@ g:ale_python_mypy_use_global *g:ale_python_mypy_use_global* =============================================================================== +pycodestyle *ale-python-pycodestyle* + + +g:ale_python_pycodestyle_executable *g:ale_python_pycodestyle_executable* + *b:ale_python_pycodestyle_executable* + Type: |String| + Default: `'pycodestyle'` + + See |ale-integrations-local-executables| + + +g:ale_python_pycodestyle_options *g:ale_python_pycodestyle_options* + *b:ale_python_pycodestyle_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + pycodestyle invocation. + + +g:ale_python_pycodestyle_use_global *g:ale_python_pycodestyle_use_global* + *b:ale_python_pycodestyle_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== pylint *ale-python-pylint* g:ale_python_pylint_executable *g:ale_python_pylint_executable* diff --git a/doc/ale-rust.txt b/doc/ale-rust.txt index 1a20757e..d03ab073 100644 --- a/doc/ale-rust.txt +++ b/doc/ale-rust.txt @@ -8,7 +8,7 @@ Integration Information Since Vim does not detect the rust file type out-of-the-box, you need the runtime files for rust from here: https://github.com/rust-lang/rust.vim - Note that there are two possible linters for rust files: + Note that there are three possible linters for Rust files: 1. rustc -- The Rust compiler is used to check the currently edited file. So, if your project consists of multiple files, you will get some errors @@ -18,6 +18,10 @@ Integration Information checked. That means that all errors are properly shown, but cargo can only operate on the files written on disk, so errors will not be reported while you type. + 3. rls -- If you have `rls` installed, you might prefer using this linter + over cargo. rls implements the Language Server Protocol for incremental + compliation of Rust code, and can check Rust files while you type. `rls` + requires Rust files to contained in Cargo projects. Only cargo is enabled by default. To switch to using rustc instead of cargo, configure |g:ale_linters| appropriately: > @@ -35,19 +39,30 @@ cargo *ale-rust-cargo* g:ale_rust_cargo_use_check *g:ale_rust_cargo_use_check* *b:ale_rust_cargo_use_check* Type: |Number| - Default: `1` + Default: `0` When set to `1`, this option will cause ALE to use "cargo check" instead of "cargo build". "cargo check" is supported since version 1.16.0 of Rust. =============================================================================== +rls *ale-rust-rls* + +g:ale_rust_rls_executable *g:ale_rust_rls_executable* + *b:ale_rust_rls_executable* + Type: |String| + Default: `'rls'` + + This variable can be modified to change the executable path for `rls`. + + +=============================================================================== rustc *ale-rust-rustc* g:ale_rust_ignore_error_codes *g:ale_rust_ignore_error_codes* *b:ale_rust_ignore_error_codes* Type: |List| of |String|s - Default: [] + Default: `[]` This variable can contain error codes which will be ignored. For example, to ignore most errors regarding failed imports, put this in your .vimrc diff --git a/doc/ale-thrift.txt b/doc/ale-thrift.txt new file mode 100644 index 00000000..ed858db8 --- /dev/null +++ b/doc/ale-thrift.txt @@ -0,0 +1,46 @@ +=============================================================================== +ALE Thrift Integration *ale-thrift-options* + + +=============================================================================== +thrift *ale-thrift-thrift* + +The `thrift` linter works by compiling the buffer's contents and reporting any +errors reported by the parser and the configured code generator(s). + +g:ale_thrift_thrift_executable *g:ale_thrift_thrift_executable* + *b:ale_thrift_thrift_executable* + Type: |String| + Default: `'thrift'` + + See |ale-integrations-local-executables| + + +g:ale_thrift_thrift_generators *g:ale_thrift_thrift_generators* + *b:ale_thrift_thrift_generators* + Type: |List| of |String|s + Default: `['cpp']` + + This list must contain one or more named code generators. Generator options + can be included as part of each string, e.g. `['py:dynamic']`. + + +g:ale_thrift_thrift_includes *g:ale_thrift_thrift_includes* + *b:ale_thrift_thrift_includes* + Type: |List| of |String|s + Default: `[]` + + This list contains paths that will be searched for thrift `include` + directives. + + +g:ale_thrift_thrift_options *g:ale_thrift_thrift_options* + *b:ale_thrift_thrift_options* + Type: |String| + Default: `'-strict'` + + This variable can be changed to customize the additional command-line + arguments that are passed to the thrift compiler. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-typescript.txt b/doc/ale-typescript.txt index df479c5a..794240ec 100644 --- a/doc/ale-typescript.txt +++ b/doc/ale-typescript.txt @@ -30,6 +30,14 @@ g:ale_typescript_tslint_config_path *g:ale_typescript_tslint_config_path* such path exists, this variable will be used instead. +g:ale_typescript_tslint_rules_dir *g:ale_typescript_tslint_rules_dir* + *b:ale_typescript_tslint_rules_dir* + Type: |String| + Default: `''` + + If this variable is set, ALE will use it as the rules directory for tslint. + + g:ale_typescript_tslint_use_global *g:ale_typescript_tslint_use_global* *b:ale_typescript_tslint_use_global* Type: |Number| diff --git a/doc/ale-xml.txt b/doc/ale-xml.txt index ddbeb31d..6c8af6c7 100644 --- a/doc/ale-xml.txt +++ b/doc/ale-xml.txt @@ -1,20 +1,20 @@ =============================================================================== -ALE XML Integration *ale-xml-options* +ALE XML Integration *ale-xml-options* =============================================================================== -xmllint *ale-xml-xmllint* +xmllint *ale-xml-xmllint* -g:ale_xml_xmllint_executable *g:ale_xml_xmllint_executable* - *b:ale_xml_xmllint_executable* +g:ale_xml_xmllint_executable *g:ale_xml_xmllint_executable* + *b:ale_xml_xmllint_executable* Type: |String| Default: `'xmllint'` This variable can be set to change the path to xmllint. -g:ale_xml_xmllint_options *g:ale_xml_xmllint_options* - *b:ale_xml_xmllint_options* +g:ale_xml_xmllint_options *g:ale_xml_xmllint_options* + *b:ale_xml_xmllint_options* Type: |String| Default: `''` diff --git a/doc/ale.txt b/doc/ale.txt index 944431df..dfdb2693 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -8,10 +8,12 @@ CONTENTS *ale-contents* 1. Introduction.........................|ale-introduction| 2. Supported Languages & Tools..........|ale-support| - 3. Global Options.......................|ale-options| - 3.1 Highlights........................|ale-highlights| + 3. Linting..............................|ale-lint| 4. Fixing Problems......................|ale-fix| - 5. Integration Documentation............|ale-integrations| + 5. Completion...........................|ale-completion| + 6. Global Options.......................|ale-options| + 6.1 Highlights........................|ale-highlights| + 7. Integration Documentation............|ale-integrations| asm...................................|ale-asm-options| gcc.................................|ale-asm-gcc| c.....................................|ale-c-options| @@ -19,6 +21,7 @@ CONTENTS *ale-contents* clangtidy...........................|ale-c-clangtidy| cppcheck............................|ale-c-cppcheck| gcc.................................|ale-c-gcc| + clang-format........................|ale-c-clangformat| chef..................................|ale-chef-options| foodcritic..........................|ale-chef-foodcritic| cpp...................................|ale-cpp-options| @@ -28,6 +31,7 @@ CONTENTS *ale-contents* cppcheck............................|ale-cpp-cppcheck| cpplint.............................|ale-cpp-cpplint| gcc.................................|ale-cpp-gcc| + clang-format........................|ale-cpp-clangformat| css...................................|ale-css-options| stylelint...........................|ale-css-stylelint| cmake.................................|ale-cmake-options| @@ -37,12 +41,15 @@ CONTENTS *ale-contents* erlang................................|ale-erlang-options| erlc................................|ale-erlang-erlc| syntaxerl...........................|ale-erlang-syntaxerl| + eruby.................................|ale-eruby-options| fortran...............................|ale-fortran-options| gcc.................................|ale-fortran-gcc| fusionscript..........................|ale-fuse-options| fusion-lint.........................|ale-fuse-fusionlint| go....................................|ale-go-options| gometalinter........................|ale-go-gometalinter| + graphql...............................|ale-graphql-options| + gqlint..............................|ale-graphql-gqlint| handlebars............................|ale-handlebars-options| ember-template-lint.................|ale-handlebars-embertemplatelint| haskell...............................|ale-haskell-options| @@ -50,6 +57,8 @@ CONTENTS *ale-contents* html..................................|ale-html-options| htmlhint............................|ale-html-htmlhint| tidy................................|ale-html-tidy| + idris.................................|ale-idris-options| + idris...............................|ale-idris-idris| java..................................|ale-java-options| checkstyle..........................|ale-java-checkstyle| javac...............................|ale-java-javac| @@ -76,9 +85,12 @@ CONTENTS *ale-contents* perl................................|ale-perl-perl| perlcritic..........................|ale-perl-perlcritic| php...................................|ale-php-options| + hack................................|ale-php-hack| + langserver..........................|ale-php-langserver| phpcs...............................|ale-php-phpcs| phpmd...............................|ale-php-phpmd| phpstan.............................|ale-php-phpstan| + phpcbf..............................|ale-php-phpcbf| pug...................................|ale-pug-options| puglint.............................|ale-pug-puglint| python................................|ale-python-options| @@ -86,6 +98,7 @@ CONTENTS *ale-contents* flake8..............................|ale-python-flake8| isort...............................|ale-python-isort| mypy................................|ale-python-mypy| + pycodestyle.........................|ale-python-pycodestyle| pylint..............................|ale-python-pylint| yapf................................|ale-python-yapf| ruby..................................|ale-ruby-options| @@ -95,6 +108,7 @@ CONTENTS *ale-contents* rubocop.............................|ale-ruby-rubocop| rust..................................|ale-rust-options| cargo...............................|ale-rust-cargo| + rls.................................|ale-rust-rls| rustc...............................|ale-rust-rustc| sass..................................|ale-sass-options| stylelint...........................|ale-sass-stylelint| @@ -114,6 +128,8 @@ CONTENTS *ale-contents* tex...................................|ale-tex-options| chktex..............................|ale-tex-chktex| lacheck.............................|ale-tex-lacheck| + thrift................................|ale-thrift-options| + thrift..............................|ale-thrift-thrift| typescript............................|ale-typescript-options| eslint..............................|ale-typescript-eslint| tslint..............................|ale-typescript-tslint| @@ -128,10 +144,10 @@ CONTENTS *ale-contents* yaml..................................|ale-yaml-options| swaglint............................|ale-yaml-swaglint| yamllint............................|ale-yaml-yamllint| - 6. Commands/Keybinds....................|ale-commands| - 7. API..................................|ale-api| - 8. Special Thanks.......................|ale-special-thanks| - 9. Contact..............................|ale-contact| + 8. Commands/Keybinds....................|ale-commands| + 9. API..................................|ale-api| + 10. Special Thanks......................|ale-special-thanks| + 11. Contact.............................|ale-contact| =============================================================================== 1. Introduction *ale-introduction* @@ -162,79 +178,247 @@ for the current buffer. The following languages and tools are supported. -* ASM: 'gcc' -* Ansible: 'ansible-lint' -* Asciidoc: 'proselint' -* Bash: 'shell' (-n flag), 'shellcheck' -* Bourne Shell: 'shell' (-n flag), 'shellcheck' -* C: 'cppcheck', 'gcc', 'clang' -* C++ (filetype cpp): 'clang', 'clangtidy', 'cppcheck', 'cpplint', 'gcc' -* C#: 'mcs' -* Chef: 'foodcritic' -* CMake: 'cmakelint' -* CoffeeScript: 'coffee', 'coffelint' -* Crystal: 'crystal' -* CSS: 'csslint', 'stylelint' -* Cython (pyrex filetype): 'cython' -* D: 'dmd' -* Dart: 'dartanalyzer' -* Dockerfile: 'hadolint' -* Elixir: 'credo', 'dogma' -* Elm: 'elm-make' -* Erlang: 'erlc' -* Fortran: 'gcc' -* Go: 'gofmt', 'go vet', 'golint', 'go build', 'gosimple', 'staticcheck' -* FusionScript: 'fusion-lint' -* Haml: 'hamllint' -* Handlebars: 'ember-template-lint' -* Haskell: 'ghc', 'stack-ghc', 'stack-build', 'ghc-mod', 'stack-ghc-mod', 'hlint', 'hdevtools' -* HTML: 'HTMLHint', 'proselint', 'tidy' -* Java: 'javac' -* JavaScript: 'eslint', 'jscs', 'jshint', 'flow', 'prettier', 'prettier-eslint', 'xo' -* JSON: 'jsonlint' -* Kotlin: 'kotlinc' -* LaTeX (tex): 'chktex', 'lacheck', 'proselint' -* Lua: 'luacheck' -* Markdown: 'mdl', 'proselint', 'vale' -* MATLAB: 'mlint' -* nim: 'nim check' -* nix: 'nix-instantiate' -* nroff: 'proselint' -* Objective-C: 'clang' -* Objective-C++: 'clang' -* OCaml: 'merlin' (see |ale-linter-integration-ocaml-merlin|) -* Perl: 'perl' (-c flag), 'perlcritic' -* PHP: 'hack', 'php' (-l flag), 'phpcs', 'phpmd', 'phpstan' -* Pod: 'proselint' -* Pug: 'pug-lint' -* Puppet: 'puppet', 'puppet-lint' -* Python: 'autopep8', 'flake8', 'isort', 'mypy', 'pylint', 'yapf' -* R: 'lintr' -* ReasonML: 'merlin' -* reStructuredText: 'proselint' -* RPM spec: 'spec' -* Rust: 'rustc' (see |ale-integration-rust|) -* Ruby: 'reek', 'rubocop' -* SASS: 'sasslint', 'stylelint' -* SCSS: 'sasslint', 'scsslint', 'stylelint' -* Scala: 'scalac', 'scalastyle' -* Slim: 'slim-lint' -* SML: 'smlnj' -* Stylus: 'stylelint' -* SQL: 'sqlint' -* Swift: 'swiftlint' -* Texinfo: 'proselint' -* Text: 'proselint', 'vale' -* TypeScript: 'eslint', 'tslint', 'tsserver', 'typecheck' -* Verilog: 'iverilog', 'verilator' -* Vim: 'vint' -* Vim help: 'proselint' -* XHTML: 'proselint' -* XML: 'xmllint' -* YAML: 'swaglint', 'yamllint' +Notes: + +`^` No linters for text or Vim help filetypes are enabled by default. +`!!` These linters check only files on disk. See |ale-lint-file-linters| + +* ASM: `gcc` +* Ansible: `ansible-lint` +* AsciiDoc: `proselint` +* Awk: `gawk` +* Bash: `shell` (-n flag), `shellcheck` +* Bourne Shell: `shell` (-n flag), `shellcheck` +* C: `cppcheck`, `cpplint`!!, `gcc`, `clang`, `clangtidy`!!, `clang-format` +* C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `cppcheck`, `cpplint`!!, `gcc`, `clang-format` +* C#: `mcs` +* Chef: `foodcritic` +* CMake: `cmakelint` +* CoffeeScript: `coffee`, `coffeelint` +* Crystal: `crystal`!! +* CSS: `csslint`, `stylelint` +* Cython (pyrex filetype): `cython` +* D: `dmd` +* Dart: `dartanalyzer` +* Dockerfile: `hadolint` +* Elixir: `credo`, `dogma`!! +* Elm: `elm-make` +* Erb: `erb`, `erubis` +* Erlang: `erlc`, `SyntaxErl` +* Fortran: `gcc` +* FusionScript: `fusion-lint` +* Go: `gofmt`, `go vet`, `golint`, `gometalinter`!!, `go build`!!, `gosimple`, `staticcheck` +* GraphQL: `gqlint` +* Haml: `haml-lint` +* Handlebars: `ember-template-lint` +* Haskell: `ghc`, `stack-ghc`, `stack-build`!!, `ghc-mod`, `stack-ghc-mod`, `hlint`, `hdevtools` +* HTML: `HTMLHint`, `proselint`, `tidy` +* Idris: `idris` +* Java: `checkstyle`, `javac` +* JavaScript: `eslint`, `jscs`, `jshint`, `flow`, `prettier`, `prettier-eslint`, `prettier-standard`, `standard`, `xo` +* JSON: `jsonlint` +* Kotlin: `kotlinc`, `ktlint` +* LaTeX (tex): `chktex`, `lacheck`, `proselint` +* Lua: `luacheck` +* Markdown: `mdl`, `proselint`, `vale` +* MATLAB: `mlint` +* Nim: `nim check`!! +* nix: `nix-instantiate` +* nroff: `proselint` +* Objective-C: `clang` +* Objective-C++: `clang` +* OCaml: `merlin` (see |ale-ocaml-merlin|) +* Perl: `perl -c`, `perl-critic` +* PHP: `hack`, `langserver`, `php -l`, `phpcs`, `phpmd`, `phpstan`, `phpcbf` +* Pod: `proselint` +* Pug: `pug-lint` +* Puppet: `puppet`, `puppet-lint` +* Python: `autopep8`, `flake8`, `isort`, `mypy`, `pycodestyle`, `pylint`!!, `yapf` +* R: `lintr` +* ReasonML: `merlin` +* reStructuredText: `proselint` +* RPM spec: `rpmlint` +* Ruby: `brakeman`, `rails_best_practices`!!, `reek`, `rubocop`, `ruby` +* Rust: `cargo`!!, `rls`, `rustc` (see |ale-integration-rust|) +* SASS: `sass-lint`, `stylelint` +* SCSS: `sass-lint`, `scss-lint`, `stylelint` +* Scala: `scalac`, `scalastyle` +* Slim: `slim-lint` +* SML: `smlnj` +* Stylus: `stylelint` +* SQL: `sqlint` +* Swift: `swiftlint`, `swiftformat` +* Tcl: `nagelfar`!! +* Texinfo: `proselint` +* Text^: `proselint`, `vale` +* Thrift: `thrift` +* TypeScript: `eslint`, `tslint`, `tsserver`, `typecheck` +* Verilog: `iverilog`, `verilator` +* Vim: `vint` +* Vim help^: `proselint` +* XHTML: `proselint` +* XML: `xmllint` +* YAML: `swaglint`, `yamllint` =============================================================================== -3. Global Options *ale-options* +3. Linting *ale-lint* + +ALE's primary focus is on checking for problems with your code with various +programs via some Vim code for integrating with those programs, referred to +as 'linters.' ALE supports a wide array of programs for linting by default, +but additional programs can be added easily by defining files in |runtimepath| +with the filename pattern `ale_linters/<filetype>/<filename>.vim`. For more +information on defining new linters, see the extensive documentation +for |ale#linter#Define()|. + +Without any configuration, ALE will attempt to check all of the code for every +file you open in Vim with all available tools by default. To see what ALE +is doing, and what options have been set, try using the |:ALEInfo| command. + +Most of the linters ALE runs will check the Vim buffer you are editing instead +of the file on disk. This allows you to check your code for errors before you +have even saved your changes. ALE will check your code in the following +circumstances, which can be configured with the associated options. + +* When you modify a buffer. - |g:ale_lint_on_text_changed| +* When you open a new or modified buffer. - |g:ale_lint_on_enter| +* When you save a buffer. - |g:ale_lint_on_save| +* When the filetype changes for a buffer. - |g:ale_lint_on_filetype_changed| +* If ALE is used to check ccode manually. - |:ALELint| + +In addition to the above options, ALE can also check buffers for errors when +you leave insert mode with |g:ale_lint_on_insert_leave|, which is off by +default. It is worth reading the documentation for every option. + + *ale-lint-file-linters* + +Some programs must be run against files which have been saved to disk, and +simply do not support reading temporary files or stdin, either of which are +required for ALE to be able to check for errors as you type. The programs +which behave this way are documented in the lists and tables of supported +programs. ALE will only lint files with these programs in the following +circumstances. + +* When you open a new or modified buffer. - |g:ale_lint_on_enter| +* When you save a buffer. - |g:ale_lint_on_save| +* When the filetype changes for a buffer. - |g:ale_lint_on_filetype_changed| +* If ALE is used to check ccode manually. - |:ALELint| + +ALE will report problems with your code in the following ways, listed with +their relevant options. + +* By updating loclist. (On by default) - |g:ale_set_loclist| +* By updating quickfix. (Off by default) - |g:ale_set_quickfix| +* By setting error highlights. - |g:ale_set_highlights| +* By creating signs in the sign column. - |g:ale_set_signs| +* By echoing messages based on your cursor. - |g:ale_echo_cursor| +* By showing balloons for your mouse cursor - |g:ale_set_balloons| + +Please consult the documentation for each option, which can reveal some other +ways of tweaking the behaviour of each way of displaying problems. You can +disable or enable whichever options you prefer. + +Most settings can be configured for each buffer. (|b:| instead of |g:|), +including disabling ALE for certain buffers with |b:ale_enabled|. The +|g:ale_pattern_options| setting can be used to configure files differently +based on regular expressions for filenames. For configuring entire projects, +the buffer-local options can be used with external plugins for reading Vim +project configuration files. + + +=============================================================================== +4. Fixing Problems *ale-fix* + +ALE can fix problems with files with the |ALEFix| command. When |ALEFix| is +run, the variable |g:ale_fixers| will be read for getting a |List| of commands +for filetypes, split on `.`, and the functions named in |g:ale_fixers| will be +executed for fixing the errors. + +The |ALEFixSuggest| command can be used to suggest tools that be used to +fix problems for the current buffer. + +The values for `g:ale_fixers` can be a list of |String|, |Funcref|, or +|lambda| values. String values must either name a function, or a short name +for a function set in the ALE fixer registry. + +Each function for fixing errors must accept either one argument `(buffer)` or +two arguments `(buffer, lines)`, representing the buffer being fixed and the +lines to fix. The functions must return either `0`, for changing nothing, a +|List| for new lines to set, or a |Dictionary| for describing a command to be +run in the background. + +Functions receiving a variable number of arguments will not receive the second +argument `lines`. Functions should name two arguments if the `lines` argument +is desired. This is required to avoid unnecessary copying of the lines of +the buffers being checked. + +When a |Dictionary| is returned for an |ALEFix| callback, the following keys +are supported for running the commands. + + `command` A |String| for the command to run. This key is required. + + When `%t` is included in a command string, a temporary + file will be created, containing the lines from the file + after previous adjustment have been done. + + `read_temporary_file` When set to `1`, ALE will read the contents of the + temporary file created for `%t`. This option can be used + for commands which need to modify some file on disk in + order to fix files. + + *ale-fix-configuration* + +Synchronous functions and asynchronous jobs will be run in a sequence for +fixing files, and can be combined. For example: +> + let g:ale_fixers = { + \ 'javascript': [ + \ 'DoSomething', + \ 'eslint', + \ {buffer, lines -> filter(lines, 'v:val !=~ ''^\s*//''')}, + \ ], + \} + + ALEFix +< +The above example will call a function called `DoSomething` which could act +upon some lines immediately, then run `eslint` from the ALE registry, and +then call a lambda function which will remove every single line comment +from the file. + +For convenience, a plug mapping is defined for |ALEFix|, so you can set up a +keybind easily for fixing files. > + + " Bind F8 to fixing problems with ALE + nmap <F8> <Plug>(ale_fix) +< +Files can be fixed automatically with the following options, which are all off +by default. + +|g:ale_fix_on_save| - Fix files when they are saved. + + +=============================================================================== +5. Completion *ale-completion* + +ALE offers some limited support for automatic completion of code while you +type. Completion is only supported via Language Server Protocol servers which +ALE can connect to for linting, which can offer good built-in support for +suggesting completion information. ALE will only suggest symbols for +completion for LSP linters that are enabled. + +NOTE: At the moment, only `tsserver` for TypeScript code is supported for +completion. + +Suggestions will be made while you type after completion is enabled. +Completion can be enabled by setting |g:ale_completion_enabled| to `1`. The +delay for completion can be configured with |g:ale_completion_delay|. ALE will +only suggest so many possible matches for completion. The maximum number of +items can be controlled with |g:ale_completion_max_suggestions|. + + +=============================================================================== +6. Global Options *ale-options* g:airline#extensions#ale#enabled *g:airline#extensions#ale#enabled* @@ -266,6 +450,44 @@ g:ale_change_sign_column_color *g:ale_change_sign_column_color* windows. +g:ale_completion_delay *g:ale_completion_delay* + + Type: |Number| + Default: `100` + + The number of milliseconds before ALE will send a request to a language + server for completions after you have finished typing. + + See |ale-completion| + + +g:ale_completion_enabled *g:ale_completion_enabled* + + Type: |Number| + Default: `0` + + When this option is set to `1`, completion support will be enabled. + + See |ale-completion| + + +g:ale_completion_max_suggestions *g:ale_completion_max_suggestions* + + Type: |Number| + Default: `50` + + The maximum number of items ALE will suggest in completion menus for + automatic completion. + + Setting this number higher will require more processing time, and may + suggest too much noise. Setting this number lower will require less + processing time, but some suggestions will not be included, so you might not + be able to see the suggestions you want. + + Adjust this option as needed, depending on the complexity of your codebase + and your available processing power. + + g:ale_echo_cursor *g:ale_echo_cursor* Type: |Number| @@ -285,8 +507,8 @@ g:ale_echo_msg_error_str *g:ale_echo_msg_error_str* Default: `Error` The string used for error severity in the echoed message. - Note |`g:ale_echo_cursor`| should be set to 1 - Note |`g:ale_echo_msg_format`| should contain the `%severity%` handler + Note |g:ale_echo_cursor| should be set to 1 + Note |g:ale_echo_msg_format| should contain the `%severity%` handler g:ale_echo_msg_format *g:ale_echo_msg_format* @@ -298,7 +520,7 @@ g:ale_echo_msg_format *g:ale_echo_msg_format* error message itself, and it can contain the following handlers: - `%linter%` for linter's name - `%severity%` for the type of severity - Note |`g:ale_echo_cursor`| should be setted to 1 + Note |g:ale_echo_cursor| should be setted to 1 g:ale_echo_msg_warning_str *g:ale_echo_msg_warning_str* @@ -307,8 +529,8 @@ g:ale_echo_msg_warning_str *g:ale_echo_msg_warning_str* Default: `Warning` The string used for warning severity in the echoed message. - Note |`g:ale_echo_cursor`| should be set to 1 - Note |`g:ale_echo_msg_format`| should contain the `%severity%` handler + Note |g:ale_echo_cursor| should be set to 1 + Note |g:ale_echo_msg_format| should contain the `%severity%` handler g:ale_emit_conflict_warnings *g:ale_emit_conflict_warnings* @@ -396,7 +618,7 @@ g:ale_history_log_output *g:ale_history_log_output* g:ale_keep_list_window_open *g:ale_keep_list_window_open* - + *b:ale_keep_list_window_open* Type: |Number| Default: `0` @@ -405,7 +627,7 @@ g:ale_keep_list_window_open *g:ale_keep_list_window_open* the loclist or quicfix windows will be closed automatically when there are no warnings or errors. - See: |g:ale_open_list| + See |g:ale_open_list| g:ale_list_window_size *g:ale_list_window_size* @@ -445,6 +667,11 @@ g:ale_lint_on_enter *g:ale_lint_on_enter* files have been changed outside of Vim. If a file is changed outside of Vim, it will be checked when it is next opened. + A |BufWinLeave| event will be used to look for the |E924|, |E925|, or |E926| + errors after moving from a loclist or quickfix window to a new buffer. If + prompts for these errors are opened after moving to new buffers, then ALE + will automatically send the `<CR>` key needed to close the prompt. + g:ale_lint_on_filetype_changed *g:ale_lint_on_filetype_changed* @@ -598,13 +825,17 @@ g:ale_maximum_file_size *g:ale_maximum_file_size* g:ale_open_list *g:ale_open_list* - - Type: |Number| + *b:ale_open_list* + Type: |Number| or |String| Default: `0` - When set to `1`, this will cause ALE to automatically open a window for - the loclist (|lopen|) or for the quickfix list instead if - |g:ale_set_quickfix| is `1`. (|copen|) + When set to `1`, this will cause ALE to automatically open a window for the + loclist (|lopen|) or for the quickfix list instead if |g:ale_set_quickfix| + is `1`. (|copen|) + + When set to `'on_save'`, ALE will only open the loclist after buffers have + been saved. The list will be opened some time after buffers are saved and + any linter for a buffer returns results. The window will be kept open until all warnings or errors are cleared, including those not set by ALE, unless |g:ale_keep_list_window_open| is set @@ -689,9 +920,12 @@ g:ale_set_quickfix *g:ale_set_quickfix* Type: |Number| Default: `0` - When this option is set to `1`, the |quickfix| list will be populated with any - warnings and errors which are found by ALE, instead of the |loclist|. The - loclist will never be populated when this option is on. + When this option is set to `1`, the |quickfix| list will be populated with + any problems which are found by ALE, instead of the |loclist|. The loclist + will never be populated when this option is on. + + Problems from every buffer ALE has checked will be included in the quickfix + list, which can be checked with |:copen|. Problems will be de-duplicated. g:ale_set_signs *g:ale_set_signs* @@ -856,7 +1090,7 @@ b:ale_warn_about_trailing_whitespace *b:ale_warn_about_trailing_whitespace* ------------------------------------------------------------------------------- -3.1. Highlights *ale-highlights* +6.1. Highlights *ale-highlights* ALEError *ALEError* @@ -950,79 +1184,7 @@ ALEWarningSign *ALEWarningSign* =============================================================================== -4. Fixing Problems *ale-fix* - -ALE can fix problems with files with the |ALEFix| command. When |ALEFix| is -run, the variable |g:ale_fixers| will be read for getting a |List| of commands -for filetypes, split on `.`, and the functions named in |g:ale_fixers| will be -executed for fixing the errors. - -The |ALEFixSuggest| command can be used to suggest tools that be used to -fix problems for the current buffer. - -The values for `g:ale_fixers` can be a list of |String|, |Funcref|, or -|lambda| values. String values must either name a function, or a short name -for a function set in the ALE fixer registry. - -Each function for fixing errors must accept either one argument `(buffer)` or -two arguments `(buffer, lines)`, representing the buffer being fixed and the -lines to fix. The functions must return either `0`, for changing nothing, a -|List| for new lines to set, or a |Dictionary| for describing a command to be -run in the background. - -Functions receiving a variable number of arguments will not receive the second -argument `lines`. Functions should name two arguments if the `lines` argument -is desired. This is required to avoid unnecessary copying of the lines of -the buffers being checked. - -When a |Dictionary| is returned for an |ALEFix| callback, the following keys -are supported for running the commands. - - `command` A |String| for the command to run. This key is required. - - When `%t` is included in a command string, a temporary - file will be created, containing the lines from the file - after previous adjustment have been done. - - `read_temporary_file` When set to `1`, ALE will read the contents of the - temporary file created for `%t`. This option can be used - for commands which need to modify some file on disk in - order to fix files. - - *ale-fix-configuration* - -Synchronous functions and asynchronous jobs will be run in a sequence for -fixing files, and can be combined. For example: -> - let g:ale_fixers = { - \ 'javascript': [ - \ 'DoSomething', - \ 'eslint', - \ {buffer, lines -> filter(lines, 'v:val !=~ ''^\s*//''')}, - \ ], - \} - - ALEFix -< -The above example will call a function called `DoSomething` which could act -upon some lines immediately, then run `eslint` from the ALE registry, and -then call a lambda function which will remove every single line comment -from the file. - -For convenience, a plug mapping is defined for |ALEFix|, so you can set up a -keybind easily for fixing files. > - - " Bind F8 to fixing problems with ALE - nmap <F8> <Plug>(ale_fix) -< -Files can be fixed automatically with the following options, which are all off -by default. - -|g:ale_fix_on_save| - Fix files when they are saved. - - -=============================================================================== -5. Integration Documentation *ale-integrations* +7. Integration Documentation *ale-integrations* Linter and fixer options are documented in individual help files. See the table of contents at |ale-contents|. @@ -1055,7 +1217,7 @@ ALE will use to search for Python executables. =============================================================================== -6. Commands/Keybinds *ale-commands* +8. Commands/Keybinds *ale-commands* ALEFix *ALEFix* @@ -1071,6 +1233,7 @@ ALEFixSuggest *ALEFixSuggest* See |ale-fix| for more information. + *:ALELint* ALELint *ALELint* Run ALE once for the current buffer. This command can be used to run ALE @@ -1140,6 +1303,7 @@ ALEDetail *ALEDetail* A plug mapping `<Plug>(ale_detail)` is defined for this command. + *:ALEInfo* ALEInfo *ALEInfo* ALEInfoToClipboard *ALEInfoToClipboard* @@ -1158,9 +1322,9 @@ ALEInfoToClipboard *ALEInfoToClipboard* =============================================================================== -7. API *ale-api* +9. API *ale-api* -ale#Queue(delay, [linting_flag]) *ale#Queue()* +ale#Queue(delay, [linting_flag, buffer_number]) *ale#Queue()* Run linters for the current buffer, based on the filetype of the buffer, with a given `delay`. A `delay` of `0` will run the linters immediately. @@ -1171,6 +1335,15 @@ ale#Queue(delay, [linting_flag]) *ale#Queue()* is `'lint_file'`, then linters where the `lint_file` option is set to `1` will be run. Linters with `lint_file` set to `1` are not run by default. + An optional `buffer_number` argument can be given for specifying the buffer + to check. The active buffer (`bufnr('')`) will be checked by default. + + *ale-cool-down* + If an exception is thrown when queuing/running ALE linters, ALE will enter + a cool down period where it will stop checking anything for a short period + of time. This is to prevent ALE from seriously annoying users if a linter + is broken, or when developing ALE itself. + ale#engine#CreateDirectory(buffer) *ale#engine#CreateDirectory()* @@ -1262,7 +1435,10 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* search by in future before being passed on to the |loclist|, etc. - This argument is required. + This argument is required, unless the linter is an + LSP linter. In which case, this argument must not be + defined, as LSP linters handle diangostics + automatically. See |ale-lsp-linters|. The keys for each item in the List will be handled in the following manner: @@ -1283,9 +1459,27 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* `end_lnum` - An optional end line number. This key can set along with `end_col` for highlighting multi-line problems. - `bufnr` - The buffer number should match the buffer - being checked, and this value will default to - the buffer being checked. + `bufnr` - This key represents the buffer number the + problems are for. This value will default to + the buffer number being checked. + + The `filename` key can be set instead of this key, + and then the eventual `bufnr` value in the final + list will either represent the number for an open + buffer or `-1` for a file not open in any buffer. + `filename` - An optional filename for the file the + problems are for. This should be an absolute path to + a file. + + Problems for files which have not yet been opened + will be set in those files after they are opened + and have been checked at least once. + + Temporary files in directories used for Vim + temporary files with `tempname()` will be asssumed + to be the buffer being checked, unless the `bufnr` + key is also set with a valid number for some other + buffer. `vcol` - Defaults to `0`. `type` - Defaults to `'E'`. `nr` - Defaults to `-1`. @@ -1393,10 +1587,47 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* be set automatically to `0`. The two options cannot be used together. + *ale-lsp-linters* + `lsp` A |String| for defining LSP (Language Server Protocol) + linters. + + This argument may be omitted or `''` when a linter + does not represent an LSP linter. + + When this argument is set to `'stdio'`, then the + linter will be defined as an LSP linter which keeps a + process for a language server runnning, and + communicates with it directly via a |channel|. + + When this argument is not empty, then the + `project_callback` and `language_callback` arguments + must also be defined. + + LSP linters handle diagonstics automatically, so + the `callback` argument must not be defined. + + `project_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned representing the path to the project for the + file being checked with the language server. If an + empty string is returned, the file will not be + checked at all. + + This argument must only be set if the `lsp` argument + is also set to a non-empty string. + + `language_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned representing the name of the language being + checked. + + This argument must only be set if the `lsp` argument + is also set to a non-empty string. + `aliases` A |List| of aliases for the linter name. - This option can be set with alternative names for - for selecting the linter with |g:ale_linters|. This + This argument can be set with alternative names for + selecting the linter with |g:ale_linters|. This setting can make it easier to guess the linter name by offering a few alternatives. @@ -1499,13 +1730,13 @@ ALELint *ALELint-autocmd* echoing messges. =============================================================================== -8. Special Thanks *ale-special-thanks* +10. Special Thanks *ale-special-thanks* Special thanks to Mark Grealish (https://www.bhalash.com/) for providing ALE's snazzy looking ale glass logo. Cheers, Mark! =============================================================================== -9. Contact *ale-contact* +11. Contact *ale-contact* If you like this plugin, and wish to get in touch, check out the GitHub page for issues and more at https://github.com/w0rp/ale @@ -1513,10 +1744,8 @@ page for issues and more at https://github.com/w0rp/ale If you wish to contact the author of this plugin directly, please feel free to send an email to devw0rp@gmail.com. - Please drink responsibly, or not at all, which is ironically the preference of w0rp, who is teetotal. - vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/plugin/ale.vim b/plugin/ale.vim index 79e6836b..a9ab88a1 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -189,8 +189,8 @@ call ale#Set('type_map', {}) " Enable automatic completion with LSP servers and tsserver call ale#Set('completion_enabled', 0) -call ale#Set('completion_delay', 300) -call ale#Set('completion_max_suggestions', 20) +call ale#Set('completion_delay', 100) +call ale#Set('completion_max_suggestions', 50) function! ALEInitAuGroups() abort " This value used to be a Boolean as a Number, and is now a String. @@ -206,11 +206,11 @@ function! ALEInitAuGroups() abort augroup ALERunOnTextChangedGroup autocmd! if g:ale_enabled - if l:text_changed ==? 'always' || l:text_changed ==# '1' + if l:text_changed is? 'always' || l:text_changed is# '1' autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay) - elseif l:text_changed ==? 'normal' + elseif l:text_changed is? 'normal' autocmd TextChanged * call ale#Queue(g:ale_lint_delay) - elseif l:text_changed ==? 'insert' + elseif l:text_changed is? 'insert' autocmd TextChangedI * call ale#Queue(g:ale_lint_delay) endif endif @@ -218,27 +218,26 @@ function! ALEInitAuGroups() abort augroup ALERunOnEnterGroup autocmd! + if g:ale_enabled + " Handle everything that needs to happen when buffers are entered. + autocmd BufEnter * call ale#events#EnterEvent(str2nr(expand('<abuf>'))) + endif if g:ale_enabled && g:ale_lint_on_enter autocmd BufWinEnter,BufRead * call ale#Queue(0, 'lint_file', str2nr(expand('<abuf>'))) " Track when the file is changed outside of Vim. autocmd FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand('<abuf>'))) - " If the file has been changed, then check it again on enter. - autocmd BufEnter * call ale#events#EnterEvent(str2nr(expand('<abuf>'))) endif augroup END augroup ALERunOnFiletypeChangeGroup autocmd! if g:ale_enabled && g:ale_lint_on_filetype_changed - " Set the filetype after a buffer is opened or read. - autocmd BufEnter,BufRead * let b:ale_original_filetype = &filetype " Only start linting if the FileType actually changes after " opening a buffer. The FileType will fire when buffers are opened. - autocmd FileType * - \ if has_key(b:, 'ale_original_filetype') - \ && b:ale_original_filetype !=# expand('<amatch>') - \| call ale#Queue(300, 'lint_file') - \| endif + autocmd FileType * call ale#events#FileTypeEvent( + \ str2nr(expand('<abuf>')), + \ expand('<amatch>') + \) endif augroup END @@ -296,12 +295,17 @@ function! s:ALEToggle() abort call ale#balloon#Enable() endif else - " Make sure the buffer number is a number, not a string, - " otherwise things can go wrong. - for l:buffer in map(keys(g:ale_buffer_info), 'str2nr(v:val)') - " Stop all jobs and clear the results for everything, and delete - " all of the data we stored for the buffer. - call ale#engine#Cleanup(l:buffer) + for l:key in keys(g:ale_buffer_info) + " The key could be a filename or a buffer number, so try and + " convert it to a number. We need a number for the other + " functions. + let l:buffer = str2nr(l:key) + + if l:buffer > 0 + " Stop all jobs and clear the results for everything, and delete + " all of the data we stored for the buffer. + call ale#engine#Cleanup(l:buffer) + endif endfor " Remove highlights for the current buffer now. @@ -55,6 +55,12 @@ while [ $# -ne 0 ]; do run_custom_checks=0 shift ;; + --custom-checks-only) + run_vim_tests=0 + run_neovim_tests=0 + run_vint=0 + shift + ;; --) shift break @@ -76,6 +82,9 @@ if [ $# -ne 0 ]; then tests="$*" fi +# Delete .swp files in the test directory, which cause Vim 8 to hang. +find test -name '*.swp' -delete + docker images -q w0rp/ale | grep "^$CURRENT_IMAGE_ID" > /dev/null \ || docker pull "$IMAGE" @@ -211,6 +220,41 @@ if ((run_custom_checks)); then echo grep --exclude=tags -roh '\*.*\*$' doc | sort | uniq -d || EXIT=$? + + echo '========================================' + echo 'Checking for invalid tag references' + echo '========================================' + echo 'Invalid tag references tags follow:' + echo + + tag_regex='[gb]\?:\?\(ale\|ALE\)[a-zA-Z_\-]\+' + + # Grep for tags and references, and complain if we find a reference without + # a tag for the reference. Only our tags will be included. + diff -u \ + <(grep --exclude=tags -roh "\*$tag_regex\*" doc | sort -u | sed 's/*//g') \ + <(grep --exclude=tags -roh "|$tag_regex|" doc | sort -u | sed 's/|//g') \ + | grep '^+[^+]' && EXIT=1 + + echo '========================================' + echo 'diff README.md and doc/ale.txt tables' + echo '========================================' + echo 'Differences follow:' + echo + + ./check-supported-tools-tables || EXIT=$? + + echo '========================================' + echo 'Look for badly aligned doc tags' + echo '========================================' + echo 'Badly aligned tags follow:' + echo + + # Documentation tags need to be aligned to the right margin, so look for + # tags which aren't at the right margin. + grep ' \*[^*]\+\*$' doc/ -r \ + | awk '{ sep = index($0, ":"); if (length(substr($0, sep + 1 )) < 79) { print } }' \ + | grep . && EXIT=1 fi exit $EXIT diff --git a/test/command_callback/c_paths/dummy.c b/test/command_callback/c_paths/dummy.c new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/c_paths/dummy.c diff --git a/test/command_callback/php_paths/project-with-phpcbf/foo/test.php b/test/command_callback/php_paths/project-with-phpcbf/foo/test.php new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php_paths/project-with-phpcbf/foo/test.php diff --git a/test/command_callback/php_paths/project-with-phpcbf/vendor/bin/phpcbf b/test/command_callback/php_paths/project-with-phpcbf/vendor/bin/phpcbf new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php_paths/project-with-phpcbf/vendor/bin/phpcbf diff --git a/test/command_callback/php_paths/project-without-phpcbf/foo/test.php b/test/command_callback/php_paths/project-without-phpcbf/foo/test.php new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php_paths/project-without-phpcbf/foo/test.php diff --git a/test/command_callback/standard-test-files/with-bin/node_modules/.bin/standard b/test/command_callback/standard-test-files/with-bin/node_modules/.bin/standard new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/standard-test-files/with-bin/node_modules/.bin/standard diff --git a/test/command_callback/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js b/test/command_callback/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js diff --git a/test/command_callback/swift_paths/dummy.swift b/test/command_callback/swift_paths/dummy.swift new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/swift_paths/dummy.swift diff --git a/test/command_callback/test_erlang_syntaxerl_command_callback.vader b/test/command_callback/test_erlang_syntaxerl_command_callback.vader new file mode 100644 index 00000000..1df2be39 --- /dev/null +++ b/test/command_callback/test_erlang_syntaxerl_command_callback.vader @@ -0,0 +1,58 @@ +Before: + Save g:ale_erlang_syntaxerl_executable + + unlet! g:ale_erlang_syntaxerl_executable + unlet! b:ale_erlang_syntaxerl_executable + + runtime ale_linters/erlang/syntaxerl.vim + + +After: + Restore + + call ale#linter#Reset() + + +Execute (The executable should be correct): + AssertEqual 'syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + AssertEqual '/some/other/syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + AssertEqual '/yet/another/syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + +Execute (The executable should be presented in the feature check command): + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + AssertEqual "'/some/other/syntaxerl' -h", ale_linters#erlang#syntaxerl#FeatureCheck(bufnr('')) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + AssertEqual "'/yet/another/syntaxerl' -h", ale_linters#erlang#syntaxerl#FeatureCheck(bufnr('')) + + +Execute (The executable should be presented in the command): + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + AssertEqual "'/some/other/syntaxerl' %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), []) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + AssertEqual "'/yet/another/syntaxerl' %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), []) + + +Execute (The -b option should be used when available): + AssertEqual "'syntaxerl' %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), [ + \ 'Syntax checker for Erlang (0.14.0)', + \ 'Usage: syntaxerl [-d | --debug] <FILENAME>', + \ ' syntaxerl <-h | --help>', + \ ' -d, --debug Enable debug output', + \ ' -h, --help Show this message', + \ ]) + + AssertEqual "'syntaxerl' -b %s %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), [ + \ 'Syntax checker for Erlang (0.14.0)', + \ 'Usage: syntaxerl [-b | --base <FILENAME>] [-d | --debug] <FILENAME>', + \ ' syntaxerl <-h | --help>', + \ ' -b, --base Set original filename', + \ ' -d, --debug Enable debug output', + \ ' -h, --help Show this message', + \ ]) diff --git a/test/command_callback/test_flake8_command_callback.vader b/test/command_callback/test_flake8_command_callback.vader index 70527ad3..c36fe4f5 100644 --- a/test/command_callback/test_flake8_command_callback.vader +++ b/test/command_callback/test_flake8_command_callback.vader @@ -19,23 +19,23 @@ Execute(The flake8 callbacks should return the correct default values): \ '''flake8'' --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ '''flake8'' --stdin-display-name %s -', + \ '''flake8'' --format=default --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) " Try with older versions. call ale_linters#python#flake8#ClearVersionCache() AssertEqual - \ '''flake8'' -', + \ '''flake8'' --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) Execute(The flake8 command callback should let you set options): let g:ale_python_flake8_options = '--some-option' AssertEqual - \ '''flake8'' --some-option --stdin-display-name %s -', + \ '''flake8'' --some-option --format=default --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.4']) call ale_linters#python#flake8#ClearVersionCache() AssertEqual - \ '''flake8'' --some-option -', + \ '''flake8'' --some-option --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) Execute(You should be able to set a custom executable and it should be escaped): @@ -48,7 +48,7 @@ Execute(You should be able to set a custom executable and it should be escaped): \ '''executable with spaces'' --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ '''executable with spaces'' --stdin-display-name %s -', + \ '''executable with spaces'' --format=default --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) Execute(The flake8 callbacks should detect virtualenv directories): @@ -62,7 +62,7 @@ Execute(The flake8 callbacks should detect virtualenv directories): \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual \ '''' . g:dir . '/python_paths/with_virtualenv/env/bin/flake8''' - \ . ' --stdin-display-name %s -', + \ . ' --format=default --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) Execute(The FindProjectRoot should detect the project root directory for namespace package via Manifest.in): @@ -114,7 +114,7 @@ Execute(Using `python -m flake8` should be supported for running flake8): \ '''python'' -m flake8 --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ '''python'' -m flake8 --some-option -', + \ '''python'' -m flake8 --some-option --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) call ale_linters#python#flake8#ClearVersionCache() @@ -129,5 +129,5 @@ Execute(Using `python -m flake8` should be supported for running flake8): \ '''python'' -m flake8 --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ '''python'' -m flake8 --some-option -', + \ '''python'' -m flake8 --some-option --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) diff --git a/test/command_callback/test_idris_command_callbacks.vader b/test/command_callback/test_idris_command_callbacks.vader new file mode 100644 index 00000000..03a69f39 --- /dev/null +++ b/test/command_callback/test_idris_command_callbacks.vader @@ -0,0 +1,42 @@ +Before: + Save g:ale_idris_idris_executable + Save g:ale_idris_idris_options + + unlet! g:ale_idris_idris_executable + unlet! b:ale_idris_idris_executable + unlet! g:ale_idris_idris_options + unlet! b:ale_idris_idris_options + + runtime ale_linters/idris/idris.vim + +After: + Restore + unlet! b:command_tail + unlet! b:ale_idris_idris_executable + unlet! b:ale_idris_idris_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'idris', ale_linters#idris#idris#GetExecutable(bufnr('')) + + let b:ale_idris_idris_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#idris#idris#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('idris') . ' --total --warnpartial --warnreach --warnipkg --check %s', + \ ale_linters#idris#idris#GetCommand(bufnr('')) + + let b:ale_idris_idris_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . ' --total --warnpartial --warnreach --warnipkg --check %s', + \ ale_linters#idris#idris#GetCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_idris_idris_options = '--something' + + AssertEqual + \ ale#Escape('idris') . ' --something --check %s', + \ ale_linters#idris#idris#GetCommand(bufnr('')) diff --git a/test/command_callback/test_phpstan_command_callbacks.vader b/test/command_callback/test_phpstan_command_callbacks.vader new file mode 100644 index 00000000..7366df8b --- /dev/null +++ b/test/command_callback/test_phpstan_command_callbacks.vader @@ -0,0 +1,29 @@ +Before: + Save g:ale_php_phpstan_executable + Save g:ale_php_phpstan_level + + unlet! g:ale_php_phpstan_executable + unlet! g:ale_php_phpstan_level + + runtime ale_linters/php/phpstan.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(Custom executables should be used for the executable and command): + let g:ale_php_phpstan_executable = 'phpstan_test' + + AssertEqual 'phpstan_test', ale_linters#php#phpstan#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('phpstan_test') . ' analyze -l4 --errorFormat raw %s', + \ ale_linters#php#phpstan#GetCommand(bufnr('')) + +Execute(project with level set to 3): + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + let g:ale_php_phpstan_level = 3 + + AssertEqual + \ ale#Escape('phpstan') . ' analyze -l3 --errorFormat raw %s', + \ ale_linters#php#phpstan#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pycodestyle_command_callback.vader b/test/command_callback/test_pycodestyle_command_callback.vader new file mode 100644 index 00000000..a5163461 --- /dev/null +++ b/test/command_callback/test_pycodestyle_command_callback.vader @@ -0,0 +1,23 @@ +Before: + runtime ale_linters/python/pycodestyle.vim + Save g:ale_python_pycodestyle_executable, + \ g:ale_python_pycodestyle_options, + \ g:ale_python_pycodestyle_use_global + +After: + call ale#linter#Reset() + Restore + +Execute(The pycodestyle command callback should return default string): + AssertEqual '''pycodestyle'' -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(The pycodestyle command callback should allow options): + let g:ale_python_pycodestyle_options = '--exclude=test*.py' + AssertEqual '''pycodestyle'' --exclude=test*.py -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(The pycodestyle executable should be configurable): + let g:ale_python_pycodestyle_executable = '~/.local/bin/pycodestyle' + AssertEqual '''~/.local/bin/pycodestyle'' -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) diff --git a/test/command_callback/test_shellcheck_command_callback.vader b/test/command_callback/test_shellcheck_command_callback.vader new file mode 100644 index 00000000..0d8fef66 --- /dev/null +++ b/test/command_callback/test_shellcheck_command_callback.vader @@ -0,0 +1,47 @@ +Before: + Save g:ale_sh_shellcheck_exclusions + Save g:ale_sh_shellcheck_executable + Save g:ale_sh_shellcheck_options + + unlet! g:ale_sh_shellcheck_exclusions + unlet! g:ale_sh_shellcheck_executable + unlet! g:ale_sh_shellcheck_options + + runtime ale_linters/sh/shellcheck.vim + +After: + Restore + + unlet! b:ale_sh_shellcheck_exclusions + unlet! b:ale_sh_shellcheck_executable + unlet! b:ale_sh_shellcheck_options + unlet! b:is_bash + + call ale#linter#Reset() + +Execute(The default shellcheck command should be correct): + AssertEqual + \ 'shellcheck -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) + +Execute(The shellcheck command should accept options): + let b:ale_sh_shellcheck_options = '--foobar' + + AssertEqual + \ 'shellcheck --foobar -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) + +Execute(The shellcheck command should accept options and exclusions): + let b:ale_sh_shellcheck_options = '--foobar' + let b:ale_sh_shellcheck_exclusions = 'foo,bar' + + AssertEqual + \ 'shellcheck --foobar -e foo,bar -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) + +Execute(The shellcheck command should include the dialect): + let b:is_bash = 1 + + AssertEqual + \ 'shellcheck -s bash -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) diff --git a/test/command_callback/test_standard_command_callback.vader b/test/command_callback/test_standard_command_callback.vader new file mode 100644 index 00000000..fa90175b --- /dev/null +++ b/test/command_callback/test_standard_command_callback.vader @@ -0,0 +1,98 @@ +Before: + Save g:ale_javascript_standard_executable + Save g:ale_javascript_standard_use_global + Save g:ale_javascript_standard_options + + unlet! b:executable + unlet! g:ale_javascript_standard_executable + unlet! b:ale_javascript_standard_executable + unlet! g:ale_javascript_standard_use_global + unlet! g:ale_javascript_standard_options + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('testfile.js') + + runtime ale_linters/javascript/standard.vim + +After: + Restore + + unlet! b:executable + + let g:ale_has_override = {} + + call ale#test#SetFilename('test.txt') + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(bin/cmd.js paths should be preferred): + call ale#test#SetFilename('standard-test-files/with-cmd/testfile.js') + + let b:executable = g:dir + \ . '/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js' + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(.bin directories should be used too): + call ale#test#SetFilename('standard-test-files/with-bin/testfile.js') + + let b:executable = g:dir + \ . '/standard-test-files/with-bin/node_modules/.bin/standard' + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(.js files should be executed with node on Windows): + let g:ale_has_override['win32'] = 1 + + call ale#test#SetFilename('standard-test-files/with-cmd/testfile.js') + + let b:executable = g:dir + \ . '/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js' + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ 'node ' . ale#Escape(b:executable) . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(The global executable should be used otherwise): + AssertEqual + \ 'standard', + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('standard') . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(The global executable should be configurable): + let b:ale_javascript_standard_executable = 'foobar' + + AssertEqual + \ 'foobar', + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('foobar') . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_javascript_standard_options = '--wat' + + AssertEqual + \ ale#Escape('standard') . ' --wat --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) diff --git a/test/command_callback/test_thrift_command_callback.vader b/test/command_callback/test_thrift_command_callback.vader new file mode 100644 index 00000000..43487f42 --- /dev/null +++ b/test/command_callback/test_thrift_command_callback.vader @@ -0,0 +1,61 @@ +Before: + Save g:ale_thrift_thrift_executable + Save g:ale_thrift_thrift_generators + Save g:ale_thrift_thrift_includes + Save g:ale_thrift_thrift_options + + unlet! b:ale_thrift_thrift_executable + unlet! b:ale_thrift_thrift_generators + unlet! b:ale_thrift_thrift_includes + unlet! b:ale_thrift_thrift_options + + function! GetCommand(buffer) abort + call ale#engine#InitBufferInfo(a:buffer) + let l:result = ale_linters#thrift#thrift#GetCommand(a:buffer) + call ale#engine#Cleanup(a:buffer) + return l:result + endfunction + + runtime ale_linters/thrift/thrift.vim + +After: + Restore + delfunction GetCommand + unlet! b:ale_thrift_thrift_executable + unlet! b:ale_thrift_thrift_generators + unlet! b:ale_thrift_thrift_includes + unlet! b:ale_thrift_thrift_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'thrift', ale_linters#thrift#thrift#GetExecutable(bufnr('')) + + let b:ale_thrift_thrift_executable = 'foobar' + AssertEqual 'foobar', ale_linters#thrift#thrift#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + Assert GetCommand(bufnr('%')) =~# "^'thrift'" + + let b:ale_thrift_thrift_executable = 'foobar' + Assert GetCommand(bufnr('%')) =~# "^'foobar'" + +Execute(The list of generators should be configurable): + Assert GetCommand(bufnr('%')) =~# '--gen cpp' + + let b:ale_thrift_thrift_generators = ['java', 'py:dynamic'] + Assert GetCommand(bufnr('%')) =~# '--gen java --gen py:dynamic' + + let b:ale_thrift_thrift_generators = [] + Assert GetCommand(bufnr('%')) =~# '--gen cpp' + +Execute(The list of include paths should be configurable): + Assert GetCommand(bufnr('%')) !~# '-I' + + let b:ale_thrift_thrift_includes = ['included/path'] + Assert GetCommand(bufnr('%')) =~# '-I included/path' + +Execute(The string of compiler options should be configurable): + Assert GetCommand(bufnr('%')) =~# '-strict' + + let b:ale_thrift_thrift_options = '-strict --allow-64bit-consts' + Assert GetCommand(bufnr('%')) =~# '-strict --allow-64bit-consts' diff --git a/test/command_callback/test_tslint_command_callback.vader b/test/command_callback/test_tslint_command_callback.vader new file mode 100644 index 00000000..51567951 --- /dev/null +++ b/test/command_callback/test_tslint_command_callback.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_typescript_tslint_executable + Save g:ale_typescript_tslint_config_path + Save g:ale_typescript_tslint_rules_dir + Save g:ale_typescript_tslint_use_global + + unlet! g:ale_typescript_tslint_executable + unlet! g:ale_typescript_tslint_config_path + unlet! g:ale_typescript_tslint_rules_dir + unlet! g:ale_typescript_tslint_use_global + + runtime ale_linters/typescript/tslint.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + unlet! b:ale_typescript_tslint_rules_dir + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default tslint command should be correct): + AssertEqual + \ 'cd ''' . expand('%:p:h') . ''' && ' + \ . 'tslint --format json %t', + \ ale_linters#typescript#tslint#GetCommand(bufnr('')) + +Execute(The rules directory option should be included if set): + let b:ale_typescript_tslint_rules_dir = '/foo/bar' + + AssertEqual + \ 'cd ''' . expand('%:p:h') . ''' && ' + \ . 'tslint --format json' + \ . ' -r ' . ale#Escape('/foo/bar') + \ . ' %t', + \ ale_linters#typescript#tslint#GetCommand(bufnr('')) diff --git a/test/fixers/long-line-project/setup.cfg b/test/fixers/long-line-project/setup.cfg new file mode 100644 index 00000000..43d7a3a1 --- /dev/null +++ b/test/fixers/long-line-project/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 90 diff --git a/test/fixers/test_break_up_long_lines_python_fixer.vader b/test/fixers/test_break_up_long_lines_python_fixer.vader new file mode 100644 index 00000000..5fd991f0 --- /dev/null +++ b/test/fixers/test_break_up_long_lines_python_fixer.vader @@ -0,0 +1,39 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + call ale#test#RestoreDirectory() + +Execute(Long lines with basic function calls should be broken up correctly): + AssertEqual + \ [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(', + \ 'first_argument,', + \ ' second_argument,', + \ ' third_with_function_call(', + \ 'foo,', + \ ' bar,', + \ '))', + \ ], + \ ale#fixers#generic_python#BreakUpLongLines(bufnr(''), [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(first_argument, second_argument, third_with_function_call(foo, bar))', + \ ]) + +Execute(Longer lines should be permitted if a configuration file allows it): + call ale#test#SetFilename('long-line-project/foo/bar.py') + + AssertEqual + \ [ + \ 'x = this_line_is_between_79_and_90_characters(first, second, third, fourth, fifth)', + \ 'y = this_line_is_longer_than_90_characters(', + \ 'much_longer_word,', + \ ' another_longer_word,', + \ ' a_third_long_word,', + \ ')' + \ ], + \ ale#fixers#generic_python#BreakUpLongLines(bufnr(''), [ + \ 'x = this_line_is_between_79_and_90_characters(first, second, third, fourth, fifth)', + \ 'y = this_line_is_longer_than_90_characters(much_longer_word, another_longer_word, a_third_long_word)', + \ ]) diff --git a/test/fixers/test_clangformat_fixer_callback.vader b/test/fixers/test_clangformat_fixer_callback.vader new file mode 100644 index 00000000..a55576bf --- /dev/null +++ b/test/fixers/test_clangformat_fixer_callback.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_c_clangformat_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_c_clangformat_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The clang-format callback should return the correct default values): + call ale#test#SetFilename('c_paths/dummy.c') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' ' + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) + +Execute(The clangformat callback should include any additional options): + call ale#test#SetFilename('c_paths/dummy.c') + let g:ale_c_clangformat_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' --some-option', + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) diff --git a/test/fixers/test_phpcbf_fixer_callback.vader b/test/fixers/test_phpcbf_fixer_callback.vader new file mode 100644 index 00000000..c2fe3a66 --- /dev/null +++ b/test/fixers/test_phpcbf_fixer_callback.vader @@ -0,0 +1,56 @@ +Before: + Save g:ale_php_phpcbf_executable + Save g:ale_php_phpcbf_standard + Save g:ale_php_phpcbf_use_global + + let g:ale_php_phpcbf_executable = 'phpcbf_test' + let g:ale_php_phpcbf_standard = '' + let g:ale_php_phpcbf_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(project with phpcbf should use local by default): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_phpcbf_use_global = 1 + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(project without phpcbf should use global): + call ale#test#SetFilename('php_paths/project-without-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(The phpcbf callback should return the correct default values): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf') . ' --stdin-path=%s ' }, + \ ale#fixers#phpcbf#Fix(bufnr('')) + +Execute(The phpcbf callback should include the phpcbf_standard option): + let g:ale_php_phpcbf_standard = 'phpcbf_ruleset.xml' + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf') . ' --stdin-path=%s ' . '--standard=phpcbf_ruleset.xml'}, + \ ale#fixers#phpcbf#Fix(bufnr('')) + diff --git a/test/fixers/test_prettier_fixer_callback.vader b/test/fixers/test_prettier_fixer_callback.vader new file mode 100644 index 00000000..1eb24dae --- /dev/null +++ b/test/fixers/test_prettier_fixer_callback.vader @@ -0,0 +1,58 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + Save g:ale_javascript_prettier_executable + Save g:ale_javascript_prettier_options + + " Use an invalid global executable, so we don't match it. + let g:ale_javascript_prettier_executable = 'xxxinvalid' + let g:ale_javascript_prettier_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + let g:ale_has_override = {} + call ale#test#RestoreDirectory() + +Execute(The prettier callback should return the correct default values): + call ale#test#SetFilename('../prettier-test-files/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --write', + \ }, + \ ale#fixers#prettier#Fix(bufnr('')) + +Execute(The prettier callback should include configuration files when the option is set): + let g:ale_javascript_prettier_use_local_config = 1 + call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --config ' . ale#Escape(simplify(g:dir . '/../prettier-test-files/with_config/.prettierrc')) + \ . ' --write', + \ }, + \ ale#fixers#prettier#Fix(bufnr('')) + +Execute(The prettier callback should include custom prettier options): + let g:ale_javascript_prettier_options = '--no-semi' + call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --no-semi' + \ . ' --config ' . ale#Escape(simplify(g:dir . '/../prettier-test-files/with_config/.prettierrc')) + \ . ' --write', + \ }, + \ ale#fixers#prettier#Fix(bufnr('')) diff --git a/test/fixers/test_rubocop_fixer_callback.vader b/test/fixers/test_rubocop_fixer_callback.vader index e3383537..87d56d07 100644 --- a/test/fixers/test_rubocop_fixer_callback.vader +++ b/test/fixers/test_rubocop_fixer_callback.vader @@ -1,8 +1,10 @@ Before: Save g:ale_ruby_rubocop_executable + Save g:ale_ruby_rubocop_options " Use an invalid global executable, so we don't match it. let g:ale_ruby_rubocop_executable = 'xxxinvalid' + let g:ale_ruby_rubocop_options = '' call ale#test#SetDirectory('/testplugin/test/fixers') silent cd .. @@ -36,3 +38,17 @@ Execute(The rubocop callback should include configuration files): \ . ' --auto-correct %t', \ }, \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop callback should include custom rubocop options): + let g:ale_ruby_rubocop_options = '--except Lint/Debugger' + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --config ' . ale#Escape(g:dir . '/ruby_paths/with_config/.rubocop.yml') + \ . ' --except Lint/Debugger' + \ . ' --auto-correct %t', + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) diff --git a/test/fixers/test_swiftformat_fixer_callback.vader b/test/fixers/test_swiftformat_fixer_callback.vader new file mode 100644 index 00000000..e3674ded --- /dev/null +++ b/test/fixers/test_swiftformat_fixer_callback.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_swift_swiftformat_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_swift_swiftformat_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The swiftformat callback should return the correct default values): + call ale#test#SetFilename('swift_paths/dummy.swift') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_swiftformat_executable) + \ . ' %t ', + \ }, + \ ale#fixers#swiftformat#Fix(bufnr('')) + +Execute(The swiftformat callback should include any additional options): + call ale#test#SetFilename('swift_paths/dummy.swift') + let g:ale_swift_swiftformat_options = '--some-option' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_swiftformat_executable) + \ . ' %t --some-option', + \ }, + \ ale#fixers#swiftformat#Fix(bufnr('')) diff --git a/test/handler/test_eslint_handler.vader b/test/handler/test_eslint_handler.vader index 7ac26c72..943e177f 100644 --- a/test/handler/test_eslint_handler.vader +++ b/test/handler/test_eslint_handler.vader @@ -1,4 +1,11 @@ +Before: + Save g:ale_javascript_eslint_suppress_eslintignore + + let g:ale_javascript_eslint_suppress_eslintignore = 0 + After: + Restore + unlet! g:config_error_lines Execute(The eslint handler should parse lines correctly): @@ -206,3 +213,24 @@ Execute(The eslint hint about using typescript-eslint-parser): \ ale#handlers#eslint#Handle(bufnr(''), [ \ 'foo.ts:451:2: Parsing error: Unexpected token ) [Error]', \ ]) + +Execute(eslint should warn about ignored files by default): + AssertEqual + \ [{ + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override. [Warning]' + \ }], + \ ale#handlers#eslint#Handle(347, [ + \ '/path/to/some/ignored.js:0:0: File ignored because of a matching ignore pattern. Use "--no-ignore" to override. [Warning]', + \ ]) + +Execute(eslint should not warn about ignored files when explicitly disabled): + let g:ale_javascript_eslint_suppress_eslintignore = 1 + + AssertEqual + \ [], + \ ale#handlers#eslint#Handle(347, [ + \ '/path/to/some/ignored.js:0:0: File ignored because of a matching ignore pattern. Use "--no-ignore" to override. [Warning]', + \ ]) diff --git a/test/handler/test_idris_handler.vader b/test/handler/test_idris_handler.vader new file mode 100644 index 00000000..1c20be7b --- /dev/null +++ b/test/handler/test_idris_handler.vader @@ -0,0 +1,52 @@ +Before: + runtime ale_linters/idris/idris.vim + +Execute(The idris handler should parse messages that reference a single column): + call ale#test#SetFilename('/tmp/foo.idr') + + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'When checking right hand side of main with expected type IO () When checking an application of function Prelude.Monad.>>=: Type mismatch between IO () (Type of putStrLn _) and _ -> _ (Is putStrLn _ applied to too many arguments?) Specifically: Type mismatch between IO and \uv => _ -> uv' + \ } + \ ], + \ ale_linters#idris#idris#Handle(bufnr(''), [ + \ '/tmp/foo.idr:4:5:', + \ 'When checking right hand side of main with expected type', + \ ' IO ()', + \ '', + \ 'When checking an application of function Prelude.Monad.>>=:', + \ ' Type mismatch between', + \ ' IO () (Type of putStrLn _)', + \ ' and', + \ ' _ -> _ (Is putStrLn _ applied to too many arguments?)', + \ '', + \ ' Specifically:', + \ ' Type mismatch between', + \ ' IO', + \ ' and', + \ ' \uv => _ -> uv', + \ ]) + +Execute(The idris handler should parse messages that reference a column range): + call ale#test#SetFilename('/tmp/foo.idr') + + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'col': 11, + \ 'type': 'E', + \ 'text': 'When checking right hand side of Main.case block in main at /tmp/foo.idr:10:10 with expected type IO () Last statement in do block must be an expression' + \ } + \ ], + \ ale_linters#idris#idris#Handle(bufnr(''), [ + \ '/tmp/foo.idr:11:11-13:', + \ 'When checking right hand side of Main.case block in main at /tmp/foo.idr:10:10 with expected type', + \ ' IO ()', + \ '', + \ 'Last statement in do block must be an expression', + \ ]) diff --git a/test/handler/test_mypy_handler.vader b/test/handler/test_mypy_handler.vader index c949b1aa..d0cf91e2 100644 --- a/test/handler/test_mypy_handler.vader +++ b/test/handler/test_mypy_handler.vader @@ -11,16 +11,39 @@ Execute(The mypy handler should parse lines correctly): AssertEqual \ [ \ { + \ 'lnum': 768, + \ 'col': 38, + \ 'filename': 'foo/bar/foo/bar/baz.py', + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''SOME_SYMBOL''', + \ }, + \ { + \ 'lnum': 821, + \ 'col': 38, + \ 'filename': 'foo/bar/foo/bar/baz.py', + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''SOME_SYMBOL''', + \ }, + \ { + \ 'lnum': 38, + \ 'col': 44, + \ 'filename': 'foo/bar/foo/bar/other.py', + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''ANOTHER_SYMBOL''', + \ }, + \ { \ 'lnum': 15, \ 'col': 3, - \ 'text': 'Argument 1 to "somefunc" has incompatible type "int"; expected "str"', + \ 'filename': 'foo/bar/foo/bar/__init__.py', \ 'type': 'E', + \ 'text': 'Argument 1 to "somefunc" has incompatible type "int"; expected "str"' \ }, \ { \ 'lnum': 72, \ 'col': 1, - \ 'text': 'Some warning', + \ 'filename': 'foo/bar/foo/bar/__init__.py', \ 'type': 'W', + \ 'text': 'Some warning', \ }, \ ], \ ale_linters#python#mypy#Handle(bufnr(''), [ @@ -47,8 +70,9 @@ Execute(The mypy handler should handle Windows names with spaces): \ { \ 'lnum': 4, \ 'col': 0, - \ 'text': "No library stub file for module 'django.db'", + \ 'filename': 'C:\something\with spaces.py', \ 'type': 'E', + \ 'text': 'No library stub file for module ''django.db''', \ }, \ ], \ ale_linters#python#mypy#Handle(bufnr(''), [ diff --git a/test/test_phpstan_executable_detection.vader b/test/handler/test_phpstan_handler.vader index 24ba8cd8..207a7758 100644 --- a/test/test_phpstan_executable_detection.vader +++ b/test/handler/test_phpstan_handler.vader @@ -1,9 +1,4 @@ Before: - Save g:ale_php_phpstan_executable - Save g:ale_php_phpstan_level - - let g:ale_php_phpstan_executable = 'phpstan_test' - call ale#test#SetDirectory('/testplugin/test') runtime ale_linters/php/phpstan.vim @@ -14,45 +9,14 @@ After: call ale#test#RestoreDirectory() call ale#linter#Reset() -Execute(project with level set to 3): - call ale#test#SetFilename('phpstan-test-files/foo/test.php') - let g:ale_php_phpstan_level = 3 - - AssertEqual - \ 'phpstan_test analyze -l3 --errorFormat raw %s', - \ ale_linters#php#phpstan#GetCommand(bufnr('')) - -Execute(project with default level): - call ale#test#SetFilename('phpstan-test-files/foo/test.php') - - AssertEqual - \ 'phpstan_test analyze -l4 --errorFormat raw %s', - \ ale_linters#php#phpstan#GetCommand(bufnr('')) - -Execute(parse output without errors): +Execute(Output without errors should be parsed correctly): call ale#test#SetFilename('phpstan-test-files/foo/test.php') AssertEqual \ [], \ ale_linters#php#phpstan#Handle(bufnr(''), [" 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%"]) -Execute(parse output with one error): - call ale#test#SetFilename('phpstan-test-files/foo/test.php') - - AssertEqual - \ [ - \ { - \ 'lnum': 9, - \ 'text': 'Access to an undefined property Test::$var.', - \ 'type': 'W' - \ } - \ ], - \ ale_linters#php#phpstan#Handle(bufnr(''), [ - \ ' 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%', - \ 'phpstan-test-files/foo/test.php:9:Access to an undefined property Test::$var.', - \]) - -Execute(parse output with three errors): +Execute(Output with some errors should be parsed correctly): call ale#test#SetFilename('phpstan-test-files/foo/test.php') AssertEqual @@ -80,7 +44,7 @@ Execute(parse output with three errors): \ 'phpstan-test-files/foo/test.php:192:Invalid command testCommand.', \]) -Execute(parse output for windows filesystem): +Execute(Output should be parsed correctly with Windows paths): call ale#test#SetFilename('phpstan-test-files/foo/test.php') AssertEqual @@ -96,7 +60,7 @@ Execute(parse output for windows filesystem): \ 'D:\phpstan-test-files\foo\test.php:9:Access to an undefined property Test::$var.', \]) -Execute(parse output for not php file): +Execute(Output for .inc files should be parsed correctly): call ale#test#SetFilename('phpstan-test-files/test.inc') AssertEqual diff --git a/test/handler/test_pycodestyle_handler.vader b/test/handler/test_pycodestyle_handler.vader new file mode 100644 index 00000000..cc83fc83 --- /dev/null +++ b/test/handler/test_pycodestyle_handler.vader @@ -0,0 +1,48 @@ +Before: + runtime ale_linters/python/pycodestyle.vim + +After: + call ale#linter#Reset() + silent file something_else.py + +Execute(The pycodestyle handler should parse output): + AssertEqual + \ [ + \ { + \ 'lnum': 69, + \ 'col': 11, + \ 'text': 'E401 multiple imports on one line', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 77, + \ 'col': 1, + \ 'text': 'E302 expected 2 blank lines, found 1', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 88, + \ 'col': 5, + \ 'text': 'E301 expected 1 blank line, found 0', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 222, + \ 'col': 34, + \ 'text': 'W602 deprecated form of raising exception', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 544, + \ 'col': 21, + \ 'text': 'W601 .has_key() is deprecated, use ''in''', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'stdin:69:11: E401 multiple imports on one line', + \ 'stdin:77:1: E302 expected 2 blank lines, found 1', + \ 'stdin:88:5: E301 expected 1 blank line, found 0', + \ 'stdin:222:34: W602 deprecated form of raising exception', + \ 'example.py:544:21: W601 .has_key() is deprecated, use ''in''', + \ ]) diff --git a/test/handler/test_syntaxerl_handler.vader b/test/handler/test_syntaxerl_handler.vader index 1308ec84..95f2bfef 100644 --- a/test/handler/test_syntaxerl_handler.vader +++ b/test/handler/test_syntaxerl_handler.vader @@ -4,7 +4,7 @@ Before: After: call ale#linter#Reset() -Execute: +Execute (Handle SyntaxErl output): AssertEqual \ [ \ { @@ -18,7 +18,7 @@ Execute: \ 'type': 'W', \ }, \ ], - \ ale_linters#erlang#syntaxerl#Handle(42, [ + \ ale_linters#erlang#syntaxerl#Handle(bufnr(''), [ \ "/tmp/v2wDixk/1/module.erl:42: syntax error before: ','", \ '/tmp/v2wDixk/2/module.erl:42: warning: function foo/0 is unused', \ ]) diff --git a/test/handler/test_thrift_handler.vader b/test/handler/test_thrift_handler.vader new file mode 100644 index 00000000..9bdb9378 --- /dev/null +++ b/test/handler/test_thrift_handler.vader @@ -0,0 +1,63 @@ +Before: + runtime ale_linters/thrift/thrift.vim + +After: + call ale#linter#Reset() + +Execute(The thrift handler should handle basic warnings and errors): + AssertEqual + \ [ + \ { + \ 'lnum': 17, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'The "byte" type is a compatibility alias for "i8". Use i8" to emphasize the signedness of this type.', + \ }, + \ { + \ 'lnum': 20, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'Could not find include file include.thrift', + \ }, + \ { + \ 'lnum': 83, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Enum FOO is already defined!', + \ }, + \ ], + \ ale_linters#thrift#thrift#Handle(1, [ + \ '[WARNING:/path/filename.thrift:17] The "byte" type is a compatibility alias for "i8". Use i8" to emphasize the signedness of this type.', + \ '[WARNING:/path/filename.thrift:20] Could not find include file include.thrift', + \ '[FAILURE:/path/filename.thrift:83] Enum FOO is already defined!', + \ ]) + +Execute(The thrift handler should handle multiline errors): + AssertEqual + \ [ + \ { + \ 'lnum': 75, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'This integer is too big: "11111111114213213453243"', + \ }, + \ { + \ 'lnum': 76, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Implicit field keys are deprecated and not allowed with -strict', + \ }, + \ { + \ 'lnum': 77, + \ 'col': 0, + \ 'type': 'E', + \ 'text': "Unknown error (last token was ';')", + \ }, + \ ], + \ ale_linters#thrift#thrift#Handle(1, [ + \ "[ERROR:/path/filename.thrift:75] (last token was '11111111114213213453243')", + \ 'This integer is too big: "11111111114213213453243"', + \ "[ERROR:/path/filename.thrift:76] (last token was ';')", + \ 'Implicit field keys are deprecated and not allowed with -strict', + \ "[ERROR:/path/filename.thrift:77] (last token was ';')", + \ ]) diff --git a/test/handler/test_tslint_handler.vader b/test/handler/test_tslint_handler.vader index 704123dd..5c8679a4 100644 --- a/test/handler/test_tslint_handler.vader +++ b/test/handler/test_tslint_handler.vader @@ -15,28 +15,40 @@ Execute(The tslint handler should parse lines correctly): \ { \ 'lnum': 1, \ 'col': 15, + \ 'filename': expand('%:p:h') . '/test.ts', \ 'end_lnum': 1, - \ 'end_col': 15, - \ 'text': 'Missing semicolon', \ 'type': 'E', + \ 'end_col': 15, + \ 'text': 'semicolon: Missing semicolon' \ }, \ { \ 'lnum': 2, \ 'col': 8, + \ 'filename': expand('%:p:h') . '/test.ts', \ 'end_lnum': 3, + \ 'type': 'W', \ 'end_col': 12, - \ 'text': 'Something else', + \ 'text': 'Something else' + \ }, + \ { + \ 'lnum': 2, + \ 'col': 8, + \ 'filename': expand('%:p:h') . '/something-else.ts', + \ 'end_lnum': 3, \ 'type': 'W', + \ 'end_col': 12, + \ 'text': 'something: Something else' \ }, \ { \ 'lnum': 31, \ 'col': 9, + \ 'filename': expand('%:p:h') . '/test.ts', \ 'end_lnum': 31, - \ 'end_col': 20, - \ 'text': 'Calls to console.log are not allowed.', \ 'type': 'E', + \ 'end_col': 20, + \ 'text': 'no-console: Calls to console.log are not allowed.' \ }, - \ ], + \ ] , \ ale_linters#typescript#tslint#Handle(bufnr(''), [json_encode([ \ { \ 'endPosition': { @@ -50,7 +62,7 @@ Execute(The tslint handler should parse lines correctly): \ 'innerStart': 14, \ 'innerText': ';' \ }, - \ 'name': 'app/test.ts', + \ 'name': 'test.ts', \ 'ruleName': 'semicolon', \ 'ruleSeverity': 'ERROR', \ 'startPosition': { @@ -71,8 +83,7 @@ Execute(The tslint handler should parse lines correctly): \ 'innerStart': 14, \ 'innerText': ';' \ }, - \ 'name': 'app/test.ts', - \ 'ruleName': 'something', + \ 'name': 'test.ts', \ 'ruleSeverity': 'WARNING', \ 'startPosition': { \ 'character': 7, @@ -92,7 +103,7 @@ Execute(The tslint handler should parse lines correctly): \ 'innerStart': 14, \ 'innerText': ';' \ }, - \ 'name': 'app/something-else.ts', + \ 'name': 'something-else.ts', \ 'ruleName': 'something', \ 'ruleSeverity': 'WARNING', \ 'startPosition': { @@ -108,7 +119,7 @@ Execute(The tslint handler should parse lines correctly): \ "position": 14590 \ }, \ "failure": "Calls to console.log are not allowed.", - \ 'name': 'app/test.ts', + \ 'name': 'test.ts', \ "ruleName": "no-console", \ "startPosition": { \ "character": 8, diff --git a/test/handler/test_vint_handler.vader b/test/handler/test_vint_handler.vader index c5af85c4..8747979c 100644 --- a/test/handler/test_vint_handler.vader +++ b/test/handler/test_vint_handler.vader @@ -1,6 +1,10 @@ -Execute(The vint handler should parse error messages correctly): - :file! gxc.vim +Before: + runtime ale_linters/vim/vint.vim + +After: + call ale#linter#Reset() +Execute(The vint handler should parse error messages correctly): AssertEqual \ [ \ { @@ -12,25 +16,44 @@ Execute(The vint handler should parse error messages correctly): \ { \ 'lnum': 3, \ 'col': 17, + \ 'end_col': 18, \ 'text': 'Use robust operators ''==#'' or ''==?'' instead of ''=='' (see Google VimScript Style Guide (Matching))', \ 'type': 'W', \ }, \ { \ 'lnum': 3, \ 'col': 8, + \ 'end_col': 15, \ 'text': 'Make the scope explicit like ''l:filename'' (see Anti-pattern of vimrc (Scope of identifier))', \ 'type': 'W', \ }, \ { \ 'lnum': 7, \ 'col': 8, + \ 'end_col': 15, \ 'text': 'Undefined variable: filename (see :help E738)', \ 'type': 'W', \ }, + \ { + \ 'lnum': 8, + \ 'col': 11, + \ 'end_col': 16, + \ 'text': 'E128: Function name must start with a capital or contain a colon: foobar (see ynkdir/vim-vimlparser)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 9, + \ 'col': 12, + \ 'end_col': 13, + \ 'text': 'Use robust operators ''=~#'' or ''=~?'' instead of ''=~'' (see Google VimScript Style Guide (Matching))', + \ 'type': 'W', + \ }, \ ], - \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ ale_linters#vim#vint#Handle(bufnr(''), [ \ 'gcc.vim:1:1: warning: Use scriptencoding when multibyte char exists (see :help :script encoding)', \ 'gcc.vim:3:17: warning: Use robust operators `==#` or `==?` instead of `==` (see Google VimScript Style Guide (Matching))', \ 'gcc.vim:3:8: style_problem: Make the scope explicit like `l:filename` (see Anti-pattern of vimrc (Scope of identifier))', \ 'gcc.vim:7:8: warning: Undefined variable: filename (see :help E738)', + \ 'gcc.vim:8:11: error: E128: Function name must start with a capital or contain a colon: foobar (see ynkdir/vim-vimlparser)', + \ 'gcc.vim:9:12: warning: Use robust operators `=~#` or `=~?` instead of `=~` (see Google VimScript Style Guide (Matching))', \ ]) diff --git a/test/prettier-test-files/testfile.js b/test/prettier-test-files/testfile.js new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/prettier-test-files/testfile.js diff --git a/test/prettier-test-files/with_config/.prettierrc b/test/prettier-test-files/with_config/.prettierrc new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/prettier-test-files/with_config/.prettierrc diff --git a/test/prettier-test-files/with_config/testfile.js b/test/prettier-test-files/with_config/testfile.js new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/prettier-test-files/with_config/testfile.js diff --git a/test/sign/test_sign_parsing.vader b/test/sign/test_sign_parsing.vader index ee112a1b..07848afb 100644 --- a/test/sign/test_sign_parsing.vader +++ b/test/sign/test_sign_parsing.vader @@ -1,6 +1,6 @@ Execute (Parsing English signs should work): AssertEqual - \ [[9, 1000001, 'ALEWarningSign']], + \ [0, [[9, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([ \ 'Signs for app.js:', \ ' line=9 id=1000001 name=ALEWarningSign', @@ -8,20 +8,28 @@ Execute (Parsing English signs should work): Execute (Parsing Russian signs should work): AssertEqual - \ [[1, 1000001, 'ALEErrorSign']], + \ [0, [[1, 1000001, 'ALEErrorSign']]], \ ale#sign#ParseSigns([' строка=1 id=1000001 имя=ALEErrorSign']) Execute (Parsing Japanese signs should work): AssertEqual - \ [[1, 1000001, 'ALEWarningSign']], + \ [0, [[1, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([' 行=1 識別子=1000001 名前=ALEWarningSign']) Execute (Parsing Spanish signs should work): AssertEqual - \ [[12, 1000001, 'ALEWarningSign']], + \ [0, [[12, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([' línea=12 id=1000001 nombre=ALEWarningSign']) Execute (Parsing Italian signs should work): AssertEqual - \ [[1, 1000001, 'ALEWarningSign']], + \ [0, [[1, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([' riga=1 id=1000001, nome=ALEWarningSign']) + \ +Execute (The sign parser should indicate if the dummy sign is set): + AssertEqual + \ [1, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([ + \ ' строка=1 id=1000001 имя=ALEErrorSign', + \ ' line=1 id=1000000 name=ALEDummySign', + \ ]) diff --git a/test/sign/test_sign_placement.vader b/test/sign/test_sign_placement.vader index 77f9bb6d..abae765b 100644 --- a/test/sign/test_sign_placement.vader +++ b/test/sign/test_sign_placement.vader @@ -1,4 +1,8 @@ Before: + Save g:ale_set_signs + + let g:ale_set_signs = 1 + function! GenerateResults(buffer, output) return [ \ { @@ -65,41 +69,43 @@ Before: \}) After: + Restore + unlet! g:loclist delfunction GenerateResults delfunction ParseSigns call ale#linter#Reset() sign unplace * -Execute(ale#sign#GetSignType should return the right sign types): - AssertEqual 'ALEErrorSign', ale#sign#GetSignType([{'type': 'E'}]) - AssertEqual 'ALEStyleErrorSign', ale#sign#GetSignType([{'type': 'E', 'sub_type': 'style'}]) - AssertEqual 'ALEWarningSign', ale#sign#GetSignType([{'type': 'W'}]) - AssertEqual 'ALEStyleWarningSign', ale#sign#GetSignType([{'type': 'W', 'sub_type': 'style'}]) - AssertEqual 'ALEInfoSign', ale#sign#GetSignType([{'type': 'I'}]) - AssertEqual 'ALEErrorSign', ale#sign#GetSignType([ +Execute(ale#sign#GetSignName should return the right sign names): + AssertEqual 'ALEErrorSign', ale#sign#GetSignName([{'type': 'E'}]) + AssertEqual 'ALEStyleErrorSign', ale#sign#GetSignName([{'type': 'E', 'sub_type': 'style'}]) + AssertEqual 'ALEWarningSign', ale#sign#GetSignName([{'type': 'W'}]) + AssertEqual 'ALEStyleWarningSign', ale#sign#GetSignName([{'type': 'W', 'sub_type': 'style'}]) + AssertEqual 'ALEInfoSign', ale#sign#GetSignName([{'type': 'I'}]) + AssertEqual 'ALEErrorSign', ale#sign#GetSignName([ \ {'type': 'E'}, \ {'type': 'W'}, \ {'type': 'I'}, \ {'type': 'E', 'sub_type': 'style'}, \ {'type': 'W', 'sub_type': 'style'}, \]) - AssertEqual 'ALEWarningSign', ale#sign#GetSignType([ + AssertEqual 'ALEWarningSign', ale#sign#GetSignName([ \ {'type': 'W'}, \ {'type': 'I'}, \ {'type': 'E', 'sub_type': 'style'}, \ {'type': 'W', 'sub_type': 'style'}, \]) - AssertEqual 'ALEInfoSign', ale#sign#GetSignType([ + AssertEqual 'ALEInfoSign', ale#sign#GetSignName([ \ {'type': 'I'}, \ {'type': 'E', 'sub_type': 'style'}, \ {'type': 'W', 'sub_type': 'style'}, \]) - AssertEqual 'ALEStyleErrorSign', ale#sign#GetSignType([ + AssertEqual 'ALEStyleErrorSign', ale#sign#GetSignName([ \ {'type': 'E', 'sub_type': 'style'}, \ {'type': 'W', 'sub_type': 'style'}, \]) - AssertEqual 'ALEStyleWarningSign', ale#sign#GetSignType([ + AssertEqual 'ALEStyleWarningSign', ale#sign#GetSignName([ \ {'type': 'W', 'sub_type': 'style'}, \]) @@ -124,34 +130,32 @@ Execute(The current signs should be set for running a job): \ ], \ ParseSigns() - Execute(Loclist items with sign_id values should be kept): - exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('%') - exec 'sign place 1000348 line=15 name=ALEErrorSign buffer=' . bufnr('%') - exec 'sign place 1000349 line=16 name=ALEWarningSign buffer=' . bufnr('%') + exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 line=15 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000349 line=16 name=ALEWarningSign buffer=' . bufnr('') let g:loclist = [ - \ {'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000348}, - \ {'lnum': 2, 'col': 1, 'type': 'W', 'text': 'b', 'sign_id': 1000349}, - \ {'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c', 'sign_id': 1000347}, - \ {'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd'}, - \ {'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}, - \ {'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f'}, + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000348}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'b', 'sign_id': 1000349}, + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c', 'sign_id': 1000347}, + \ {'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd'}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f'}, \] - call ale#sign#SetSigns(bufnr('%'), g:loclist) - call ale#sign#RemoveDummySignIfNeeded(bufnr('%')) + call ale#sign#SetSigns(bufnr(''), g:loclist) " Sign IDs from before should be kept, and new signs should use " IDs that haven't been used yet. AssertEqual \ [ - \ {'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c', 'sign_id': 1000347}, - \ {'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd', 'sign_id': 1000350}, - \ {'lnum': 15, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000351}, - \ {'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e', 'sign_id': 1000351}, - \ {'lnum': 16, 'col': 1, 'type': 'W', 'text': 'b', 'sign_id': 1000352}, - \ {'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f', 'sign_id': 1000352}, + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c', 'sign_id': 1000347}, + \ {'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd', 'sign_id': 1000350}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000348}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e', 'sign_id': 1000348}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 1, 'type': 'W', 'text': 'b', 'sign_id': 1000351}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f', 'sign_id': 1000351}, \ ], \ g:loclist @@ -161,13 +165,104 @@ Execute(Loclist items with sign_id values should be kept): AssertEqual \ [ \ ['15', '1000348', 'ALEErrorSign'], - \ ['15', '1000351', 'ALEErrorSign'], - \ ['16', '1000349', 'ALEWarningSign'], - \ ['16', '1000352', 'ALEErrorSign'], + \ ['16', '1000351', 'ALEErrorSign'], \ ['3', '1000347', 'ALEErrorSign'], \ ['4', '1000350', 'ALEWarningSign'], \ ], \ sort(ParseSigns()) -Execute(No excpetions should be thrown when setting signs for invalid buffers): +Execute(Items for other buffers should be ignored): + let g:loclist = [ + \ {'bufnr': bufnr('') - 1, 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a'}, + \ {'bufnr': bufnr('') - 1, 'lnum': 2, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000347}, + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'b'}, + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c'}, + \ {'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd'}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f'}, + \ {'bufnr': bufnr('') + 1, 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a'}, + \] + + call ale#sign#SetSigns(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEErrorSign'], + \ ['15', '1000005', 'ALEWarningSign'], + \ ['16', '1000006', 'ALEErrorSign'], + \ ['2', '1000002', 'ALEWarningSign'], + \ ['3', '1000003', 'ALEErrorSign'], + \ ['4', '1000004', 'ALEWarningSign'], + \ ], + \ sort(ParseSigns()) + +Execute(Signs should be downgraded correctly): + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEErrorSign'], + \ ['2', '1000002', 'ALEWarningSign'], + \ ], + \ sort(ParseSigns()) + + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'I', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000003', 'ALEWarningSign'], + \ ['2', '1000004', 'ALEInfoSign'], + \ ], + \ sort(ParseSigns()) + +Execute(Signs should be upgraded correctly): + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'I', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEWarningSign'], + \ ['2', '1000002', 'ALEInfoSign'], + \ ], + \ sort(ParseSigns()) + + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000003', 'ALEErrorSign'], + \ ['2', '1000004', 'ALEWarningSign'], + \ ], + \ sort(ParseSigns()) + +Execute(It should be possible to clear signs with empty lists): + let g:loclist = [ + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f'}, + \] + + call ale#sign#SetSigns(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ ['16', '1000001', 'ALEErrorSign'], + \ ], + \ sort(ParseSigns()) + + call ale#sign#SetSigns(bufnr(''), []) + + AssertEqual [], ParseSigns() + +Execute(No exceptions should be thrown when setting signs for invalid buffers): call ale#sign#SetSigns(123456789, [{'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}]) diff --git a/test/test_ale_fix.vader b/test/test_ale_fix.vader index bafeee2b..b5c16724 100644 --- a/test/test_ale_fix.vader +++ b/test/test_ale_fix.vader @@ -82,6 +82,8 @@ After: call ale#fix#registry#ResetToDefaults() call ale#linter#Reset() + setlocal buftype=nofile + if filereadable('fix_test_file') call delete('fix_test_file') endif @@ -245,6 +247,9 @@ Execute(ALEFix should save files on the save event): let g:ale_fixers.testft = ['AddDollars'] + " We have to set the buftype to empty so the file will be written. + setlocal buftype= + call SetUpLinters() call ale#events#SaveEvent(bufnr('')) diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index a8913df4..8ab5ad54 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -1,14 +1,19 @@ Before: Save g:ale_warn_about_trailing_whitespace Save g:ale_linters + Save g:ale_fixers + + unlet! b:ale_history let g:ale_warn_about_trailing_whitespace = 1 let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout'} let g:testlinter2 = {'name': 'testlinter2', 'executable': 'testlinter2', 'command': 'testlinter2', 'callback': 'testCB2', 'output_stream': 'stdout'} + call ale#engine#ResetExecutableCache() call ale#linter#Reset() let g:ale_linters = {} + let g:ale_fixers = {} let g:ale_linter_aliases = {} let g:ale_buffer_info = {} let g:globals_lines = [ @@ -59,9 +64,10 @@ After: let g:ale_buffer_info = {} - unlet! g:testlinter1 - unlet! g:testlinter2 + unlet! g:testlinter1 + unlet! g:testlinter2 + unlet! b:ale_history unlet! b:ale_linters unlet! g:output unlet! g:globals_string @@ -247,12 +253,10 @@ Execute (ALEInfo should output linter aliases): Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo should return command history): - let g:ale_buffer_info[bufnr('%')] = { - \ 'history': [ - \ {'status': 'started', 'job_id': 347, 'command': 'first command'}, - \ {'status': 'started', 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, - \ ], - \} + let b:ale_history = [ + \ {'status': 'started', 'job_id': 347, 'command': 'first command'}, + \ {'status': 'started', 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, + \] call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) @@ -271,12 +275,10 @@ Execute (ALEInfo should return command history): Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo command history should print exit codes correctly): - let g:ale_buffer_info[bufnr('%')] = { - \ 'history': [ - \ {'status': 'finished', 'exit_code': 0, 'job_id': 347, 'command': 'first command'}, - \ {'status': 'finished', 'exit_code': 1, 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, - \ ], - \} + let b:ale_history = [ + \ {'status': 'finished', 'exit_code': 0, 'job_id': 347, 'command': 'first command'}, + \ {'status': 'finished', 'exit_code': 1, 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, + \] call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) @@ -297,31 +299,29 @@ Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo command history should print command output if logging is on): let g:ale_history_log_output = 1 - let g:ale_buffer_info[bufnr('%')] = { - \ 'history': [ - \ { - \ 'status': 'finished', - \ 'exit_code': 0, - \ 'job_id': 347, - \ 'command': 'first command', - \ 'output': ['some', 'first command output'], - \ }, - \ { - \ 'status': 'finished', - \ 'exit_code': 1, - \ 'job_id': 347, - \ 'command': ['/bin/bash', '\c', 'last command'], - \ 'output': ['different second command output'], - \ }, - \ { - \ 'status': 'finished', - \ 'exit_code': 0, - \ 'job_id': 347, - \ 'command': 'command with no output', - \ 'output': [], - \ }, - \ ], - \} + let b:ale_history = [ + \ { + \ 'status': 'finished', + \ 'exit_code': 0, + \ 'job_id': 347, + \ 'command': 'first command', + \ 'output': ['some', 'first command output'], + \ }, + \ { + \ 'status': 'finished', + \ 'exit_code': 1, + \ 'job_id': 347, + \ 'command': ['/bin/bash', '\c', 'last command'], + \ 'output': ['different second command output'], + \ }, + \ { + \ 'status': 'finished', + \ 'exit_code': 0, + \ 'job_id': 347, + \ 'command': 'command with no output', + \ 'output': [], + \ }, + \] call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) @@ -351,3 +351,20 @@ Execute (ALEInfo command history should print command output if logging is on): \ '', \ '<<<NO OUTPUT RETURNED>>>', \]) + +Execute (ALEInfo should include executable checks in the history): + call ale#linter#Define('testft', g:testlinter1) + call ale#engine#IsExecutable(bufnr(''), 'echo') + call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') + + call CheckInfo([ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Linter Variables:', + \ '', + \] + g:globals_lines + g:command_header + [ + \ '', + \ '(executable check - success) echo', + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \]) diff --git a/test/test_ale_init_au_groups.vader b/test/test_ale_init_au_groups.vader index e036343b..2685f50b 100644 --- a/test/test_ale_init_au_groups.vader +++ b/test/test_ale_init_au_groups.vader @@ -21,8 +21,14 @@ Before: let l:header = split(l:line)[1] let l:header = get(l:event_name_corrections, l:header, l:header) elseif !empty(l:header) - call add(l:matches, join(split(l:header . l:line))) - let l:header = '' + " There's an extra line for buffer events, and we should only look + " for the one matching the current buffer. + if l:line =~# '<buffer=' . bufnr('') . '>' + let l:header .= ' <buffer>' + else + call add(l:matches, join(split(l:header . l:line))) + let l:header = '' + endif endif endfor @@ -40,11 +46,18 @@ Before: Save g:ale_lint_on_save Save g:ale_echo_cursor Save g:ale_fix_on_save + Save g:ale_completion_enabled After: delfunction CheckAutocmd Restore + if g:ale_completion_enabled + call ale#completion#Enable() + else + call ale#completion#Disable() + endif + call ALEInitAuGroups() Execute (g:ale_lint_on_text_changed = 0 should bind no events): @@ -107,10 +120,12 @@ Execute (g:ale_pattern_options_enabled = 1 should bind BufReadPost and BufEnter) \ 'BufReadPost * call ale#pattern_options#SetOptions()', \], CheckAutocmd('ALEPatternOptionsGroup') -Execute (g:ale_lint_on_enter = 0 should bind no events): +Execute (g:ale_lint_on_enter = 0 should bind only the BufEnter event): let g:ale_lint_on_enter = 0 - AssertEqual [], CheckAutocmd('ALERunOnEnterGroup') + AssertEqual + \ ['BufEnter * call ale#events#EnterEvent(str2nr(expand(''<abuf>'')))'], + \ CheckAutocmd('ALERunOnEnterGroup') Execute (g:ale_lint_on_enter = 1 should bind the required events): let g:ale_lint_on_enter = 1 @@ -127,18 +142,17 @@ Execute (g:ale_lint_on_filetype_changed = 0 should bind no events): AssertEqual [], CheckAutocmd('ALERunOnFiletypeChangeGroup') -Execute (g:ale_lint_on_filetype_changed = 1 should bind FileType, and required buffer events): +Execute (g:ale_lint_on_filetype_changed = 1 should bind the FileType event): let g:ale_lint_on_filetype_changed = 1 - AssertEqual [ - \ 'BufEnter * let b:ale_original_filetype = &filetype', - \ 'BufReadPost * let b:ale_original_filetype = &filetype', - \ 'FileType * ' - \ . 'if has_key(b:, ''ale_original_filetype'') ' - \ . '&& b:ale_original_filetype !=# expand(''<amatch>'')' - \ . '| call ale#Queue(300, ''lint_file'')' - \ . '| endif', - \], CheckAutocmd('ALERunOnFiletypeChangeGroup') + AssertEqual + \ [ + \ 'FileType * call ale#events#FileTypeEvent( ' + \ . 'str2nr(expand(''<abuf>'')), ' + \ . 'expand(''<amatch>'')' + \ . ')', + \ ], + \ CheckAutocmd('ALERunOnFiletypeChangeGroup') Execute (g:ale_lint_on_save = 0 should bind no events): let g:ale_lint_on_save = 0 @@ -184,3 +198,21 @@ Execute (g:ale_echo_cursor = 1 should bind cursor events): \ 'CursorMoved * call ale#cursor#EchoCursorWarningWithDelay()', \ 'InsertLeave * call ale#cursor#EchoCursorWarning()', \], CheckAutocmd('ALECursorGroup') + +Execute(Enabling completion should set up autocmd events correctly): + let g:ale_completion_enabled = 0 + call ale#completion#Enable() + + AssertEqual [ + \ 'CompleteDone * call ale#completion#Done()', + \ 'TextChangedI * call ale#completion#Queue()', + \], CheckAutocmd('ALECompletionGroup') + AssertEqual 1, g:ale_completion_enabled + +Execute(Disabling completion should remove autocmd events correctly): + let g:ale_completion_enabled = 1 + call ale#completion#Enable() + call ale#completion#Disable() + + AssertEqual [], CheckAutocmd('ALECompletionGroup') + AssertEqual 0, g:ale_completion_enabled diff --git a/test/test_ale_lint_command.vader b/test/test_ale_lint_command.vader index 9e70017c..42554ec1 100644 --- a/test/test_ale_lint_command.vader +++ b/test/test_ale_lint_command.vader @@ -1,4 +1,8 @@ Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + let g:expected_loclist = [{ \ 'bufnr': bufnr('%'), \ 'lnum': 2, @@ -38,6 +42,8 @@ Before: \}) After: + Restore + unlet! g:expected_loclist unlet! g:expected_groups diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader index 202d8a35..f5d8599f 100644 --- a/test/test_ale_toggle.vader +++ b/test/test_ale_toggle.vader @@ -1,4 +1,12 @@ Before: + Save g:ale_buffer_info + Save g:ale_set_signs + Save g:ale_set_lists_synchronously + + let g:ale_set_signs = 1 + let g:ale_set_lists_synchronously = 1 + + let g:ale_buffer_info = {} let g:expected_loclist = [{ \ 'bufnr': bufnr('%'), \ 'lnum': 2, @@ -42,10 +50,11 @@ Before: for l:line in split(l:output, "\n") let l:match = matchlist(l:line, '^ALE[a-zA-Z]\+Group') - " We don't care about checking for the completion or fixing groups here. + " We don't care about some groups here. if !empty(l:match) \&& l:match[0] !=# 'ALECompletionGroup' \&& l:match[0] !=# 'ALEBufferFixGroup' + \&& l:match[0] !=# 'ALEPatternOptionsGroup' call add(l:results, l:match[0]) endif endfor @@ -63,11 +72,14 @@ Before: \ 'read_buffer': 0, \}) + sign unplace * + After: + Restore + unlet! g:expected_loclist unlet! g:expected_groups - let g:ale_buffer_info = {} call ale#linter#Reset() " Toggle ALE back on if we fail when it's disabled. @@ -91,7 +103,7 @@ Execute(ALEToggle should reset everything and then run again): " First check that everything is there... AssertEqual g:expected_loclist, getloclist(0) - AssertEqual [[2, 1000001, 'ALEErrorSign']], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') @@ -104,7 +116,7 @@ Execute(ALEToggle should reset everything and then run again): " Everything should be cleared. Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' AssertEqual [], getloclist(0), 'The loclist was not cleared' - AssertEqual [], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' AssertEqual [], getmatches(), 'The highlights were not cleared' AssertEqual ['ALECleanupGroup', 'ALEHighlightBufferGroup'], ParseAuGroups() @@ -113,9 +125,53 @@ Execute(ALEToggle should reset everything and then run again): call ale#engine#WaitForJobs(2000) AssertEqual g:expected_loclist, getloclist(0) - AssertEqual [[2, 1000001, 'ALEErrorSign']], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) AssertEqual \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') AssertEqual g:expected_groups, ParseAuGroups() AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + +Execute(ALEToggle should skip filename keys and preserve them): + AssertEqual 'foobar', &filetype + + let g:ale_buffer_info['/foo/bar/baz.txt'] = { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \} + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + " Now Toggle ALE off. + ALEToggle + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) + + " Toggle ALE on again. + ALEToggle + call ale#engine#WaitForJobs(2000) + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) diff --git a/test/test_balloon_messages.vader b/test/test_balloon_messages.vader index 50dc6af4..ec09fe29 100644 --- a/test/test_balloon_messages.vader +++ b/test/test_balloon_messages.vader @@ -3,16 +3,19 @@ Before: let g:ale_buffer_info[347] = {'loclist': [ \ { + \ 'bufnr': 347, \ 'lnum': 1, \ 'col': 10, \ 'text': 'Missing semicolon. (semi)', \ }, \ { + \ 'bufnr': 347, \ 'lnum': 2, \ 'col': 10, \ 'text': 'Infix operators must be spaced. (space-infix-ops)' \ }, \ { + \ 'bufnr': 347, \ 'lnum': 2, \ 'col': 15, \ 'text': 'Missing radix parameter (radix)' diff --git a/test/test_completion.vader b/test/test_completion.vader index 65cef465..811a2645 100644 --- a/test/test_completion.vader +++ b/test/test_completion.vader @@ -1,10 +1,34 @@ Before: Save g:ale_completion_enabled + Save g:ale_completion_delay + Save g:ale_completion_max_suggestions + Save &l:omnifunc + Save &l:completeopt + + let g:test_vars = { + \ 'feedkeys_calls': [], + \} + + function! ale#util#FeedKeys(string, mode) abort + call add(g:test_vars.feedkeys_calls, [a:string, a:mode]) + endfunction After: Restore - if !g:ale_completion_enabled + unlet! g:test_vars + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completopt + unlet! b:ale_completion_info + unlet! b:ale_completion_response + unlet! b:ale_completion_parser + + runtime autoload/ale/completion.vim + runtime autoload/ale/lsp.vim + + if g:ale_completion_enabled + call ale#completion#Enable() + else call ale#completion#Disable() endif @@ -84,6 +108,89 @@ Execute(TypeScript completion details responses should be parsed correctly): \ ], \}) +Execute(Prefix filtering should work for Lists of strings): + AssertEqual + \ ['FooBar', 'foo'], + \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], 'foo') + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], '.') + +Execute(Prefix filtering should work for completion items): + AssertEqual + \ [{'word': 'FooBar'}, {'word': 'foo'}], + \ ale#completion#Filter( + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ 'foo' + \ ) + AssertEqual + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ ale#completion#Filter( + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ '.' + \ ) + +Execute(The right message sent to the tsserver LSP when the first completion message is received): + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 1, 0]) + let b:ale_completion_info = { + \ 'conn_id': 123, + \ 'prefix': 'f', + \ 'request_id': 4, + \ 'line': 1, + \ 'column': 1, + \} + " We should only show up to this many suggestions. + let g:ale_completion_max_suggestions = 3 + + " Replace the Send function for LSP, so we can monitor calls to it. + function! ale#lsp#Send(conn_id, message) abort + let g:test_vars.message = a:message + endfunction + + " Handle the response for completions. + call ale#completion#HandleTSServerLSPResponse(123, { + \ 'request_seq': 4, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'Baz'}, + \ {'name': 'dingDong'}, + \ {'name': 'Foo'}, + \ {'name': 'FooBar'}, + \ {'name': 'frazzle'}, + \ {'name': 'FFS'}, + \ ], + \}) + + " The entry details messages should have been sent. + AssertEqual + \ [ + \ 0, + \ 'ts@completionEntryDetails', + \ { + \ 'file': expand('%:p'), + \ 'entryNames': ['Foo', 'FooBar', 'frazzle'], + \ 'offset': 1, + \ 'line': 1, + \ }, + \ ], + \ g:test_vars.message + Given typescript(): let abc = y. let foo = ab @@ -100,3 +207,122 @@ Execute(Completion should be done after words in parens in TypeScript): Execute(Completion should not be done after parens in TypeScript): AssertEqual '', ale#completion#GetPrefix(&filetype, 3, 15) + +Execute(ale#completion#Show() should remember the omnifunc setting and replace it): + let &l:omnifunc = 'FooBar' + + call ale#completion#Show('Response', 'Parser') + + AssertEqual 'FooBar', b:ale_old_omnifunc + AssertEqual 'ale#completion#OmniFunc', &l:omnifunc + +Execute(ale#completion#Show() should remember the completeopt setting and replace it): + let &l:completeopt = 'menu' + + call ale#completion#Show('Response', 'Parser') + + AssertEqual 'menu', b:ale_old_completopt + AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt + +Execute(ale#completion#OmniFunc() should also remember the completeopt setting and replace it): + let &l:completeopt = 'menu' + + call ale#completion#OmniFunc(0, '') + + AssertEqual 'menu', b:ale_old_completopt + AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt + +Execute(ale#completion#Show() should make the correct feedkeys() call): + call ale#completion#Show('Response', 'Parser') + + AssertEqual [["\<C-x>\<C-o>", 'n']], g:test_vars.feedkeys_calls + +Execute(ale#completion#Show() should set up the response and parser): + call ale#completion#Show('Response', 'Parser') + + AssertEqual 'Response', b:ale_completion_response + AssertEqual 'Parser', b:ale_completion_parser + +Execute(ale#completion#Done() should restore old omnifunc values): + let b:ale_old_omnifunc = 'FooBar' + + call ale#completion#Done() + + " We reset the old omnifunc setting and remove the buffer variable. + AssertEqual 'FooBar', &l:omnifunc + Assert !has_key(b:, 'ale_old_omnifunc') + +Execute(ale#completion#Done() should restore the old completeopt setting): + let b:ale_old_completopt = 'menu' + let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' + + call ale#completion#Done() + + AssertEqual 'menu', &l:completeopt + Assert !has_key(b:, 'ale_old_completopt') + +Execute(ale#completion#Done() should leave settings alone when none were remembered): + let &l:omnifunc = 'BazBoz' + let &l:completeopt = 'menu' + + call ale#completion#Done() + + AssertEqual 'BazBoz', &l:omnifunc + AssertEqual 'menu', &l:completeopt + +Execute(The completion request_id should be reset when queuing again): + let b:ale_completion_info = {'request_id': 123} + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + sleep 1m + + AssertEqual 0, b:ale_completion_info.request_id + +Execute(b:ale_completion_info should be set up correctly when requesting completions): + call setpos('.', [bufnr(''), 3, 14, 0]) + call ale#completion#GetCompletions() + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ }, + \ b:ale_completion_info + +Execute(ale#completion#GetCompletions should be called when the cursor position stays the same): + let g:test_vars.get_completions_called = 0 + + " We just want to check if the function is called. + function! ale#completion#GetCompletions() + let g:test_vars.get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + sleep 1m + + Assert g:test_vars.get_completions_called + +Execute(ale#completion#GetCompletions should not be called when the cursor position changes): + call setpos('.', [bufnr(''), 1, 2, 0]) + + let g:test_vars.get_completions_called = 0 + + " We just want to check if the function is called. + function! ale#completion#GetCompletions() + let g:test_vars.get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + + " Change the cursor position before the callback is triggered. + call setpos('.', [bufnr(''), 2, 2, 0]) + + sleep 1m + + Assert !g:test_vars.get_completions_called diff --git a/test/test_get_abspath.vader b/test/test_get_abspath.vader new file mode 100644 index 00000000..2def3773 --- /dev/null +++ b/test/test_get_abspath.vader @@ -0,0 +1,15 @@ +Execute(Relative paths should be resolved correctly): + AssertEqual + \ '/foo/bar/baz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', '../baz/whatever.txt') + AssertEqual + \ '/foo/bar/xyz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', './whatever.txt') + AssertEqual + \ '/foo/bar/xyz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', 'whatever.txt') + +Execute(Absolute paths should be resolved correctly): + AssertEqual + \ '/ding/dong', + \ ale#path#GetAbsPath('/foo/bar/xyz', '/ding/dong') diff --git a/test/test_highlight_placement.vader b/test/test_highlight_placement.vader index 02f570b9..c1909c4f 100644 --- a/test/test_highlight_placement.vader +++ b/test/test_highlight_placement.vader @@ -162,6 +162,7 @@ Execute(Higlight end columns should set an appropriate size): Execute(Higlight end columns should set an appropriate size): call ale#highlight#SetHighlights(bufnr('%'), [ + \ {'bufnr': bufnr('%') - 1, 'type': 'E', 'lnum': 1, 'col': 1}, \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 1, 'col': 1}, \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 2, 'col': 1}, \ {'bufnr': bufnr('%'), 'type': 'E', 'sub_type': 'style', 'lnum': 3, 'col': 1}, @@ -169,6 +170,7 @@ Execute(Higlight end columns should set an appropriate size): \ {'bufnr': bufnr('%'), 'type': 'W', 'lnum': 5, 'col': 1}, \ {'bufnr': bufnr('%'), 'type': 'W', 'sub_type': 'style', 'lnum': 6, 'col': 1}, \ {'bufnr': bufnr('%'), 'type': 'I', 'lnum': 7, 'col': 1}, + \ {'bufnr': bufnr('%') + 1, 'type': 'E', 'lnum': 1, 'col': 1}, \]) AssertEqual diff --git a/test/test_history_saving.vader b/test/test_history_saving.vader index 8207d153..3b8fb2aa 100644 --- a/test/test_history_saving.vader +++ b/test/test_history_saving.vader @@ -2,6 +2,8 @@ Before: Save g:ale_max_buffer_history_size Save g:ale_history_log_output + unlet! b:ale_history + " Temporarily set the shell to /bin/sh, if it isn't already set that way. " This will make it so the test works when running it directly. let g:current_shell = &shell @@ -26,6 +28,9 @@ Before: After: Restore + " Clear the history we changed. + unlet! b:ale_history + " Reset the shell back to what it was before. let &shell = g:current_shell unlet g:current_shell @@ -46,7 +51,7 @@ Execute(History should be set when commands are run): call ale#Lint() call ale#engine#WaitForJobs(2000) - let g:history = g:ale_buffer_info[bufnr('%')].history + let g:history = ale#history#Get(bufnr('')) AssertEqual 1, len(g:history) AssertEqual sort(['status', 'exit_code', 'job_id', 'command']), sort(keys(g:history[0])) @@ -64,7 +69,7 @@ Execute(History should be not set when disabled): call ale#Lint() call ale#engine#WaitForJobs(2000) - AssertEqual 0, len(g:ale_buffer_info[bufnr('%')].history) + AssertEqual [], ale#history#Get(bufnr('')) Execute(History should include command output if logging is enabled): AssertEqual 'foobar', &filetype @@ -74,35 +79,32 @@ Execute(History should include command output if logging is enabled): call ale#Lint() call ale#engine#WaitForJobs(2000) - let g:history = g:ale_buffer_info[bufnr('%')].history + let g:history = ale#history#Get(bufnr('')) AssertEqual 1, len(g:history) AssertEqual ['command history test'], g:history[0].output Execute(History items should be popped after going over the max): - let g:ale_buffer_info[1] = { - \ 'history': map(range(20), '{''status'': ''started'', ''job_id'': v:val, ''command'': ''foobar''}'), - \} + let b:ale_history = map(range(20), '{''status'': ''started'', ''job_id'': v:val, ''command'': ''foobar''}') - call ale#history#Add(1, 'started', 347, 'last command') + call ale#history#Add(bufnr(''), 'started', 347, 'last command') AssertEqual \ ( \ map(range(1, 19), '{''status'': ''started'', ''job_id'': v:val, ''command'': ''foobar''}') \ + [{'status': 'started', 'job_id': 347, 'command': 'last command'}] \ ), - \ g:ale_buffer_info[1].history + \ ale#history#Get(bufnr('')) Execute(Nothing should be added to history if the size is too low): let g:ale_max_buffer_history_size = 0 - let g:ale_buffer_info[1] = {'history': []} - call ale#history#Add(1, 'started', 347, 'last command') + call ale#history#Add(bufnr(''), 'started', 347, 'last command') - AssertEqual [], g:ale_buffer_info[1].history + AssertEqual [], ale#history#Get(bufnr('')) let g:ale_max_buffer_history_size = -2 call ale#history#Add(1, 'started', 347, 'last command') - AssertEqual [], g:ale_buffer_info[1].history + AssertEqual [], ale#history#Get(bufnr('')) diff --git a/test/test_lint_error_delay.vader b/test/test_lint_error_delay.vader new file mode 100644 index 00000000..4c7f0947 --- /dev/null +++ b/test/test_lint_error_delay.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_filetype_blacklist + + " Delete some variable which should be defined. + unlet! g:ale_filetype_blacklist + +After: + Restore + + call ale#ResetErrorDelays() + +Execute(ALE should stop queuing for a while after exceptions are thrown): + AssertThrows call ale#Queue(100) + call ale#Queue(100) + +Execute(ALE should stop linting for a while after exceptions are thrown): + AssertThrows call ale#Lint() + call ale#Lint() + +Execute(ALE should stop queuing echo messages for a while after exceptions are thrown): + AssertThrows call ale#cursor#EchoCursorWarningWithDelay() + call ale#cursor#EchoCursorWarningWithDelay() + +Execute(ALE should stop echoing messages for a while after exceptions are thrown): + AssertThrows call ale#cursor#EchoCursorWarning() + call ale#cursor#EchoCursorWarning() diff --git a/test/test_lint_file_linters.vader b/test/test_lint_file_linters.vader index 4110c059..cb859790 100644 --- a/test/test_lint_file_linters.vader +++ b/test/test_lint_file_linters.vader @@ -79,8 +79,14 @@ Before: call ale#test#SetFilename(g:filename) After: + if !g:ale_run_synchronously + call ale#engine#WaitForJobs(2000) + endif + Restore + unlet! b:ale_save_event_fired + unlet! b:ale_enabled unlet g:buffer_result let g:ale_buffer_info = {} call ale#linter#Reset() @@ -251,3 +257,33 @@ Execute(The Save event should respect the buffer number): \ 'type': 'E', \ }, \], GetSimplerLoclist() + +Execute(The Save event should set b:ale_save_event_fired to 1): + let b:ale_enabled = 0 + call ale#events#SaveEvent(bufnr('')) + + " This flag needs to be set so windows can be opened, etc. + AssertEqual 1, b:ale_save_event_fired + +Execute(b:ale_save_event_fired should be set to 0 when results are set): + let b:ale_save_event_fired = 1 + + call ale#engine#SetResults(bufnr(''), []) + + AssertEqual 0, b:ale_save_event_fired + +Execute(lint_file linters should stay running after checking without them): + let g:ale_run_synchronously = 0 + + " Run all linters, then just the buffer linters. + call ale#Queue(0, 'lint_file') + call ale#Queue(0) + + " The lint_file linter should still be running. + AssertEqual + \ ['lint_file_linter', 'buffer_linter'], + \ g:ale_buffer_info[bufnr('')].active_linter_list + " We should have 1 job for each linter. + AssertEqual 2, len(g:ale_buffer_info[bufnr('')].job_list) + + call ale#engine#WaitForJobs(2000) diff --git a/test/test_lint_on_filetype_changed.vader b/test/test_lint_on_filetype_changed.vader new file mode 100644 index 00000000..44446ef0 --- /dev/null +++ b/test/test_lint_on_filetype_changed.vader @@ -0,0 +1,47 @@ +Before: + Save &filetype + + let g:queue_calls = [] + + function! ale#Queue(...) + call add(g:queue_calls, a:000) + endfunction + +After: + Restore + + unlet! g:queue_calls + + " Reload the ALE code to load the real function again. + runtime autoload/ale.vim + + unlet! b:ale_original_filetype + +Execute(The original filetype should be set on BufEnter): + let &filetype = 'foobar' + + call ale#events#EnterEvent(bufnr('')) + + AssertEqual 'foobar', b:ale_original_filetype + + let &filetype = 'bazboz' + + call ale#events#EnterEvent(bufnr('')) + + AssertEqual 'bazboz', b:ale_original_filetype + +Execute(Linting should not be queued when the filetype is the same): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'foobar') + + AssertEqual [], g:queue_calls + +Execute(Linting should be queued when the filetype changes): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls diff --git a/test/test_linting_updates_loclist.vader b/test/test_linting_updates_loclist.vader index 19adfa1f..a1daf28d 100644 --- a/test/test_linting_updates_loclist.vader +++ b/test/test_linting_updates_loclist.vader @@ -1,4 +1,7 @@ Before: + Save g:ale_set_signs + let g:ale_set_signs = 1 + let g:expected_data = [ \ { \ 'lnum': 1, @@ -49,7 +52,11 @@ Before: \ 'read_buffer': 0, \}) + sign unplace * + After: + Restore + delfunction TestCallback unlet! g:expected_data diff --git a/test/test_list_opening.vader b/test/test_list_opening.vader index a46f28e5..7d386d80 100644 --- a/test/test_list_opening.vader +++ b/test/test_list_opening.vader @@ -5,6 +5,7 @@ Before: Save g:ale_open_list Save g:ale_keep_list_window_open Save g:ale_list_window_size + Save g:ale_buffer_info let g:ale_set_loclist = 1 let g:ale_set_quickfix = 0 @@ -13,11 +14,12 @@ Before: let g:ale_list_window_size = 10 let g:loclist = [ - \ {'lnum': 5, 'col': 5}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 2, 'text': 'x'}, \] + let g:ale_buffer_info = {bufnr(''): {'loclist': g:loclist}} function GetQuickfixHeight() abort for l:win in range(1, winnr('$')) @@ -34,6 +36,10 @@ After: unlet! g:loclist unlet! b:ale_list_window_size + unlet! b:ale_open_list + unlet! b:ale_keep_list_window_open + unlet! b:ale_save_event_fired + delfunction GetQuickfixHeight " Close quickfix window after every execute block @@ -117,6 +123,10 @@ Execute(The quickfix window should open for the quickfix list): let g:ale_set_quickfix = 1 let g:ale_open_list = 1 + let g:ale_buffer_info[bufnr('') + 1] = { + \ 'loclist': [{'bufnr': -1, 'filename': '/foo/bar', 'lnum': 5, 'col': 5, 'text': 'x'}], + \} + " It should not open for an empty list. call ale#list#SetLists(bufnr('%'), []) call ale#list#CloseWindowIfNeeded(bufnr('')) @@ -127,10 +137,17 @@ Execute(The quickfix window should open for the quickfix list): call ale#list#CloseWindowIfNeeded(bufnr('')) Assert ale#list#IsQuickfixOpen(), 'The quickfix window was closed when the list was not empty' - " Clear the list and it should close again. + " Clear this List. The window should stay open, as there are other items. + let g:ale_buffer_info[bufnr('')].loclist = [] + call ale#list#SetLists(bufnr('%'), []) + call ale#list#CloseWindowIfNeeded(bufnr('')) + Assert ale#list#IsQuickfixOpen(), 'The quickfix window closed even though there are items in another buffer' + + " Clear the other List now. Now the window should close. + call remove(g:ale_buffer_info, bufnr('') + 1) call ale#list#SetLists(bufnr('%'), []) call ale#list#CloseWindowIfNeeded(bufnr('')) - Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was not closed when the list was empty' + Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was not closed' Execute(The quickfix window should stay open for the quickfix list): let g:ale_set_quickfix = 1 @@ -163,3 +180,41 @@ Execute(The quickfix window height should be correct for the quickfix list with call ale#list#CloseWindowIfNeeded(bufnr('')) AssertEqual 8, GetQuickfixHeight() + +Execute(The buffer ale_open_list option should be respected): + let b:ale_open_list = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen() + +Execute(The buffer ale_keep_list_window_open option should be respected): + let b:ale_open_list = 1 + let b:ale_keep_list_window_open = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + call ale#list#CloseWindowIfNeeded(bufnr('')) + call ale#list#SetLists(bufnr('%'), []) + call ale#list#CloseWindowIfNeeded(bufnr('')) + + Assert ale#list#IsQuickfixOpen() + +Execute(The ale_open_list='on_save' option should work): + let b:ale_open_list = 'on_save' + + call ale#list#SetLists(bufnr('%'), g:loclist) + " The list shouldn't open yet, the event wasn't fired. + Assert !ale#list#IsQuickfixOpen() + + let b:ale_save_event_fired = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + " Now the list should have opened. + Assert ale#list#IsQuickfixOpen() + +Execute(The window shouldn't open on save when ale_open_list=0): + let b:ale_open_list = 0 + let b:ale_save_event_fired = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + " Now the list should have opened. + Assert !ale#list#IsQuickfixOpen() diff --git a/test/test_list_titles.vader b/test/test_list_titles.vader index fe28629d..74cb4bcc 100644 --- a/test/test_list_titles.vader +++ b/test/test_list_titles.vader @@ -1,7 +1,9 @@ Before: Save g:ale_set_loclist Save g:ale_set_quickfix + Save g:ale_buffer_info + let g:ale_buffer_info = {} let g:ale_set_loclist = 0 let g:ale_set_quickfix = 0 @@ -43,6 +45,10 @@ Execute(The quickfix titles should be set appropriately): let g:ale_set_quickfix = 1 + let g:ale_buffer_info[bufnr('')] = { + \ 'loclist': [{'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x', 'type': 'E'}], + \} + call ale#list#SetLists(bufnr(''), [ \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x', 'type': 'E'}, \]) diff --git a/test/test_loclist_binary_search.vader b/test/test_loclist_binary_search.vader index e0b2c651..5558191c 100644 --- a/test/test_loclist_binary_search.vader +++ b/test/test_loclist_binary_search.vader @@ -1,26 +1,49 @@ Before: let g:loclist = [ - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, - \ {'lnum': 3, 'col': 10}, - \ {'lnum': 3, 'col': 12}, - \ {'lnum': 3, 'col': 25}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 5, 'col': 5}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 3, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 12}, + \ {'bufnr': 1, 'lnum': 3, 'col': 25}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 1, 'lnum': 9, 'col': 5}, + \ {'bufnr': 1, 'lnum': 10, 'col': 1}, + \ {'bufnr': 2, 'lnum': 7, 'col': 10}, + \ {'bufnr': 2, 'lnum': 9, 'col': 2}, + \ {'bufnr': 2, 'lnum': 10, 'col': 2}, + \ {'bufnr': 2, 'lnum': 11, 'col': 2}, \] -Execute (Exact column matches should be correct): - AssertEqual 1, ale#util#BinarySearch(g:loclist, 3, 2) +After: + unlet g:loclist -Execute (Off lines, there should be no match): - AssertEqual -1, ale#util#BinarySearch(g:loclist, 4, 2) +Execute(Exact column matches should be correct): + AssertEqual 1, ale#util#BinarySearch(g:loclist, 1, 3, 2) -Execute (Near column matches should be taken): - AssertEqual 2, ale#util#BinarySearch(g:loclist, 3, 11) - AssertEqual 4, ale#util#BinarySearch(g:loclist, 3, 13) +Execute(Off lines, there should be no match): + AssertEqual -1, ale#util#BinarySearch(g:loclist, 1, 4, 2) -Execute (Columns before should be taken when the cursor is far ahead): - AssertEqual 4, ale#util#BinarySearch(g:loclist, 3, 300) +Execute(Near column matches should be taken): + AssertEqual 2, ale#util#BinarySearch(g:loclist, 1, 3, 11) + AssertEqual 3, ale#util#BinarySearch(g:loclist, 1, 3, 13) -After: - unlet g:loclist +Execute(Columns before should be taken when the cursor is far ahead): + AssertEqual 4, ale#util#BinarySearch(g:loclist, 1, 3, 300) + +Execute(The only problems on lines in later columns should be matched): + AssertEqual 7, ale#util#BinarySearch(g:loclist, 1, 9, 1) + +Execute(The only problems on lines in earlier columns should be matched): + AssertEqual 8, ale#util#BinarySearch(g:loclist, 1, 10, 30) + +Execute(Lines for other buffers should not be matched): + AssertEqual -1, ale#util#BinarySearch(g:loclist, 1, 7, 10) + +Execute(Searches for buffers later in the list should work): + AssertEqual 10, ale#util#BinarySearch(g:loclist, 2, 9, 10) + +Execute(Searches should work with just one item): + let g:loclist = [{'bufnr': 1, 'lnum': 3, 'col': 10}] + + AssertEqual 0, ale#util#BinarySearch(g:loclist, 1, 3, 2) diff --git a/test/test_loclist_corrections.vader b/test/test_loclist_corrections.vader index e23109ed..e6844d8e 100644 --- a/test/test_loclist_corrections.vader +++ b/test/test_loclist_corrections.vader @@ -1,3 +1,7 @@ +After: + unlet! b:temp_name + unlet! b:other_bufnr + Given foo (Some file with lines to count): foo12345678 bar12345678 @@ -202,7 +206,6 @@ Execute(FixLocList should pass on end_lnum values): \ ], \ ) - Execute(FixLocList should allow subtypes to be set): AssertEqual \ [ @@ -223,3 +226,104 @@ Execute(FixLocList should allow subtypes to be set): \ 'foobar', \ [{'text': 'a', 'lnum': 11, 'sub_type': 'style'}], \ ) + +Execute(FixLocList should accept filenames): + let b:other_bufnr = bufnr('/foo/bar/baz', 1) + + " Make sure we actually get another buffer number, or the test is invalid. + AssertNotEqual -1, b:other_bufnr + + call ale#test#SetFilename('test.txt') + + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'filename': expand('%:p'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'filename': expand('%:p'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 4, + \ 'col': 0, + \ 'bufnr': b:other_bufnr, + \ 'filename': '/foo/bar/baz', + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 5, + \ 'col': 0, + \ 'bufnr': b:other_bufnr, + \ 'filename': '/foo/bar/baz', + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ [ + \ {'text': 'a', 'lnum': 2, 'filename': expand('%:p')}, + \ {'text': 'a', 'lnum': 3, 'filename': expand('%:p')}, + \ {'text': 'a', 'lnum': 4, 'filename': '/foo/bar/baz'}, + \ {'text': 'a', 'lnum': 5, 'filename': '/foo/bar/baz'}, + \ ], + \ ) + +Execute(FixLocList should interpret temporary filenames as being the current buffer): + let b:temp_name = tempname() + + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \], + \ ale#engine#FixLocList( + \ bufnr(''), + \ 'foobar', + \ [ + \ {'text': 'a', 'lnum': 2, 'filename': b:temp_name}, + \ {'text': 'a', 'lnum': 3, 'filename': b:temp_name}, + \ ], + \ ) diff --git a/test/test_loclist_jumping.vader b/test/test_loclist_jumping.vader index 13eac5ce..5e18499e 100644 --- a/test/test_loclist_jumping.vader +++ b/test/test_loclist_jumping.vader @@ -1,14 +1,16 @@ Before: let g:ale_buffer_info = { - \ bufnr('%'): { + \ bufnr(''): { \ 'loclist': [ - \ {'lnum': 1, 'col': 2}, - \ {'lnum': 1, 'col': 3}, - \ {'lnum': 2, 'col': 1}, - \ {'lnum': 2, 'col': 2}, - \ {'lnum': 2, 'col': 3}, - \ {'lnum': 2, 'col': 6}, - \ {'lnum': 2, 'col': 700}, + \ {'bufnr': bufnr('') - 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 2}, + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 3}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 2}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 3}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 6}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 700}, + \ {'bufnr': bufnr('') + 1, 'lnum': 3, 'col': 2}, \ ], \ }, \} @@ -74,3 +76,15 @@ Execute(We shouldn't move when jumping to the first item where there are none): let g:ale_buffer_info[bufnr('%')].loclist = [] AssertEqual [1, 6], TestJump(0, 0, [1, 6]) + +Execute(We should be able to jump when the error line is blank): + " Add a blank line at the end. + call setline(1, getline('.', '$') + ['']) + " Add a problem on the blank line. + call add(g:ale_buffer_info[bufnr('%')].loclist, {'bufnr': bufnr(''), 'lnum': 3, 'col': 1}) + + AssertEqual 0, len(getline(3)) + AssertEqual [2, 8], TestJump('before', 0, [3, 1]) + AssertEqual [2, 8], TestJump('before', 1, [3, 1]) + AssertEqual [3, 1], TestJump('after', 0, [3, 1]) + AssertEqual [1, 2], TestJump('after', 1, [3, 1]) diff --git a/test/test_loclist_sorting.vader b/test/test_loclist_sorting.vader index 6e52be6f..157b2a25 100644 --- a/test/test_loclist_sorting.vader +++ b/test/test_loclist_sorting.vader @@ -1,21 +1,27 @@ -Before: - let g:loclist = [ - \ {'lnum': 5, 'col': 5}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, - \] - -Execute (Sort loclist with comparison function): - call sort(g:loclist, 'ale#util#LocItemCompare') - -Then (loclist item should be sorted): +Execute(loclist item should be sorted): AssertEqual [ - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 5, 'col': 5}, - \], g:loclist - -After: - unlet g:loclist + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ ], + \ sort([ + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \], 'ale#util#LocItemCompare') diff --git a/test/test_quickfix_deduplication.vader b/test/test_quickfix_deduplication.vader new file mode 100644 index 00000000..0dff3f2e --- /dev/null +++ b/test/test_quickfix_deduplication.vader @@ -0,0 +1,50 @@ +Before: + Save g:ale_buffer_info + +After: + Restore + +Execute: + " Results from multiple buffers should be gathered together. + " Equal problems should be de-duplicated. + let g:ale_buffer_info = { + \ '1': {'loclist': [ + \ {'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1, 'text': 'foo'}, + \ ]}, + \ '2': {'loclist': [ + \ {'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'another error'}, + \ ]}, + \} + + AssertEqual + \ [ + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'another error'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1, 'text': 'foo'}, + \ ], + \ ale#list#GetCombinedList() diff --git a/test/test_results_not_cleared_when_opening_loclist.vader b/test/test_results_not_cleared_when_opening_loclist.vader index 07d3d303..0c053b85 100644 --- a/test/test_results_not_cleared_when_opening_loclist.vader +++ b/test/test_results_not_cleared_when_opening_loclist.vader @@ -26,6 +26,9 @@ After: delfunction TestCallback let g:ale_buffer_info = {} call ale#linter#Reset() + call setloclist(0, []) + call clearmatches() + sign unplace * Given foobar (Some file): abc diff --git a/test/test_set_list_timers.vader b/test/test_set_list_timers.vader new file mode 100644 index 00000000..90aacb55 --- /dev/null +++ b/test/test_set_list_timers.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_set_lists_synchronously + Save g:ale_open_list + + let g:ale_set_lists_synchronously = 0 + +After: + Restore + + sleep 1ms + call setloclist(0, []) + lclose + +Execute(The SetLists function should work when run in a timer): + call ale#list#SetLists(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x', 'type': 'E'}, + \]) + sleep 1ms + AssertEqual [{ + \ 'lnum': 5, + \ 'bufnr': bufnr(''), + \ 'col': 5, + \ 'text': 'x', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \}], getloclist(0) + +Execute(The CloseWindowIfNeeded function should work when run in a timer): + let g:ale_open_list = 1 + lopen + + call ale#list#CloseWindowIfNeeded(bufnr('')) + sleep 1ms + + Assert !ale#list#IsQuickfixOpen(), 'The window was not closed!' diff --git a/test/test_setting_loclist_from_another_buffer.vader b/test/test_setting_loclist_from_another_buffer.vader index 4b757c61..10a44cce 100644 --- a/test/test_setting_loclist_from_another_buffer.vader +++ b/test/test_setting_loclist_from_another_buffer.vader @@ -1,12 +1,25 @@ Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = { + \ bufnr(''): { + \ 'loclist': [{'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'text': 'foo'}] + \ }, + \} + let g:original_buffer = bufnr('%') - new + noautocmd new After: + Restore + unlet! g:original_buffer Execute(Errors should be set in the loclist for the original buffer, not the new one): - call ale#list#SetLists(g:original_buffer, [{'lnum': 4, 'text': 'foo'}]) + call ale#list#SetLists( + \ g:original_buffer, + \ g:ale_buffer_info[(g:original_buffer)].loclist, + \ ) AssertEqual [], getloclist(0) AssertEqual 1, len(getloclist(bufwinid(g:original_buffer))) diff --git a/test/test_setting_problems_found_in_previous_buffers.vader b/test/test_setting_problems_found_in_previous_buffers.vader new file mode 100644 index 00000000..45dfa662 --- /dev/null +++ b/test/test_setting_problems_found_in_previous_buffers.vader @@ -0,0 +1,98 @@ +Before: + Save g:ale_buffer_info + Save &filetype + Save g:ale_set_lists_synchronously + + let g:ale_set_lists_synchronously = 1 + + " Set up items in other buffers which should set in this one. + let g:ale_buffer_info = {} + call ale#engine#InitBufferInfo(bufnr('') + 1) + let g:ale_buffer_info[bufnr('') + 1].loclist = + \ ale#engine#FixLocList(bufnr('') + 1, 'linter_one', [ + \ {'lnum': 1, 'filename': expand('%:p'), 'text': 'foo'}, + \ {'lnum': 2, 'filename': expand('%:p'), 'text': 'bar'}, + \ {'lnum': 2, 'text': 'ignore this one'}, + \ ]) + call ale#engine#InitBufferInfo(bufnr('') + 2) + let g:ale_buffer_info[bufnr('') + 2].loclist = + \ ale#engine#FixLocList(bufnr('') + 2, 'linter_one', [ + \ {'lnum': 1, 'filename': expand('%:p'), 'text': 'foo'}, + \ {'lnum': 3, 'filename': expand('%:p'), 'text': 'baz'}, + \ {'lnum': 5, 'text': 'ignore this one'}, + \ ]) + + call ale#linter#Define('foobar', { + \ 'name': 'linter_one', + \ 'callback': 'WhoCares', + \ 'executable': 'echo', + \ 'command': 'sleep 1000', + \ 'lint_file': 1, + \}) + +After: + call ale#engine#Cleanup(bufnr('')) + Restore + call ale#linter#Reset() + + " Items and markers, etc. + call setloclist(0, []) + call clearmatches() + sign unplace * + +Given foobar(A file with some lines): + foo + bar + baz + +Execute(Problems found from previously opened buffers should be set when linting for the first time): + call ale#engine#RunLinters(bufnr(''), ale#linter#Get(&filetype), 0) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'foo', + \ 'sign_id': 1000001, + \ }, + \ { + \ 'lnum': 2, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'bar', + \ 'sign_id': 1000002, + \ }, + \ { + \ 'lnum': 3, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'baz', + \ 'sign_id': 1000003, + \ }, + \ ], + \ g:ale_buffer_info[bufnr('')].loclist + + AssertEqual + \ [ + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'foo'}, + \ {'lnum': 2, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'bar'}, + \ {'lnum': 3, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'baz'}, + \ ], + \ getloclist(0) diff --git a/test/test_shell_detection.vader b/test/test_shell_detection.vader new file mode 100644 index 00000000..37cf43ce --- /dev/null +++ b/test/test_shell_detection.vader @@ -0,0 +1,83 @@ +Before: + runtime ale_linters/sh/shell.vim + runtime ale_linters/sh/shellcheck.vim + +After: + call ale#linter#Reset() + + unlet! b:is_bash + unlet! b:is_sh + unlet! b:is_kornshell + +Given(A file with a Bash hashbang): + #!/bin/bash + +Execute(/bin/bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with /bin/sh): + #!/usr/bin/env sh -eu --foobar + +Execute(/bin/sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with bash as an argument to env): + #!/usr/bin/env bash + +Execute(/usr/bin/env bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a tcsh hash bang and arguments): + #!/usr/bin/env tcsh -eu --foobar + +Execute(tcsh should be detected appropriately): + AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'tcsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'tcsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a zsh hash bang and arguments): + #!/usr/bin/env zsh -eu --foobar + +Execute(zsh should be detected appropriately): + AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'zsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'zsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a csh hash bang and arguments): + #!/usr/bin/env csh -eu --foobar + +Execute(zsh should be detected appropriately): + AssertEqual 'csh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'csh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'csh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a sh hash bang and arguments): + #!/usr/bin/env sh -eu --foobar + +Execute(sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file without a hashbang): + +Execute(The bash dialect should be used for shellcheck if b:is_bash is 1): + let b:is_bash = 1 + + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The sh dialect should be used for shellcheck if b:is_sh is 1): + let b:is_sh = 1 + + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The ksh dialect should be used for shellcheck if b:is_kornshell is 1): + let b:is_kornshell = 1 + + AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) diff --git a/test/test_statusline.vader b/test/test_statusline.vader index bd0fd5dc..7978a509 100644 --- a/test/test_statusline.vader +++ b/test/test_statusline.vader @@ -1,9 +1,11 @@ Before: - Save g:ale_statusline_format, g:ale_buffer_info + Save g:ale_statusline_format + Save g:ale_buffer_info + let g:ale_buffer_info = {} " A function for conveniently creating expected count objects. - function Counts(data) abort + function! Counts(data) abort let l:res = { \ '0': 0, \ '1': 0, @@ -28,10 +30,11 @@ Before: After: Restore + delfunction Counts Execute (Count should be 0 when data is empty): - AssertEqual Counts({}), ale#statusline#Count(bufnr('%')) + AssertEqual Counts({}), ale#statusline#Count(bufnr('')) Execute (Count should read data from the cache): let g:ale_buffer_info = {'44': {'count': Counts({'error': 1, 'warning': 2})}} @@ -44,23 +47,33 @@ Execute (The count should be correct after an update): Execute (Count should be match the loclist): let g:ale_buffer_info = { - \ bufnr('%'): { + \ bufnr(''): { \ 'loclist': [ - \ {'type': 'E'}, - \ {'type': 'E', 'sub_type': 'style'}, - \ {'type': 'E', 'sub_type': 'style'}, - \ {'type': 'W'}, - \ {'type': 'W'}, - \ {'type': 'W'}, - \ {'type': 'W', 'sub_type': 'style'}, - \ {'type': 'W', 'sub_type': 'style'}, - \ {'type': 'W', 'sub_type': 'style'}, - \ {'type': 'W', 'sub_type': 'style'}, - \ {'type': 'I'}, - \ {'type': 'I'}, - \ {'type': 'I'}, - \ {'type': 'I'}, - \ {'type': 'I'}, + \ {'bufnr': bufnr('') - 1, 'type': 'E'}, + \ {'bufnr': bufnr('') - 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') - 1, 'type': 'W'}, + \ {'bufnr': bufnr('') - 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') - 1, 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr('') + 1, 'type': 'E'}, + \ {'bufnr': bufnr('') + 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') + 1, 'type': 'W'}, + \ {'bufnr': bufnr('') + 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') + 1, 'type': 'I'}, \ ], \ }, \} @@ -73,59 +86,56 @@ Execute (Count should be match the loclist): \ '0': 3, \ '1': 12, \ 'total': 15, - \}, ale#statusline#Count(bufnr('%')) + \}, ale#statusline#Count(bufnr('')) Execute (Output should be empty for non-existant buffer): AssertEqual Counts({}), ale#statusline#Count(9001) Execute (Status() should return just errors for the old format): let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), [ - \ {'type': 'E'}, - \ {'type': 'E', 'sub_type': 'style'}, + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, \]) AssertEqual '2E', ale#statusline#Status() Execute (Status() should return just warnings for the old format): let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), [ - \ {'type': 'W'}, - \ {'type': 'W', 'sub_type': 'style'}, - \ {'type': 'I'}, + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, \]) AssertEqual '3W', ale#statusline#Status() Execute (Status() should return errors and warnings for the old format): let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), [ - \ {'type': 'E'}, - \ {'type': 'E', 'sub_type': 'style'}, - \ {'type': 'W'}, - \ {'type': 'W', 'sub_type': 'style'}, - \ {'type': 'I'}, + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, \]) AssertEqual '2E 3W', ale#statusline#Status() Execute (Status() should return the custom 'OK' string with the old format): let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), []) + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), []) AssertEqual 'OKIE', ale#statusline#Status() Execute(ale#statusline#Update shouldn't blow up when globals are undefined): - unlet! g:ale_buffer_info unlet! g:ale_statusline_format call ale#statusline#Update(1, []) Execute(ale#statusline#Count should return 0 counts when globals are undefined): - unlet! g:ale_buffer_info unlet! g:ale_statusline_format AssertEqual Counts({}), ale#statusline#Count(1) Execute(ale#statusline#Status should return 'OK' when globals are undefined): - unlet! g:ale_buffer_info unlet! g:ale_statusline_format AssertEqual 'OK', ale#statusline#Status() diff --git a/test/test_writefile_function.vader b/test/test_writefile_function.vader new file mode 100644 index 00000000..4e4aab53 --- /dev/null +++ b/test/test_writefile_function.vader @@ -0,0 +1,48 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + noautocmd :e! ++ff=unix + setlocal buftype=nofile + + if filereadable('.newline-test') + call delete('.newline-test') + endif + + call ale#test#RestoreDirectory() + +Given(A file with Windows line ending characters): + first
+ second
+ third
+ +Execute(Carriage returns should be included for ale#util#Writefile): + call ale#test#SetFilename('.newline-test') + + setlocal buftype= + noautocmd :w + noautocmd :e! ++ff=dos + + call ale#util#Writefile(bufnr(''), getline(1, '$'), '.newline-test') + + AssertEqual + \ ["first\r", "second\r", "third\r", ''], + \ readfile('.newline-test', 'b') + \ +Given(A file with Unix line ending characters): + first + second + third + +Execute(Unix file lines should be written as normal): + call ale#test#SetFilename('.newline-test') + + setlocal buftype= + noautocmd :w + noautocmd :e! ++ff=unix + + call ale#util#Writefile(bufnr(''), getline(1, '$'), '.newline-test') + + AssertEqual + \ ['first', 'second', 'third', ''], + \ readfile('.newline-test', 'b') @@ -1,5 +1,8 @@ " vint: -ProhibitSetNoCompatible +" Make most tests just set lists synchronously when run in Docker. +let g:ale_set_lists_synchronously = 1 + " Load builtin plugins " We need this because run_vim.sh sets -i NONE set runtimepath=/home/vim,$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after,/testplugin,/vader |