diff options
348 files changed, 9761 insertions, 1704 deletions
@@ -1,6 +1,7 @@ -/init.vim -/doc/tags -.* !.editorconfig *.obj +.* +/doc/tags +/init.vim +/test/ale-info-test-file tags diff --git a/.travis.yml b/.travis.yml index 24237322..f5389f74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,6 @@ sudo: required services: - docker -language: python +language: generic script: | ./run-tests @@ -1,4 +1,4 @@ -Copyright (c) 2016-2017, w0rp <devw0rp@gmail.com> +Copyright (c) 2016-2018, w0rp <devw0rp@gmail.com> All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 94116532..e1ca94f6 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -12,7 +12,8 @@ READ THIS: Before creating a pull request, please consider the following first. that ALE can work with, please add Vader tests for them. Look at existing tests in the test/handler directory, etc. * If you add or modify a function for computing a command line string for - running a command, please add Vader tests for that. + running a command, please add Vader tests for that. Look at existing + tests in the test/command_callback directory, etc. * Generally try and cover anything with Vader tests, although some things just can't be tested with Vader, or at least they can be hard to test. Consider breaking up your code so that some parts can be tested, and generally open up @@ -4,7 +4,7 @@ ![ALE Logo by Mark Grealish - https://www.bhalash.com/](img/logo.jpg?raw=true) ALE (Asynchronous Lint Engine) is a plugin for providing linting in NeoVim -and Vim 8 while you edit your text files. +0.2.0+ and Vim 8 while you edit your text files. ![linting example](img/example.gif?raw=true) @@ -16,8 +16,22 @@ back to a filesystem. In other words, this plugin allows you to lint while you type. -In addition to linting support, ALE offers some support for fixing code with -formatting tools, and some Language Server Protocol and `tsserver` features. +ALE offers support for fixing code with command line tools in a non-blocking +manner with the `:ALEFix` feature, supporting tools in many languages, like +`prettier`, `eslint`, `autopep8`, and more. + +ALE acts as a "language client" to support a variety of Language Server Protocol +features, including: + +* Diagnostics (via Language Server Protocol linters) +* Go To Definition (`:ALEGoToDefinition`) +* Completion (`let g:ale_completion_enabled = 1`) +* Finding references (`:ALEFindReferences`) +* Hover information (`:ALEHover`) + +If you don't care about Language Server Protocol, ALE won't load any of the code +for working with it unless needed. One of ALE's general missions is that you +won't pay for the features that you don't use. ## Table of Contents @@ -27,6 +41,8 @@ formatting tools, and some Language Server Protocol and `tsserver` features. 2. [Fixing](#usage-fixing) 3. [Completion](#usage-completion) 4. [Go To Definition](#usage-go-to-definition) + 5. [Find References](#usage-find-references) + 6. [Hovering](#usage-hover) 3. [Installation](#installation) 1. [Installation with Vim package management](#standard-installation) 2. [Installation with Pathogen](#installation-with-pathogen) @@ -48,6 +64,7 @@ formatting tools, and some Language Server Protocol and `tsserver` features. 13. [Will this plugin eat all of my laptop battery power?](#faq-my-battery-is-sad) 14. [How can I configure my C or C++ project?](#faq-c-configuration) 15. [How can I configure ALE differently for different buffers?](#faq-buffer-configuration) + 16. [How can I configure the height of the list in which ALE displays errors?](#faq-list-window-height) <a name="supported-languages"></a> @@ -77,24 +94,26 @@ formatting. | API Blueprint | [drafter](https://github.com/apiaryio/drafter) | | AsciiDoc | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [write-good](https://github.com/btford/write-good) | | Awk | [gawk](https://www.gnu.org/software/gawk/)| -| Bash | shell [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | +| Bash | [language-server](https://github.com/mads-hartmann/bash-language-server), shell [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | | Bourne Shell | shell [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | -| 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/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) !!, [gcc](https://gcc.gnu.org/) | +| C | [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [clang](http://clang.llvm.org/), [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | +| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) !!, [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) !!, [cquery](https://github.com/cquery-project/cquery), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | | CUDA | [nvcc](http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html) | | C# | [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) see:`help ale-cs-mcs` for details, [mcsc](http://www.mono-project.com/docs/about-mono/languages/csharp/) !! see:`help ale-cs-mcsc` for details and configuration| | Chef | [foodcritic](http://www.foodcritic.io/) | | Clojure | [joker](https://github.com/candid82/joker) | +| CloudFormation | [cfn-python-lint](https://github.com/awslabs/cfn-python-lint) | | 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/) !! | | CSS | [csslint](http://csslint.net/), [prettier](https://github.com/prettier/prettier), [stylelint](https://github.com/stylelint/stylelint) | +| Cucumber | [cucumber](https://cucumber.io/) | | Cython (pyrex filetype) | [cython](http://cython.org/) | | D | [dmd](https://dlang.org/dmd-linux.html) | | Dafny | [dafny](https://rise4fun.com/Dafny) !! | | Dart | [dartanalyzer](https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli) !!, [language_server](https://github.com/natebosch/dart_language_server) | | Dockerfile | [hadolint](https://github.com/hadolint/hadolint) | -| Elixir | [credo](https://github.com/rrrene/credo), [dialyxir](https://github.com/jeremyjh/dialyxir), [dogma](https://github.com/lpil/dogma) !!| +| Elixir | [credo](https://github.com/rrrene/credo), [dialyxir](https://github.com/jeremyjh/dialyxir), [dogma](https://github.com/lpil/dogma), [mix](https://hexdocs.pm/mix/Mix.html) !!| | Elm | [elm-format](https://github.com/avh4/elm-format), [elm-make](https://github.com/elm-lang/elm-make) | | Erb | [erb](https://apidock.com/ruby/ERB), [erubi](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) | @@ -104,15 +123,15 @@ formatting. | FusionScript | [fusion-lint](https://github.com/RyanSquared/fusionscript) | | Git Commit Messages | [gitlint](https://github.com/jorisroovers/gitlint) | | GLSL | [glslang](https://github.com/KhronosGroup/glslang), [glslls](https://github.com/svenstaro/glsl-language-server) | -| Go | [gofmt](https://golang.org/cmd/gofmt/), [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports), [go vet](https://golang.org/cmd/vet/), [golint](https://godoc.org/github.com/golang/lint), [gotype](https://godoc.org/golang.org/x/tools/cmd/gotype), [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/), [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports), [go vet](https://golang.org/cmd/vet/) !!, [golint](https://godoc.org/github.com/golang/lint), [gotype](https://godoc.org/golang.org/x/tools/cmd/gotype) !!, [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 | [eslint](http://eslint.org/), [gqlint](https://github.com/happylinks/gqlint), [prettier](https://github.com/prettier/prettier) | | Haml | [haml-lint](https://github.com/brigade/haml-lint) | | Handlebars | [ember-template-lint](https://github.com/rwjblue/ember-template-lint) | | Haskell | [brittany](https://github.com/lspitzner/brittany), [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), [hfmt](https://github.com/danstiner/hfmt) | | HTML | [alex](https://github.com/wooorm/alex) !!, [HTMLHint](http://htmlhint.com/), [proselint](http://proselint.com/), [tidy](http://www.html-tidy.org/), [write-good](https://github.com/btford/write-good) | | Idris | [idris](http://www.idris-lang.org/) | -| Java | [checkstyle](http://checkstyle.sourceforge.net), [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html), [google-java-format](https://github.com/google/google-java-format) | -| JavaScript | [eslint](http://eslint.org/), [flow](https://flowtype.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [prettier](https://github.com/prettier/prettier), [prettier-eslint](https://github.com/prettier/prettier-eslint), [prettier-standard](https://github.com/sheerun/prettier-standard), [standard](http://standardjs.com/), [xo](https://github.com/sindresorhus/xo) +| Java | [checkstyle](http://checkstyle.sourceforge.net), [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html), [google-java-format](https://github.com/google/google-java-format), [PMD](https://pmd.github.io/) | +| JavaScript | [eslint](http://eslint.org/), [flow](https://flowtype.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [prettier](https://github.com/prettier/prettier), [prettier-eslint](https://github.com/prettier/prettier-eslint-cli), [prettier-standard](https://github.com/sheerun/prettier-standard), [standard](http://standardjs.com/), [xo](https://github.com/sindresorhus/xo) | JSON | [fixjson](https://github.com/rhysd/fixjson), [jsonlint](http://zaa.ch/jsonlint/), [jq](https://stedolan.github.io/jq/), [prettier](https://github.com/prettier/prettier) | | Kotlin | [kotlinc](https://kotlinlang.org) !!, [ktlint](https://ktlint.github.io) !! see `:help ale-integration-kotlin` for configuration instructions | | LaTeX | [alex](https://github.com/wooorm/alex) !!, [chktex](http://www.nongnu.org/chktex/), [lacheck](https://www.ctan.org/pkg/lacheck), [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | @@ -121,32 +140,36 @@ formatting. | Lua | [luac](https://www.lua.org/manual/5.1/luac.html), [luacheck](https://github.com/mpeterv/luacheck) | | Mail | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale) | | Make | [checkmake](https://github.com/mrtazz/checkmake) | -| Markdown | [alex](https://github.com/wooorm/alex) !!, [mdl](https://github.com/mivok/markdownlint), [prettier](https://github.com/prettier/prettier), [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [remark-lint](https://github.com/wooorm/remark-lint) !!, [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | +| Markdown | [alex](https://github.com/wooorm/alex) !!, [markdownlint](https://github.com/DavidAnson/markdownlint) !!, [mdl](https://github.com/mivok/markdownlint), [prettier](https://github.com/prettier/prettier), [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [remark-lint](https://github.com/wooorm/remark-lint) !!, [textlint](https://textlint.github.io/), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | | MATLAB | [mlint](https://www.mathworks.com/help/matlab/ref/mlint.html) | +| Mercury | [mmc](http://mercurylang.org) !! | +| NASM | [nasm](https://www.nasm.us/) !! | | Nim | [nim check](https://nim-lang.org/docs/nimc.html) !! | | nix | [nix-instantiate](http://nixos.org/nix/manual/#sec-nix-instantiate) | | nroff | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good)| | 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-ocaml-merlin` for configuration instructions, [ols](https://github.com/freebroccolo/ocaml-language-server) | -| Perl | [perl -c](https://perl.org/), [perl-critic](https://metacpan.org/pod/Perl::Critic) | -| PHP | [hack](http://hacklang.org/), [hackfmt](https://github.com/facebook/flow/tree/master/hack/hackfmt), [langserver](https://github.com/felixfbecker/php-language-server), [phan](https://github.com/phan/phan) see `:help ale-php-phan` to instructions, [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) | +| Perl | [perl -c](https://perl.org/), [perl-critic](https://metacpan.org/pod/Perl::Critic), [perltidy](https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy) | +| PHP | [hack](http://hacklang.org/), [hackfmt](https://github.com/facebook/flow/tree/master/hack/hackfmt), [langserver](https://github.com/felixfbecker/php-language-server), [phan](https://github.com/phan/phan) see `:help ale-php-phan` to instructions, [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), [php-cs-fixer](http://cs.sensiolabs.org/) | | PO | [alex](https://github.com/wooorm/alex) !!, [msgfmt](https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html), [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | | Pod | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | +| Pony | [ponyc](https://github.com/ponylang/ponyc) | | proto | [protoc-gen-lint](https://github.com/ckaznocha/protoc-gen-lint) | | 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/), [prospector](http://github.com/landscapeio/prospector), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pyls](https://github.com/palantir/python-language-server), [pylint](https://www.pylint.org/) !!, [yapf](https://github.com/google/yapf) | +| Python | [autopep8](https://github.com/hhatto/autopep8), [black](https://github.com/ambv/black), [flake8](http://flake8.pycqa.org/en/latest/), [isort](https://github.com/timothycrosley/isort), [mypy](http://mypy-lang.org/), [prospector](http://github.com/landscapeio/prospector), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pyls](https://github.com/palantir/python-language-server), [pylint](https://www.pylint.org/) !!, [yapf](https://github.com/google/yapf) | +| QML | [qmlfmt](https://github.com/jesperhh/qmlfmt), [qmllint](https://github.com/qt/qtdeclarative/tree/5.11/tools/qmllint) | | 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, [ols](https://github.com/freebroccolo/ocaml-language-server), [refmt](https://github.com/reasonml/reason-cli) | +| ReasonML | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-reasonml-ols` for configuration instructions, [ols](https://github.com/freebroccolo/ocaml-language-server), [refmt](https://github.com/reasonml/reason-cli) | | reStructuredText | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [rstcheck](https://github.com/myint/rstcheck), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | | Re:VIEW | [redpen](http://redpen.cc/) | | 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) | +| 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), [rufo](https://github.com/ruby-formatter/rufo) | | Rust | cargo !! (see `:help ale-integration-rust` for configuration instructions), [rls](https://github.com/rust-lang-nursery/rls), [rustc](https://www.rust-lang.org/), [rustfmt](https://github.com/rust-lang-nursery/rustfmt) | | SASS | [sass-lint](https://www.npmjs.com/package/sass-lint), [stylelint](https://github.com/stylelint/stylelint) | | SCSS | [prettier](https://github.com/prettier/prettier), [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) | +| Scala | [fsc](https://www.scala-lang.org/old/sites/default/files/linuxsoft_archives/docu/files/tools/fsc.html), [scalac](http://scala-lang.org), [scalafmt](https://scalameta.org/scalafmt/), [scalastyle](http://www.scalastyle.org) | | Slim | [slim-lint](https://github.com/sds/slim-lint) | | SML | [smlnj](http://www.smlnj.org/) | | Solidity | [solhint](https://github.com/protofire/solhint), [solium](https://github.com/duaraghav8/Solium) | @@ -156,7 +179,7 @@ formatting. | Tcl | [nagelfar](http://nagelfar.sourceforge.net) !! | | Terraform | [tflint](https://github.com/wata727/tflint) | | Texinfo | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good)| -| Text^ | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good), [redpen](http://redpen.cc/) | +| Text^ | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [textlint](https://textlint.github.io/), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | | Thrift | [thrift](http://thrift.apache.org/) | | TypeScript | [eslint](http://eslint.org/), [prettier](https://github.com/prettier/prettier), [tslint](https://github.com/palantir/tslint), tsserver, typecheck | | Verilog | [iverilog](https://github.com/steveicarus/iverilog), [verilator](http://www.veripool.org/projects/verilator/wiki/Intro) | @@ -192,24 +215,42 @@ ale-linter-options` for options specified to particular linters. ### 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. For example, the -following code can be used to fix JavaScript code with ESLint: +either in each buffer with a `b:ale_fixers`, or globally with `g:ale_fixers`. + +The recommended way to configure fixers is to define a List in an ftplugin file. + +```vim +" In ~/.vim/ftplugin/javascript.vim, or somewhere similar. + +" Fix files with prettier, and then ESLint. +let b:ale_fixers = ['prettier', 'eslint'] +" Equivalent to the above. +let b:ale_fixers = {'javascript': ['prettier', 'eslint']} +``` + +You can also configure your fixers from vimrc using `g:ale_fixers`, before +or after ALE has been loaded. ```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. +" In ~/.vim/vimrc, or somewhere similar. let g:ale_fixers = { \ 'javascript': ['eslint'], \} +``` + +If you want to automatically fix files when you save them, you need to turn +a setting on in vimrc. -" Set this setting in vimrc if you want to fix files automatically on save. -" This is off by default. +```vim +" Set this variable to 1 to fix files when you save them. 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. +The `:ALEFixSuggest` command will suggest some supported tools for fixing code. +Both `g:ale_fixers` and `b:ale_fixers` can also accept functions, including +lambda functions, as fixers, for fixing files with custom tools. + +See `:help ale-fix` for complete information on how to fix files with ALE. <a name="usage-completion"></a> @@ -217,9 +258,7 @@ too. See `:help ale-fix` for detailed information. 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: +Protocol linters, or from `tsserver` for TypeScript. ```vim " Enable completion where available. @@ -233,10 +272,34 @@ See `:help ale-completion` for more information. ### 2.iv Go To Definition ALE supports jumping to the definition of words under your cursor with the -`:ALEGoToDefinition` command using any enabled LSP linters and `tsserver`. +`:ALEGoToDefinition` command using any enabled Language Server Protocol linters +and `tsserver`. See `:help ale-go-to-definition` for more information. +<a name="usage-find-references"></a> + +### 2.v Find References + +ALE supports finding references for words under your cursor with the +`:ALEFindReferences` command using any enabled Language Server Protocol linters +and `tsserver`. + +See `:help ale-find-references` for more information. + +<a name="usage-hover"></a> + +### 2.vi Hovering + +ALE supports "hover" information for printing brief information about symbols at +the cursor taken from Language Server Protocol linters and `tsserver` with the +`ALEHover` command. + +On vim/gvim with `balloon` support you can see the information in a tooltip +that appears under the mouse when you mouseover a symbol. + +See `:help ale-hover` for more information. + <a name="installation"></a> ## 3. Installation @@ -345,12 +408,28 @@ on Freenode. Web chat is available [here](https://webchat.freenode.net/?channels ### 5.i. How do I disable particular linters? -By default, all available tools for all supported languages will be run. -If you want to only select a subset of the tools, simply create a -`g:ale_linters` dictionary in your vimrc file mapping filetypes -to lists of linters to run. +By default, all available tools for all supported languages will be run. If you +want to only select a subset of the tools, you can define `b:ale_linters` for a +single buffer, or `g:ale_linters` globally. + +The recommended way to configure linters is to define a List in an ftplugin +file. ```vim +" In ~/.vim/ftplugin/javascript.vim, or somewhere similar. + +" Enable ESLint only for JavaScript. +let b:ale_linters = ['eslint'] + +" Equivalent to the above. +let b:ale_linters = {'javascript': ['eslint']} +``` + +You can also declare which linters you want to run in your vimrc file, before or +after ALE has been loaded. + +```vim +" In ~/.vim/vimrc, or somewhere similar. let g:ale_linters = { \ 'javascript': ['eslint'], \} @@ -361,6 +440,14 @@ be run for those languages, just as when the dictionary is not defined. Running many linters should not typically obstruct editing in Vim, as they will all be executed in separate processes simultaneously. +If you don't want ALE to run anything other than what you've explicitly asked +for, you can set `g:ale_linters_explicit` to `1`. + +```vim +" Only run linters named in ale_linters settings. +let g:ale_linters_explicit = 1 +``` + This plugin will look for linters in the [`ale_linters`](ale_linters) directory. Each directory within corresponds to a particular filetype in Vim, and each file in each directory corresponds to the name of a particular linter. @@ -479,6 +566,8 @@ There are 3 global options that allow customizing the echoed message. - `g:ale_echo_msg_format` where: * `%s` is the error message itself + * `%...code...%` is an optional error code, and most characters can be + written between the `%` characters. * `%linter%` is the linter name * `%severity` is the severity type - `g:ale_echo_msg_error_str` is the string used for error severity. @@ -496,20 +585,28 @@ Will give you: ![Echoed message](img/echo.png) +See `:help g:ale_echo_msg_format` for more information. + <a name="faq-autocmd"></a> ### 5.viii. How can I execute some code when ALE starts or stops linting? ALE runs its own [autocmd](http://vimdoc.sourceforge.net/htmldoc/autocmd.html) -events whenever has a linter is started and has been successfully executed and -processed. These events can be used to call arbitrary functions before and after -ALE stops linting. +events when a lint or fix cycle are started and stopped. There is also an event +that runs when a linter job has been successfully started. These events can be +used to call arbitrary functions during these respective parts of the ALE's +operation. ```vim augroup YourGroup autocmd! - autocmd User ALELintPre call YourFunction() - autocmd User ALELintPost call YourFunction() + autocmd User ALELintPre call YourFunction() + autocmd User ALELintPost call YourFunction() + + autocmd User ALEJobStarted call YourFunction() + + autocmd User ALEFixPre call YourFunction() + autocmd User ALEFixPost call YourFunction() augroup END ``` @@ -576,6 +673,9 @@ let g:ale_open_list = 1 let g:ale_keep_list_window_open = 1 ``` +You can also set `let g:ale_list_vertical = 1` to open the windows vertically +instead of the default horizontally. + <a name="faq-jsx-stylelint-eslint"></a> ### 5.xii. How can I check JSX files with both stylelint and eslint? @@ -597,9 +697,18 @@ augroup END ``` Supposing the filetype has been set correctly, you can set the following -options in your vimrc file: +options in a jsx.vim ftplugin file. + +```vim +" In ~/.vim/ftplugin/jsx.vim, or somewhere similar. +let b:ale_linters = ['stylelint', 'eslint'] +let b:ale_linter_aliases = ['css'] +``` + +Or if you want, you can configure the linters from your vimrc file. ```vim +" In ~/.vim/vimrc, or somewhere similar. let g:ale_linters = {'jsx': ['stylelint', 'eslint']} let g:ale_linter_aliases = {'jsx': 'css'} ``` @@ -645,8 +754,9 @@ ALE cannot easily detect which compiler flags to use. Some tools and build configurations can generate [compile_commands.json](https://clang.llvm.org/docs/JSONCompilationDatabase.html) -files. The `cppcheck`, `clangcheck` and `clangtidy` linters can read these -files for automatically determining the appropriate compiler flags to use. +files. The `cppcheck`, `clangcheck`, `clangtidy` and `cquery` linters can read +these files for automatically determining the appropriate compiler flags to +use. For linting with compilers like `gcc` and `clang`, and with other tools, you will need to tell ALE which compiler flags to use yourself. You can use @@ -694,3 +804,14 @@ let g:ale_pattern_options_enabled = 1 ``` Buffer-local variables for settings always override the global settings. + +<a name="faq-list-window-height"></a> + +### 5.xvi. How can I configure the height of the list in which ALE displays errors? + +To set a default height for the error list, use the `g:ale_list_window_size` variable. + +```vim +" Show 5 lines of errors (default: 10) +let g:ale_list_window_size = 5 +``` diff --git a/after/plugin/ale.vim b/after/plugin/ale.vim deleted file mode 100644 index d738dbdd..00000000 --- a/after/plugin/ale.vim +++ /dev/null @@ -1,37 +0,0 @@ -" Author: w0rp <devw0rp@gmail.com> -" Description: Follow-up checks for the plugin: warn about conflicting plugins. - -" A flag for ensuring that this is not run more than one time. -if exists('g:loaded_ale_after') - finish -endif - -" Set the flag so this file is not run more than one time. -let g:loaded_ale_after = 1 - -" Check if the flag is available and set to 0 to disable checking for and -" emitting conflicting plugin warnings. -if exists('g:ale_emit_conflict_warnings') && !g:ale_emit_conflict_warnings - finish -endif - -" Conflicting Plugins Checks - -function! s:GetConflictingPluginWarning(plugin_name) abort - return 'ALE conflicts with ' . a:plugin_name - \ . '. Uninstall it, or disable this warning with ' - \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' - \ . '*before* plugins are loaded.' -endfunction - -if exists('g:loaded_syntastic_plugin') - throw s:GetConflictingPluginWarning('Syntastic') -endif - -if exists('g:loaded_neomake') - throw s:GetConflictingPluginWarning('Neomake') -endif - -if exists('g:loaded_validator_plugin') - throw s:GetConflictingPluginWarning('Validator') -endif diff --git a/ale_linters/awk/gawk.vim b/ale_linters/awk/gawk.vim index ac6e9154..3e9987b3 100644 --- a/ale_linters/awk/gawk.vim +++ b/ale_linters/awk/gawk.vim @@ -12,7 +12,11 @@ function! ale_linters#awk#gawk#GetExecutable(buffer) abort endfunction function! ale_linters#awk#gawk#GetCommand(buffer) abort + " note the --source 'BEGIN ...' is to prevent + " gawk from attempting to execute the body of the script + " it is linting. return ale_linters#awk#gawk#GetExecutable(a:buffer) + \ . " --source 'BEGIN { exit } END { exit 1 }'" \ . ' ' . ale#Var(a:buffer, 'awk_gawk_options') \ . ' ' . '-f %t --lint /dev/null' endfunction @@ -21,6 +25,6 @@ call ale#linter#Define('awk', { \ 'name': 'gawk', \ 'executable_callback': 'ale_linters#awk#gawk#GetExecutable', \ 'command_callback': 'ale_linters#awk#gawk#GetCommand', -\ 'callback': 'ale#handlers#cpplint#HandleCppLintFormat', +\ 'callback': 'ale#handlers#gawk#HandleGawkFormat', \ 'output_stream': 'both' \}) diff --git a/ale_linters/c/clang.vim b/ale_linters/c/clang.vim index 76803056..ddec4fcb 100644 --- a/ale_linters/c/clang.vim +++ b/ale_linters/c/clang.vim @@ -8,15 +8,15 @@ function! ale_linters#c#clang#GetExecutable(buffer) abort return ale#Var(a:buffer, 'c_clang_executable') endfunction -function! ale_linters#c#clang#GetCommand(buffer) abort - let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) +function! ale_linters#c#clang#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) " -iquote with the directory the file is in makes #include work for " headers in the same directory. return ale#Escape(ale_linters#c#clang#GetExecutable(a:buffer)) \ . ' -S -x c -fsyntax-only ' \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' - \ . ale#c#IncludeOptions(l:paths) + \ . l:cflags \ . ale#Var(a:buffer, 'c_clang_options') . ' -' endfunction @@ -24,6 +24,9 @@ call ale#linter#Define('c', { \ 'name': 'clang', \ 'output_stream': 'stderr', \ 'executable_callback': 'ale_linters#c#clang#GetExecutable', -\ 'command_callback': 'ale_linters#c#clang#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#c#clang#GetCommand'} +\ ], \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/c/flawfinder.vim b/ale_linters/c/flawfinder.vim new file mode 100644 index 00000000..df6fbebe --- /dev/null +++ b/ale_linters/c/flawfinder.vim @@ -0,0 +1,31 @@ +" Author: Christian Gibbons <cgibbons@gmu.edu> +" Description: flawfinder linter for c files + +call ale#Set('c_flawfinder_executable', 'flawfinder') +call ale#Set('c_flawfinder_options', '') +call ale#Set('c_flawfinder_minlevel', 1) +call ale#Set('c_flawfinder_error_severity', 6) + +function! ale_linters#c#flawfinder#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'c_flawfinder_executable') +endfunction + +function! ale_linters#c#flawfinder#GetCommand(buffer) abort + + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'c_flawfinder_minlevel') + + return ale#Escape(ale_linters#c#flawfinder#GetExecutable(a:buffer)) + \ . ' -CDQS' + \ . ale#Var(a:buffer, 'c_flawfinder_options') + \ . l:minlevel + \ . ' %t' +endfunction + +call ale#linter#Define('c', { +\ 'name': 'flawfinder', +\ 'output_stream': 'stdout', +\ 'executable_callback': 'ale_linters#c#flawfinder#GetExecutable', +\ 'command_callback': 'ale_linters#c#flawfinder#GetCommand', +\ 'callback': 'ale#handlers#flawfinder#HandleFlawfinderFormat', +\}) diff --git a/ale_linters/c/gcc.vim b/ale_linters/c/gcc.vim index 4b241e37..98563952 100644 --- a/ale_linters/c/gcc.vim +++ b/ale_linters/c/gcc.vim @@ -8,15 +8,15 @@ function! ale_linters#c#gcc#GetExecutable(buffer) abort return ale#Var(a:buffer, 'c_gcc_executable') endfunction -function! ale_linters#c#gcc#GetCommand(buffer) abort - let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) +function! ale_linters#c#gcc#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) " -iquote with the directory the file is in makes #include work for " headers in the same directory. return ale#Escape(ale_linters#c#gcc#GetExecutable(a:buffer)) \ . ' -S -x c -fsyntax-only ' \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' - \ . ale#c#IncludeOptions(l:paths) + \ . l:cflags \ . ale#Var(a:buffer, 'c_gcc_options') . ' -' endfunction @@ -24,6 +24,9 @@ call ale#linter#Define('c', { \ 'name': 'gcc', \ 'output_stream': 'stderr', \ 'executable_callback': 'ale_linters#c#gcc#GetExecutable', -\ 'command_callback': 'ale_linters#c#gcc#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#c#gcc#GetCommand'} +\ ], \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/cloudformation/cfn_python_lint.vim b/ale_linters/cloudformation/cfn_python_lint.vim new file mode 100644 index 00000000..d0ac7b28 --- /dev/null +++ b/ale_linters/cloudformation/cfn_python_lint.vim @@ -0,0 +1,35 @@ +" Author: Yasuhiro Kiyota <yasuhiroki.duck@gmail.com> +" Description: Support cfn-python-lint for AWS Cloudformation template file + +function! ale_linters#cloudformation#cfn_python_lint#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " sample.template.yaml:96:7:96:15:E3012:Property Resources/Sample/Properties/FromPort should be of type Integer + let l:pattern = '\v^(.*):(\d+):(\d+):(\d+):(\d+):([[:alnum:]]+):(.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[6] + + if ale#path#IsBufferPath(a:buffer, l:match[1]) + call add(l:output, { + \ 'lnum': l:match[2], + \ 'col': l:match[3], + \ 'end_lnum': l:match[4], + \ 'end_col': l:match[5], + \ 'code': l:code, + \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', + \ 'text': l:match[7] + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('cloudformation', { +\ 'name': 'cloudformation', +\ 'executable': 'cfn-lint', +\ 'command': 'cfn-lint --template %t --format parseable', +\ 'callback': 'ale_linters#cloudformation#cfn_python_lint#Handle', +\}) diff --git a/ale_linters/cpp/clang.vim b/ale_linters/cpp/clang.vim index 105df821..e8d96187 100644 --- a/ale_linters/cpp/clang.vim +++ b/ale_linters/cpp/clang.vim @@ -8,15 +8,15 @@ function! ale_linters#cpp#clang#GetExecutable(buffer) abort return ale#Var(a:buffer, 'cpp_clang_executable') endfunction -function! ale_linters#cpp#clang#GetCommand(buffer) abort - let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) +function! ale_linters#cpp#clang#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) " -iquote with the directory the file is in makes #include work for " headers in the same directory. return ale#Escape(ale_linters#cpp#clang#GetExecutable(a:buffer)) \ . ' -S -x c++ -fsyntax-only ' \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' - \ . ale#c#IncludeOptions(l:paths) + \ . l:cflags \ . ale#Var(a:buffer, 'cpp_clang_options') . ' -' endfunction @@ -24,6 +24,9 @@ call ale#linter#Define('cpp', { \ 'name': 'clang', \ 'output_stream': 'stderr', \ 'executable_callback': 'ale_linters#cpp#clang#GetExecutable', -\ 'command_callback': 'ale_linters#cpp#clang#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#cpp#clang#GetCommand'}, +\ ], \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/cpp/cquery.vim b/ale_linters/cpp/cquery.vim new file mode 100644 index 00000000..2fd77d46 --- /dev/null +++ b/ale_linters/cpp/cquery.vim @@ -0,0 +1,34 @@ +" Author: Ben Falconer <ben@falconers.me.uk> +" Description: A language server for C++ + +call ale#Set('cpp_cquery_executable', 'cquery') +call ale#Set('cpp_cquery_cache_directory', expand('~/.cache/cquery')) + +function! ale_linters#cpp#cquery#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile(a:buffer, 'compile_commands.json') + + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : '' +endfunction + +function! ale_linters#cpp#cquery#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_cquery_executable') +endfunction + +function! ale_linters#cpp#cquery#GetCommand(buffer) abort + let l:executable = ale_linters#cpp#cquery#GetExecutable(a:buffer) + return ale#Escape(l:executable) +endfunction + +function! ale_linters#cpp#cquery#GetInitializationOptions(buffer) abort + return {'cacheDirectory': ale#Var(a:buffer, 'cpp_cquery_cache_directory')} +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cquery', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#cpp#cquery#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#cquery#GetCommand', +\ 'project_root_callback': 'ale_linters#cpp#cquery#GetProjectRoot', +\ 'initialization_options_callback': 'ale_linters#cpp#cquery#GetInitializationOptions', +\ 'language': 'cpp', +\}) diff --git a/ale_linters/cpp/flawfinder.vim b/ale_linters/cpp/flawfinder.vim new file mode 100644 index 00000000..5a7092cf --- /dev/null +++ b/ale_linters/cpp/flawfinder.vim @@ -0,0 +1,31 @@ +" Author: Christian Gibbons <cgibbons@gmu.edu> +" Description: flawfinder linter for c++ files + +call ale#Set('cpp_flawfinder_executable', 'flawfinder') +call ale#Set('cpp_flawfinder_options', '') +call ale#Set('cpp_flawfinder_minlevel', 1) +call ale#Set('c_flawfinder_error_severity', 6) + +function! ale_linters#cpp#flawfinder#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_flawfinder_executable') +endfunction + +function! ale_linters#cpp#flawfinder#GetCommand(buffer) abort + + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'cpp_flawfinder_minlevel') + + return ale#Escape(ale_linters#cpp#flawfinder#GetExecutable(a:buffer)) + \ . ' -CDQS' + \ . ale#Var(a:buffer, 'cpp_flawfinder_options') + \ . l:minlevel + \ . ' %t' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'flawfinder', +\ 'output_stream': 'stdout', +\ 'executable_callback': 'ale_linters#cpp#flawfinder#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#flawfinder#GetCommand', +\ 'callback': 'ale#handlers#flawfinder#HandleFlawfinderFormat', +\}) diff --git a/ale_linters/cpp/gcc.vim b/ale_linters/cpp/gcc.vim index 40dffc98..a663eaa3 100644 --- a/ale_linters/cpp/gcc.vim +++ b/ale_linters/cpp/gcc.vim @@ -8,22 +8,26 @@ function! ale_linters#cpp#gcc#GetExecutable(buffer) abort return ale#Var(a:buffer, 'cpp_gcc_executable') endfunction -function! ale_linters#cpp#gcc#GetCommand(buffer) abort - let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) +function! ale_linters#cpp#gcc#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) " -iquote with the directory the file is in makes #include work for " headers in the same directory. return ale#Escape(ale_linters#cpp#gcc#GetExecutable(a:buffer)) \ . ' -S -x c++ -fsyntax-only ' \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' - \ . ale#c#IncludeOptions(l:paths) + \ . l:cflags \ . ale#Var(a:buffer, 'cpp_gcc_options') . ' -' endfunction call ale#linter#Define('cpp', { -\ 'name': 'g++', +\ 'name': 'gcc', +\ 'aliases': ['g++'], \ 'output_stream': 'stderr', \ 'executable_callback': 'ale_linters#cpp#gcc#GetExecutable', -\ 'command_callback': 'ale_linters#cpp#gcc#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#cpp#gcc#GetCommand'}, +\ ], \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/cs/mcsc.vim b/ale_linters/cs/mcsc.vim index f16e4b4a..8a78d3b3 100644 --- a/ale_linters/cs/mcsc.vim +++ b/ale_linters/cs/mcsc.vim @@ -10,7 +10,7 @@ function! s:GetWorkingDirectory(buffer) abort return l:working_directory endif - return fnamemodify(bufname(a:buffer), ':p:h') + return expand('#' . a:buffer . ':p:h') endfunction function! ale_linters#cs#mcsc#GetCommand(buffer) abort diff --git a/ale_linters/css/stylelint.vim b/ale_linters/css/stylelint.vim index 9f683190..a16dfde2 100644 --- a/ale_linters/css/stylelint.vim +++ b/ale_linters/css/stylelint.vim @@ -2,7 +2,7 @@ call ale#Set('css_stylelint_executable', 'stylelint') call ale#Set('css_stylelint_options', '') -call ale#Set('css_stylelint_use_global', 0) +call ale#Set('css_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#css#stylelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'css_stylelint', [ diff --git a/ale_linters/cucumber/cucumber.vim b/ale_linters/cucumber/cucumber.vim new file mode 100644 index 00000000..6708d32f --- /dev/null +++ b/ale_linters/cucumber/cucumber.vim @@ -0,0 +1,45 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Cucumber, a BDD test tool + +function! ale_linters#cucumber#cucumber#GetCommand(buffer) abort + let l:features_dir = ale#path#FindNearestDirectory(a:buffer, 'features') + + if !empty(l:features_dir) + let l:features_arg = '-r ' . ale#Escape(l:features_dir) + else + let l:features_arg = '' + endif + + return 'cucumber --dry-run --quiet --strict --format=json ' + \ . l:features_arg . ' %t' +endfunction + +function! ale_linters#cucumber#cucumber#Handle(buffer, lines) abort + try + let l:json = ale#util#FuzzyJSONDecode(a:lines, {})[0] + catch + return [] + endtry + + let l:output = [] + for l:element in get(l:json, 'elements', []) + for l:step in l:element['steps'] + if l:step['result']['status'] is# 'undefined' + call add(l:output, { + \ 'lnum': l:step['line'], + \ 'code': 'E', + \ 'text': 'Undefined step' + \}) + endif + endfor + endfor + + return l:output +endfunction + +call ale#linter#Define('cucumber', { +\ 'name': 'cucumber', +\ 'executable': 'cucumber', +\ 'command_callback': 'ale_linters#cucumber#cucumber#GetCommand', +\ 'callback': 'ale_linters#cucumber#cucumber#Handle' +\}) diff --git a/ale_linters/d/dmd.vim b/ale_linters/d/dmd.vim index b91238ae..d64b6c3d 100644 --- a/ale_linters/d/dmd.vim +++ b/ale_linters/d/dmd.vim @@ -46,7 +46,7 @@ function! ale_linters#d#dmd#DMDCommand(buffer, dub_output) abort endif endfor - return 'dmd '. join(l:import_list) . ' -o- -vcolumns -c %t' + return 'dmd '. join(l:import_list) . ' -o- -wi -vcolumns -c %t' endfunction function! ale_linters#d#dmd#Handle(buffer, lines) abort diff --git a/ale_linters/dart/language_server.vim b/ale_linters/dart/language_server.vim index 15c77017..bed77c52 100644 --- a/ale_linters/dart/language_server.vim +++ b/ale_linters/dart/language_server.vim @@ -7,10 +7,6 @@ function! ale_linters#dart#language_server#GetExecutable(buffer) abort return ale#Var(a:buffer, 'dart_language_server_executable') endfunction -function! ale_linters#dart#language_server#GetLanguage(buffer) abort - return 'dart' -endfunction - function! ale_linters#dart#language_server#GetProjectRoot(buffer) abort " Note: pub only looks for pubspec.yaml, there's no point in adding " support for pubspec.yml @@ -24,7 +20,6 @@ call ale#linter#Define('dart', { \ 'lsp': 'stdio', \ 'executable_callback': 'ale_linters#dart#language_server#GetExecutable', \ 'command_callback': 'ale_linters#dart#language_server#GetExecutable', -\ 'language_callback': 'ale_linters#dart#language_server#GetLanguage', +\ 'language': 'dart', \ 'project_root_callback': 'ale_linters#dart#language_server#GetProjectRoot', \}) - diff --git a/ale_linters/elixir/mix.vim b/ale_linters/elixir/mix.vim new file mode 100644 index 00000000..1a95e37f --- /dev/null +++ b/ale_linters/elixir/mix.vim @@ -0,0 +1,61 @@ +" Author: evnu - https://github.com/evnu +" Author: colbydehart - https://github.com/colbydehart +" Description: Mix compile checking for Elixir files + +function! ale_linters#elixir#mix#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " Error format + " ** (CompileError) apps/sim/lib/sim/server.ex:87: undefined function update_in/4 + " + " TODO: Warning format + " warning: variable "foobar" does not exist and is being expanded to "foobar()", please use parentheses to remove the ambiguity or change the variable name + + let l:pattern = '\v\(([^\)]+Error)\) ([^:]+):([^:]+): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = 'E' + let l:text = l:match[4] + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[3] + 0, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +function! ale_linters#elixir#mix#FindProjectRoot(buffer) abort + let l:mix_file = ale#path#FindNearestFile(a:buffer, 'mix.exs') + if !empty(l:mix_file) + return fnamemodify(l:mix_file, ':p:h') + endif + return '.' +endfunction + +function! ale_linters#elixir#mix#GetCommand(buffer) abort + let l:project_root = ale_linters#elixir#mix#FindProjectRoot(a:buffer) + + let l:temp_dir = ale#engine#CreateDirectory(a:buffer) + + let l:mix_build_path = has('win32') + \ ? 'set MIX_BUILD_PATH=' . ale#Escape(l:temp_dir) . ' &&' + \ : 'MIX_BUILD_PATH=' . ale#Escape(l:temp_dir) + + return ale#path#CdString(l:project_root) + \ . l:mix_build_path + \ . ' mix compile %s' +endfunction + +call ale#linter#Define('elixir', { +\ 'name': 'mix', +\ 'executable': 'mix', +\ 'command_callback': 'ale_linters#elixir#mix#GetCommand', +\ 'callback': 'ale_linters#elixir#mix#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/elm/make.vim b/ale_linters/elm/make.vim index 3783b5e3..cc14fe4e 100644 --- a/ale_linters/elm/make.vim +++ b/ale_linters/elm/make.vim @@ -1,45 +1,26 @@ -" Author: buffalocoder - https://github.com/buffalocoder, soywod - https://github.com/soywod +" Author: buffalocoder - https://github.com/buffalocoder, soywod - https://github.com/soywod, hecrj - https://github.com/hecrj " Description: Elm linting in Ale. Closely follows the Syntastic checker in https://github.com/ElmCast/elm-vim. -call ale#Set('elm_make_executable', 'elm-make') -call ale#Set('elm_make_use_global', 0) +call ale#Set('elm_make_executable', 'elm') +call ale#Set('elm_make_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#elm#make#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'elm_make', [ - \ 'node_modules/.bin/elm-make', + \ 'node_modules/.bin/elm', \]) endfunction function! ale_linters#elm#make#Handle(buffer, lines) abort let l:output = [] - let l:is_windows = has('win32') - let l:temp_dir = l:is_windows ? $TMP : $TMPDIR let l:unparsed_lines = [] - for l:line in a:lines - 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] is? l:temp_dir - else - let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] is# l:temp_dir - endif - if l:file_is_buffer - call add(l:output, { - \ 'lnum': l:error.region.start.line, - \ 'col': l:error.region.start.column, - \ 'end_lnum': l:error.region.end.line, - \ 'end_col': l:error.region.end.column, - \ 'type': (l:error.type is? 'error') ? 'E' : 'W', - \ 'text': l:error.overview, - \ 'detail': l:error.overview . "\n\n" . l:error.details - \}) - endif - endfor + for l:line in a:lines + if l:line[0] is# '{' + " Elm 0.19 + call ale_linters#elm#make#HandleElm019Line(l:line, l:output) + elseif l:line[0] is# '[' + " Elm 0.18 + call ale_linters#elm#make#HandleElm018Line(l:line, l:output) elseif l:line isnot# 'Successfully generated /dev/null' call add(l:unparsed_lines, l:line) endif @@ -57,23 +38,142 @@ function! ale_linters#elm#make#Handle(buffer, lines) abort return l:output endfunction +function! ale_linters#elm#make#HandleElm019Line(line, output) abort + let l:report = json_decode(a:line) + + if l:report.type is? 'error' + " General problem + let l:details = ale_linters#elm#make#ParseMessage(l:report.message) + + if empty(l:report.path) + let l:report.path = 'Elm' + endif + + if ale_linters#elm#make#FileIsBuffer(l:report.path) + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:details, + \}) + else + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:report.path .' - '. l:details, + \ 'detail': l:report.path ." ----------\n\n". l:details, + \}) + endif + else + " Compilation errors + for l:error in l:report.errors + let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.path) + + for l:problem in l:error.problems + let l:details = ale_linters#elm#make#ParseMessage(l:problem.message) + + if l:file_is_buffer + " Buffer module has problems + call add(a:output, { + \ 'lnum': l:problem.region.start.line, + \ 'col': l:problem.region.start.column, + \ 'end_lnum': l:problem.region.end.line, + \ 'end_col': l:problem.region.end.column, + \ 'type': 'E', + \ 'text': l:details, + \}) + else + " Imported module has problems + let l:location = l:error.path .':'. l:problem.region.start.line + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:location .' - '. l:details, + \ 'detail': l:location ." ----------\n\n". l:details, + \}) + endif + endfor + endfor + endif +endfunction + +function! ale_linters#elm#make#HandleElm018Line(line, output) abort + let l:errors = json_decode(a:line) + + for l:error in l:errors + let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.file) + + if l:file_is_buffer + " Current buffer has problems + call add(a:output, { + \ 'lnum': l:error.region.start.line, + \ 'col': l:error.region.start.column, + \ 'end_lnum': l:error.region.end.line, + \ 'end_col': l:error.region.end.column, + \ 'type': (l:error.type is? 'error') ? 'E' : 'W', + \ 'text': l:error.overview, + \ 'detail': l:error.overview . "\n\n" . l:error.details + \}) + elseif l:error.type is? 'error' + " Imported module has errors + let l:location = l:error.file .':'. l:error.region.start.line + + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:location .' - '. l:error.overview, + \ 'detail': l:location ." ----------\n\n". l:error.overview . "\n\n" . l:error.details + \}) + endif + endfor +endfunction + +function! ale_linters#elm#make#FileIsBuffer(path) abort + let l:is_windows = has('win32') + let l:temp_dir = l:is_windows ? $TMP : $TMPDIR + + if has('win32') + return a:path[0:len(l:temp_dir) - 1] is? l:temp_dir + else + return a:path[0:len(l:temp_dir) - 1] is# l:temp_dir + endif +endfunction + +function! ale_linters#elm#make#ParseMessage(message) abort + return join(map(copy(a:message), 'ale_linters#elm#make#ParseMessageItem(v:val)'), '') +endfunction + +function! ale_linters#elm#make#ParseMessageItem(item) abort + if type(a:item) == type('') + return a:item + else + return a:item.string + endif +endfunction + " Return the command to execute the linter in the projects directory. " If it doesn't, then this will fail when imports are needed. function! ale_linters#elm#make#GetCommand(buffer) abort - let l:elm_package = ale#path#FindNearestFile(a:buffer, 'elm-package.json') + let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json') let l:elm_exe = ale_linters#elm#make#GetExecutable(a:buffer) - if empty(l:elm_package) + + if empty(l:elm_json) + " Fallback to Elm 0.18 + let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm-package.json') + endif + + if empty(l:elm_json) let l:dir_set_cmd = '' else - let l:root_dir = fnamemodify(l:elm_package, ':p:h') + let l:root_dir = fnamemodify(l:elm_json, ':p:h') let l:dir_set_cmd = 'cd ' . ale#Escape(l:root_dir) . ' && ' endif - " The elm-make compiler, at the time of this writing, uses '/dev/null' as + " The elm compiler, at the time of this writing, uses '/dev/null' as " a sort of flag to tell the compiler not to generate an output file, - " which is why this is hard coded here. It does not use NUL on Windows. - " Source: https://github.com/elm-lang/elm-make/blob/master/src/Flags.hs + " which is why this is hard coded here. + " Source: https://github.com/elm-lang/elm-compiler/blob/19d5a769b30ec0b2fc4475985abb4cd94cd1d6c3/builder/src/Generate/Output.hs#L253 let l:elm_cmd = ale#Escape(l:elm_exe) + \ . ' make' \ . ' --report=json' \ . ' --output=/dev/null' diff --git a/ale_linters/fish/fish.vim b/ale_linters/fish/fish.vim index 19158cb0..87ede29a 100644 --- a/ale_linters/fish/fish.vim +++ b/ale_linters/fish/fish.vim @@ -7,22 +7,53 @@ function! ale_linters#fish#fish#Handle(buffer, lines) abort " home/.config/fish/functions/foo.fish (line 1): Missing end to balance this function definition " function foo " ^ - " <W> fish: Error while reading file .config/fish/functions/foo.fish - let l:pattern = '^.* (line \(\d\+\)): \(.*\)$' + " + " OR, patterns such as: + " + " Unsupported use of '||'. In fish, please use 'COMMAND; or COMMAND'. + " /tmp/vLz620o/258/test.fish (line 2): if set -q SSH_CLIENT || set -q SSH_TTY + " ^ + " + " fish -n can return errors in either format. + let l:pattern = '^\(.* (line \(\d\+\)): \)\(.*\)$' + let l:column_pattern = '^ *\^' let l:output = [] + let l:column_offset = 0 + let l:last_line_with_message = '' + + for l:line in a:lines + " Look for error lines first. + let l:match = matchlist(l:line, l:pattern) + + if !empty(l:match) + if !empty(l:last_line_with_message) + let l:text = l:last_line_with_message + else + let l:text = l:match[3] + endif + + let l:column_offset = len(l:match[1]) + + let l:last_line_with_message = '' + call add(l:output, { + \ 'col': 0, + \ 'lnum': str2nr(l:match[2]), + \ 'text': l:text, + \}) + else + " Look for column markers like ' ^' second. + " The column index will be set according to how long the line is. + let l:column_match = matchstr(l:line, l:column_pattern) - let l:i = 0 - while l:i < len(a:lines) - let l:match = matchlist(a:lines[l:i], l:pattern) - if len(l:match) && len(l:match[2]) - call add(l:output, { - \ 'col': len(a:lines[l:i + 2]), - \ 'lnum': str2nr(l:match[1]), - \ 'text': l:match[2], - \}) - endif - let l:i += 1 - endwhile + if !empty(l:column_match) && !empty(l:output) + let l:output[-1].col = len(l:column_match) - l:column_offset + let l:last_line_with_message = '' + else + let l:last_line_with_message = l:line + let l:column_offset = 0 + endif + endif + endfor return l:output endfunction diff --git a/ale_linters/gitcommit/gitlint.vim b/ale_linters/gitcommit/gitlint.vim index 49aeda7c..64731055 100644 --- a/ale_linters/gitcommit/gitlint.vim +++ b/ale_linters/gitcommit/gitlint.vim @@ -4,7 +4,7 @@ let g:ale_gitcommit_gitlint_executable = \ get(g:, 'ale_gitcommit_gitlint_executable', 'gitlint') let g:ale_gitcommit_gitlint_options = get(g:, 'ale_gitcommit_gitlint_options', '') -let g:ale_gitcommit_gitlint_use_global = get(g:, 'ale_gitcommit_gitlint_use_global', 0) +let g:ale_gitcommit_gitlint_use_global = get(g:, 'ale_gitcommit_gitlint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#gitcommit#gitlint#GetExecutable(buffer) abort @@ -28,6 +28,10 @@ function! ale_linters#gitcommit#gitlint#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:code = l:match[2] + if l:code is# 'T2' && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + continue + endif + let l:item = { \ 'lnum': l:match[1] + 0, \ 'text': l:match[3], diff --git a/ale_linters/glsl/glslls.vim b/ale_linters/glsl/glslls.vim index 67ea379c..c19f28c2 100644 --- a/ale_linters/glsl/glslls.vim +++ b/ale_linters/glsl/glslls.vim @@ -18,10 +18,6 @@ function! ale_linters#glsl#glslls#GetCommand(buffer) abort return ale#Escape(l:executable) . l:logfile_args . ' --stdin' endfunction -function! ale_linters#glsl#glslls#GetLanguage(buffer) abort - return 'glsl' -endfunction - function! ale_linters#glsl#glslls#GetProjectRoot(buffer) abort let l:project_root = ale#c#FindProjectRoot(a:buffer) @@ -33,6 +29,6 @@ call ale#linter#Define('glsl', { \ 'lsp': 'stdio', \ 'executable_callback': 'ale_linters#glsl#glslls#GetExecutable', \ 'command_callback': 'ale_linters#glsl#glslls#GetCommand', -\ 'language_callback': 'ale_linters#glsl#glslls#GetLanguage', +\ 'language': 'glsl', \ 'project_root_callback': 'ale_linters#glsl#glslls#GetProjectRoot', \}) diff --git a/ale_linters/go/gosimple.vim b/ale_linters/go/gosimple.vim index 8a4c01e1..dbdc3fcf 100644 --- a/ale_linters/go/gosimple.vim +++ b/ale_linters/go/gosimple.vim @@ -1,11 +1,15 @@ " Author: Ben Reedy <https://github.com/breed808> " Description: gosimple for Go files +function! ale_linters#go#gosimple#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) . ' gosimple .' +endfunction + call ale#linter#Define('go', { \ 'name': 'gosimple', \ 'executable': 'gosimple', -\ 'command': 'gosimple %s', -\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\ 'command_callback': 'ale_linters#go#gosimple#GetCommand', +\ 'callback': 'ale#handlers#go#Handler', \ 'output_stream': 'both', \ 'lint_file': 1, \}) diff --git a/ale_linters/go/gotype.vim b/ale_linters/go/gotype.vim index 731f4c92..f4bb274e 100644 --- a/ale_linters/go/gotype.vim +++ b/ale_linters/go/gotype.vim @@ -1,23 +1,20 @@ " Author: Jelte Fennema <github-public@jeltef.nl> " Description: gotype for Go files -call ale#linter#Define('go', { -\ 'name': 'gotype', -\ 'output_stream': 'stderr', -\ 'executable': 'gotype', -\ 'command_callback': 'ale_linters#go#gotype#GetCommand', -\ 'callback': 'ale#handlers#unix#HandleAsError', -\}) - -"\ 'command': function! ale_linters#go#gotype#GetCommand(buffer) abort - let l:cur_file = expand('#' . a:buffer . ':p') - if l:cur_file =~# '_test\.go$' + if expand('#' . a:buffer . ':p') =~# '_test\.go$' return endif - let l:module_files = globpath(expand('#' . a:buffer . ':p:h'), '*.go', 0, 1) - let l:other_module_files = filter(l:module_files, 'v:val isnot# ' . ale#util#EscapeVim(l:cur_file) . ' && v:val !~# "_test\.go$"') - return 'gotype %t ' . join(map(l:other_module_files, 'ale#Escape(v:val)')) + return ale#path#BufferCdString(a:buffer) . ' gotype .' endfunction + +call ale#linter#Define('go', { +\ 'name': 'gotype', +\ 'output_stream': 'stderr', +\ 'executable': 'gotype', +\ 'command_callback': 'ale_linters#go#gotype#GetCommand', +\ 'callback': 'ale#handlers#go#Handler', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/go/govet.vim b/ale_linters/go/govet.vim index f5bb47a3..edf9eb66 100644 --- a/ale_linters/go/govet.vim +++ b/ale_linters/go/govet.vim @@ -1,10 +1,18 @@ " Author: neersighted <bjorn@neersighted.com> " Description: go vet for Go files +" +" Author: John Eikenberry <jae@zhar.net> +" Description: updated to work with go1.10 + +function! ale_linters#go#govet#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) . ' go vet .' +endfunction call ale#linter#Define('go', { \ 'name': 'go vet', \ 'output_stream': 'stderr', \ 'executable': 'go', -\ 'command': 'go vet %t', -\ 'callback': 'ale#handlers#unix#HandleAsError', +\ 'command_callback': 'ale_linters#go#govet#GetCommand', +\ 'callback': 'ale#handlers#go#Handler', +\ 'lint_file': 1, \}) diff --git a/ale_linters/go/staticcheck.vim b/ale_linters/go/staticcheck.vim index 255fd17c..a3464015 100644 --- a/ale_linters/go/staticcheck.vim +++ b/ale_linters/go/staticcheck.vim @@ -1,11 +1,33 @@ " Author: Ben Reedy <https://github.com/breed808> " Description: staticcheck for Go files +call ale#Set('go_staticcheck_options', '') +call ale#Set('go_staticcheck_lint_package', 0) + +function! ale_linters#go#staticcheck#GetCommand(buffer) abort + let l:filename = expand('#' . a:buffer . ':t') + let l:options = ale#Var(a:buffer, 'go_staticcheck_options') + let l:lint_package = ale#Var(a:buffer, 'go_staticcheck_lint_package') + + " BufferCdString is used so that we can be sure the paths output from + " staticcheck can be calculated to absolute paths in the Handler + if l:lint_package + return ale#path#BufferCdString(a:buffer) + \ . 'staticcheck' + \ . (!empty(l:options) ? ' ' . l:options : '') . ' .' + endif + + return ale#path#BufferCdString(a:buffer) + \ . 'staticcheck' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' ' . ale#Escape(l:filename) +endfunction + call ale#linter#Define('go', { \ 'name': 'staticcheck', \ 'executable': 'staticcheck', -\ 'command': 'staticcheck %s', -\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\ 'command_callback': 'ale_linters#go#staticcheck#GetCommand', +\ 'callback': 'ale#handlers#go#Handler', \ 'output_stream': 'both', \ 'lint_file': 1, \}) diff --git a/ale_linters/handlebars/embertemplatelint.vim b/ale_linters/handlebars/embertemplatelint.vim index 68ea7155..162a033c 100644 --- a/ale_linters/handlebars/embertemplatelint.vim +++ b/ale_linters/handlebars/embertemplatelint.vim @@ -2,7 +2,7 @@ " Description: Ember-template-lint for checking Handlebars files call ale#Set('handlebars_embertemplatelint_executable', 'ember-template-lint') -call ale#Set('handlebars_embertemplatelint_use_global', 0) +call ale#Set('handlebars_embertemplatelint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#handlebars#embertemplatelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'handlebars_embertemplatelint', [ diff --git a/ale_linters/haskell/hdevtools.vim b/ale_linters/haskell/hdevtools.vim index 93c7ddd6..dc902152 100644 --- a/ale_linters/haskell/hdevtools.vim +++ b/ale_linters/haskell/hdevtools.vim @@ -2,7 +2,7 @@ " Description: hdevtools for Haskell files call ale#Set('haskell_hdevtools_executable', 'hdevtools') -call ale#Set('haskell_hdevtools_options', '-g -Wall') +call ale#Set('haskell_hdevtools_options', get(g:, 'hdevtools_options', '-g -Wall')) function! ale_linters#haskell#hdevtools#GetExecutable(buffer) abort return ale#Var(a:buffer, 'haskell_hdevtools_executable') diff --git a/ale_linters/html/htmlhint.vim b/ale_linters/html/htmlhint.vim index 88a83f1d..caa15bbb 100644 --- a/ale_linters/html/htmlhint.vim +++ b/ale_linters/html/htmlhint.vim @@ -3,7 +3,7 @@ call ale#Set('html_htmlhint_options', '') call ale#Set('html_htmlhint_executable', 'htmlhint') -call ale#Set('html_htmlhint_use_global', 0) +call ale#Set('html_htmlhint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#html#htmlhint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'html_htmlhint', [ diff --git a/ale_linters/html/tidy.vim b/ale_linters/html/tidy.vim index 34152c6b..3af64d74 100644 --- a/ale_linters/html/tidy.vim +++ b/ale_linters/html/tidy.vim @@ -3,11 +3,19 @@ " CLI options let g:ale_html_tidy_executable = get(g:, 'ale_html_tidy_executable', 'tidy') +" remove in 2.0 " Look for the old _args variable first. +let s:deprecation_warning_echoed = 0 let s:default_options = get(g:, 'ale_html_tidy_args', '-q -e -language en') let g:ale_html_tidy_options = get(g:, 'ale_html_tidy_options', s:default_options) function! ale_linters#html#tidy#GetCommand(buffer) abort + " remove in 2.0 + if exists('g:ale_html_tidy_args') && !s:deprecation_warning_echoed + execute 'echom ''Rename your g:ale_html_tidy_args setting to g:ale_html_tidy_options instead. Support for this will removed in ALE 2.0.''' + let s:deprecation_warning_echoed = 1 + endif + " Specify file encoding in options " (Idea taken from https://github.com/scrooloose/syntastic/blob/master/syntax_checkers/html/tidy.vim) let l:file_encoding = get({ diff --git a/ale_linters/java/javac.vim b/ale_linters/java/javac.vim index 73e84147..b1f74a6d 100644 --- a/ale_linters/java/javac.vim +++ b/ale_linters/java/javac.vim @@ -3,8 +3,9 @@ let s:classpath_sep = has('unix') ? ':' : ';' -let g:ale_java_javac_options = get(g:, 'ale_java_javac_options', '') -let g:ale_java_javac_classpath = get(g:, 'ale_java_javac_classpath', '') +call ale#Set('java_javac_executable', 'javac') +call ale#Set('java_javac_options', '') +call ale#Set('java_javac_classpath', '') function! ale_linters#java#javac#GetImportPaths(buffer) abort let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') @@ -35,6 +36,10 @@ function! s:BuildClassPathOption(buffer, import_paths) abort \ : '' endfunction +function! ale_linters#java#javac#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'java_javac_executable') +endfunction + function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort let l:cp_option = s:BuildClassPathOption(a:buffer, a:import_paths) let l:sp_option = '' @@ -72,11 +77,13 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort " Create .class files in a temporary directory, which we will delete later. let l:class_file_directory = ale#engine#CreateDirectory(a:buffer) + let l:executable = ale_linters#java#javac#GetExecutable(a:buffer) " Always run javac from the directory the file is in, so we can resolve " relative paths correctly. return ale#path#BufferCdString(a:buffer) - \ . 'javac -Xlint' + \ . ale#Escape(l:executable) + \ . ' -Xlint' \ . ' ' . l:cp_option \ . ' ' . l:sp_option \ . ' -d ' . ale#Escape(l:class_file_directory) @@ -119,7 +126,7 @@ endfunction call ale#linter#Define('java', { \ 'name': 'javac', -\ 'executable': 'javac', +\ 'executable_callback': 'ale_linters#java#javac#GetExecutable', \ 'command_chain': [ \ {'callback': 'ale_linters#java#javac#GetImportPaths', 'output_stream': 'stdout'}, \ {'callback': 'ale_linters#java#javac#GetCommand', 'output_stream': 'stderr'}, diff --git a/ale_linters/java/pmd.vim b/ale_linters/java/pmd.vim new file mode 100644 index 00000000..d461e094 --- /dev/null +++ b/ale_linters/java/pmd.vim @@ -0,0 +1,36 @@ +" Author: Johannes Wienke <languitar@semipol.de> +" Description: PMD for Java files + +function! ale_linters#java#pmd#Handle(buffer, lines) abort + let l:pattern = '"\(\d\+\)",".\+","\(.\+\)","\(\d\+\)","\(\d\+\)","\(.\+\)","\(.\+\)","\(.\+\)"$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'type': 'W', + \ 'lnum': l:match[4] + 0, + \ 'text': l:match[5], + \ 'code': l:match[6] . ' - ' . l:match[7], + \}) + endfor + + return l:output +endfunction + +function! ale_linters#java#pmd#GetCommand(buffer) abort + return 'pmd ' + \ . ale#Var(a:buffer, 'java_pmd_options') + \ . ' -f csv' + \ . ' -d %t' +endfunction + +if !exists('g:ale_java_pmd_options') + let g:ale_java_pmd_options = '-R category/java/bestpractices.xml' +endif + +call ale#linter#Define('java', { +\ 'name': 'pmd', +\ 'executable': 'pmd', +\ 'command_callback': 'ale_linters#java#pmd#GetCommand', +\ 'callback': 'ale_linters#java#pmd#Handle', +\}) diff --git a/ale_linters/javascript/flow.vim b/ale_linters/javascript/flow.vim index 643ea190..d555184e 100755 --- a/ale_linters/javascript/flow.vim +++ b/ale_linters/javascript/flow.vim @@ -4,7 +4,8 @@ call ale#Set('javascript_flow_executable', 'flow') call ale#Set('javascript_flow_use_home_config', 0) -call ale#Set('javascript_flow_use_global', 0) +call ale#Set('javascript_flow_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_flow_use_respect_pragma', 1) function! ale_linters#javascript#flow#GetExecutable(buffer) abort let l:flow_config = ale#path#FindNearestFile(a:buffer, '.flowconfig') @@ -47,8 +48,8 @@ function! ale_linters#javascript#flow#GetCommand(buffer, version_lines) abort " If we can parse the version number, then only use --respect-pragma " if the version is >= 0.36.0, which added the argument. - let l:use_respect_pragma = empty(l:version) - \ || ale#semver#GTE(l:version, [0, 36]) + let l:use_respect_pragma = ale#Var(a:buffer, 'javascript_flow_use_respect_pragma') + \ && (empty(l:version) || ale#semver#GTE(l:version, [0, 36])) return ale#Escape(l:executable) \ . ' check-contents' diff --git a/ale_linters/javascript/jscs.vim b/ale_linters/javascript/jscs.vim index bcf3ee3a..60044037 100644 --- a/ale_linters/javascript/jscs.vim +++ b/ale_linters/javascript/jscs.vim @@ -2,7 +2,7 @@ " Description: jscs for JavaScript files call ale#Set('javascript_jscs_executable', 'jscs') -call ale#Set('javascript_jscs_use_global', 0) +call ale#Set('javascript_jscs_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#javascript#jscs#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_jscs', [ diff --git a/ale_linters/javascript/jshint.vim b/ale_linters/javascript/jshint.vim index 93b16a8f..2e9bb9fd 100644 --- a/ale_linters/javascript/jshint.vim +++ b/ale_linters/javascript/jshint.vim @@ -2,7 +2,7 @@ " Description: JSHint for Javascript files call ale#Set('javascript_jshint_executable', 'jshint') -call ale#Set('javascript_jshint_use_global', 0) +call ale#Set('javascript_jshint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#javascript#jshint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_jshint', [ diff --git a/ale_linters/javascript/standard.vim b/ale_linters/javascript/standard.vim index aa6a3a72..f16b837a 100644 --- a/ale_linters/javascript/standard.vim +++ b/ale_linters/javascript/standard.vim @@ -2,7 +2,7 @@ " Description: standardjs for JavaScript files call ale#Set('javascript_standard_executable', 'standard') -call ale#Set('javascript_standard_use_global', 0) +call ale#Set('javascript_standard_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('javascript_standard_options', '') function! ale_linters#javascript#standard#GetExecutable(buffer) abort diff --git a/ale_linters/javascript/tsserver.vim b/ale_linters/javascript/tsserver.vim new file mode 100644 index 00000000..62dded10 --- /dev/null +++ b/ale_linters/javascript/tsserver.vim @@ -0,0 +1,26 @@ +" Author: Chaucerbao, w0rp <devw0rp@gmail.com> +" Description: tsserver integration for ALE + +call ale#Set('javascript_tsserver_executable', 'tsserver') +call ale#Set('javascript_tsserver_config_path', '') +call ale#Set('javascript_tsserver_use_global', get(g:, 'ale_use_global_executables', 0)) + +" These functions need to be defined just to comply with the API for LSP. +function! ale_linters#javascript#tsserver#GetProjectRoot(buffer) abort + return '' +endfunction + +function! ale_linters#javascript#tsserver#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_tsserver', [ + \ 'node_modules/.bin/tsserver', + \]) +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'tsserver', +\ 'lsp': 'tsserver', +\ 'executable_callback': 'ale_linters#javascript#tsserver#GetExecutable', +\ 'command_callback': 'ale_linters#javascript#tsserver#GetExecutable', +\ 'project_root_callback': 'ale_linters#javascript#tsserver#GetProjectRoot', +\ 'language': '', +\}) diff --git a/ale_linters/javascript/xo.vim b/ale_linters/javascript/xo.vim index cf305eb4..bc8657ed 100644 --- a/ale_linters/javascript/xo.vim +++ b/ale_linters/javascript/xo.vim @@ -2,7 +2,7 @@ " Description: xo for JavaScript files call ale#Set('javascript_xo_executable', 'xo') -call ale#Set('javascript_xo_use_global', 0) +call ale#Set('javascript_xo_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('javascript_xo_options', '') function! ale_linters#javascript#xo#GetExecutable(buffer) abort diff --git a/ale_linters/less/lessc.vim b/ale_linters/less/lessc.vim index 108679de..5fd9a383 100755 --- a/ale_linters/less/lessc.vim +++ b/ale_linters/less/lessc.vim @@ -3,7 +3,7 @@ call ale#Set('less_lessc_executable', 'lessc') call ale#Set('less_lessc_options', '') -call ale#Set('less_lessc_use_global', 0) +call ale#Set('less_lessc_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#less#lessc#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'less_lessc', [ diff --git a/ale_linters/less/stylelint.vim b/ale_linters/less/stylelint.vim index 690c8c93..8e16a098 100644 --- a/ale_linters/less/stylelint.vim +++ b/ale_linters/less/stylelint.vim @@ -2,7 +2,7 @@ call ale#Set('less_stylelint_executable', 'stylelint') call ale#Set('less_stylelint_options', '') -call ale#Set('less_stylelint_use_global', 0) +call ale#Set('less_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#less#stylelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'less_stylelint', [ diff --git a/ale_linters/markdown/markdownlint.vim b/ale_linters/markdown/markdownlint.vim new file mode 100644 index 00000000..5c8af650 --- /dev/null +++ b/ale_linters/markdown/markdownlint.vim @@ -0,0 +1,11 @@ +" Author: Ty-Lucas Kelley <tylucaskelley@gmail.com> +" Description: Adds support for markdownlint + +call ale#linter#Define('markdown', { + \ 'name': 'markdownlint', + \ 'executable': 'markdownlint', + \ 'lint_file': 1, + \ 'output_stream': 'both', + \ 'command': 'markdownlint %s', + \ 'callback': 'ale#handlers#markdownlint#Handle' +\ }) diff --git a/ale_linters/markdown/mdl.vim b/ale_linters/markdown/mdl.vim index f2390250..0953144b 100644 --- a/ale_linters/markdown/mdl.vim +++ b/ale_linters/markdown/mdl.vim @@ -1,5 +1,24 @@ -" Author: Steve Dignam <steve@dignam.xyz> -" Description: Support for mdl, a markdown linter +" Author: Steve Dignam <steve@dignam.xyz>, Josh Leeb-du Toit <joshleeb.com> +" Description: Support for mdl, a markdown linter. + +call ale#Set('markdown_mdl_executable', 'mdl') +call ale#Set('markdown_mdl_options', '') + +function! ale_linters#markdown#mdl#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'markdown_mdl_executable') +endfunction + +function! ale_linters#markdown#mdl#GetCommand(buffer) abort + let l:executable = ale_linters#markdown#mdl#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? 'bundle$' + \ ? ' exec mdl' + \ : '' + + let l:options = ale#Var(a:buffer, 'markdown_mdl_options') + + return ale#Escape(l:executable) . l:exec_args + \ . (!empty(l:options) ? ' ' . l:options : '') +endfunction function! ale_linters#markdown#mdl#Handle(buffer, lines) abort " matches: '(stdin):173: MD004 Unordered list style' @@ -19,7 +38,7 @@ endfunction call ale#linter#Define('markdown', { \ 'name': 'mdl', -\ 'executable': 'mdl', -\ 'command': 'mdl', +\ 'executable_callback': 'ale_linters#markdown#mdl#GetExecutable', +\ 'command_callback': 'ale_linters#markdown#mdl#GetCommand', \ 'callback': 'ale_linters#markdown#mdl#Handle' \}) diff --git a/ale_linters/markdown/remark_lint.vim b/ale_linters/markdown/remark_lint.vim index 5b3b3d47..98dd0d7b 100644 --- a/ale_linters/markdown/remark_lint.vim +++ b/ale_linters/markdown/remark_lint.vim @@ -1,18 +1,24 @@ -" Author rhysd https://rhysd.github.io/ +" Author rhysd https://rhysd.github.io/, Dirk Roorda (dirkroorda), Adrián González Rus (@adrigzr) " Description: remark-lint for Markdown files function! ale_linters#markdown#remark_lint#Handle(buffer, lines) abort " matches: ' 1:4 warning Incorrect list-item indent: add 1 space list-item-indent remark-lint' - let l:pattern = '^ \+\(\d\+\):\(\d\+\) \(warning\|error\) \(.\+\)$' + " matches: ' 18:71-19:1 error Missing new line after list item list-item-spacing remark-lint', + let l:pattern = '^ \+\(\d\+\):\(\d\+\)\(-\(\d\+\):\(\d\+\)\)\? \(warning\|error\) \(.\+\)$' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) - call add(l:output, { + let l:item = { \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 0, - \ 'type': l:match[3] is# 'error' ? 'E' : 'W', - \ 'text': l:match[4], - \}) + \ 'type': l:match[6] is# 'error' ? 'E' : 'W', + \ 'text': l:match[7], + \} + if l:match[3] isnot# '' + let l:item.end_lnum = l:match[4] + 0 + let l:item.end_col = l:match[5] + 0 + endif + call add(l:output, l:item) endfor return l:output diff --git a/ale_linters/markdown/textlint.vim b/ale_linters/markdown/textlint.vim new file mode 100644 index 00000000..26458506 --- /dev/null +++ b/ale_linters/markdown/textlint.vim @@ -0,0 +1,9 @@ +" Author: tokida https://rouger.info, Yasuhiro Kiyota <yasuhiroki.duck@gmail.com> +" Description: textlint, a proofreading tool (https://textlint.github.io/) + +call ale#linter#Define('markdown', { +\ 'name': 'textlint', +\ 'executable_callback': 'ale#handlers#textlint#GetExecutable', +\ 'command_callback': 'ale#handlers#textlint#GetCommand', +\ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', +\}) diff --git a/ale_linters/mercury/mmc.vim b/ale_linters/mercury/mmc.vim new file mode 100644 index 00000000..c7bfc59d --- /dev/null +++ b/ale_linters/mercury/mmc.vim @@ -0,0 +1,45 @@ +" Author: stewy33 <slocumstewy@gmail.com> +" Description: Lints mercury files using mmc + +call ale#Set('mercury_mmc_executable', 'mmc') +call ale#Set('mercury_mmc_options', '--make --output-compile-error-lines 100') + +function! ale_linters#mercury#mmc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'mercury_mmc_executable') +endfunction + +function! ale_linters#mercury#mmc#GetCommand(buffer) abort + let l:module_name = expand('#' . a:buffer . ':t:r') + + return ale#path#BufferCdString(a:buffer) + \ . ale_linters#mercury#mmc#GetExecutable(a:buffer) + \ . ' --errorcheck-only ' + \ . ale#Var(a:buffer, 'mercury_mmc_options') + \ . ' ' . l:module_name +endfunction + +function! ale_linters#mercury#mmc#Handle(buffer, lines) abort + " output format + " <filename>:<line>: <issue type>: <message> + let l:pattern = '\v^\w+\.m:(\d+):\s+([W|w]arning|.*[E|e]rror.*): (.*)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': substitute(l:match[1], '\v^0*', '', '') + 0, + \ 'type': l:match[2][0] =~? 'W' ? 'W' : 'E', + \ 'text': l:match[2] . ': ' . l:match[3] + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('mercury', { +\ 'name': 'mmc', +\ 'output_stream': 'stderr', +\ 'executable_callback': 'ale_linters#mercury#mmc#GetExecutable', +\ 'command_callback': 'ale_linters#mercury#mmc#GetCommand', +\ 'callback': 'ale_linters#mercury#mmc#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/nasm/nasm.vim b/ale_linters/nasm/nasm.vim new file mode 100644 index 00000000..f4b2ca4b --- /dev/null +++ b/ale_linters/nasm/nasm.vim @@ -0,0 +1,50 @@ +" Author: Oyvind Ingvaldsen <oyvind.ingvaldsen@gmail.com> +" Description: NASM linter for asmsyntax nasm. + +call ale#Set('nasm_nasm_executable', 'nasm') +call ale#Set('nasm_nasm_options', '') + +function! ale_linters#nasm#nasm#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'nasm_nasm_executable') +endfunction + +function! ale_linters#nasm#nasm#GetOptions(buffer) abort + return ale#Var(a:buffer, 'nasm_nasm_options') +endfunction + +function! ale_linters#nasm#nasm#GetCommand(buffer) abort + " Note that NASM require a trailing slash to the -I option. + let l:executable = ale#Escape(ale_linters#nasm#nasm#GetExecutable(a:buffer)) + let l:separator = has('win32') ? '\' : '/' + let l:path = ale#Escape(fnamemodify(bufname(a:buffer), ':p:h') . l:separator) + let l:options = ale_linters#nasm#nasm#GetOptions(a:buffer) + + return l:executable + \ . ' -X gnu' + \ . ' -I ' . l:path + \ . ' ' . l:options + \ . ' %s' +endfunction + +function! ale_linters#nasm#nasm#Handle(buffer, lines) abort + " Note that we treat 'fatal' as errors. + let l:pattern = '^.\+:\(\d\+\): \([^:]\+\): \(.\+\)$' + let l:output = [] + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'type': l:match[2] =~? 'error\|fatal' ? 'E' : 'W', + \ 'text': l:match[3], + \}) + endfor + return l:output +endfunction + +call ale#linter#Define('nasm', { +\ 'name': 'nasm', +\ 'executable': 'nasm', +\ 'output_stream': 'stderr', +\ 'lint_file': 1, +\ 'command_callback': 'ale_linters#nasm#nasm#GetCommand', +\ 'callback': 'ale_linters#nasm#nasm#Handle', +\}) diff --git a/ale_linters/ocaml/ols.vim b/ale_linters/ocaml/ols.vim index c0255a6c..077862fc 100644 --- a/ale_linters/ocaml/ols.vim +++ b/ale_linters/ocaml/ols.vim @@ -2,7 +2,7 @@ " Description: A language server for OCaml call ale#Set('ocaml_ols_executable', 'ocaml-language-server') -call ale#Set('ocaml_ols_use_global', 0) +call ale#Set('ocaml_ols_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#linter#Define('ocaml', { \ 'name': 'ols', diff --git a/ale_linters/perl/perl.vim b/ale_linters/perl/perl.vim index fcc88f35..1b9aa95e 100644 --- a/ale_linters/perl/perl.vim +++ b/ale_linters/perl/perl.vim @@ -27,12 +27,20 @@ function! ale_linters#perl#perl#Handle(buffer, lines) abort let l:output = [] let l:basename = expand('#' . a:buffer . ':t') + let l:type = 'E' + if a:lines[-1] =~# 'syntax OK' + let l:type = 'W' + endif + + let l:seen = {} + for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[3] + let l:file = l:match[2] let l:text = l:match[1] - let l:type = 'E' - if ale#path#IsBufferPath(a:buffer, l:match[2]) + if ale#path#IsBufferPath(a:buffer, l:file) + \ && !has_key(l:seen,l:line) \ && ( \ l:text isnot# 'BEGIN failed--compilation aborted' \ || empty(l:output) @@ -43,6 +51,8 @@ function! ale_linters#perl#perl#Handle(buffer, lines) abort \ 'text': l:text, \ 'type': l:type, \}) + + let l:seen[l:line] = 1 endif endfor diff --git a/ale_linters/perl/perlcritic.vim b/ale_linters/perl/perlcritic.vim index 24f7eb86..e91c8a03 100644 --- a/ale_linters/perl/perlcritic.vim +++ b/ale_linters/perl/perlcritic.vim @@ -18,9 +18,9 @@ function! ale_linters#perl#perlcritic#GetExecutable(buffer) abort endfunction 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 is? '' return '' endif @@ -31,6 +31,7 @@ endfunction function! ale_linters#perl#perlcritic#GetCommand(buffer) abort let l:critic_verbosity = '%l:%c %m\n' + if ale#Var(a:buffer, 'perl_perlcritic_showrules') let l:critic_verbosity = '%l:%c %m [%p]\n' endif @@ -38,17 +39,11 @@ function! ale_linters#perl#perlcritic#GetCommand(buffer) abort let l:profile = ale_linters#perl#perlcritic#GetProfile(a:buffer) let l:options = ale#Var(a:buffer, 'perl_perlcritic_options') - let l:command = ale#Escape(ale_linters#perl#perlcritic#GetExecutable(a:buffer)) - \ . " --verbose '". l:critic_verbosity . "' --nocolor" - - if l:profile isnot? '' - let l:command .= ' --profile ' . ale#Escape(l:profile) - endif - if l:options isnot? '' - let l:command .= ' ' . l:options - endif - - return l:command + return ale#Escape(ale_linters#perl#perlcritic#GetExecutable(a:buffer)) + \ . ' --verbose ' . ale#Escape(l:critic_verbosity) + \ . ' --nocolor' + \ . (!empty(l:profile) ? ' --profile ' . ale#Escape(l:profile) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') endfunction diff --git a/ale_linters/php/langserver.vim b/ale_linters/php/langserver.vim index be2d6ef1..0f3ead66 100644 --- a/ale_linters/php/langserver.vim +++ b/ale_linters/php/langserver.vim @@ -2,7 +2,7 @@ " Description: PHP Language server integration for ALE call ale#Set('php_langserver_executable', 'php-language-server.php') -call ale#Set('php_langserver_use_global', 0) +call ale#Set('php_langserver_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#php#langserver#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'php_langserver', [ @@ -14,10 +14,6 @@ function! ale_linters#php#langserver#GetCommand(buffer) abort return 'php ' . ale#Escape(ale_linters#php#langserver#GetExecutable(a:buffer)) endfunction -function! ale_linters#php#langserver#GetLanguage(buffer) abort - return 'php' -endfunction - function! ale_linters#php#langserver#GetProjectRoot(buffer) abort let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') @@ -29,6 +25,6 @@ call ale#linter#Define('php', { \ 'lsp': 'stdio', \ 'executable_callback': 'ale_linters#php#langserver#GetExecutable', \ 'command_callback': 'ale_linters#php#langserver#GetCommand', -\ 'language_callback': 'ale_linters#php#langserver#GetLanguage', +\ 'language': 'php', \ 'project_root_callback': 'ale_linters#php#langserver#GetProjectRoot', \}) diff --git a/ale_linters/php/phan.vim b/ale_linters/php/phan.vim index f3b3d48f..c6f16356 100644 --- a/ale_linters/php/phan.vim +++ b/ale_linters/php/phan.vim @@ -1,28 +1,65 @@ -" Author: diegoholiveira <https://github.com/diegoholiveira> +" Author: diegoholiveira <https://github.com/diegoholiveira>, haginaga <https://github.com/haginaga> " Description: static analyzer for PHP " Define the minimum severity let g:ale_php_phan_minimum_severity = get(g:, 'ale_php_phan_minimum_severity', 0) +let g:ale_php_phan_executable = get(g:, 'ale_php_phan_executable', 'phan') +let g:ale_php_phan_use_client = get(g:, 'ale_php_phan_use_client', 0) + +function! ale_linters#php#phan#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'php_phan_executable') + + if ale#Var(a:buffer, 'php_phan_use_client') == 1 && l:executable is# 'phan' + let l:executable = 'phan_client' + endif + + return l:executable +endfunction + function! ale_linters#php#phan#GetCommand(buffer) abort - return 'phan -y ' - \ . ale#Var(a:buffer, 'php_phan_minimum_severity') - \ . ' %s' + if ale#Var(a:buffer, 'php_phan_use_client') == 1 + let l:args = '-l ' + \ . ' %s' + else + let l:args = '-y ' + \ . ale#Var(a:buffer, 'php_phan_minimum_severity') + \ . ' %s' + endif + + let l:executable = ale_linters#php#phan#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' ' . l:args endfunction function! ale_linters#php#phan#Handle(buffer, lines) abort " Matches against lines like the following: - " - " /path/to/some-filename.php:18 ERRORTYPE message - let l:pattern = '^.*:\(\d\+\)\s\(\w\+\)\s\(.\+\)$' + if ale#Var(a:buffer, 'php_phan_use_client') == 1 + " Phan error: ERRORTYPE: message in /path/to/some-filename.php on line nnn + let l:pattern = '^Phan error: \(\w\+\): \(.\+\) in \(.\+\) on line \(\d\+\)$' + else + " /path/to/some-filename.php:18 ERRORTYPE message + let l:pattern = '^.*:\(\d\+\)\s\(\w\+\)\s\(.\+\)$' + endif + let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) - call add(l:output, { - \ 'lnum': l:match[1] + 0, - \ 'text': l:match[3], - \ 'type': 'W', - \}) + if ale#Var(a:buffer, 'php_phan_use_client') == 1 + let l:dict = { + \ 'lnum': l:match[4] + 0, + \ 'text': l:match[2], + \ 'type': 'W', + \} + else + let l:dict = { + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[3], + \ 'type': 'W', + \} + endif + + call add(l:output, l:dict) endfor return l:output @@ -30,7 +67,7 @@ endfunction call ale#linter#Define('php', { \ 'name': 'phan', -\ 'executable': 'phan', +\ 'executable_callback': 'ale_linters#php#phan#GetExecutable', \ 'command_callback': 'ale_linters#php#phan#GetCommand', \ 'callback': 'ale_linters#php#phan#Handle', \}) diff --git a/ale_linters/php/phpcs.vim b/ale_linters/php/phpcs.vim index 399fbd23..faf8ad55 100644 --- a/ale_linters/php/phpcs.vim +++ b/ale_linters/php/phpcs.vim @@ -4,7 +4,7 @@ let g:ale_php_phpcs_standard = get(g:, 'ale_php_phpcs_standard', '') call ale#Set('php_phpcs_executable', 'phpcs') -call ale#Set('php_phpcs_use_global', 0) +call ale#Set('php_phpcs_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#php#phpcs#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'php_phpcs', [ diff --git a/ale_linters/po/msgfmt.vim b/ale_linters/po/msgfmt.vim index 60c25d30..8279ccdc 100644 --- a/ale_linters/po/msgfmt.vim +++ b/ale_linters/po/msgfmt.vim @@ -1,10 +1,30 @@ " Author: Cian Butler https://github.com/butlerx " Description: msgfmt for PO files +function! ale_linters#po#msgfmt#Handle(buffer, lines) abort + let l:results = ale#handlers#unix#HandleAsWarning(a:buffer, a:lines) + let l:index = 0 + + for l:item in l:results + if l:index > 0 && l:item.text =~? 'this is the location of the first definition' + let l:last_item = l:results[l:index - 1] + + if l:last_item.text =~? 'duplicate message definition' + let l:last_item.text = 'duplicate of message at line ' . l:item.lnum + let l:item.text = 'first location of duplicate of message at line ' . l:last_item.lnum + endif + endif + + let l:index += 1 + endfor + + return l:results +endfunction + call ale#linter#Define('po', { \ 'name': 'msgfmt', \ 'executable': 'msgfmt', \ 'output_stream': 'stderr', -\ 'command': 'msgfmt --statistics %t', -\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\ 'command': 'msgfmt --statistics --output-file=- %t', +\ 'callback': 'ale_linters#po#msgfmt#Handle', \}) diff --git a/ale_linters/pony/ponyc.vim b/ale_linters/pony/ponyc.vim new file mode 100644 index 00000000..b3329053 --- /dev/null +++ b/ale_linters/pony/ponyc.vim @@ -0,0 +1,21 @@ +" Description: ponyc linter for pony files + +call ale#Set('pony_ponyc_executable', 'ponyc') +call ale#Set('pony_ponyc_options', '--pass paint') + +function! ale_linters#pony#ponyc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'pony_ponyc_executable') +endfunction + +function! ale_linters#pony#ponyc#GetCommand(buffer) abort + return ale#Escape(ale_linters#pony#ponyc#GetExecutable(a:buffer)) + \ . ' ' . ale#Var(a:buffer, 'pony_ponyc_options') +endfunction + +call ale#linter#Define('pony', { +\ 'name': 'ponyc', +\ 'output_stream': 'stderr', +\ 'executable_callback': 'ale_linters#pony#ponyc#GetExecutable', +\ 'command_callback': 'ale_linters#pony#ponyc#GetCommand', +\ 'callback': 'ale#handlers#pony#HandlePonycFormat', +\}) diff --git a/ale_linters/pug/puglint.vim b/ale_linters/pug/puglint.vim index 6c29efe9..165e68b7 100644 --- a/ale_linters/pug/puglint.vim +++ b/ale_linters/pug/puglint.vim @@ -3,7 +3,7 @@ call ale#Set('pug_puglint_options', '') call ale#Set('pug_puglint_executable', 'pug-lint') -call ale#Set('pug_puglint_use_global', 0) +call ale#Set('pug_puglint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#pug#puglint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'pug_puglint', [ diff --git a/ale_linters/puppet/puppet.vim b/ale_linters/puppet/puppet.vim index 8beeb61e..4ca0dd55 100644 --- a/ale_linters/puppet/puppet.vim +++ b/ale_linters/puppet/puppet.vim @@ -4,14 +4,15 @@ function! ale_linters#puppet#puppet#Handle(buffer, lines) abort " Matches patterns like the following: " Error: Could not parse for environment production: Syntax error at ':' at /root/puppetcode/modules/nginx/manifests/init.pp:43:12 " Error: Could not parse for environment production: Syntax error at '='; expected '}' at /root/puppetcode/modules/pancakes/manifests/init.pp:5" + " Error: Could not parse for environment production: Syntax error at 'parameter1' (file: /tmp/modules/mariadb/manifests/slave.pp, line: 4, column: 5) - let l:pattern = '^Error: .*: \(.\+\) at .\+\.pp:\(\d\+\):\=\(\d*\)' + let l:pattern = '^Error: .*: \(.\+\) \((file:\|at\) .\+\.pp\(, line: \|:\)\(\d\+\)\(, column: \|:\)\=\(\d*\)' let l:output = [] 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, + \ 'lnum': l:match[4] + 0, + \ 'col': l:match[6] + 0, \ 'text': l:match[1], \}) endfor diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index 400e60f0..fdc4ac94 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -1,14 +1,15 @@ " Author: w0rp <devw0rp@gmail.com> " Description: flake8 for python files -let g:ale_python_flake8_executable = -\ get(g:, 'ale_python_flake8_executable', 'flake8') - +" remove in 2.0 " Support an old setting as a fallback. +let s:deprecation_warning_echoed = 0 let s:default_options = get(g:, 'ale_python_flake8_args', '') -let g:ale_python_flake8_options = -\ get(g:, 'ale_python_flake8_options', s:default_options) -let g:ale_python_flake8_use_global = get(g:, 'ale_python_flake8_use_global', 0) + +call ale#Set('python_flake8_executable', 'flake8') +call ale#Set('python_flake8_options', s:default_options) +call ale#Set('python_flake8_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_flake8_change_directory', 1) function! s:UsingModule(buffer) abort return ale#Var(a:buffer, 'python_flake8_options') =~# ' *-m flake8' @@ -39,9 +40,22 @@ function! ale_linters#python#flake8#VersionCheck(buffer) abort endfunction function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort + " remove in 2.0 + if exists('g:ale_python_flake8_args') && !s:deprecation_warning_echoed + execute 'echom ''Rename your g:ale_python_flake8_args setting to g:ale_python_flake8_options instead. Support for this will removed in ALE 2.0.''' + let s:deprecation_warning_echoed = 1 + endif + + let l:cd_string = ale#Var(a:buffer, 'python_flake8_change_directory') + \ ? ale#path#BufferCdString(a:buffer) + \ : '' let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) let l:version = ale#semver#GetVersion(l:executable, a:version_output) + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run flake8' + \ : '' + " Only include the --stdin-display-name argument if we can parse the " flake8 version, and it is recent enough to support it. let l:display_name_args = ale#semver#GTE(l:version, [3, 0, 0]) @@ -50,7 +64,8 @@ function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort let l:options = ale#Var(a:buffer, 'python_flake8_options') - return ale#Escape(l:executable) + return l:cd_string + \ . ale#Escape(l:executable) . l:exec_args \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --format=default' \ . l:display_name_args . ' -' @@ -105,11 +120,16 @@ function! ale_linters#python#flake8#Handle(buffer, lines) abort \ 'type': 'W', \} - if l:code[:0] is# 'F' || l:code is# 'E999' - let l:item.type = 'E' + if l:code[:0] is# 'F' + if l:code isnot# 'F401' + let l:item.type = 'E' + endif elseif l:code[:0] is# 'E' let l:item.type = 'E' - let l:item.sub_type = 'style' + + if l:code isnot# 'E999' && l:code isnot# 'E112' + let l:item.sub_type = 'style' + endif 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 c1c91742..b38ccdeb 100644 --- a/ale_linters/python/mypy.vim +++ b/ale_linters/python/mypy.vim @@ -4,7 +4,7 @@ call ale#Set('python_mypy_executable', 'mypy') call ale#Set('python_mypy_ignore_invalid_syntax', 0) call ale#Set('python_mypy_options', '') -call ale#Set('python_mypy_use_global', 0) +call ale#Set('python_mypy_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#python#mypy#GetExecutable(buffer) abort return ale#python#FindExecutable(a:buffer, 'python_mypy', ['mypy']) @@ -23,10 +23,14 @@ 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) + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run mypy' + \ : '' + " 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) + \ . ale#Escape(l:executable) . l:exec_args \ . ' --show-column-numbers ' \ . ale#Var(a:buffer, 'python_mypy_options') \ . ' --shadow-file %s %t %s' diff --git a/ale_linters/python/prospector.vim b/ale_linters/python/prospector.vim index f2968a05..fff37147 100644 --- a/ale_linters/python/prospector.vim +++ b/ale_linters/python/prospector.vim @@ -7,14 +7,21 @@ let g:ale_python_prospector_executable = let g:ale_python_prospector_options = \ get(g:, 'ale_python_prospector_options', '') -let g:ale_python_prospector_use_global = get(g:, 'ale_python_prospector_use_global', 0) +let g:ale_python_prospector_use_global = get(g:, 'ale_python_prospector_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#python#prospector#GetExecutable(buffer) abort return ale#python#FindExecutable(a:buffer, 'python_prospector', ['prospector']) endfunction function! ale_linters#python#prospector#GetCommand(buffer) abort - return ale#Escape(ale_linters#python#prospector#GetExecutable(a:buffer)) + let l:executable = ale_linters#python#prospector#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run prospector' + \ : '' + + return ale#Escape(l:executable) + \ . l:exec_args \ . ' ' . ale#Var(a:buffer, 'python_prospector_options') \ . ' --messages-only --absolute-paths --zero-exit --output-format json' \ . ' %s' diff --git a/ale_linters/python/pycodestyle.vim b/ale_linters/python/pycodestyle.vim index bbecdf03..de96363f 100644 --- a/ale_linters/python/pycodestyle.vim +++ b/ale_linters/python/pycodestyle.vim @@ -3,14 +3,20 @@ call ale#Set('python_pycodestyle_executable', 'pycodestyle') call ale#Set('python_pycodestyle_options', '') -call ale#Set('python_pycodestyle_use_global', 0) +call ale#Set('python_pycodestyle_use_global', get(g:, 'ale_use_global_executables', 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)) + let l:executable = ale_linters#python#pycodestyle#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pycodestyle' + \ : '' + + return ale#Escape(l:executable) . l:exec_args \ . ' ' \ . ale#Var(a:buffer, 'python_pycodestyle_options') \ . ' -' @@ -44,8 +50,8 @@ function! ale_linters#python#pycodestyle#Handle(buffer, lines) abort \ 'code': l:match[4], \} - " E999 is not a style error, it's a syntax error. - if l:match[4] is# 'E999' + " E999 and E112 are syntax errors. + if l:match[4] is# 'E999' || l:match[4] is# 'E112' unlet l:item.sub_type endif diff --git a/ale_linters/python/pyflakes.vim b/ale_linters/python/pyflakes.vim index b4a0b5f9..86ff8773 100644 --- a/ale_linters/python/pyflakes.vim +++ b/ale_linters/python/pyflakes.vim @@ -2,7 +2,7 @@ " Description: pyflakes for python files call ale#Set('python_pyflakes_executable', 'pyflakes') -call ale#Set('python_pyflakes_use_global', 0) +call ale#Set('python_pyflakes_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#python#pyflakes#GetExecutable(buffer) abort return ale#python#FindExecutable(a:buffer, 'python_pyflakes', ['pyflakes']) @@ -11,7 +11,13 @@ endfunction function! ale_linters#python#pyflakes#GetCommand(buffer) abort let l:executable = ale_linters#python#pyflakes#GetExecutable(a:buffer) - return ale#Escape(l:executable) . ' %t' + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pyflakes' + \ : '' + + return ale#Escape(l:executable) + \ . l:exec_args + \ . ' %t' endfunction function! ale_linters#python#pyflakes#Handle(buffer, lines) abort diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim index e3e6624d..9239f835 100644 --- a/ale_linters/python/pylint.vim +++ b/ale_linters/python/pylint.vim @@ -1,20 +1,28 @@ " Author: keith <k@keith.so> " Description: pylint for python files -let g:ale_python_pylint_executable = -\ get(g:, 'ale_python_pylint_executable', 'pylint') - -let g:ale_python_pylint_options = -\ get(g:, 'ale_python_pylint_options', '') - -let g:ale_python_pylint_use_global = get(g:, 'ale_python_pylint_use_global', 0) +call ale#Set('python_pylint_executable', 'pylint') +call ale#Set('python_pylint_options', '') +call ale#Set('python_pylint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pylint_change_directory', 1) function! ale_linters#python#pylint#GetExecutable(buffer) abort return ale#python#FindExecutable(a:buffer, 'python_pylint', ['pylint']) endfunction function! ale_linters#python#pylint#GetCommand(buffer) abort - return ale#Escape(ale_linters#python#pylint#GetExecutable(a:buffer)) + let l:cd_string = ale#Var(a:buffer, 'python_pylint_change_directory') + \ ? ale#path#BufferCdString(a:buffer) + \ : '' + + let l:executable = ale_linters#python#pylint#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pylint' + \ : '' + + return l:cd_string + \ . ale#Escape(l:executable) . l:exec_args \ . ' ' . ale#Var(a:buffer, 'python_pylint_options') \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n' \ . ' %s' @@ -24,7 +32,7 @@ function! ale_linters#python#pylint#Handle(buffer, lines) abort " Matches patterns like the following: " " test.py:4:4: W0101 (unreachable) Unreachable code - let l:pattern = '\v^[^:]+:(\d+):(\d+): ([[:alnum:]]+) \(([^(]*)\) (.*)$' + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):(\d+): ([[:alnum:]]+) \(([^(]*)\) (.*)$' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) diff --git a/ale_linters/python/pyls.vim b/ale_linters/python/pyls.vim index 9888853f..010cb31f 100644 --- a/ale_linters/python/pyls.vim +++ b/ale_linters/python/pyls.vim @@ -2,7 +2,7 @@ " Description: A language server for Python call ale#Set('python_pyls_executable', 'pyls') -call ale#Set('python_pyls_use_global', 0) +call ale#Set('python_pyls_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#python#pyls#GetExecutable(buffer) abort return ale#python#FindExecutable(a:buffer, 'python_pyls', ['pyls']) @@ -11,11 +11,11 @@ endfunction function! ale_linters#python#pyls#GetCommand(buffer) abort let l:executable = ale_linters#python#pyls#GetExecutable(a:buffer) - return ale#Escape(l:executable) -endfunction + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pyls' + \ : '' -function! ale_linters#python#pyls#GetLanguage(buffer) abort - return 'python' + return ale#Escape(l:executable) . l:exec_args endfunction call ale#linter#Define('python', { @@ -23,6 +23,7 @@ call ale#linter#Define('python', { \ 'lsp': 'stdio', \ 'executable_callback': 'ale_linters#python#pyls#GetExecutable', \ 'command_callback': 'ale_linters#python#pyls#GetCommand', -\ 'language_callback': 'ale_linters#python#pyls#GetLanguage', +\ 'language': 'python', \ 'project_root_callback': 'ale#python#FindProjectRoot', +\ 'completion_filter': 'ale#completion#python#CompletionItemFilter', \}) diff --git a/ale_linters/qml/qmlfmt.vim b/ale_linters/qml/qmlfmt.vim new file mode 100644 index 00000000..85b131fd --- /dev/null +++ b/ale_linters/qml/qmlfmt.vim @@ -0,0 +1,40 @@ +" Author: pylipp (www.github.com/pylipp) +" Description: qmlfmt for QML files + +let g:ale_qml_qmlfmt_executable = get(g:, 'ale_qml_qmlfmt_executable', 'qmlfmt') + +function! ale_linters#qml#qmlfmt#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'qml_qmlfmt_executable') +endfunction + +function! ale_linters#qml#qmlfmt#GetCommand(buffer) abort + return ale#Escape(ale_linters#qml#qmlfmt#GetExecutable(a:buffer)) + \ . ' -e' +endfunction + +" Find lines like +" Error:11:1: Expected token `}' +function! ale_linters#qml#qmlfmt#Handle(buffer, lines) abort + let l:pattern = '\v^(Error|Warning):(\d+):(\d+): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': l:match[1] is# 'Warning' ? 'W' : 'E', + \} + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('qml', { +\ 'name': 'qmlfmt', +\ 'output_stream': 'stderr', +\ 'executable_callback': 'ale_linters#qml#qmlfmt#GetExecutable', +\ 'command_callback': 'ale_linters#qml#qmlfmt#GetCommand', +\ 'callback': 'ale_linters#qml#qmlfmt#Handle', +\}) diff --git a/ale_linters/qml/qmllint.vim b/ale_linters/qml/qmllint.vim new file mode 100644 index 00000000..c2258a14 --- /dev/null +++ b/ale_linters/qml/qmllint.vim @@ -0,0 +1,29 @@ +" Author: pylipp (www.github.com/pylipp) +" Description: qmllint for QML files + +" Find lines like +" /home/foo_user42/code-base/qml/Screen.qml:11 : Expected token `}' +function! ale_linters#qml#qmllint#Handle(buffer, lines) abort + let l:pattern = '\v^[/_-a-zA-z0-9\. ]+:(\d+) : (.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': 0, + \ 'text': l:match[2], + \ 'type': 'E', + \} + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('qml', { +\ 'name': 'qmllint', +\ 'output_stream': 'stderr', +\ 'executable': 'qmllint', +\ 'command': 'qmllint %t', +\ 'callback': 'ale_linters#qml#qmllint#Handle', +\}) diff --git a/ale_linters/r/lintr.vim b/ale_linters/r/lintr.vim index 51e5c562..8f74c9b8 100644 --- a/ale_linters/r/lintr.vim +++ b/ale_linters/r/lintr.vim @@ -22,7 +22,7 @@ function! ale_linters#r#lintr#GetCommand(buffer) abort \ . l:lint_cmd return ale#path#BufferCdString(a:buffer) - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape(l:cmd_string) . ' %t' endfunction diff --git a/ale_linters/reason/ols.vim b/ale_linters/reason/ols.vim index b2cd5f79..4e5bd395 100644 --- a/ale_linters/reason/ols.vim +++ b/ale_linters/reason/ols.vim @@ -2,7 +2,7 @@ " Description: A language server for Reason call ale#Set('reason_ols_executable', 'ocaml-language-server') -call ale#Set('reason_ols_use_global', 0) +call ale#Set('reason_ols_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#linter#Define('reason', { \ 'name': 'ols', diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim index 09f41bbb..ef0c3bd1 100644 --- a/ale_linters/rust/cargo.vim +++ b/ale_linters/rust/cargo.vim @@ -4,6 +4,8 @@ call ale#Set('rust_cargo_use_check', 1) call ale#Set('rust_cargo_check_all_targets', 0) +call ale#Set('rust_cargo_check_examples', 0) +call ale#Set('rust_cargo_check_tests', 0) call ale#Set('rust_cargo_default_feature_behavior', 'default') call ale#Set('rust_cargo_include_features', '') @@ -31,6 +33,12 @@ function! ale_linters#rust#cargo#GetCommand(buffer, version_output) abort let l:use_all_targets = l:use_check \ && ale#Var(a:buffer, 'rust_cargo_check_all_targets') \ && ale#semver#GTE(l:version, [0, 22, 0]) + let l:use_examples = l:use_check + \ && ale#Var(a:buffer, 'rust_cargo_check_examples') + \ && ale#semver#GTE(l:version, [0, 22, 0]) + let l:use_tests = l:use_check + \ && ale#Var(a:buffer, 'rust_cargo_check_tests') + \ && ale#semver#GTE(l:version, [0, 22, 0]) let l:include_features = ale#Var(a:buffer, 'rust_cargo_include_features') if !empty(l:include_features) @@ -50,6 +58,8 @@ function! ale_linters#rust#cargo#GetCommand(buffer, version_output) abort return 'cargo ' \ . (l:use_check ? 'check' : 'build') \ . (l:use_all_targets ? ' --all-targets' : '') + \ . (l:use_examples ? ' --examples' : '') + \ . (l:use_tests ? ' --tests' : '') \ . ' --frozen --message-format=json -q' \ . l:default_feature \ . l:include_features diff --git a/ale_linters/rust/rls.vim b/ale_linters/rust/rls.vim index 832fe3e2..d5160b14 100644 --- a/ale_linters/rust/rls.vim +++ b/ale_linters/rust/rls.vim @@ -12,12 +12,11 @@ function! ale_linters#rust#rls#GetCommand(buffer) abort let l:executable = ale_linters#rust#rls#GetExecutable(a:buffer) let l:toolchain = ale#Var(a:buffer, 'rust_rls_toolchain') - return ale#Escape(l:executable) - \ . ' +' . ale#Escape(l:toolchain) -endfunction - -function! ale_linters#rust#rls#GetLanguage(buffer) abort - return 'rust' + if empty(l:toolchain) + return ale#Escape(l:executable) + else + return ale#Escape(l:executable) . ' +' . ale#Escape(l:toolchain) + endif endfunction function! ale_linters#rust#rls#GetProjectRoot(buffer) abort @@ -31,6 +30,6 @@ call ale#linter#Define('rust', { \ 'lsp': 'stdio', \ 'executable_callback': 'ale_linters#rust#rls#GetExecutable', \ 'command_callback': 'ale_linters#rust#rls#GetCommand', -\ 'language_callback': 'ale_linters#rust#rls#GetLanguage', +\ 'language': 'rust', \ 'project_root_callback': 'ale_linters#rust#rls#GetProjectRoot', \}) diff --git a/ale_linters/sass/sasslint.vim b/ale_linters/sass/sasslint.vim index bbe71255..4df56dfd 100644 --- a/ale_linters/sass/sasslint.vim +++ b/ale_linters/sass/sasslint.vim @@ -1,8 +1,9 @@ -" Author: KabbAmine - https://github.com/KabbAmine +" Author: KabbAmine - https://github.com/KabbAmine, +" Ben Falconer <ben@falconers.me.uk> call ale#linter#Define('sass', { \ 'name': 'sasslint', \ 'executable': 'sass-lint', -\ 'command': 'sass-lint -v -q -f compact %t', +\ 'command_callback': 'ale#handlers#sasslint#GetCommand', \ 'callback': 'ale#handlers#css#HandleCSSLintFormat', \}) diff --git a/ale_linters/sass/stylelint.vim b/ale_linters/sass/stylelint.vim index 98c37257..fe941d6a 100644 --- a/ale_linters/sass/stylelint.vim +++ b/ale_linters/sass/stylelint.vim @@ -1,7 +1,7 @@ " Author: diartyz <diartyz@gmail.com> call ale#Set('sass_stylelint_executable', 'stylelint') -call ale#Set('sass_stylelint_use_global', 0) +call ale#Set('sass_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#sass#stylelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'sass_stylelint', [ diff --git a/ale_linters/scala/fsc.vim b/ale_linters/scala/fsc.vim new file mode 100644 index 00000000..17b26f0b --- /dev/null +++ b/ale_linters/scala/fsc.vim @@ -0,0 +1,29 @@ +" Author: Nils Leuzinger - https://github.com/PawkyPenguin +" Description: Basic scala support using fsc +" +function! ale_linters#scala#fsc#GetExecutable(buffer) abort + if index(split(getbufvar(a:buffer, '&filetype'), '\.'), 'sbt') >= 0 + " Don't check sbt files + return '' + endif + + return 'fsc' +endfunction + +function! ale_linters#scala#fsc#GetCommand(buffer) abort + let l:executable = ale_linters#scala#fsc#GetExecutable(a:buffer) + + if empty(l:executable) + return '' + endif + + return ale#Escape(l:executable) . ' -Ystop-after:parser %t' +endfunction + +call ale#linter#Define('scala', { +\ 'name': 'fsc', +\ 'executable_callback': 'ale_linters#scala#fsc#GetExecutable', +\ 'command_callback': 'ale_linters#scala#fsc#GetCommand', +\ 'callback': 'ale#handlers#scala#HandleScalacLintFormat', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/scala/scalac.vim b/ale_linters/scala/scalac.vim index 584aee74..551284af 100644 --- a/ale_linters/scala/scalac.vim +++ b/ale_linters/scala/scalac.vim @@ -4,7 +4,7 @@ function! ale_linters#scala#scalac#GetExecutable(buffer) abort if index(split(getbufvar(a:buffer, '&filetype'), '\.'), 'sbt') >= 0 - " Don't check sbt files with scalac. + " Don't check sbt files return '' endif @@ -21,45 +21,10 @@ function! ale_linters#scala#scalac#GetCommand(buffer) abort return ale#Escape(l:executable) . ' -Ystop-after:parser %t' endfunction -function! ale_linters#scala#scalac#Handle(buffer, lines) abort - " Matches patterns line the following: - " - " /var/folders/5q/20rgxx3x1s34g3m14n5bq0x80000gn/T/vv6pSsy/0:26: error: expected class or object definition - let l:pattern = '^.\+:\(\d\+\): \(\w\+\): \(.\+\)' - let l:output = [] - let l:ln = 0 - - for l:line in a:lines - let l:ln = l:ln + 1 - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - - let l:text = l:match[3] - let l:type = l:match[2] is# 'error' ? 'E' : 'W' - let l:col = 0 - - if l:ln + 1 < len(a:lines) - let l:col = stridx(a:lines[l:ln + 1], '^') - endif - - call add(l:output, { - \ 'lnum': l:match[1] + 0, - \ 'col': l:col + 1, - \ 'text': l:text, - \ 'type': l:type, - \}) - endfor - - return l:output -endfunction - call ale#linter#Define('scala', { \ 'name': 'scalac', \ 'executable_callback': 'ale_linters#scala#scalac#GetExecutable', \ 'command_callback': 'ale_linters#scala#scalac#GetCommand', -\ 'callback': 'ale_linters#scala#scalac#Handle', +\ 'callback': 'ale#handlers#scala#HandleScalacLintFormat', \ 'output_stream': 'stderr', \}) diff --git a/ale_linters/scss/sasslint.vim b/ale_linters/scss/sasslint.vim index bd016465..f6075001 100644 --- a/ale_linters/scss/sasslint.vim +++ b/ale_linters/scss/sasslint.vim @@ -1,8 +1,18 @@ -" Author: KabbAmine - https://github.com/KabbAmine +" Author: KabbAmine - https://github.com/KabbAmine, Ben Falconer +" <ben@falconers.me.uk> + +function! ale_linters#scss#sasslint#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) + \ . ale#Escape('sass-lint') + \ . ' -v' + \ . ' -q' + \ . ' -f compact' + \ . ' %t' +endfunction call ale#linter#Define('scss', { \ 'name': 'sasslint', \ 'executable': 'sass-lint', -\ 'command': 'sass-lint -v -q -f compact %t', +\ 'command_callback': 'ale_linters#scss#sasslint#GetCommand', \ 'callback': 'ale#handlers#css#HandleCSSLintFormat', \}) diff --git a/ale_linters/scss/stylelint.vim b/ale_linters/scss/stylelint.vim index 00189a8b..6bfdd09a 100644 --- a/ale_linters/scss/stylelint.vim +++ b/ale_linters/scss/stylelint.vim @@ -1,7 +1,7 @@ " Author: diartyz <diartyz@gmail.com> call ale#Set('scss_stylelint_executable', 'stylelint') -call ale#Set('scss_stylelint_use_global', 0) +call ale#Set('scss_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#scss#stylelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'scss_stylelint', [ diff --git a/ale_linters/sh/language_server.vim b/ale_linters/sh/language_server.vim new file mode 100644 index 00000000..2f66e27b --- /dev/null +++ b/ale_linters/sh/language_server.vim @@ -0,0 +1,33 @@ +" Author: Christian Höltje (https://docwhat.org/) +" Description: BASH Language server integration for ALE +scriptencoding utf-8 + +call ale#Set('sh_language_server_executable', 'bash-language-server') +call ale#Set('sh_language_server_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#sh#language_server#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'sh_language_server', [ + \ 'node_modules/.bin/bash-language-server', + \]) +endfunction + +function! ale_linters#sh#language_server#GetCommand(buffer) abort + let l:exe = ale#Escape(ale_linters#sh#language_server#GetExecutable(a:buffer)) + + return l:exe . ' start' +endfunction + +function! ale_linters#sh#language_server#GetProjectRoot(buffer) abort + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' +endfunction + +call ale#linter#Define('sh', { +\ 'name': 'language_server', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#sh#language_server#GetExecutable', +\ 'command_callback': 'ale_linters#sh#language_server#GetCommand', +\ 'language': 'sh', +\ 'project_root_callback': 'ale_linters#sh#language_server#GetProjectRoot', +\}) diff --git a/ale_linters/stylus/stylelint.vim b/ale_linters/stylus/stylelint.vim index 2721529b..1562692a 100644 --- a/ale_linters/stylus/stylelint.vim +++ b/ale_linters/stylus/stylelint.vim @@ -2,7 +2,7 @@ call ale#Set('stylus_stylelint_executable', 'stylelint') call ale#Set('stylus_stylelint_options', '') -call ale#Set('stylus_stylelint_use_global', 0) +call ale#Set('stylus_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#stylus#stylelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'stylus_stylelint', [ diff --git a/ale_linters/text/textlint.vim b/ale_linters/text/textlint.vim new file mode 100644 index 00000000..8fafdd7d --- /dev/null +++ b/ale_linters/text/textlint.vim @@ -0,0 +1,9 @@ +" Author: Yasuhiro Kiyota <yasuhiroki.duck@gmail.com> +" Description: textlint, a proofreading tool (https://textlint.github.io/) + +call ale#linter#Define('text', { +\ 'name': 'textlint', +\ 'executable_callback': 'ale#handlers#textlint#GetExecutable', +\ 'command_callback': 'ale#handlers#textlint#GetCommand', +\ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', +\}) diff --git a/ale_linters/typescript/tslint.vim b/ale_linters/typescript/tslint.vim index f4b48169..b28f7cee 100644 --- a/ale_linters/typescript/tslint.vim +++ b/ale_linters/typescript/tslint.vim @@ -4,7 +4,7 @@ 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) +call ale#Set('typescript_tslint_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('typescript_tslint_ignore_empty_files', 0) function! ale_linters#typescript#tslint#GetExecutable(buffer) abort @@ -70,7 +70,7 @@ function! ale_linters#typescript#tslint#GetCommand(buffer) abort \ : '' return ale#path#BufferCdString(a:buffer) - \ . ale_linters#typescript#tslint#GetExecutable(a:buffer) + \ . ale#Escape(ale_linters#typescript#tslint#GetExecutable(a:buffer)) \ . ' --format json' \ . l:tslint_config_option \ . l:tslint_rules_option diff --git a/ale_linters/typescript/tsserver.vim b/ale_linters/typescript/tsserver.vim index 7a155bd9..08bd0f41 100644 --- a/ale_linters/typescript/tsserver.vim +++ b/ale_linters/typescript/tsserver.vim @@ -3,17 +3,13 @@ call ale#Set('typescript_tsserver_executable', 'tsserver') call ale#Set('typescript_tsserver_config_path', '') -call ale#Set('typescript_tsserver_use_global', 0) +call ale#Set('typescript_tsserver_use_global', get(g:, 'ale_use_global_executables', 0)) " These functions need to be defined just to comply with the API for LSP. function! ale_linters#typescript#tsserver#GetProjectRoot(buffer) abort return '' endfunction -function! ale_linters#typescript#tsserver#GetLanguage(buffer) abort - return '' -endfunction - function! ale_linters#typescript#tsserver#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'typescript_tsserver', [ \ 'node_modules/.bin/tsserver', @@ -26,5 +22,5 @@ call ale#linter#Define('typescript', { \ 'executable_callback': 'ale_linters#typescript#tsserver#GetExecutable', \ 'command_callback': 'ale_linters#typescript#tsserver#GetExecutable', \ 'project_root_callback': 'ale_linters#typescript#tsserver#GetProjectRoot', -\ 'language_callback': 'ale_linters#typescript#tsserver#GetLanguage', +\ 'language': '', \}) diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim index dfa00dc0..cf2d4afd 100644 --- a/ale_linters/vim/vint.vim +++ b/ale_linters/vim/vint.vim @@ -2,31 +2,38 @@ " Description: This file adds support for checking Vim code with Vint. " This flag can be used to change enable/disable style issues. -let g:ale_vim_vint_show_style_issues = -\ get(g:, 'ale_vim_vint_show_style_issues', 1) -let s:enable_neovim = has('nvim') ? ' --enable-neovim ' : '' +call ale#Set('vim_vint_show_style_issues', 1) +call ale#Set('vim_vint_executable', 'vint') +let s:enable_neovim = has('nvim') ? ' --enable-neovim' : '' let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})"' +function! ale_linters#vim#vint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'vim_vint_executable') +endfunction + function! ale_linters#vim#vint#VersionCommand(buffer) abort + let l:executable = ale_linters#vim#vint#GetExecutable(a:buffer) + " Check the Vint version if we haven't checked it already. - return !ale#semver#HasVersion('vint') - \ ? 'vint --version' + return !ale#semver#HasVersion(l:executable) + \ ? ale#Escape(l:executable) . ' --version' \ : '' endfunction function! ale_linters#vim#vint#GetCommand(buffer, version_output) abort - let l:version = ale#semver#GetVersion('vint', a:version_output) + let l:executable = ale_linters#vim#vint#GetExecutable(a:buffer) + let l:version = ale#semver#GetVersion(l:executable, a:version_output) let l:can_use_no_color_flag = empty(l:version) \ || ale#semver#GTE(l:version, [0, 3, 7]) let l:warning_flag = ale#Var(a:buffer, 'vim_vint_show_style_issues') ? '-s' : '-w' - return 'vint ' - \ . l:warning_flag . ' ' - \ . (l:can_use_no_color_flag ? '--no-color ' : '') + return ale#Escape(l:executable) + \ . ' ' . l:warning_flag + \ . (l:can_use_no_color_flag ? ' --no-color' : '') \ . s:enable_neovim - \ . s:format + \ . ' ' . s:format \ . ' %t' endfunction @@ -58,7 +65,7 @@ endfunction call ale#linter#Define('vim', { \ 'name': 'vint', -\ 'executable': 'vint', +\ 'executable_callback': 'ale_linters#vim#vint#GetExecutable', \ 'command_chain': [ \ {'callback': 'ale_linters#vim#vint#VersionCommand', 'output_stream': 'stderr'}, \ {'callback': 'ale_linters#vim#vint#GetCommand', 'output_stream': 'stdout'}, diff --git a/ale_linters/yaml/swaglint.vim b/ale_linters/yaml/swaglint.vim index 75a496c5..4a22c70f 100644 --- a/ale_linters/yaml/swaglint.vim +++ b/ale_linters/yaml/swaglint.vim @@ -2,7 +2,7 @@ " Description: This file adds support for linting Swagger / OpenAPI documents using swaglint call ale#Set('yaml_swaglint_executable', 'swaglint') -call ale#Set('yaml_swaglint_use_global', 0) +call ale#Set('yaml_swaglint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#yaml#swaglint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'yaml_swaglint', [ diff --git a/autoload/ale.vim b/autoload/ale.vim index f6c06cf1..dcc12150 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -2,38 +2,20 @@ " Description: Primary code path for the plugin " Manages execution of linters when requested by autocommands +" Strings used for severity in the echoed message +let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error') +let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info') +let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') +" Ignoring linters, for disabling some, or ignoring LSP diagnostics. +let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {}) + 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 - let l:max = ale#Var(bufnr(''), 'maximum_file_size') +function! ale#FileTooLarge(buffer) abort + let l:max = getbufvar(a:buffer, 'ale_maximum_file_size', get(g:, 'ale_maximum_file_size', 0)) return l:max > 0 ? (line2byte(line('$') + 1) > l:max) : 0 endfunction @@ -46,39 +28,47 @@ function! ale#ShouldDoNothing(buffer) abort " The checks are split into separate if statements to make it possible to " profile each check individually with Vim's profiling tools. + " Do nothing if ALE is disabled. + if !getbufvar(a:buffer, 'ale_enabled', get(g:, 'ale_enabled', 0)) + return 1 + endif + " Don't perform any checks when newer NeoVim versions are exiting. if get(v:, 'exiting', v:null) isnot v:null return 1 endif - " Do nothing for blacklisted files - if index(g:ale_filetype_blacklist, getbufvar(a:buffer, '&filetype')) >= 0 + let l:filetype = getbufvar(a:buffer, '&filetype') + + " Do nothing when there's no filetype. + if l:filetype is# '' + return 1 + endif + + " Do nothing for blacklisted files. + if index(get(g:, 'ale_filetype_blacklist', []), l:filetype) >= 0 return 1 endif - " Do nothing if running from command mode + " Do nothing if running from command mode. if s:getcmdwintype_exists && !empty(getcmdwintype()) return 1 endif let l:filename = fnamemodify(bufname(a:buffer), ':t') + " Do nothing for directories. if l:filename is# '.' return 1 endif - " Do nothing if running in the sandbox + " Do nothing if running in the sandbox. if ale#util#InSandbox() return 1 endif - " Do nothing if ALE is disabled. - if !ale#Var(a:buffer, 'enabled') - return 1 - endif - " Do nothing if the file is too large. - if ale#FileTooLarge() + if ale#FileTooLarge(a:buffer) return 1 endif @@ -101,30 +91,22 @@ function! ale#Queue(delay, ...) abort 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 - -function! s:ALEQueueImpl(delay, linting_flag, buffer) abort - if a:linting_flag isnot# '' && a:linting_flag isnot# 'lint_file' + if l:linting_flag isnot# '' && l:linting_flag isnot# 'lint_file' throw "linting_flag must be either '' or 'lint_file'" endif - if type(a:buffer) != type(0) + if type(l:buffer) != type(0) throw 'buffer_number must be a Number' endif - if ale#ShouldDoNothing(a:buffer) + if ale#ShouldDoNothing(l: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 a:linting_flag is# 'lint_file' - let s:should_lint_file_for_buffer[a:buffer] = 1 + if l:linting_flag is# 'lint_file' + let s:should_lint_file_for_buffer[l:buffer] = 1 endif if s:lint_timer != -1 @@ -132,24 +114,24 @@ function! s:ALEQueueImpl(delay, linting_flag, buffer) abort let s:lint_timer = -1 endif - let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype')) + let l:linters = ale#linter#Get(getbufvar(l: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, a:buffer) - call ale#engine#RunLinters(a:buffer, [], 1) + if has_key(g:ale_buffer_info, l:buffer) + call ale#engine#RunLinters(l:buffer, [], 1) endif return endif if a:delay > 0 - let s:queued_buffer_number = a:buffer + let s:queued_buffer_number = l:buffer let s:lint_timer = timer_start(a:delay, function('ale#Lint')) else - call ale#Lint(-1, a:buffer) + call ale#Lint(-1, l:buffer) endif endfunction @@ -165,30 +147,29 @@ function! ale#Lint(...) abort let l:buffer = bufnr('') endif - return ale#CallWithCooldown( - \ 'dont_lint_until', - \ function('s:ALELintImpl'), - \ [l:buffer], - \) -endfunction - -function! s:ALELintImpl(buffer) abort - if ale#ShouldDoNothing(a:buffer) + if ale#ShouldDoNothing(l:buffer) return endif " Use the filetype from the buffer - let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype')) + let l:filetype = getbufvar(l:buffer, '&filetype') + let l:linters = ale#linter#Get(l:filetype) let l:should_lint_file = 0 " Check if we previously requested checking the file. - if has_key(s:should_lint_file_for_buffer, a:buffer) - unlet s:should_lint_file_for_buffer[a:buffer] + if has_key(s:should_lint_file_for_buffer, l:buffer) + unlet s:should_lint_file_for_buffer[l:buffer] " Lint files if they exist. - let l:should_lint_file = filereadable(expand('#' . a:buffer . ':p')) + let l:should_lint_file = filereadable(expand('#' . l:buffer . ':p')) endif - call ale#engine#RunLinters(a:buffer, l:linters, l:should_lint_file) + " Apply ignore lists for linters only if needed. + let l:ignore_config = ale#Var(l:buffer, 'linters_ignore') + let l:linters = !empty(l:ignore_config) + \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config) + \ : l:linters + + call ale#engine#RunLinters(l:buffer, l:linters, l:should_lint_file) endfunction " Reset flags indicating that files should be checked for all buffers. @@ -196,10 +177,6 @@ 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 diff --git a/autoload/ale/balloon.vim b/autoload/ale/balloon.vim index 41fa95fa..d49dcbf9 100644 --- a/autoload/ale/balloon.vim +++ b/autoload/ale/balloon.vim @@ -2,10 +2,31 @@ " Description: balloonexpr support for ALE. function! ale#balloon#MessageForPos(bufnr, lnum, col) abort + " Don't show balloons if they are disabled, or linting is disabled. + if !ale#Var(a:bufnr, 'set_balloons') + \|| !g:ale_enabled + \|| !getbufvar(a:bufnr, 'ale_enabled', 1) + return '' + endif + let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col) - return l:index >= 0 ? l:loclist[l:index].text : '' + " Show the diagnostics message if found, 'Hover' output otherwise + if l:index >= 0 + return l:loclist[l:index].text + elseif exists('*balloon_show') || getbufvar( + \ a:bufnr, + \ 'ale_set_balloons_legacy_echo', + \ get(g:, 'ale_set_balloons_legacy_echo', 0) + \) + " Request LSP/tsserver hover information, but only if this version of + " Vim supports the balloon_show function, or if we turned a legacy + " setting on. + call ale#hover#Show(a:bufnr, a:lnum, a:col, {'called_from_balloonexpr': 1}) + endif + + return '' endfunction function! ale#balloon#Expr() abort @@ -13,9 +34,22 @@ function! ale#balloon#Expr() abort endfunction function! ale#balloon#Disable() abort - set noballooneval + set noballooneval noballoonevalterm + set balloonexpr= endfunction function! ale#balloon#Enable() abort - set ballooneval balloonexpr=ale#balloon#Expr() + if !has('balloon_eval') && !has('balloon_eval_term') + return + endif + + if has('balloon_eval') + set ballooneval + endif + + if has('balloon_eval_term') + set balloonevalterm + endif + + set balloonexpr=ale#balloon#Expr() endfunction diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim index f6ad7deb..5ab10f00 100644 --- a/autoload/ale/c.vim +++ b/autoload/ale/c.vim @@ -1,6 +1,7 @@ -" Author: gagbo <gagbobada@gmail.com>, w0rp <devw0rp@gmail.com> +" Author: gagbo <gagbobada@gmail.com>, w0rp <devw0rp@gmail.com>, roel0 <postelmansroel@gmail.com> " Description: Functions for integrating with C-family linters. +call ale#Set('c_parse_makefile', 0) let s:sep = has('win32') ? '\' : '/' function! ale#c#FindProjectRoot(buffer) abort @@ -22,6 +23,88 @@ function! ale#c#FindProjectRoot(buffer) abort return '' endfunction +function! ale#c#ParseCFlagsToList(path_prefix, cflags) abort + let l:cflags_list = [] + let l:previous_options = [] + + for l:option in a:cflags + call add(l:previous_options, l:option) + " Check if cflag contained a '-' and should not have been splitted + let l:option_list = split(l:option, '\zs') + if l:option_list[-1] isnot# ' ' + continue + endif + + let l:option = join(l:previous_options, '-') + let l:previous_options = [] + + let l:option = '-' . substitute(l:option, '^\s*\(.\{-}\)\s*$', '\1', '') + + " Fix relative paths if needed + if stridx(l:option, '-I') >= 0 && + \ stridx(l:option, '-I' . s:sep) < 0 + let l:rel_path = join(split(l:option, '\zs')[2:], '') + let l:rel_path = substitute(l:rel_path, '"', '', 'g') + let l:rel_path = substitute(l:rel_path, '''', '', 'g') + let l:option = ale#Escape('-I' . a:path_prefix . + \ s:sep . l:rel_path) + endif + + " Parse the cflag + if stridx(l:option, '-I') >= 0 || + \ stridx(l:option, '-D') >= 0 + if index(l:cflags_list, l:option) < 0 + call add(l:cflags_list, l:option) + endif + endif + endfor + + return l:cflags_list +endfunction + +function! ale#c#ParseCFlags(buffer, stdout_make) abort + if !g:ale_c_parse_makefile + return [] + endif + + let l:buffer_filename = expand('#' . a:buffer . ':t') + let l:cflags = [] + for l:lines in split(a:stdout_make, '\\n') + if stridx(l:lines, l:buffer_filename) >= 0 + let l:cflags = split(l:lines, '-') + break + endif + endfor + + let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile') + return ale#c#ParseCFlagsToList(fnamemodify(l:makefile_path, ':p:h'), l:cflags) +endfunction + +function! ale#c#GetCFlags(buffer, output) abort + let l:cflags = ' ' + + if g:ale_c_parse_makefile && !empty(a:output) + let l:cflags = join(ale#c#ParseCFlags(a:buffer, join(a:output, '\n')), ' ') . ' ' + endif + + if l:cflags is# ' ' + let l:cflags = ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer)) + endif + + return l:cflags +endfunction + +function! ale#c#GetMakeCommand(buffer) abort + if g:ale_c_parse_makefile + let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile') + if !empty(l:makefile_path) + return 'cd '. fnamemodify(l:makefile_path, ':p:h') . ' && make -n' + endif + endif + + return '' +endfunction + " Given a buffer number, search for a project root, and output a List " of directories to include based on some heuristics. " diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index 7ad7f9da..4823b00c 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -1,6 +1,10 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Completion support for LSP linters +let g:ale_completion_delay = get(g:, 'ale_completion_delay', 100) +let g:ale_completion_excluded_words = get(g:, 'ale_completion_excluded_words', []) +let g:ale_completion_max_suggestions = get(g:, 'ale_completion_max_suggestions', 50) + let s:timer_id = -1 let s:last_done_pos = [] @@ -28,6 +32,8 @@ let s:LSP_COMPLETION_REFERENCE_KIND = 18 " the insert cursor is. If one of these matches, we'll check for completions. let s:should_complete_map = { \ '<default>': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$', +\ 'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|''$|"$', +\ 'rust': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|::$', \} " Regular expressions for finding the start column to replace with completion. @@ -38,6 +44,8 @@ let s:omni_start_map = { " A map of exact characters for triggering LSP completions. let s:trigger_character_map = { \ '<default>': ['.'], +\ 'typescript': ['.', '''', '"'], +\ 'rust': ['.', '::'], \} function! s:GetFiletypeValue(map, filetype) abort @@ -74,33 +82,49 @@ function! ale#completion#GetTriggerCharacter(filetype, prefix) abort return '' endfunction -function! ale#completion#Filter(suggestions, prefix) abort +function! ale#completion#Filter(buffer, suggestions, prefix) abort + let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words') + " For completing... " foo. " ^ " We need to include all of the given suggestions. if a:prefix is# '.' - return a:suggestions + let l:filtered_suggestions = a:suggestions + else + let l:filtered_suggestions = [] + + " 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: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:word[: len(a:prefix) - 1] is? a:prefix + call add(l:filtered_suggestions, l:item) + endif + endfor endif - let l:filtered_suggestions = [] - - " 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: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:word[: len(a:prefix) - 1] is? a:prefix - call add(l:filtered_suggestions, l:item) + if !empty(l:excluded_words) + " Copy the List if needed. We don't want to modify the argument. + " We shouldn't make a copy if we don't need to. + if l:filtered_suggestions is a:suggestions + let l:filtered_suggestions = copy(a:suggestions) endif - endfor + + " Remove suggestions with words in the exclusion List. + call filter( + \ l:filtered_suggestions, + \ 'index(l:excluded_words, type(v:val) is type('''') ? v:val : v:val.word) < 0', + \) + endif return l:filtered_suggestions endfunction @@ -178,7 +202,9 @@ function! ale#completion#ParseTSServerCompletions(response) abort endfunction function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort + let l:buffer = bufnr('') let l:results = [] + let l:names_with_details = [] for l:suggestion in a:response.body let l:displayParts = [] @@ -212,10 +238,44 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort \}) endfor + let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', []) + + if !empty(l:names) && len(l:names) != len(l:results) + let l:names_with_details = map(copy(l:results), 'v:val.word') + let l:missing_names = filter( + \ copy(l:names), + \ 'index(l:names_with_details, v:val) < 0', + \) + + for l:name in l:missing_names + call add(l:results, { + \ 'word': l:name, + \ 'kind': 'v', + \ 'icase': 1, + \ 'menu': '', + \ 'info': '', + \}) + endfor + endif + return l:results endfunction +function! ale#completion#NullFilter(buffer, item) abort + return 1 +endfunction + function! ale#completion#ParseLSPCompletions(response) abort + let l:buffer = bufnr('') + let l:info = get(b:, 'ale_completion_info', {}) + let l:Filter = get(l:info, 'completion_filter', v:null) + + if l:Filter is v:null + let l:Filter = function('ale#completion#NullFilter') + else + let l:Filter = ale#util#GetFunction(l:Filter) + endif + let l:item_list = [] if type(get(a:response, 'result')) is type([]) @@ -228,6 +288,16 @@ function! ale#completion#ParseLSPCompletions(response) abort let l:results = [] for l:item in l:item_list + if !call(l:Filter, [l:buffer, l:item]) + continue + endif + + let l:word = matchstr(l:item.label, '\v^[^(]+') + + if empty(l:word) + continue + endif + " See :help complete-items for Vim completion kinds if l:item.kind is s:LSP_COMPLETION_METHOD_KIND let l:kind = 'm' @@ -244,14 +314,18 @@ function! ale#completion#ParseLSPCompletions(response) abort endif call add(l:results, { - \ 'word': l:item.label, + \ 'word': l:word, \ 'kind': l:kind, \ 'icase': 1, - \ 'menu': l:item.detail, - \ 'info': l:item.documentation, + \ 'menu': get(l:item, 'detail', ''), + \ 'info': get(l:item, 'documentation', ''), \}) endfor + if has_key(l:info, 'prefix') + return ale#completion#Filter(l:buffer, l:results, l:info.prefix) + endif + return l:results endfunction @@ -264,19 +338,25 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort return endif + let l:buffer = bufnr('') let l:command = get(a:response, 'command', '') if l:command is# 'completions' let l:names = ale#completion#Filter( + \ l:buffer, \ ale#completion#ParseTSServerCompletions(a:response), \ b:ale_completion_info.prefix, \)[: g:ale_completion_max_suggestions - 1] + " We need to remember some names for tsserver, as it doesn't send + " details back for everything we send. + call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names) + if !empty(l:names) let b:ale_completion_info.request_id = ale#lsp#Send( \ b:ale_completion_info.conn_id, \ ale#lsp#tsserver_message#CompletionEntryDetails( - \ bufnr(''), + \ l:buffer, \ b:ale_completion_info.line, \ b:ale_completion_info.column, \ l:names, @@ -309,14 +389,13 @@ function! s:GetLSPCompletions(linter) abort \ ? function('ale#completion#HandleTSServerResponse') \ : function('ale#completion#HandleLSPResponse') - let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root if a:linter.lsp is# 'tsserver' let l:message = ale#lsp#tsserver_message#Completions( @@ -328,7 +407,7 @@ function! s:GetLSPCompletions(linter) abort else " Send a message saying the buffer has changed first, otherwise " completions won't know what text is nearby. - call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) + call ale#lsp#NotifyForChanges(l:lsp_details) " For LSP completions, we need to clamp the column to the length of " the line. python-language-server and perhaps others do not implement @@ -344,11 +423,15 @@ function! s:GetLSPCompletions(linter) abort \) endif - let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) if l:request_id let b:ale_completion_info.conn_id = l:id let b:ale_completion_info.request_id = l:request_id + + if has_key(a:linter, 'completion_filter') + let b:ale_completion_info.completion_filter = a:linter.completion_filter + endif endif endfunction @@ -378,10 +461,7 @@ function! ale#completion#GetCompletions() abort for l:linter in ale#linter#Get(&filetype) if !empty(l:linter.lsp) - if l:linter.lsp is# 'tsserver' - \|| get(g:, 'ale_completion_experimental_lsp_support', 0) - call s:GetLSPCompletions(l:linter) - endif + call s:GetLSPCompletions(l:linter) endif endfor endfunction diff --git a/autoload/ale/completion/python.vim b/autoload/ale/completion/python.vim new file mode 100644 index 00000000..6b65c5b5 --- /dev/null +++ b/autoload/ale/completion/python.vim @@ -0,0 +1,3 @@ +function! ale#completion#python#CompletionItemFilter(buffer, item) abort + return a:item.label !~# '\v^__[a-z_]+__' +endfunction diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim index 50b1fb50..73dbebb2 100644 --- a/autoload/ale/cursor.vim +++ b/autoload/ale/cursor.vim @@ -1,6 +1,11 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Echoes lint message for the current line, if any +" Controls the milliseconds delay before echoing a message. +let g:ale_echo_delay = get(g:, 'ale_echo_delay', 10) +" A string format for the echoed message. +let g:ale_echo_msg_format = get(g:, 'ale_echo_msg_format', '%code: %%s') + let s:cursor_timer = -1 let s:last_pos = [0, 0, 0] @@ -51,10 +56,6 @@ 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 !g:ale_echo_cursor return endif diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index 9be1fbf6..34c13770 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -29,6 +29,7 @@ let s:global_variable_list = [ \ 'ale_linters', \ 'ale_linters_explicit', \ 'ale_list_window_size', +\ 'ale_list_vertical', \ 'ale_loclist_msg_format', \ 'ale_max_buffer_history_size', \ 'ale_max_signs', @@ -50,6 +51,7 @@ let s:global_variable_list = [ \ 'ale_sign_warning', \ 'ale_statusline_format', \ 'ale_type_map', +\ 'ale_use_global_executables', \ 'ale_warn_about_trailing_blank_lines', \ 'ale_warn_about_trailing_whitespace', \] @@ -166,6 +168,30 @@ function! s:EchoLinterAliases(all_linters) abort endfor endfunction +function! s:EchoLSPErrorMessages(all_linter_names) abort + let l:lsp_error_messages = get(g:, 'ale_lsp_error_messages', {}) + let l:header_echoed = 0 + + for l:linter_name in a:all_linter_names + let l:error_list = get(l:lsp_error_messages, l:linter_name, []) + + if !empty(l:error_list) + if !l:header_echoed + call s:Echo(' LSP Error Messages:') + call s:Echo('') + endif + + call s:Echo('(Errors for ' . l:linter_name . ')') + + for l:message in l:error_list + for l:line in split(l:message, "\n") + call s:Echo(l:line) + endfor + endfor + endif + endfor +endfunction + function! ale#debugging#Info() abort let l:filetype = &filetype @@ -188,16 +214,22 @@ function! ale#debugging#Info() abort " This must be done after linters are loaded. let l:variable_list = s:GetLinterVariables(l:filetype, l:enabled_names) + let l:fixers = ale#fix#registry#SuggestedFixers(l:filetype) + let l:fixers = uniq(sort(l:fixers[0] + l:fixers[1])) + let l:fixers_string = join(map(copy(l:fixers), '"\n " . v:val'), '') + call s:Echo(' Current Filetype: ' . l:filetype) call s:Echo('Available Linters: ' . string(l:all_names)) call s:EchoLinterAliases(l:all_linters) call s:Echo(' Enabled Linters: ' . string(l:enabled_names)) + call s:Echo(' Suggested Fixers: ' . l:fixers_string) call s:Echo(' Linter Variables:') call s:Echo('') call s:EchoLinterVariables(l:variable_list) call s:Echo(' Global Variables:') call s:Echo('') call s:EchoGlobalVariables() + call s:EchoLSPErrorMessages(l:all_names) call s:Echo(' Command History:') call s:Echo('') call s:EchoCommandHistory() @@ -210,3 +242,14 @@ function! ale#debugging#InfoToClipboard() abort call s:Echo('ALEInfo copied to your clipboard') endfunction + +function! ale#debugging#InfoToFile(filename) abort + let l:expanded_filename = expand(a:filename) + + redir => l:output + silent call ale#debugging#Info() + redir END + + call writefile(split(l:output, "\n"), l:expanded_filename) + call s:Echo('ALEInfo written to ' . l:expanded_filename) +endfunction diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index b20c01a0..6c70b64c 100644 --- a/autoload/ale/definition.vim +++ b/autoload/ale/definition.vim @@ -13,20 +13,8 @@ function! ale#definition#SetMap(map) abort let s:go_to_definition_map = a:map endfunction -" This function is used so we can check the execution of commands without -" running them. -function! ale#definition#Execute(expr) abort - execute a:expr -endfunction - -function! ale#definition#Open(options, filename, line, column) abort - if a:options.open_in_tab - call ale#definition#Execute('tabedit ' . fnameescape(a:filename)) - else - call ale#definition#Execute('edit ' . fnameescape(a:filename)) - endif - - call cursor(a:line, a:column) +function! ale#definition#ClearLSPData() abort + let s:go_to_definition_map = {} endfunction function! ale#definition#HandleTSServerResponse(conn_id, response) abort @@ -34,12 +22,12 @@ function! ale#definition#HandleTSServerResponse(conn_id, response) abort \&& has_key(s:go_to_definition_map, a:response.request_seq) let l:options = remove(s:go_to_definition_map, a:response.request_seq) - if get(a:response, 'success', v:false) is v:true + if get(a:response, 'success', v:false) is v:true && !empty(a:response.body) let l:filename = a:response.body[0].file let l:line = a:response.body[0].start.line let l:column = a:response.body[0].start.offset - call ale#definition#Open(l:options, l:filename, l:line, l:column) + call ale#util#Open(l:filename, l:line, l:column, l:options) endif endif endfunction @@ -63,7 +51,7 @@ function! ale#definition#HandleLSPResponse(conn_id, response) abort let l:line = l:item.range.start.line + 1 let l:column = l:item.range.start.character - call ale#definition#Open(l:options, l:filename, l:line, l:column) + call ale#util#Open(l:filename, l:line, l:column, l:options) break endfor endif @@ -77,14 +65,13 @@ function! s:GoToLSPDefinition(linter, options) abort \ ? function('ale#definition#HandleTSServerResponse') \ : function('ale#definition#HandleLSPResponse') - let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root if a:linter.lsp is# 'tsserver' let l:message = ale#lsp#tsserver_message#Definition( @@ -95,7 +82,7 @@ function! s:GoToLSPDefinition(linter, options) abort else " Send a message saying the buffer has changed first, or the " definition position probably won't make sense. - call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) + call ale#lsp#NotifyForChanges(l:lsp_details) let l:column = min([l:column, len(getline(l:line))]) @@ -105,7 +92,7 @@ function! s:GoToLSPDefinition(linter, options) abort let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column) endif - let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) let s:go_to_definition_map[l:request_id] = { \ 'open_in_tab': get(a:options, 'open_in_tab', 0), diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 8c3d4c73..714c75b1 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -2,6 +2,9 @@ " Description: Backend execution and job management " Executes linters in the background, using NeoVim or Vim 8 jobs +" Remapping of linter problems. +let g:ale_type_map = get(g:, 'ale_type_map', {}) + " Stores information for each job including: " " linter: The linter dictionary for the job. @@ -11,11 +14,6 @@ if !has_key(s:, 'job_info_map') let s:job_info_map = {} endif -" Associates LSP connection IDs with linter names. -if !has_key(s:, 'lsp_linter_map') - let s:lsp_linter_map = {} -endif - if !has_key(s:, 'executable_cache_map') let s:executable_cache_map = {} endif @@ -44,7 +42,7 @@ function! ale#engine#IsExecutable(buffer, executable) abort " Cache the executable check if we found it, or if the option to cache " failing checks is on. - if l:result || g:ale_cache_executable_check_failures + if l:result || get(g:, 'ale_cache_executable_check_failures', 0) let s:executable_cache_map[a:executable] = l:result endif @@ -76,6 +74,8 @@ function! ale#engine#InitBufferInfo(buffer) abort return 0 endfunction +" This function is documented and part of the public API. +" " Return 1 if ALE is busy checking a given buffer function! ale#engine#IsCheckingBuffer(buffer) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) @@ -86,11 +86,13 @@ endfunction " Register a temporary file to be managed with the ALE engine for " a current job run. function! ale#engine#ManageFile(buffer, filename) abort + call ale#engine#InitBufferInfo(a:buffer) call add(g:ale_buffer_info[a:buffer].temporary_file_list, a:filename) endfunction " Same as the above, but manage an entire directory. function! ale#engine#ManageDirectory(buffer, directory) abort + call ale#engine#InitBufferInfo(a:buffer) call add(g:ale_buffer_info[a:buffer].temporary_directory_list, a:directory) endfunction @@ -142,36 +144,40 @@ function! s:GatherOutput(job_id, line) abort endif endfunction -function! s:HandleLoclist(linter_name, buffer, loclist) abort - let l:buffer_info = get(g:ale_buffer_info, a:buffer, {}) +function! ale#engine#HandleLoclist(linter_name, buffer, loclist) abort + let l:info = get(g:ale_buffer_info, a:buffer, {}) - if empty(l:buffer_info) + if empty(l:info) return endif " 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 isnot# a:linter_name') + call filter(l: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 isnot# a:linter_name') - " Add the new items. - call extend(g:ale_buffer_info[a:buffer].loclist, l:linter_loclist) + call filter(l:info.loclist, 'v:val.linter_name isnot# a:linter_name') + + " We don't need to add items or sort the list when this list is empty. + if !empty(l:linter_loclist) + " Add the new items. + call extend(l:info.loclist, l:linter_loclist) - " Sort the loclist again. - " We need a sorted list so we can run a binary search against it - " for efficient lookup of the messages in the cursor handler. - call sort(g:ale_buffer_info[a:buffer].loclist, 'ale#util#LocItemCompare') + " Sort the loclist again. + " We need a sorted list so we can run a binary search against it + " for efficient lookup of the messages in the cursor handler. + call sort(l:info.loclist, 'ale#util#LocItemCompare') + endif if ale#ShouldDoNothing(a:buffer) return endif - call ale#engine#SetResults(a:buffer, g:ale_buffer_info[a:buffer].loclist) + call ale#engine#SetResults(a:buffer, l:info.loclist) endfunction function! s:HandleExit(job_id, exit_code) abort @@ -217,71 +223,7 @@ function! s:HandleExit(job_id, exit_code) abort let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output) - call s:HandleLoclist(l:linter.name, l:buffer, l:loclist) -endfunction - -function! s:HandleLSPDiagnostics(conn_id, response) abort - let l:linter_name = s:lsp_linter_map[a:conn_id] - let l:filename = ale#path#FromURI(a:response.params.uri) - let l:buffer = bufnr(l:filename) - - if l:buffer <= 0 - return - endif - - let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) - - call s:HandleLoclist(l:linter_name, l:buffer, l:loclist) -endfunction - -function! s:HandleTSServerDiagnostics(response, error_type) abort - let l:buffer = bufnr(a:response.body.file) - let l:info = get(g:ale_buffer_info, l:buffer, {}) - - if empty(l:info) - return - endif - - let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) - - " 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 is# 'syntax' - let l:info.syntax_loclist = l:thislist - else - let l:info.semantic_loclist = l:thislist - endif - - let l:loclist = get(l:info, 'semantic_loclist', []) - \ + get(l:info, 'syntax_loclist', []) - - call s:HandleLoclist('tsserver', l:buffer, l:loclist) -endfunction - -function! s:HandleLSPErrorMessage(error_message) abort - execute 'echoerr ''Error from LSP:''' - - for l:line in split(a:error_message, "\n") - execute 'echoerr l:line' - endfor -endfunction - -function! ale#engine#HandleLSPResponse(conn_id, response) abort - let l:method = get(a:response, 'method', '') - - 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 is# 'textDocument/publishDiagnostics' - call s:HandleLSPDiagnostics(a:conn_id, a:response) - elseif get(a:response, 'type', '') is# 'event' - \&& get(a:response, 'event', '') is# 'semanticDiag' - call s:HandleTSServerDiagnostics(a:response, 'semantic') - elseif get(a:response, 'type', '') is# 'event' - \&& get(a:response, 'event', '') is# 'syntaxDiag' - call s:HandleTSServerDiagnostics(a:response, 'syntax') - endif + call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist) endfunction function! ale#engine#SetResults(buffer, loclist) abort @@ -315,6 +257,12 @@ function! ale#engine#SetResults(buffer, loclist) abort " Reset the save event marker, used for opening windows, etc. call setbufvar(a:buffer, 'ale_save_event_fired', 0) + " Set a marker showing how many times a buffer has been checked. + call setbufvar( + \ a:buffer, + \ 'ale_linted', + \ getbufvar(a:buffer, 'ale_linted', 0) + 1 + \) " Automatically remove all managed temporary files and directories " now that all jobs have completed. @@ -322,6 +270,7 @@ function! ale#engine#SetResults(buffer, loclist) abort " Call user autocommands. This allows users to hook into ALE's lint cycle. silent doautocmd <nomodeline> User ALELintPost + " remove in 2.0 " Old DEPRECATED name; call it for backwards compatibility. silent doautocmd <nomodeline> User ALELint endif @@ -349,9 +298,6 @@ 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 = [] @@ -379,7 +325,7 @@ function! ale#engine#FixLocList(buffer, linter_name, loclist) abort \ 'text': l:old_item.text, \ 'lnum': str2nr(l:old_item.lnum), \ 'col': str2nr(get(l:old_item, 'col', 0)), - \ 'vcol': get(l:old_item, 'vcol', 0), + \ 'vcol': 0, \ 'type': get(l:old_item, 'type', 'E'), \ 'nr': get(l:old_item, 'nr', -1), \ 'linter_name': a:linter_name, @@ -439,6 +385,20 @@ function! ale#engine#FixLocList(buffer, linter_name, loclist) abort " 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 + elseif get(l:old_item, 'vcol', 0) + " Convert virtual column positions to byte positions. + " The positions will be off if the buffer has changed recently. + let l:line = getbufline(a:buffer, l:item.lnum)[0] + + let l:item.col = ale#util#Col(l:line, l:item.col) + + if has_key(l:item, 'end_col') + let l:end_line = get(l:item, 'end_lnum', l:line) != l:line + \ ? getbufline(a:buffer, l:item.end_lnum)[0] + \ : l:line + + let l:item.end_col = ale#util#Col(l:end_line, l:item.end_col) + endif endif call add(l:new_loclist, l:item) @@ -558,6 +518,8 @@ function! s:RunJob(options) abort \ 'output': [], \ 'next_chain_index': l:next_chain_index, \} + + silent doautocmd <nomodeline> User ALEJobStarted endif if g:ale_history_enabled @@ -680,44 +642,6 @@ function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort 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, - \ function('ale#engine#HandleLSPResponse'), - \) - - if empty(l:lsp_details) - return 0 - endif - - let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root - - " Remember the linter this connection is for. - let s:lsp_linter_map[l:id] = a:linter.name - - 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 this was a file save event, also notify the server of that. - if a:linter.lsp isnot# 'tsserver' - \&& getbufvar(a:buffer, 'ale_save_event_fired', 0) - let l:save_message = ale#lsp#message#DidSave(a:buffer) - let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root) - endif - - if l:request_id != 0 - 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 -endfunction function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort " Figure out which linters are still enabled, and remove @@ -773,7 +697,7 @@ endfunction " Returns 1 if the linter was successfully run. function! s:RunLinter(buffer, linter) abort if !empty(a:linter.lsp) - return s:CheckWithLSP(a:buffer, a:linter) + return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter) else let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) @@ -855,7 +779,7 @@ endfunction " The time taken will be a very rough approximation, and more time may be " permitted than is specified. function! ale#engine#WaitForJobs(deadline) abort - let l:start_time = ale#util#ClockMilliseconds() + let l:start_time = ale#events#ClockMilliseconds() if l:start_time == 0 throw 'Failed to read milliseconds from the clock!' @@ -886,7 +810,7 @@ function! ale#engine#WaitForJobs(deadline) abort for l:job_id in l:job_list if ale#job#IsRunning(l:job_id) - let l:now = ale#util#ClockMilliseconds() + let l:now = ale#events#ClockMilliseconds() if l:now - l:start_time > a:deadline " Stop waiting after a timeout, so we don't wait forever. @@ -923,7 +847,7 @@ function! ale#engine#WaitForJobs(deadline) abort if l:has_new_jobs " We have to wait more. Offset the timeout by the time taken so far. - let l:now = ale#util#ClockMilliseconds() + let l:now = ale#events#ClockMilliseconds() let l:new_deadline = a:deadline - (l:now - l:start_time) if l:new_deadline <= 0 diff --git a/autoload/ale/engine/ignore.vim b/autoload/ale/engine/ignore.vim new file mode 100644 index 00000000..65347e21 --- /dev/null +++ b/autoload/ale/engine/ignore.vim @@ -0,0 +1,46 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: Code for ignoring linters. Only loaded and if configured. + +" Given a filetype and a configuration for ignoring linters, return a List of +" Strings for linter names to ignore. +function! ale#engine#ignore#GetList(filetype, config) abort + if type(a:config) is type([]) + return a:config + endif + + if type(a:config) is type({}) + let l:names_to_remove = [] + + for l:part in split(a:filetype , '\.') + call extend(l:names_to_remove, get(a:config, l:part, [])) + endfor + + return l:names_to_remove + endif + + return [] +endfunction + +" Given a List of linter descriptions, exclude the linters to be ignored. +function! ale#engine#ignore#Exclude(filetype, all_linters, config) abort + let l:names_to_remove = ale#engine#ignore#GetList(a:filetype, a:config) + let l:filtered_linters = [] + + for l:linter in a:all_linters + let l:name_list = [l:linter.name] + l:linter.aliases + let l:should_include = 1 + + for l:name in l:name_list + if index(l:names_to_remove, l:name) >= 0 + let l:should_include = 0 + break + endif + endfor + + if l:should_include + call add(l:filtered_linters, l:linter) + endif + endfor + + return l:filtered_linters +endfunction diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim index c7d17ea5..44ef4b7a 100644 --- a/autoload/ale/events.vim +++ b/autoload/ale/events.vim @@ -1,14 +1,25 @@ " Author: w0rp <devw0rp@gmail.com> +" Description: ALE functions for autocmd events. + +" Get the number of milliseconds since some vague, but consistent, point in +" the past. +" +" This function can be used for timing execution, etc. +" +" The time will be returned as a Number. +function! ale#events#ClockMilliseconds() abort + return float2nr(reltimefloat(reltime()) * 1000) +endfunction function! ale#events#QuitEvent(buffer) abort " Remember when ALE is quitting for BufWrite, etc. - call setbufvar(a:buffer, 'ale_quitting', ale#util#ClockMilliseconds()) + call setbufvar(a:buffer, 'ale_quitting', ale#events#ClockMilliseconds()) endfunction function! ale#events#QuitRecently(buffer) abort let l:time = getbufvar(a:buffer, 'ale_quitting', 0) - return l:time && ale#util#ClockMilliseconds() - l:time < 1000 + return l:time && ale#events#ClockMilliseconds() - l:time < 1000 endfunction function! ale#events#SaveEvent(buffer) abort @@ -19,7 +30,7 @@ function! ale#events#SaveEvent(buffer) abort endif if ale#Var(a:buffer, 'fix_on_save') - let l:will_fix = ale#fix#Fix('save_file') + let l:will_fix = ale#fix#Fix(a:buffer, 'save_file') let l:should_lint = l:should_lint && !l:will_fix endif @@ -67,3 +78,56 @@ function! ale#events#FileChangedEvent(buffer) abort call s:LintOnEnter(a:buffer) endif endfunction + +function! ale#events#Init() abort + " This value used to be a Boolean as a Number, and is now a String. + let l:text_changed = '' . g:ale_lint_on_text_changed + + augroup ALEEvents + autocmd! + + " These events always need to be set up. + autocmd BufEnter,BufRead * call ale#pattern_options#SetOptions(str2nr(expand('<abuf>'))) + autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand('<abuf>'))) + + if g:ale_enabled + 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 is? 'normal' + autocmd TextChanged * call ale#Queue(g:ale_lint_delay) + elseif l:text_changed is? 'insert' + autocmd TextChangedI * call ale#Queue(g:ale_lint_delay) + endif + + " Handle everything that needs to happen when buffers are entered. + autocmd BufEnter * call ale#events#EnterEvent(str2nr(expand('<abuf>'))) + + if 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>'))) + endif + + if g:ale_lint_on_filetype_changed + " Only start linting if the FileType actually changes after + " opening a buffer. The FileType will fire when buffers are opened. + autocmd FileType * call ale#events#FileTypeEvent( + \ str2nr(expand('<abuf>')), + \ expand('<amatch>') + \) + endif + + if g:ale_lint_on_insert_leave + autocmd InsertLeave * call ale#Queue(0) + endif + + if g:ale_echo_cursor + autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() + " Look for a warning to echo as soon as we leave Insert mode. + " The script's position variable used when moving the cursor will + " not be changed here. + autocmd InsertLeave * call ale#cursor#EchoCursorWarning() + endif + endif + augroup END +endfunction diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index 62a4f9b1..1e056200 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -28,8 +28,6 @@ function! ale#fix#ApplyQueuedFixes() abort call remove(g:ale_fix_buffer_data, l:buffer) if l:data.changes_made - call setline(1, l:data.output) - let l:start_line = len(l:data.output) + 1 let l:end_line = len(l:data.lines_before) @@ -39,6 +37,8 @@ function! ale#fix#ApplyQueuedFixes() abort call winrestview(l:save) endif + call setline(1, l:data.output) + if l:data.should_save if empty(&buftype) noautocmd :w! @@ -54,6 +54,8 @@ function! ale#fix#ApplyQueuedFixes() abort let l:should_lint = l:data.changes_made endif + silent doautocmd <nomodeline> User ALEFixPost + " If ALE linting is enabled, check for problems with the file again after " fixing problems. if g:ale_enabled @@ -354,25 +356,41 @@ function! s:RunFixer(options) abort call ale#fix#ApplyFixes(l:buffer, l:input) endfunction -function! s:GetCallbacks() abort - if type(get(b:, 'ale_fixers')) is type([]) +function! s:AddSubCallbacks(full_list, callbacks) abort + if type(a:callbacks) == type('') + call add(a:full_list, a:callbacks) + elseif type(a:callbacks) == type([]) + call extend(a:full_list, a:callbacks) + else + return 0 + endif + + return 1 +endfunction + +function! s:GetCallbacks(buffer, fixers) abort + if len(a:fixers) + let l:callback_list = a:fixers + elseif type(get(b:, 'ale_fixers')) is type([]) " Lists can be used for buffer-local variables only let l:callback_list = b:ale_fixers else " buffer and global options can use dictionaries mapping filetypes to " callbacks to run. - let l:fixers = ale#Var(bufnr(''), 'fixers') + let l:fixers = ale#Var(a:buffer, 'fixers') let l:callback_list = [] + let l:matched = 0 for l:sub_type in split(&filetype, '\.') - let l:sub_type_callacks = get(l:fixers, l:sub_type, []) - - if type(l:sub_type_callacks) == type('') - call add(l:callback_list, l:sub_type_callacks) - else - call extend(l:callback_list, l:sub_type_callacks) + if s:AddSubCallbacks(l:callback_list, get(l:fixers, l:sub_type)) + let l:matched = 1 endif endfor + + " If we couldn't find fixers for a filetype, default to '*' fixers. + if !l:matched + call s:AddSubCallbacks(l:callback_list, get(l:fixers, '*')) + endif endif if empty(l:callback_list) @@ -392,7 +410,13 @@ function! s:GetCallbacks() abort endif endif - call add(l:corrected_list, ale#util#GetFunction(l:Item)) + try + call add(l:corrected_list, ale#util#GetFunction(l:Item)) + catch /E475/ + " Rethrow exceptions for failing to get a function so we can print + " a friendly message about it. + throw 'BADNAME ' . v:exception + endtry endfor return l:corrected_list @@ -414,20 +438,14 @@ endfunction " Accepts an optional argument for what to do when fixing. " " Returns 0 if no fixes can be applied, and 1 if fixing can be done. -function! ale#fix#Fix(...) abort - if len(a:0) > 1 - throw 'too many arguments!' - endif - - let l:fixing_flag = get(a:000, 0, '') - - if l:fixing_flag isnot# '' && l:fixing_flag isnot# 'save_file' +function! ale#fix#Fix(buffer, fixing_flag, ...) abort + if a:fixing_flag isnot# '' && a:fixing_flag isnot# 'save_file' throw "fixing_flag must be either '' or 'save_file'" endif try - let l:callback_list = s:GetCallbacks() - catch /E700/ + let l:callback_list = s:GetCallbacks(a:buffer, a:000) + catch /E700\|BADNAME/ let l:function_name = join(split(split(v:exception, ':')[3])) let l:echo_message = printf( \ 'There is no fixer named `%s`. Check :ALEFixSuggest', @@ -439,27 +457,27 @@ function! ale#fix#Fix(...) abort endtry if empty(l:callback_list) - if l:fixing_flag is# '' + if a:fixing_flag is# '' execute 'echom ''No fixers have been defined. Try :ALEFixSuggest''' endif return 0 endif - let l:buffer = bufnr('') - for l:job_id in keys(s:job_info_map) call remove(s:job_info_map, l:job_id) call ale#job#Stop(l:job_id) endfor " Clean up any files we might have left behind from a previous run. - call ale#fix#RemoveManagedFiles(l:buffer) - call ale#fix#InitBufferData(l:buffer, l:fixing_flag) + call ale#fix#RemoveManagedFiles(a:buffer) + call ale#fix#InitBufferData(a:buffer, a:fixing_flag) + + silent doautocmd <nomodeline> User ALEFixPre call s:RunFixer({ - \ 'buffer': l:buffer, - \ 'input': g:ale_fix_buffer_data[l:buffer].lines_before, + \ 'buffer': a:buffer, + \ 'input': g:ale_fix_buffer_data[a:buffer].lines_before, \ 'callback_index': 0, \ 'callback_list': l:callback_list, \}) diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim index 29e263a9..60d7d7a8 100644 --- a/autoload/ale/fix/registry.vim +++ b/autoload/ale/fix/registry.vim @@ -17,12 +17,28 @@ let s:default_registry = { \ 'suggested_filetypes': ['python'], \ 'description': 'Fix PEP8 issues with autopep8.', \ }, +\ 'black': { +\ 'function': 'ale#fixers#black#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix PEP8 issues with black.', +\ }, +\ 'tidy': { +\ 'function': 'ale#fixers#tidy#Fix', +\ 'suggested_filetypes': ['html'], +\ 'description': 'Fix HTML files with tidy.', +\ }, \ 'prettier_standard': { \ 'function': 'ale#fixers#prettier_standard#Fix', \ 'suggested_filetypes': ['javascript'], \ 'description': 'Apply prettier-standard to a file.', \ 'aliases': ['prettier-standard'], \ }, +\ 'elm-format': { +\ 'function': 'ale#fixers#elm_format#Fix', +\ 'suggested_filetypes': ['elm'], +\ 'description': 'Apply elm-format to a file.', +\ 'aliases': ['format'], +\ }, \ 'eslint': { \ 'function': 'ale#fixers#eslint#Fix', \ 'suggested_filetypes': ['javascript', 'typescript'], @@ -33,11 +49,6 @@ let s:default_registry = { \ 'suggested_filetypes': ['elixir'], \ 'description': 'Apply mix format to a file.', \ }, -\ 'format': { -\ 'function': 'ale#fixers#format#Fix', -\ 'suggested_filetypes': ['elm'], -\ 'description': 'Apply elm-format to a file.', -\ }, \ 'isort': { \ 'function': 'ale#fixers#isort#Fix', \ 'suggested_filetypes': ['python'], @@ -45,7 +56,7 @@ let s:default_registry = { \ }, \ 'prettier': { \ 'function': 'ale#fixers#prettier#Fix', -\ 'suggested_filetypes': ['javascript', 'typescript', 'json', 'css', 'scss', 'less', 'markdown', 'graphql', 'vue'], +\ 'suggested_filetypes': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue'], \ 'description': 'Apply prettier to a file.', \ }, \ 'prettier_eslint': { @@ -84,6 +95,16 @@ let s:default_registry = { \ 'suggested_filetypes': ['ruby'], \ 'description': 'Fix ruby files with rubocop --auto-correct.', \ }, +\ 'rufo': { +\ 'function': 'ale#fixers#rufo#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with rufo', +\ }, +\ 'scalafmt': { +\ 'function': 'ale#fixers#scalafmt#Fix', +\ 'suggested_filetypes': ['scala'], +\ 'description': 'Fix Scala files using scalafmt', +\ }, \ 'standard': { \ 'function': 'ale#fixers#standard#Fix', \ 'suggested_filetypes': ['javascript'], @@ -104,6 +125,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['php'], \ 'description': 'Fix PHP files with phpcbf.', \ }, +\ 'php_cs_fixer': { +\ 'function': 'ale#fixers#php_cs_fixer#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix PHP files with php-cs-fixer.', +\ }, \ 'clang-format': { \ 'function': 'ale#fixers#clangformat#Fix', \ 'suggested_filetypes': ['c', 'cpp'], @@ -169,6 +195,21 @@ let s:default_registry = { \ 'suggested_filetypes': ['json'], \ 'description': 'Fix JSON files with jq.', \ }, +\ 'perltidy': { +\ 'function': 'ale#fixers#perltidy#Fix', +\ 'suggested_filetypes': ['perl'], +\ 'description': 'Fix Perl files with perltidy.', +\ }, +\ 'xo': { +\ 'function': 'ale#fixers#xo#Fix', +\ 'suggested_filetypes': ['javascript'], +\ 'description': 'Fix JavaScript files using xo --fix.', +\ }, +\ 'qmlfmt': { +\ 'function': 'ale#fixers#qmlfmt#Fix', +\ 'suggested_filetypes': ['qml'], +\ 'description': 'Fix QML files with qmlfmt.', +\ }, \} " Reset the function registry to the default entries. @@ -261,6 +302,14 @@ function! s:ShouldSuggestForType(suggested_filetypes, type_list) abort return 0 endfunction +function! s:IsGenericFixer(suggested_filetypes) abort + if empty(a:suggested_filetypes) + return 1 + endif + + return 0 +endfunction + function! s:FormatEntry(key, entry) abort let l:aliases_str = '' @@ -280,8 +329,28 @@ function! s:FormatEntry(key, entry) abort \) endfunction -" Suggest functions to use from the registry. -function! ale#fix#registry#Suggest(filetype) abort +" Get list of applicable fixers for filetype, including generic fixers +function! ale#fix#registry#GetApplicableFixers(filetype) abort + let l:type_list = split(a:filetype, '\.') + let l:fixer_name_list = [] + + for l:key in sort(keys(s:entries)) + let l:suggested_filetypes = s:entries[l:key].suggested_filetypes + + if s:IsGenericFixer(l:suggested_filetypes) || s:ShouldSuggestForType(l:suggested_filetypes, l:type_list) + call add(l:fixer_name_list, l:key) + endif + endfor + + return l:fixer_name_list +endfunction + +" Function that returns autocomplete candidates for ALEFix command +function! ale#fix#registry#CompleteFixers(ArgLead, CmdLine, CursorPos) abort + return filter(ale#fix#registry#GetApplicableFixers(&filetype), 'v:val =~? a:ArgLead') +endfunction + +function! ale#fix#registry#SuggestedFixers(filetype) abort let l:type_list = split(a:filetype, '\.') let l:filetype_fixer_list = [] @@ -299,7 +368,7 @@ function! ale#fix#registry#Suggest(filetype) abort let l:generic_fixer_list = [] for l:key in sort(keys(s:entries)) - if empty(s:entries[l:key].suggested_filetypes) + if s:IsGenericFixer(s:entries[l:key].suggested_filetypes) call add( \ l:generic_fixer_list, \ s:FormatEntry(l:key, s:entries[l:key]), @@ -307,6 +376,15 @@ function! ale#fix#registry#Suggest(filetype) abort endif endfor + return [l:filetype_fixer_list, l:generic_fixer_list] +endfunction + +" Suggest functions to use from the registry. +function! ale#fix#registry#Suggest(filetype) abort + let l:suggested = ale#fix#registry#SuggestedFixers(a:filetype) + let l:filetype_fixer_list = l:suggested[0] + let l:generic_fixer_list = l:suggested[1] + let l:filetype_fixer_header = !empty(l:filetype_fixer_list) \ ? ['Try the following fixers appropriate for the filetype:', ''] \ : [] diff --git a/autoload/ale/fixers/autopep8.vim b/autoload/ale/fixers/autopep8.vim index e2dd7bfe..5798d827 100644 --- a/autoload/ale/fixers/autopep8.vim +++ b/autoload/ale/fixers/autopep8.vim @@ -2,7 +2,7 @@ " Description: Fixing files with autopep8. call ale#Set('python_autopep8_executable', 'autopep8') -call ale#Set('python_autopep8_use_global', 0) +call ale#Set('python_autopep8_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('python_autopep8_options', '') function! ale#fixers#autopep8#Fix(buffer) abort diff --git a/autoload/ale/fixers/black.vim b/autoload/ale/fixers/black.vim new file mode 100644 index 00000000..4169322a --- /dev/null +++ b/autoload/ale/fixers/black.vim @@ -0,0 +1,26 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: Fixing Python files with black. +" +call ale#Set('python_black_executable', 'black') +call ale#Set('python_black_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_black_options', '') + +function! ale#fixers#black#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'python_black', + \ ['black'], + \) + + if !executable(l:executable) + return 0 + endif + + let l:options = ale#Var(a:buffer, 'python_black_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/brittany.vim b/autoload/ale/fixers/brittany.vim index fed5eb8b..57c77325 100644 --- a/autoload/ale/fixers/brittany.vim +++ b/autoload/ale/fixers/brittany.vim @@ -1,4 +1,4 @@ -" Author: eborden <evan@evan-borden.com> +" Author: eborden <evan@evan-borden.com>, ifyouseewendy <ifyouseewendy@gmail.com>, aspidiets <emarshall85@gmail.com> " Description: Integration of brittany with ALE. call ale#Set('haskell_brittany_executable', 'brittany') @@ -8,6 +8,7 @@ function! ale#fixers#brittany#Fix(buffer) abort return { \ 'command': ale#Escape(l:executable) + \ . ' --write-mode inplace' \ . ' %t', \ 'read_temporary_file': 1, \} diff --git a/autoload/ale/fixers/clangformat.vim b/autoload/ale/fixers/clangformat.vim index b50b7047..eae1a7b4 100644 --- a/autoload/ale/fixers/clangformat.vim +++ b/autoload/ale/fixers/clangformat.vim @@ -3,7 +3,7 @@ scriptencoding utf-8 " 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_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('c_clangformat_options', '') function! ale#fixers#clangformat#GetExecutable(buffer) abort diff --git a/autoload/ale/fixers/format.vim b/autoload/ale/fixers/elm_format.vim index be130f00..cd2be2c3 100644 --- a/autoload/ale/fixers/format.vim +++ b/autoload/ale/fixers/elm_format.vim @@ -2,20 +2,20 @@ " Description: Integration of elm-format with ALE. call ale#Set('elm_format_executable', 'elm-format') -call ale#Set('elm_format_use_global', 0) +call ale#Set('elm_format_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('elm_format_options', '--yes') -function! ale#fixers#format#GetExecutable(buffer) abort +function! ale#fixers#elm_format#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'elm_format', [ \ 'node_modules/.bin/elm-format', \]) endfunction -function! ale#fixers#format#Fix(buffer) abort +function! ale#fixers#elm_format#Fix(buffer) abort let l:options = ale#Var(a:buffer, 'elm_format_options') return { - \ 'command': ale#Escape(ale#fixers#format#GetExecutable(a:buffer)) + \ 'command': ale#Escape(ale#fixers#elm_format#GetExecutable(a:buffer)) \ . ' %t' \ . (empty(l:options) ? '' : ' ' . l:options), \ 'read_temporary_file': 1, diff --git a/autoload/ale/fixers/fixjson.vim b/autoload/ale/fixers/fixjson.vim index 43eb0632..64c6ba81 100644 --- a/autoload/ale/fixers/fixjson.vim +++ b/autoload/ale/fixers/fixjson.vim @@ -3,7 +3,7 @@ call ale#Set('json_fixjson_executable', 'fixjson') call ale#Set('json_fixjson_options', '') -call ale#Set('json_fixjson_use_global', 0) +call ale#Set('json_fixjson_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale#fixers#fixjson#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'json_fixjson', [ diff --git a/autoload/ale/fixers/google_java_format.vim b/autoload/ale/fixers/google_java_format.vim index 92632e84..6a2f5491 100644 --- a/autoload/ale/fixers/google_java_format.vim +++ b/autoload/ale/fixers/google_java_format.vim @@ -2,7 +2,7 @@ " Description: Integration of Google-java-format with ALE. call ale#Set('google_java_format_executable', 'google-java-format') -call ale#Set('google_java_format_use_global', 0) +call ale#Set('google_java_format_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('google_java_format_options', '') function! ale#fixers#google_java_format#Fix(buffer) abort diff --git a/autoload/ale/fixers/isort.vim b/autoload/ale/fixers/isort.vim index b6318221..74ba3bc8 100644 --- a/autoload/ale/fixers/isort.vim +++ b/autoload/ale/fixers/isort.vim @@ -2,7 +2,7 @@ " Description: Fixing Python imports with isort. call ale#Set('python_isort_executable', 'isort') -call ale#Set('python_isort_use_global', 0) +call ale#Set('python_isort_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale#fixers#isort#Fix(buffer) abort let l:executable = ale#python#FindExecutable( diff --git a/autoload/ale/fixers/mix_format.vim b/autoload/ale/fixers/mix_format.vim index 04866408..7a091701 100644 --- a/autoload/ale/fixers/mix_format.vim +++ b/autoload/ale/fixers/mix_format.vim @@ -1,16 +1,25 @@ -" Author: carakan <carakan@gmail.com> +" Author: carakan <carakan@gmail.com>, Fernando Mendes <fernando@mendes.codes> " Description: Fixing files with elixir formatter 'mix format'. call ale#Set('elixir_mix_executable', 'mix') +call ale#Set('elixir_mix_format_options', '') function! ale#fixers#mix_format#GetExecutable(buffer) abort return ale#Var(a:buffer, 'elixir_mix_executable') endfunction +function! ale#fixers#mix_format#GetCommand(buffer) abort + let l:executable = ale#Escape(ale#fixers#mix_format#GetExecutable(a:buffer)) + let l:options = ale#Var(a:buffer, 'elixir_mix_format_options') + + return l:executable . ' format' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + function! ale#fixers#mix_format#Fix(buffer) abort return { - \ 'command': ale#Escape(ale#fixers#mix_format#GetExecutable(a:buffer)) - \ . ' format %t', + \ 'command': ale#fixers#mix_format#GetCommand(a:buffer), \ 'read_temporary_file': 1, \} endfunction diff --git a/autoload/ale/fixers/perltidy.vim b/autoload/ale/fixers/perltidy.vim new file mode 100644 index 00000000..a55a572b --- /dev/null +++ b/autoload/ale/fixers/perltidy.vim @@ -0,0 +1,18 @@ +" Author: kfly8 <kentafly88@gmail.com> +" Description: Integration of perltidy with ALE. + +call ale#Set('perl_perltidy_executable', 'perltidy') +call ale#Set('perl_perltidy_options', '') + +function! ale#fixers#perltidy#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'perl_perltidy_executable') + let l:options = ale#Var(a:buffer, 'perl_perltidy_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -b' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/php_cs_fixer.vim b/autoload/ale/fixers/php_cs_fixer.vim new file mode 100644 index 00000000..26b8e5de --- /dev/null +++ b/autoload/ale/fixers/php_cs_fixer.vim @@ -0,0 +1,23 @@ +" Author: Julien Deniau <julien.deniau@gmail.com> +" Description: Fixing files with php-cs-fixer. + +call ale#Set('php_cs_fixer_executable', 'php-cs-fixer') +call ale#Set('php_cs_fixer_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('php_cs_fixer_options', '') + +function! ale#fixers#php_cs_fixer#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'php_cs_fixer', [ + \ 'vendor/bin/php-cs-fixer', + \ 'php-cs-fixer' + \]) +endfunction + +function! ale#fixers#php_cs_fixer#Fix(buffer) abort + let l:executable = ale#fixers#php_cs_fixer#GetExecutable(a:buffer) + return { + \ 'command': ale#Escape(l:executable) + \ . ' ' . ale#Var(a:buffer, 'php_cs_fixer_options') + \ . ' fix %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/phpcbf.vim b/autoload/ale/fixers/phpcbf.vim index 649e17d3..487f369a 100644 --- a/autoload/ale/fixers/phpcbf.vim +++ b/autoload/ale/fixers/phpcbf.vim @@ -3,7 +3,7 @@ call ale#Set('php_phpcbf_standard', '') call ale#Set('php_phpcbf_executable', 'phpcbf') -call ale#Set('php_phpcbf_use_global', 0) +call ale#Set('php_phpcbf_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale#fixers#phpcbf#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'php_phpcbf', [ diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim index d75299eb..e8f4e92e 100644 --- a/autoload/ale/fixers/prettier.vim +++ b/autoload/ale/fixers/prettier.vim @@ -3,7 +3,7 @@ " 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_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('javascript_prettier_options', '') function! ale#fixers#prettier#GetExecutable(buffer) abort @@ -30,8 +30,26 @@ endfunction function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort let l:executable = ale#fixers#prettier#GetExecutable(a:buffer) let l:options = ale#Var(a:buffer, 'javascript_prettier_options') - let l:version = ale#semver#GetVersion(l:executable, a:version_output) + let l:parser = '' + + " Append the --parser flag depending on the current filetype (unless it's + " already set in g:javascript_prettier_options). + if empty(expand('#' . a:buffer . ':e')) && match(l:options, '--parser') == -1 + let l:prettier_parsers = ['typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue'] + let l:parser = 'babylon' + + for l:filetype in split(getbufvar(a:buffer, '&filetype'), '\.') + if index(l:prettier_parsers, l:filetype) > -1 + let l:parser = l:filetype + break + endif + endfor + endif + + if !empty(l:parser) + let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--parser ' . l:parser + endif " 1.4.0 is the first version with --stdin-filepath if ale#semver#GTE(l:version, [1, 4, 0]) diff --git a/autoload/ale/fixers/prettier_eslint.vim b/autoload/ale/fixers/prettier_eslint.vim index 5dd9102e..bc0caadd 100644 --- a/autoload/ale/fixers/prettier_eslint.vim +++ b/autoload/ale/fixers/prettier_eslint.vim @@ -4,7 +4,7 @@ function! ale#fixers#prettier_eslint#SetOptionDefaults() abort call ale#Set('javascript_prettier_eslint_executable', 'prettier-eslint') - call ale#Set('javascript_prettier_eslint_use_global', 0) + call ale#Set('javascript_prettier_eslint_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('javascript_prettier_eslint_options', '') endfunction diff --git a/autoload/ale/fixers/prettier_standard.vim b/autoload/ale/fixers/prettier_standard.vim index 7d938e19..b6e0a6f9 100644 --- a/autoload/ale/fixers/prettier_standard.vim +++ b/autoload/ale/fixers/prettier_standard.vim @@ -2,7 +2,7 @@ " Description: Integration of Prettier Standard with ALE. call ale#Set('javascript_prettier_standard_executable', 'prettier-standard') -call ale#Set('javascript_prettier_standard_use_global', 0) +call ale#Set('javascript_prettier_standard_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('javascript_prettier_standard_options', '') function! ale#fixers#prettier_standard#GetExecutable(buffer) abort diff --git a/autoload/ale/fixers/qmlfmt.vim b/autoload/ale/fixers/qmlfmt.vim new file mode 100644 index 00000000..d750d1c4 --- /dev/null +++ b/autoload/ale/fixers/qmlfmt.vim @@ -0,0 +1,11 @@ +call ale#Set('qml_qmlfmt_executable', 'qmlfmt') + +function! ale#fixers#qmlfmt#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'qml_qmlfmt_executable') +endfunction + +function! ale#fixers#qmlfmt#Fix(buffer) abort + return { + \ 'command': ale#Escape(ale#fixers#qmlfmt#GetExecutable(a:buffer)), + \} +endfunction diff --git a/autoload/ale/fixers/rufo.vim b/autoload/ale/fixers/rufo.vim new file mode 100644 index 00000000..01d537a9 --- /dev/null +++ b/autoload/ale/fixers/rufo.vim @@ -0,0 +1,20 @@ +" Author: Fohte (Hayato Kawai) https://github.com/fohte +" Description: Integration of Rufo with ALE. + +call ale#Set('ruby_rufo_executable', 'rufo') + +function! ale#fixers#rufo#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_rufo_executable') + let l:exec_args = l:executable =~? 'bundle$' + \ ? ' exec rufo' + \ : '' + + return ale#Escape(l:executable) . l:exec_args . ' %t' +endfunction + +function! ale#fixers#rufo#Fix(buffer) abort + return { + \ 'command': ale#fixers#rufo#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/scalafmt.vim b/autoload/ale/fixers/scalafmt.vim new file mode 100644 index 00000000..07d28275 --- /dev/null +++ b/autoload/ale/fixers/scalafmt.vim @@ -0,0 +1,26 @@ +" Author: Jeffrey Lau https://github.com/zoonfafer +" Description: Integration of Scalafmt with ALE. + +call ale#Set('scala_scalafmt_executable', 'scalafmt') +call ale#Set('scala_scalafmt_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('scala_scalafmt_options', '') + +function! ale#fixers#scalafmt#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'scala_scalafmt_executable') + let l:options = ale#Var(a:buffer, 'scala_scalafmt_options') + let l:exec_args = l:executable =~? 'ng$' + \ ? ' scalafmt' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t' + +endfunction + +function! ale#fixers#scalafmt#Fix(buffer) abort + return { + \ 'command': ale#fixers#scalafmt#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/standard.vim b/autoload/ale/fixers/standard.vim index c998cfd0..2b1f6f92 100644 --- a/autoload/ale/fixers/standard.vim +++ b/autoload/ale/fixers/standard.vim @@ -2,7 +2,7 @@ " Description: Fixing files with Standard. call ale#Set('javascript_standard_executable', 'standard') -call ale#Set('javascript_standard_use_global', 0) +call ale#Set('javascript_standard_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('javascript_standard_options', '') function! ale#fixers#standard#GetExecutable(buffer) abort diff --git a/autoload/ale/fixers/stylelint.vim b/autoload/ale/fixers/stylelint.vim index 899fcf4e..30309c7e 100644 --- a/autoload/ale/fixers/stylelint.vim +++ b/autoload/ale/fixers/stylelint.vim @@ -2,7 +2,7 @@ " Description: Fixing files with stylelint. call ale#Set('stylelint_executable', 'stylelint') -call ale#Set('stylelint_use_global', 0) +call ale#Set('stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale#fixers#stylelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'stylelint', [ diff --git a/autoload/ale/fixers/swiftformat.vim b/autoload/ale/fixers/swiftformat.vim index dcc204b1..304182b2 100644 --- a/autoload/ale/fixers/swiftformat.vim +++ b/autoload/ale/fixers/swiftformat.vim @@ -2,7 +2,7 @@ " 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_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('swift_swiftformat_options', '') function! ale#fixers#swiftformat#GetExecutable(buffer) abort diff --git a/autoload/ale/fixers/tidy.vim b/autoload/ale/fixers/tidy.vim new file mode 100644 index 00000000..1af4120b --- /dev/null +++ b/autoload/ale/fixers/tidy.vim @@ -0,0 +1,26 @@ +" Author: meain <abinsimon10@gmail.com> +" Description: Fixing HTML files with tidy. + +call ale#Set('html_tidy_executable', 'tidy') +call ale#Set('html_tidy_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#fixers#tidy#Fix(buffer) abort + let l:executable = ale#node#FindExecutable( + \ a:buffer, + \ 'html_tidy', + \ ['tidy'], + \) + + if !executable(l:executable) + return 0 + endif + + let l:config = ale#path#FindNearestFile(a:buffer, '.tidyrc') + let l:config_options = !empty(l:config) + \ ? ' -q --tidy-mark no --show-errors 0 --show-warnings 0 -config ' . ale#Escape(l:config) + \ : ' -q --tidy-mark no --show-errors 0 --show-warnings 0' + + return { + \ 'command': ale#Escape(l:executable) . l:config_options, + \} +endfunction diff --git a/autoload/ale/fixers/xo.vim b/autoload/ale/fixers/xo.vim new file mode 100644 index 00000000..882350be --- /dev/null +++ b/autoload/ale/fixers/xo.vim @@ -0,0 +1,23 @@ +" Author: Albert Marquez - https://github.com/a-marquez +" Description: Fixing files with XO. + +call ale#Set('javascript_xo_executable', 'xo') +call ale#Set('javascript_xo_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_xo_options', '') + +function! ale#fixers#xo#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_xo', [ + \ 'node_modules/xo/cli.js', + \ 'node_modules/.bin/xo', + \]) +endfunction + +function! ale#fixers#xo#Fix(buffer) abort + let l:executable = ale#fixers#xo#GetExecutable(a:buffer) + + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ' --fix %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/yapf.vim b/autoload/ale/fixers/yapf.vim index ba7453b8..f04bb1f9 100644 --- a/autoload/ale/fixers/yapf.vim +++ b/autoload/ale/fixers/yapf.vim @@ -2,7 +2,7 @@ " Description: Fixing Python files with yapf. call ale#Set('python_yapf_executable', 'yapf') -call ale#Set('python_yapf_use_global', 0) +call ale#Set('python_yapf_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale#fixers#yapf#Fix(buffer) abort let l:executable = ale#python#FindExecutable( diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim index ff590162..bc10ec21 100644 --- a/autoload/ale/handlers/eslint.vim +++ b/autoload/ale/handlers/eslint.vim @@ -5,7 +5,7 @@ let s:sep = has('win32') ? '\' : '/' 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_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('javascript_eslint_suppress_eslintignore', 0) call ale#Set('javascript_eslint_suppress_missing_config', 0) diff --git a/autoload/ale/handlers/flawfinder.vim b/autoload/ale/handlers/flawfinder.vim new file mode 100644 index 00000000..a650d6dd --- /dev/null +++ b/autoload/ale/handlers/flawfinder.vim @@ -0,0 +1,47 @@ +" Author: Christian Gibbons <cgibbons@gmu.edu> +" Description: This file defines a handler function that should work for the +" flawfinder format with the -CDQS flags. + +" Swiped this function from the GCC handler. Not sure if needed, but doesn't +" hurt to have it. +function! s:RemoveUnicodeQuotes(text) abort + let l:text = a:text + let l:text = substitute(l:text, '[`´‘’]', '''', 'g') + let l:text = substitute(l:text, '\v\\u2018([^\\]+)\\u2019', '''\1''', 'g') + let l:text = substitute(l:text, '[“”]', '"', 'g') + + return l:text +endfunction + +function! ale#handlers#flawfinder#HandleFlawfinderFormat(buffer, lines) abort + " Look for lines like the following. + " + " <stdin>:12:4: [2] (buffer) char:Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. + " <stdin>:31:4: [1] (buffer) strncpy:Easily used incorrectly; doesn't always \0-terminate or check for invalid pointers [MS-banned] (CWE-120). + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ( \[[0-5]\] [^:]+):(.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " Use severity level to determine if it should be considered a warning + " or error. + let l:severity = str2nr(matchstr(split(l:match[4])[0], '[0-5]')) + + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'type': (l:severity < ale#Var(a:buffer, 'c_flawfinder_error_severity')) + \ ? 'W' : 'E', + \ 'text': s:RemoveUnicodeQuotes(join(split(l:match[4])[1:]) . ': ' . l:match[5]), + \} + + " If the filename is something like <stdin>, <nofile> or -, then + " this is an error for the file we checked. + if l:match[1] isnot# '-' && l:match[1][0] isnot# '<' + let l:item['filename'] = l:match[1] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/gawk.vim b/autoload/ale/handlers/gawk.vim new file mode 100644 index 00000000..942bc2b2 --- /dev/null +++ b/autoload/ale/handlers/gawk.vim @@ -0,0 +1,25 @@ +" Author: Anthony DeDominic <adedomin@gmail.com> +" Description: Handle output from gawk's --lint option + +function! ale#handlers#gawk#HandleGawkFormat(buffer, lines) abort + " Look for lines like the following: + " gawk: /tmp/v0fddXz/1/something.awk:1: ^ invalid char ''' in expression + let l:pattern = '^.\{-}:\(\d\+\):\s\+\(warning:\|\^\)\s*\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:ecode = 'E' + if l:match[2] is? 'warning:' + let l:ecode = 'W' + endif + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': 0, + \ 'text': l:match[3], + \ 'code': 0, + \ 'type': l:ecode, + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/gcc.vim b/autoload/ale/handlers/gcc.vim index 9ec7b110..4b53652a 100644 --- a/autoload/ale/handlers/gcc.vim +++ b/autoload/ale/handlers/gcc.vim @@ -48,7 +48,7 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort let l:item = { \ 'lnum': str2nr(l:match[2]), - \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'type': (l:match[4] is# 'error' || l:match[4] is# 'fatal error') ? 'E' : 'W', \ 'text': s:RemoveUnicodeQuotes(l:match[5]), \} diff --git a/autoload/ale/handlers/go.vim b/autoload/ale/handlers/go.vim new file mode 100644 index 00000000..224df664 --- /dev/null +++ b/autoload/ale/handlers/go.vim @@ -0,0 +1,25 @@ +" Author: neersighted <bjorn@neersighted.com> +" Description: go vet for Go files +" +" Author: John Eikenberry <jae@zhar.net> +" Description: updated to work with go1.10 +" +" Author: Ben Paxton <ben@gn32.uk> +" Description: moved to generic Golang file from govet + +function! ale#handlers#go#Handler(buffer, lines) abort + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? ?(.+)$' + let l:output = [] + let l:dir = expand('#' . a:buffer . ':p:h') + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \}) + endfor + return l:output +endfunction diff --git a/autoload/ale/handlers/markdownlint.vim b/autoload/ale/handlers/markdownlint.vim new file mode 100644 index 00000000..12fc501c --- /dev/null +++ b/autoload/ale/handlers/markdownlint.vim @@ -0,0 +1,17 @@ +" Author: Ty-Lucas Kelley <tylucaskelley@gmail.com> +" Description: Adds support for markdownlint + +function! ale#handlers#markdownlint#Handle(buffer, lines) abort + let l:pattern=': \(\d*\): \(MD\d\{3}\)\(\/\)\([A-Za-z0-9-]\+\)\(.*\)$' + let l:output=[] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'text': '(' . l:match[2] . l:match[3] . l:match[4] . ')' . l:match[5], + \ 'type': 'W', + \ }) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/pony.vim b/autoload/ale/handlers/pony.vim new file mode 100644 index 00000000..0ac18e76 --- /dev/null +++ b/autoload/ale/handlers/pony.vim @@ -0,0 +1,34 @@ +scriptencoding utf-8 +" Description: This file defines a handler function which ought to work for +" any program which outputs errors in the format that ponyc uses. + +function! s:RemoveUnicodeQuotes(text) abort + let l:text = a:text + let l:text = substitute(l:text, '[`´‘’]', '''', 'g') + let l:text = substitute(l:text, '\v\\u2018([^\\]+)\\u2019', '''\1''', 'g') + let l:text = substitute(l:text, '[“”]', '"', 'g') + + return l:text +endfunction + +function! ale#handlers#pony#HandlePonycFormat(buffer, lines) abort + " Look for lines like the following. + " /home/code/pony/classes/Wombat.pony:22:30: can't lookup private fields from outside the type + + let l:pattern = '\v^([^:]+):(\d+):(\d+)?:? (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'filename': l:match[1], + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'type': 'E', + \ 'text': s:RemoveUnicodeQuotes(l:match[4]), + \} + + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/redpen.vim b/autoload/ale/handlers/redpen.vim index 2fb05684..c136789c 100644 --- a/autoload/ale/handlers/redpen.vim +++ b/autoload/ale/handlers/redpen.vim @@ -25,6 +25,30 @@ function! ale#handlers#redpen#HandleRedpenOutput(buffer, lines) abort let l:item.lnum = l:err.lineNum let l:item.col = l:err.sentenceStartColumnNum + 1 endif + + " Adjust column number for multibyte string + let l:line = getline(l:item.lnum) + if l:line is# '' + let l:line = l:err.sentence + endif + let l:line = split(l:line, '\zs') + + if l:item.col >= 2 + let l:col = 0 + for l:strlen in map(l:line[0:(l:item.col - 2)], 'strlen(v:val)') + let l:col = l:col + l:strlen + endfor + let l:item.col = l:col + 1 + endif + + if has_key(l:item, 'end_col') + let l:col = 0 + for l:strlen in map(l:line[0:(l:item.end_col - 1)], 'strlen(v:val)') + let l:col = l:col + l:strlen + endfor + let l:item.end_col = l:col + endif + call add(l:output, l:item) endfor return l:output diff --git a/autoload/ale/handlers/sasslint.vim b/autoload/ale/handlers/sasslint.vim new file mode 100644 index 00000000..399bf47c --- /dev/null +++ b/autoload/ale/handlers/sasslint.vim @@ -0,0 +1,8 @@ +" Author: KabbAmine - https://github.com/KabbAmine, +" Ben Falconer <ben@falconers.me.uk> + +function! ale#handlers#sasslint#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) + \ . ale#Escape('sass-lint') + \ . ' -v -q -f compact %t' +endfunction diff --git a/autoload/ale/handlers/scala.vim b/autoload/ale/handlers/scala.vim new file mode 100644 index 00000000..3fe7a12e --- /dev/null +++ b/autoload/ale/handlers/scala.vim @@ -0,0 +1,37 @@ +" Author: Nils Leuzinger - https://github.com/PawkyPenguin +" Description: Scala linting handlers for scalac-like compilers. + +function! ale#handlers#scala#HandleScalacLintFormat(buffer, lines) abort + " Matches patterns line the following: + " + " /var/folders/5q/20rgxx3x1s34g3m14n5bq0x80000gn/T/vv6pSsy/0:26: error: expected class or object definition + let l:pattern = '^.\+:\(\d\+\): \(\w\+\): \(.\+\)' + let l:output = [] + let l:ln = 0 + + for l:line in a:lines + let l:ln = l:ln + 1 + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + let l:text = l:match[3] + let l:type = l:match[2] is# 'error' ? 'E' : 'W' + let l:col = 0 + + if l:ln + 1 < len(a:lines) + let l:col = stridx(a:lines[l:ln + 1], '^') + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:col + 1, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/textlint.vim b/autoload/ale/handlers/textlint.vim new file mode 100644 index 00000000..6d495b0d --- /dev/null +++ b/autoload/ale/handlers/textlint.vim @@ -0,0 +1,39 @@ +" Author: tokida https://rouger.info, Yasuhiro Kiyota <yasuhiroki.duck@gmail.com> +" Description: textlint, a proofreading tool (https://textlint.github.io/) + +call ale#Set('textlint_executable', 'textlint') +call ale#Set('textlint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('textlint_options', '') + +function! ale#handlers#textlint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'textlint', [ + \ 'node_modules/.bin/textlint', + \ 'node_modules/textlint/bin/textlint.js', + \]) +endfunction + +function! ale#handlers#textlint#GetCommand(buffer) abort + let l:executable = ale#handlers#textlint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'textlint_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -f json --stdin --stdin-filename %s' +endfunction + +function! ale#handlers#textlint#HandleTextlintOutput(buffer, lines) abort + let l:res = get(ale#util#FuzzyJSONDecode(a:lines, []), 0, {'messages': []}) + let l:output = [] + + for l:err in l:res.messages + call add(l:output, { + \ 'text': l:err.message, + \ 'type': 'W', + \ 'code': l:err.ruleId, + \ 'lnum': l:err.line, + \ 'col' : l:err.column + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/writegood.vim b/autoload/ale/handlers/writegood.vim index f9d452ea..aee7c4de 100644 --- a/autoload/ale/handlers/writegood.vim +++ b/autoload/ale/handlers/writegood.vim @@ -4,7 +4,7 @@ function! ale#handlers#writegood#ResetOptions() abort call ale#Set('writegood_options', '') call ale#Set('writegood_executable', 'write-good') - call ale#Set('writegood_use_global', 0) + call ale#Set('writegood_use_global', get(g:, 'ale_use_global_executables', 0)) endfunction " Reset the options so the tests can test how they are set. diff --git a/autoload/ale/history.vim b/autoload/ale/history.vim index a6282ea5..27ae74c2 100644 --- a/autoload/ale/history.vim +++ b/autoload/ale/history.vim @@ -1,6 +1,9 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Tools for managing command history +" A flag for controlling the maximum size of the command history to store. +let g:ale_max_buffer_history_size = get(g:, 'ale_max_buffer_history_size', 20) + " 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', [])) diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim new file mode 100644 index 00000000..6d131adc --- /dev/null +++ b/autoload/ale/hover.vim @@ -0,0 +1,152 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: Hover support for LSP linters. + +let s:hover_map = {} + +" Used to get the hover map in tests. +function! ale#hover#GetMap() abort + return deepcopy(s:hover_map) +endfunction + +" Used to set the hover map in tests. +function! ale#hover#SetMap(map) abort + let s:hover_map = a:map +endfunction + +function! ale#hover#ClearLSPData() abort + let s:hover_map = {} +endfunction + +function! ale#hover#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') is# 'quickinfo' + \&& has_key(s:hover_map, a:response.request_seq) + let l:options = remove(s:hover_map, a:response.request_seq) + + if get(a:response, 'success', v:false) is v:true + \&& get(a:response, 'body', v:null) isnot v:null + if get(l:options, 'hover_from_balloonexpr', 0) + \&& exists('*balloon_show') + \&& ale#Var(l:options.buffer, 'set_balloons') + call balloon_show(a:response.body.displayString) + else + call ale#util#ShowMessage(a:response.body.displayString) + endif + endif + endif +endfunction + +function! ale#hover#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:hover_map, a:response.id) + let l:options = remove(s:hover_map, a:response.id) + + " If the call did __not__ come from balloonexpr... + if !get(l:options, 'hover_from_balloonexpr', 0) + let l:buffer = bufnr('') + let [l:line, l:column] = getcurpos()[1:2] + let l:end = len(getline(l:line)) + + if l:buffer isnot l:options.buffer + \|| l:line isnot l:options.line + \|| min([l:column, l:end]) isnot min([l:options.column, l:end]) + " ... Cancel display the message if the cursor has moved. + return + endif + endif + + " The result can be a Dictionary item, a List of the same, or null. + let l:result = get(a:response, 'result', v:null) + + if l:result is v:null + return + endif + + let l:result = l:result.contents + + if type(l:result) is type('') + " The result can be just a string. + let l:result = [l:result] + endif + + if type(l:result) is type({}) + " If the result is an object, then it's markup content. + let l:result = [l:result.value] + endif + + if type(l:result) is type([]) + " Replace objects with text values. + call map(l:result, 'type(v:val) is type('''') ? v:val : v:val.value') + let l:str = join(l:result, "\n") + let l:str = substitute(l:str, '^\s*\(.\{-}\)\s*$', '\1', '') + + if !empty(l:str) + if get(l:options, 'hover_from_balloonexpr', 0) + \&& exists('*balloon_show') + \&& ale#Var(l:options.buffer, 'set_balloons') + call balloon_show(l:str) + else + call ale#util#ShowMessage(l:str) + endif + endif + endif + endif +endfunction + +function! s:ShowDetails(linter, buffer, line, column, opt) abort + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#hover#HandleTSServerResponse') + \ : function('ale#hover#HandleLSPResponse') + + let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter, l:Callback) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + let l:language_id = l:lsp_details.language_id + + if a:linter.lsp is# 'tsserver' + let l:column = a:column + + let l:message = ale#lsp#tsserver_message#Quickinfo( + \ a:buffer, + \ a:line, + \ l:column + \) + else + " Send a message saying the buffer has changed first, or the + " hover position probably won't make sense. + call ale#lsp#NotifyForChanges(l:lsp_details) + + let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])]) + + let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + + let s:hover_map[l:request_id] = { + \ 'buffer': a:buffer, + \ 'line': a:line, + \ 'column': l:column, + \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0), + \} +endfunction + +" Obtain Hover information for the specified position +" Pass optional arguments in the dictionary opt. +" Currently, only one key/value is useful: +" - called_from_balloonexpr, this flag marks if we want the result from this +" ale#hover#Show to display in a balloon if possible +" +" Currently, the callbacks displays the info from hover : +" - in the balloon if opt.called_from_balloonexpr and balloon_show is detected +" - as status message otherwise +function! ale#hover#Show(buffer, line, col, opt) abort + for l:linter in ale#linter#Get(getbufvar(a:buffer, '&filetype')) + if !empty(l:linter.lsp) + call s:ShowDetails(l:linter, a:buffer, a:line, a:col, a:opt) + endif + endfor +endfunction diff --git a/autoload/ale/job.vim b/autoload/ale/job.vim index 2909dab4..6ffc2a06 100644 --- a/autoload/ale/job.vim +++ b/autoload/ale/job.vim @@ -8,6 +8,9 @@ " ale#job#IsRunning(job_id) -> 1 if running, 0 otherwise. " ale#job#Stop(job_id) +" A setting for wrapping commands. +let g:ale_command_wrapper = get(g:, 'ale_command_wrapper', '') + if !has_key(s:, 'job_map') let s:job_map = {} endif @@ -119,7 +122,7 @@ function! s:VimCloseCallback(channel) abort 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) + call ale#util#GetFunction(l:info.exit_cb)(l:job_id, get(l:info, 'exit_code', 1)) endif finally " Automatically forget about the job after it's done. @@ -208,7 +211,7 @@ function! ale#job#PrepareCommand(buffer, command) abort return 'cmd /s/c "' . l:command . '"' endif - if &shell =~? 'fish$' + if &shell =~? 'fish$\|pwsh$' return ['/bin/sh', '-c', l:command] endif diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index d059a12d..299f5dad 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -3,15 +3,20 @@ call ale#Set('wrap_command_as_one_argument', 0) " Description: Linter registration and lazy-loading " Retrieves linters as requested by the engine, loading them if needed. +let s:runtime_loaded_map = {} let s:linters = {} " Default filetype aliases. " The user defined aliases will be merged with this Dictionary. +" +" NOTE: Update the g:ale_linter_aliases documentation when modifying this. let s:default_ale_linter_aliases = { \ 'Dockerfile': 'dockerfile', \ 'csh': 'sh', \ 'plaintex': 'tex', \ 'systemverilog': 'verilog', +\ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'], +\ 'vimwiki': 'markdown', \ 'zsh': 'sh', \} @@ -38,6 +43,7 @@ let s:default_ale_linters = { " Testing/debugging helper to unload all linters. function! ale#linter#Reset() abort + let s:runtime_loaded_map = {} let s:linters = {} endfunction @@ -49,6 +55,10 @@ function! s:IsBoolean(value) abort return type(a:value) == type(0) && (a:value == 0 || a:value == 1) endfunction +function! s:LanguageGetter(buffer) dict abort + return l:self.language +endfunction + function! ale#linter#PreProcess(linter) abort if type(a:linter) != type({}) throw 'The linter object must be a Dictionary' @@ -183,10 +193,26 @@ function! ale#linter#PreProcess(linter) abort endif if l:needs_lsp_details - let l:obj.language_callback = get(a:linter, 'language_callback') + if has_key(a:linter, 'language') + if has_key(a:linter, 'language_callback') + throw 'Only one of `language` or `language_callback` ' + \ . 'should be set' + endif + + let l:obj.language = get(a:linter, 'language') - if !s:IsCallback(l:obj.language_callback) - throw '`language_callback` must be a callback for LSP linters' + if type(l:obj.language) != type('') + throw '`language` must be a string' + endif + + " Make 'language_callback' return the 'language' value. + let l:obj.language_callback = function('s:LanguageGetter') + else + let l:obj.language_callback = get(a:linter, 'language_callback') + + if !s:IsCallback(l:obj.language_callback) + throw '`language_callback` must be a callback for LSP linters' + endif endif let l:obj.project_root_callback = get(a:linter, 'project_root_callback') @@ -194,6 +220,29 @@ function! ale#linter#PreProcess(linter) abort if !s:IsCallback(l:obj.project_root_callback) throw '`project_root_callback` must be a callback for LSP linters' endif + + if has_key(a:linter, 'completion_filter') + let l:obj.completion_filter = a:linter.completion_filter + + if !s:IsCallback(l:obj.completion_filter) + throw '`completion_filter` must be a callback' + endif + endif + + if has_key(a:linter, 'initialization_options_callback') + if has_key(a:linter, 'initialization_options') + throw 'Only one of `initialization_options` or ' + \ . '`initialization_options_callback` should be set' + endif + + let l:obj.initialization_options_callback = a:linter.initialization_options_callback + + if !s:IsCallback(l:obj.initialization_options_callback) + throw '`initialization_options_callback` must be a callback if defined' + endif + elseif has_key(a:linter, 'initialization_options') + let l:obj.initialization_options = a:linter.initialization_options + endif endif let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout') @@ -242,20 +291,20 @@ function! ale#linter#Define(filetype, linter) abort call add(s:linters[a:filetype], l:new_linter) endfunction +" Prevent any linters from being loaded for a given filetype. +function! ale#linter#PreventLoading(filetype) abort + let s:runtime_loaded_map[a:filetype] = 1 +endfunction + function! ale#linter#GetAll(filetypes) abort let l:combined_linters = [] for l:filetype in a:filetypes - " Load linter defintions from files if we haven't loaded them yet. - if !has_key(s:linters, l:filetype) + " Load linters from runtimepath if we haven't done that yet. + if !has_key(s:runtime_loaded_map, l:filetype) execute 'silent! runtime! ale_linters/' . l:filetype . '/*.vim' - " Always set an empty List for the loaded linters if we don't find - " any. This will prevent us from executing the runtime command - " many times, redundantly. - if !has_key(s:linters, l:filetype) - let s:linters[l:filetype] = [] - endif + let s:runtime_loaded_map[l:filetype] = 1 endif call extend(l:combined_linters, get(s:linters, l:filetype, [])) @@ -403,72 +452,3 @@ function! ale#linter#GetAddress(buffer, linter) abort \ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer) \ : a:linter.address endfunction - -" Given a buffer, an LSP linter, and a callback to register for handling -" messages, start up an LSP linter and get ready to receive errors or -" completions. -function! ale#linter#StartLSP(buffer, linter, callback) abort - let l:command = '' - let l:address = '' - let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) - - 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 is# 'socket' - let l:address = ale#linter#GetAddress(a:buffer, a:linter) - let l:conn_id = ale#lsp#ConnectToAddress( - \ l:address, - \ l:root, - \ a:callback, - \) - else - let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) - - if !executable(l:executable) - return {} - endif - - let l:command = ale#job#PrepareCommand( - \ a:buffer, - \ ale#linter#GetCommand(a:buffer, a:linter), - \) - let l:conn_id = ale#lsp#StartProgram( - \ l:executable, - \ l:command, - \ l:root, - \ a:callback, - \) - endif - - let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) - - if !l:conn_id - if g:ale_history_enabled && !empty(l:command) - call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command) - endif - - return {} - endif - - if ale#lsp#OpenDocumentIfNeeded(l:conn_id, a:buffer, l:root, l:language_id) - if g:ale_history_enabled && !empty(l:command) - call ale#history#Add(a:buffer, 'started', l:conn_id, l:command) - endif - endif - - " The change message needs to be sent for tsserver before doing anything. - if a:linter.lsp is# 'tsserver' - call ale#lsp#Send(l:conn_id, ale#lsp#tsserver_message#Change(a:buffer)) - endif - - return { - \ 'connection_id': l:conn_id, - \ 'command': l:command, - \ 'project_root': l:root, - \ 'language_id': l:language_id, - \} -endfunction diff --git a/autoload/ale/list.vim b/autoload/ale/list.vim index b1a8d4a7..35304a09 100644 --- a/autoload/ale/list.vim +++ b/autoload/ale/list.vim @@ -1,6 +1,19 @@ " Author: Bjorn Neergaard <bjorn@neersighted.com>, modified by Yann fery <yann@fery.me> " Description: Manages the loclist and quickfix lists +" This flag dictates if ale open the configured loclist +let g:ale_open_list = get(g:, 'ale_open_list', 0) +" This flag dictates if ale keeps open loclist even if there is no error in loclist +let g:ale_keep_list_window_open = get(g:, 'ale_keep_list_window_open', 0) +" This flag dictates that quickfix windows should be opened vertically +let g:ale_list_vertical = get(g:, 'ale_list_vertical', 0) +" The window size to set for the quickfix and loclist windows +let g:ale_list_window_size = get(g:, 'ale_list_window_size', 10) +" A string format for the loclist messages. +let g:ale_loclist_msg_format = get(g:, 'ale_loclist_msg_format', +\ get(g:, 'ale_echo_msg_format', '%code: %%s') +\) + if !exists('s:timer_args') let s:timer_args = {} endif @@ -97,12 +110,17 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort 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>" + " open windows vertically instead of default horizontally + let l:open_type = '' + if ale#Var(a:buffer, 'list_vertical') == 1 + let l:open_type = 'vert ' + endif if g:ale_set_quickfix if !ale#list#IsQuickfixOpen() - silent! execute 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) + silent! execute l:open_type . 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) endif elseif g:ale_set_loclist - silent! execute 'lopen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) + silent! execute l:open_type . 'lopen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) endif " If focus changed, restore it (jump to the last window). diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 126d6c18..29759f66 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -6,18 +6,22 @@ let s:connections = [] let g:ale_lsp_next_message_id = 1 -function! s:NewConnection() abort +" Exposed only so tests can get at it. +" Do not call this function basically anywhere. +function! ale#lsp#NewConnection(initialization_options) abort " id: The job ID as a Number, or the server address as a string. " data: The message data received so far. " executable: An executable only set for program connections. - " open_documents: A list of buffers we told the server we opened. + " open_documents: A Dictionary mapping buffers to b:changedtick, keeping + " track of when documents were opened, and when we last changed them. " callback_list: A list of callbacks for handling LSP responses. let l:conn = { \ 'id': '', \ 'data': '', \ 'projects': {}, - \ 'open_documents': [], + \ 'open_documents': {}, \ 'callback_list': [], + \ 'initialization_options': a:initialization_options, \} call add(s:connections, l:conn) @@ -25,6 +29,11 @@ function! s:NewConnection() abort return l:conn endfunction +" Remove an LSP connection with a given ID. This is only for tests. +function! ale#lsp#RemoveConnectionWithID(id) abort + call filter(s:connections, 'v:val.id isnot a:id') +endfunction + function! s:FindConnection(key, value) abort for l:conn in s:connections if has_key(l:conn, a:key) && get(l:conn, a:key) == a:value @@ -207,6 +216,11 @@ function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort endfunction function! ale#lsp#HandleMessage(conn, message) abort + if type(a:message) != type('') + " Ignore messages that aren't strings. + return + endif + let a:conn.data .= a:message " Parse the objects now if we can, and keep the remaining text. @@ -266,7 +280,7 @@ endfunction " " The job ID will be returned for for the program if it ran, otherwise " 0 will be returned. -function! ale#lsp#StartProgram(executable, command, project_root, callback) abort +function! ale#lsp#StartProgram(executable, command, project_root, callback, initialization_options) abort if !executable(a:executable) return 0 endif @@ -274,7 +288,7 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback) abor let l:conn = s:FindConnection('executable', a:executable) " Get the current connection or a new one. - let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() + let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options) let l:conn.executable = a:executable if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id) @@ -300,10 +314,10 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback) abor endfunction " Connect to an address and set up a callback for handling responses. -function! ale#lsp#ConnectToAddress(address, project_root, callback) abort +function! ale#lsp#ConnectToAddress(address, project_root, callback, initialization_options) abort let l:conn = s:FindConnection('id', a:address) " Get the current connection or a new one. - let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() + let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options) if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) isnot# 'open' let l:conn.channnel = ch_open(a:address, { @@ -325,6 +339,20 @@ function! ale#lsp#ConnectToAddress(address, project_root, callback) abort return 1 endfunction +" Stop all LSP connections, closing all jobs and channels, and removing any +" queued messages. +function! ale#lsp#StopAll() abort + for l:conn in s:connections + if has_key(l:conn, 'channel') + call ch_close(l:conn.channel) + else + call ale#job#Stop(l:conn.id) + endif + endfor + + let s:connections = [] +endfunction + function! s:SendMessageData(conn, data) abort if has_key(a:conn, 'executable') call ale#job#SendRaw(a:conn.id, a:data) @@ -364,7 +392,7 @@ function! ale#lsp#Send(conn_id, message, ...) abort " Only send the init message once. if !l:project.init_request_id let [l:init_id, l:init_data] = ale#lsp#CreateMessageData( - \ ale#lsp#message#Initialize(l:project_root), + \ ale#lsp#message#Initialize(l:project_root, l:conn.initialization_options), \) let l:project.init_request_id = l:init_id @@ -386,21 +414,72 @@ function! ale#lsp#Send(conn_id, message, ...) abort return l:id == 0 ? -1 : l:id endfunction -function! ale#lsp#OpenDocumentIfNeeded(conn_id, buffer, project_root, language_id) abort - let l:conn = s:FindConnection('id', a:conn_id) +" The Document details Dictionary should contain the following keys. +" +" buffer - The buffer number for the document. +" connection_id - The connection ID for the LSP server. +" command - The command to run to start the LSP connection. +" project_root - The project root for the LSP project. +" language_id - The language ID for the project, like 'python', 'rust', etc. + +" Create a new Dictionary containing more connection details, with the +" following information added: +" +" conn - An existing LSP connection for the document. +" document_open - 1 if the document is currently open, 0 otherwise. +function! s:ExtendDocumentDetails(details) abort + let l:extended = copy(a:details) + let l:conn = s:FindConnection('id', a:details.connection_id) + + let l:extended.conn = l:conn + let l:extended.document_open = !empty(l:conn) + \ && has_key(l:conn.open_documents, a:details.buffer) + + return l:extended +endfunction + +" Notify LSP servers or tsserver if a document is opened, if needed. +" If a document is opened, 1 will be returned, otherwise 0 will be returned. +function! ale#lsp#OpenDocument(basic_details) abort + let l:d = s:ExtendDocumentDetails(a:basic_details) let l:opened = 0 - if !empty(l:conn) && index(l:conn.open_documents, a:buffer) < 0 - if empty(a:language_id) - let l:message = ale#lsp#tsserver_message#Open(a:buffer) + if !empty(l:d.conn) && !l:d.document_open + if empty(l:d.language_id) + let l:message = ale#lsp#tsserver_message#Open(l:d.buffer) else - let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id) + let l:message = ale#lsp#message#DidOpen(l:d.buffer, l:d.language_id) endif - call ale#lsp#Send(a:conn_id, l:message, a:project_root) - call add(l:conn.open_documents, a:buffer) + call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root) + let l:d.conn.open_documents[l:d.buffer] = getbufvar(l:d.buffer, 'changedtick') let l:opened = 1 endif return l:opened endfunction + +" Notify LSP servers or tsserver that a document has changed, if needed. +" If a notification is sent, 1 will be returned, otherwise 0 will be returned. +function! ale#lsp#NotifyForChanges(basic_details) abort + let l:d = s:ExtendDocumentDetails(a:basic_details) + let l:notified = 0 + + if l:d.document_open + let l:new_tick = getbufvar(l:d.buffer, 'changedtick') + + if l:d.conn.open_documents[l:d.buffer] < l:new_tick + if empty(l:d.language_id) + let l:message = ale#lsp#tsserver_message#Change(l:d.buffer) + else + let l:message = ale#lsp#message#DidChange(l:d.buffer) + endif + + call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root) + let l:d.conn.open_documents[l:d.buffer] = l:new_tick + let l:notified = 1 + endif + endif + + return l:notified +endfunction diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim index 0b73cfc2..9e05156d 100644 --- a/autoload/ale/lsp/message.vim +++ b/autoload/ale/lsp/message.vim @@ -24,12 +24,15 @@ function! ale#lsp#message#GetNextVersionID() abort return l:id endfunction -function! ale#lsp#message#Initialize(root_path) abort +function! ale#lsp#message#Initialize(root_path, initialization_options) abort " TODO: Define needed capabilities. + " NOTE: rootPath is deprecated in favour of rootUri return [0, 'initialize', { \ 'processId': getpid(), \ 'rootPath': a:root_path, \ 'capabilities': {}, + \ 'initializationOptions': a:initialization_options, + \ 'rootUri': ale#path#ToURI(a:root_path), \}] endfunction @@ -116,3 +119,22 @@ function! ale#lsp#message#Definition(buffer, line, column) abort \ 'position': {'line': a:line - 1, 'character': a:column}, \}] endfunction + +function! ale#lsp#message#References(buffer, line, column) abort + return [0, 'textDocument/references', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column}, + \ 'context': {'includeDeclaration': v:false}, + \}] +endfunction + +function! ale#lsp#message#Hover(buffer, line, column) abort + return [0, 'textDocument/hover', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column}, + \}] +endfunction diff --git a/autoload/ale/lsp/reset.vim b/autoload/ale/lsp/reset.vim new file mode 100644 index 00000000..c7c97a47 --- /dev/null +++ b/autoload/ale/lsp/reset.vim @@ -0,0 +1,25 @@ +" Stop all LSPs and remove all of the data for them. +function! ale#lsp#reset#StopAllLSPs() abort + call ale#lsp#StopAll() + + if exists('*ale#definition#ClearLSPData') + " Clear the mapping for connections, etc. + call ale#definition#ClearLSPData() + endif + + if exists('*ale#lsp_linter#ClearLSPData') + " Clear the mapping for connections, etc. + call ale#lsp_linter#ClearLSPData() + + " Remove the problems for all of the LSP linters in every buffer. + for l:buffer_string in keys(g:ale_buffer_info) + let l:buffer = str2nr(l:buffer_string) + + for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype')) + if !empty(l:linter.lsp) + call ale#engine#HandleLoclist(l:linter.name, l:buffer, []) + endif + endfor + endfor + endif +endfunction diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim index 13219ef6..94794e98 100644 --- a/autoload/ale/lsp/response.vim +++ b/autoload/ale/lsp/response.vim @@ -1,6 +1,20 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Parsing and transforming of LSP server responses. +" Constants for error codes. +" Defined by JSON RPC +let s:PARSE_ERROR = -32700 +let s:INVALID_REQUEST = -32600 +let s:METHOD_NOT_FOUND = -32601 +let s:INVALID_PARAMS = -32602 +let s:INTERNAL_ERROR = -32603 +let s:SERVER_ERROR_START = -32099 +let s:SERVER_ERROR_END = -32000 +let s:SERVER_NOT_INITIALIZED = -32002 +let s:UNKNOWN_ERROR_CODE = -32001 +" Defined by the protocol. +let s:REQUEST_CANCELLED = -32800 + " Constants for message severity codes. let s:SEVERITY_ERROR = 1 let s:SEVERITY_WARNING = 2 @@ -59,8 +73,44 @@ function! ale#lsp#response#ReadTSServerDiagnostics(response) abort let l:loclist_item.nr = l:diagnostic.code endif + if get(l:diagnostic, 'category') is# 'warning' + let l:loclist_item.type = 'W' + endif + + if get(l:diagnostic, 'category') is# 'suggestion' + let l:loclist_item.type = 'I' + endif + call add(l:loclist, l:loclist_item) endfor return l:loclist endfunction + +function! ale#lsp#response#GetErrorMessage(response) abort + if type(get(a:response, 'error', 0)) isnot type({}) + return '' + endif + + let l:code = get(a:response.error, 'code') + + " Only report things for these error codes. + if l:code isnot s:INVALID_PARAMS && l:code isnot s:INTERNAL_ERROR + return '' + endif + + let l:message = get(a:response.error, 'message', '') + + if empty(l:message) + return '' + endif + + " Include the traceback as details, if it's there. + let l:traceback = get(get(a:response.error, 'data', {}), 'traceback', []) + + if type(l:traceback) is type([]) && !empty(l:traceback) + let l:message .= "\n" . join(l:traceback, "\n") + endif + + return l:message +endfunction diff --git a/autoload/ale/lsp/tsserver_message.vim b/autoload/ale/lsp/tsserver_message.vim index b9bd7a01..d6919516 100644 --- a/autoload/ale/lsp/tsserver_message.vim +++ b/autoload/ale/lsp/tsserver_message.vim @@ -61,3 +61,19 @@ function! ale#lsp#tsserver_message#Definition(buffer, line, column) abort \ 'file': expand('#' . a:buffer . ':p'), \}] endfunction + +function! ale#lsp#tsserver_message#References(buffer, line, column) abort + return [0, 'ts@references', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction + +function! ale#lsp#tsserver_message#Quickinfo(buffer, line, column) abort + return [0, 'ts@quickinfo', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim new file mode 100644 index 00000000..781d04c4 --- /dev/null +++ b/autoload/ale/lsp_linter.vim @@ -0,0 +1,252 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: Integration between linters and LSP/tsserver. + +" This code isn't loaded if a user never users LSP features or linters. + +" Associates LSP connection IDs with linter names. +if !has_key(s:, 'lsp_linter_map') + let s:lsp_linter_map = {} +endif + +" Check if diagnostics for a particular linter should be ignored. +function! s:ShouldIgnore(buffer, linter_name) abort + let l:config = ale#Var(a:buffer, 'linters_ignore') + + " Don't load code for ignoring diagnostics if there's nothing to ignore. + if empty(l:config) + return 0 + endif + + let l:filetype = getbufvar(a:buffer, '&filetype') + let l:ignore_list = ale#engine#ignore#GetList(l:filetype, l:config) + + return index(l:ignore_list, a:linter_name) >= 0 +endfunction + +function! s:HandleLSPDiagnostics(conn_id, response) abort + let l:linter_name = s:lsp_linter_map[a:conn_id] + let l:filename = ale#path#FromURI(a:response.params.uri) + let l:buffer = bufnr(l:filename) + + if s:ShouldIgnore(l:buffer, l:linter_name) + return + endif + + if l:buffer <= 0 + return + endif + + let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) + + call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist) +endfunction + +function! s:HandleTSServerDiagnostics(response, error_type) abort + let l:linter_name = 'tsserver' + let l:buffer = bufnr(a:response.body.file) + let l:info = get(g:ale_buffer_info, l:buffer, {}) + + if empty(l:info) + return + endif + + if s:ShouldIgnore(l:buffer, l:linter_name) + return + endif + + let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) + + " 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 is# 'syntax' + let l:info.syntax_loclist = l:thislist + else + let l:info.semantic_loclist = l:thislist + endif + + let l:loclist = get(l:info, 'semantic_loclist', []) + \ + get(l:info, 'syntax_loclist', []) + + call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist) +endfunction + +function! s:HandleLSPErrorMessage(linter_name, response) abort + if !g:ale_history_enabled || !g:ale_history_log_output + return + endif + + if empty(a:linter_name) + return + endif + + let l:message = ale#lsp#response#GetErrorMessage(a:response) + + if empty(l:message) + return + endif + + " This global variable is set here so we don't load the debugging.vim file + " until someone uses :ALEInfo. + let g:ale_lsp_error_messages = get(g:, 'ale_lsp_error_messages', {}) + + if !has_key(g:ale_lsp_error_messages, a:linter_name) + let g:ale_lsp_error_messages[a:linter_name] = [] + endif + + call add(g:ale_lsp_error_messages[a:linter_name], l:message) +endfunction + +function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort + let l:method = get(a:response, 'method', '') + let l:linter_name = get(s:lsp_linter_map, a:conn_id, '') + + if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error') + call s:HandleLSPErrorMessage(l:linter_name, a:response) + elseif l:method is# 'textDocument/publishDiagnostics' + call s:HandleLSPDiagnostics(a:conn_id, a:response) + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'semanticDiag' + call s:HandleTSServerDiagnostics(a:response, 'semantic') + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'syntaxDiag' + call s:HandleTSServerDiagnostics(a:response, 'syntax') + endif +endfunction + +" Given a buffer, an LSP linter, and a callback to register for handling +" messages, start up an LSP linter and get ready to receive errors or +" completions. +function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort + let l:command = '' + let l:address = '' + let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) + + 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 + + let l:initialization_options = {} + + if has_key(a:linter, 'initialization_options_callback') + let l:initialization_options = ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer) + elseif has_key(a:linter, 'initialization_options') + let l:initialization_options = a:linter.initialization_options + endif + + 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, + \ l:root, + \ a:callback, + \ l:initialization_options, + \) + else + let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) + + if !executable(l:executable) + return {} + endif + + let l:command = ale#job#PrepareCommand( + \ a:buffer, + \ ale#linter#GetCommand(a:buffer, a:linter), + \) + let l:conn_id = ale#lsp#StartProgram( + \ l:executable, + \ l:command, + \ l:root, + \ a:callback, + \ l:initialization_options, + \) + endif + + let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) + + if !l:conn_id + if g:ale_history_enabled && !empty(l:command) + call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command) + endif + + return {} + endif + + let l:details = { + \ 'buffer': a:buffer, + \ 'connection_id': l:conn_id, + \ 'command': l:command, + \ 'project_root': l:root, + \ 'language_id': l:language_id, + \} + + if ale#lsp#OpenDocument(l:details) + if g:ale_history_enabled && !empty(l:command) + call ale#history#Add(a:buffer, 'started', l:conn_id, l:command) + endif + endif + + " The change message needs to be sent for tsserver before doing anything. + if a:linter.lsp is# 'tsserver' + call ale#lsp#NotifyForChanges(l:details) + endif + + return l:details +endfunction + +function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort + let l:info = g:ale_buffer_info[a:buffer] + let l:lsp_details = ale#lsp_linter#StartLSP( + \ a:buffer, + \ a:linter, + \ function('ale#lsp_linter#HandleLSPResponse'), + \) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root + + " Remember the linter this connection is for. + let s:lsp_linter_map[l:id] = a:linter.name + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Geterr(a:buffer) + let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + + let l:notified = l:request_id != 0 + else + let l:notified = ale#lsp#NotifyForChanges(l:lsp_details) + endif + + " If this was a file save event, also notify the server of that. + if a:linter.lsp isnot# 'tsserver' + \&& getbufvar(a:buffer, 'ale_save_event_fired', 0) + let l:save_message = ale#lsp#message#DidSave(a:buffer) + let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root) + + let l:notified = l:request_id != 0 + endif + + if l:notified + 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:notified +endfunction + +" Clear LSP linter data for the linting engine. +function! ale#lsp_linter#ClearLSPData() abort + let s:lsp_linter_map = {} +endfunction + +" Just for tests. +function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort + let s:lsp_linter_map = a:replacement_map +endfunction diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim index 16dabf21..91832b35 100644 --- a/autoload/ale/path.vim +++ b/autoload/ale/path.vim @@ -84,12 +84,12 @@ function! ale#path#IsAbsolute(filename) abort return a:filename[:0] is# '/' || a:filename[1:2] is# ':\' endfunction -let s:temp_dir = fnamemodify(tempname(), ':h') +let s:temp_dir = ale#path#Simplify(fnamemodify(tempname(), ':h')) " Given a filename, return 1 if the file represents some temporary file " created by Vim. function! ale#path#IsTempName(filename) abort - return a:filename[:len(s:temp_dir) - 1] is# s:temp_dir + return ale#path#Simplify(a:filename)[:len(s:temp_dir) - 1] is# s:temp_dir endfunction " Given a base directory, which must not have a trailing slash, and a diff --git a/autoload/ale/pattern_options.vim b/autoload/ale/pattern_options.vim index e58b8cf2..d1f91785 100644 --- a/autoload/ale/pattern_options.vim +++ b/autoload/ale/pattern_options.vim @@ -1,6 +1,11 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Set options in files based on regex patterns. +" A dictionary mapping regular expression patterns to arbitrary buffer +" variables to be set. Useful for configuring ALE based on filename patterns. +let g:ale_pattern_options = get(g:, 'ale_pattern_options', {}) +let g:ale_pattern_options_enabled = get(g:, 'ale_pattern_options_enabled', !empty(g:ale_pattern_options)) + " These variables are used to cache the sorting of patterns below. let s:last_pattern_options = {} let s:sorted_items = [] @@ -18,7 +23,8 @@ function! s:CmpPatterns(left_item, right_item) abort endfunction function! ale#pattern_options#SetOptions(buffer) abort - if !g:ale_pattern_options_enabled || empty(g:ale_pattern_options) + if !get(g:, 'ale_pattern_options_enabled', 0) + \|| empty(get(g:, 'ale_pattern_options', 0)) return endif diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim index 3b1c16a6..aefbb691 100644 --- a/autoload/ale/preview.vim +++ b/autoload/ale/preview.vim @@ -2,17 +2,86 @@ " Description: Preview windows for showing whatever information in. " Open a preview window and show some lines in it. -function! ale#preview#Show(lines) abort +" A second argument can be passed as a Dictionary with options. They are... +" +" filetype - The filetype to use, defaulting to 'ale-preview' +" stay_here - If 1, stay in the window you came from. +function! ale#preview#Show(lines, ...) abort + let l:options = get(a:000, 0, {}) + silent pedit ALEPreviewWindow wincmd P + setlocal modifiable setlocal noreadonly setlocal nobuflisted - setlocal filetype=ale-preview + let &l:filetype = get(l:options, 'filetype', 'ale-preview') setlocal buftype=nofile setlocal bufhidden=wipe :%d call setline(1, a:lines) setlocal nomodifiable setlocal readonly + + if get(l:options, 'stay_here') + wincmd p + endif +endfunction + +" Close the preview window if the filetype matches the given one. +function! ale#preview#CloseIfTypeMatches(filetype) abort + for l:win in getwininfo() + let l:wintype = gettabwinvar(l:win.tabnr, l:win.winnr, '&filetype') + + if l:wintype is# a:filetype + silent! pclose! + endif + endfor +endfunction + +" Show a location selection preview window, given some items. +" Each item should have 'filename', 'line', and 'column' keys. +function! ale#preview#ShowSelection(item_list) abort + let l:lines = [] + + " Create lines to display to users. + for l:item in a:item_list + call add( + \ l:lines, + \ l:item.filename + \ . ':' . l:item.line + \ . ':' . l:item.column, + \) + endfor + + call ale#preview#Show(l:lines, {'filetype': 'ale-preview-selection'}) + let b:ale_preview_item_list = a:item_list +endfunction + +function! s:Open(open_in_tab) abort + let l:item_list = get(b:, 'ale_preview_item_list', []) + let l:item = get(l:item_list, getcurpos()[1] - 1, {}) + + if empty(l:item) + return + endif + + if !a:open_in_tab + :q! + endif + + call ale#util#Open( + \ l:item.filename, + \ l:item.line, + \ l:item.column, + \ {'open_in_tab': a:open_in_tab}, + \) +endfunction + +function! ale#preview#OpenSelectionInBuffer() abort + call s:Open(0) +endfunction + +function! ale#preview#OpenSelectionInTab() abort + call s:Open(1) endfunction diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim index 82dd9d7c..cdc71cdd 100644 --- a/autoload/ale/python.vim +++ b/autoload/ale/python.vim @@ -15,6 +15,7 @@ let g:ale_virtualenv_dir_names = get(g:, 'ale_virtualenv_dir_names', [ function! ale#python#FindProjectRootIni(buffer) abort for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + " If you change this, update ale-python-root documentation. if filereadable(l:path . '/MANIFEST.in') \|| filereadable(l:path . '/setup.cfg') \|| filereadable(l:path . '/pytest.ini') diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim new file mode 100644 index 00000000..89df69eb --- /dev/null +++ b/autoload/ale/references.vim @@ -0,0 +1,110 @@ +let s:references_map = {} + +" Used to get the references map in tests. +function! ale#references#GetMap() abort + return deepcopy(s:references_map) +endfunction + +" Used to set the references map in tests. +function! ale#references#SetMap(map) abort + let s:references_map = a:map +endfunction + +function! ale#references#ClearLSPData() abort + let s:references_map = {} +endfunction + +function! ale#references#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') is# 'references' + \&& has_key(s:references_map, a:response.request_seq) + call remove(s:references_map, a:response.request_seq) + + if get(a:response, 'success', v:false) is v:true + let l:item_list = [] + + for l:response_item in a:response.body.refs + call add(l:item_list, { + \ 'filename': l:response_item.file, + \ 'line': l:response_item.start.line, + \ 'column': l:response_item.start.offset, + \}) + endfor + + if empty(l:item_list) + call ale#util#Execute('echom ''No references found.''') + else + call ale#preview#ShowSelection(l:item_list) + endif + endif + endif +endfunction + +function! ale#references#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:references_map, a:response.id) + call remove(s:references_map, a:response.id) + + " The result can be a Dictionary item, a List of the same, or null. + let l:result = get(a:response, 'result', []) + let l:item_list = [] + + for l:response_item in l:result + call add(l:item_list, { + \ 'filename': ale#path#FromURI(l:response_item.uri), + \ 'line': l:response_item.range.start.line + 1, + \ 'column': l:response_item.range.start.character + 1, + \}) + endfor + + if empty(l:item_list) + call ale#util#Execute('echom ''No references found.''') + else + call ale#preview#ShowSelection(l:item_list) + endif + endif +endfunction + +function! s:FindReferences(linter) abort + let l:buffer = bufnr('') + let [l:line, l:column] = getcurpos()[1:2] + + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#references#HandleTSServerResponse') + \ : function('ale#references#HandleLSPResponse') + + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#References( + \ l:buffer, + \ l:line, + \ l:column + \) + else + " Send a message saying the buffer has changed first, or the + " references position probably won't make sense. + call ale#lsp#NotifyForChanges(l:lsp_details) + + let l:column = min([l:column, len(getline(l:line))]) + + let l:message = ale#lsp#message#References(l:buffer, l:line, l:column) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) + + let s:references_map[l:request_id] = {} +endfunction + +function! ale#references#Find() abort + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + call s:FindReferences(l:linter) + endif + endfor +endfunction diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim index 1c439bc8..31663721 100644 --- a/autoload/ale/sign.vim +++ b/autoload/ale/sign.vim @@ -2,6 +2,25 @@ scriptencoding utf8 " Author: w0rp <devw0rp@gmail.com> " Description: Draws error and warning signs into signcolumn +" This flag can be set to some integer to control the maximum number of signs +" that ALE will set. +let g:ale_max_signs = get(g:, 'ale_max_signs', -1) +" This flag can be set to 1 to enable changing the sign column colors when +" there are errors. +let g:ale_change_sign_column_color = get(g:, 'ale_change_sign_column_color', 0) +" These variables dictate what signs are used to indicate errors and warnings. +let g:ale_sign_error = get(g:, 'ale_sign_error', '>>') +let g:ale_sign_style_error = get(g:, 'ale_sign_style_error', g:ale_sign_error) +let g:ale_sign_warning = get(g:, 'ale_sign_warning', '--') +let g:ale_sign_style_warning = get(g:, 'ale_sign_style_warning', g:ale_sign_warning) +let g:ale_sign_info = get(g:, 'ale_sign_info', g:ale_sign_warning) +" This variable sets an offset which can be set for sign IDs. +" This ID can be changed depending on what IDs are set for other plugins. +" The dummy sign will use the ID exactly equal to the offset. +let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) +" This flag can be set to 1 to keep sign gutter always open +let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0) + if !hlexists('ALEErrorSign') highlight link ALEErrorSign error endif @@ -60,55 +79,35 @@ 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 + let l:priority = g:ale#util#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 + let l:item_priority = ale#util#GetItemPriority(l:item) - if l:item_priority < l:priority + if l:item_priority > l:priority let l:priority = l:item_priority endif endfor - if l:priority is# s:error_priority + if l:priority is# g:ale#util#error_priority return 'ALEErrorSign' endif - if l:priority is# s:warning_priority + if l:priority is# g:ale#util#warning_priority return 'ALEWarningSign' endif - if l:priority is# s:style_error_priority + if l:priority is# g:ale#util#style_error_priority return 'ALEStyleErrorSign' endif - if l:priority is# s:style_warning_priority + if l:priority is# g:ale#util#style_warning_priority return 'ALEStyleWarningSign' endif - if l:priority is# s:info_priority + if l:priority is# g:ale#util#info_priority return 'ALEInfoSign' endif diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim index a073f7a0..851618cc 100644 --- a/autoload/ale/statusline.vim +++ b/autoload/ale/statusline.vim @@ -1,6 +1,14 @@ " Author: KabbAmine <amine.kabb@gmail.com> " Description: Statusline related function(s) +" remove in 2.0 +" +" A deprecated setting for ale#statusline#Status() +" See :help ale#statusline#Count() for getting status reports. +let g:ale_statusline_format = get(g:, 'ale_statusline_format', +\ ['%d error(s)', '%d warning(s)', 'OK'] +\) + function! s:CreateCountDict() abort " Keys 0 and 1 are for backwards compatibility. " The count object used to be a List of [error_count, warning_count]. @@ -90,11 +98,18 @@ function! s:StatusForListFormat() abort return l:res endfunction +" remove in 2.0 +" " Returns a formatted string that can be integrated in the statusline. " " This function is deprecated, and should not be used. Use the airline plugin " instead, or write your own status function with ale#statusline#Count() function! ale#statusline#Status() abort + if !get(g:, 'ale_deprecation_ale_statusline_status', 0) + execute 'echom ''ale#statusline#Status() is deprecated, use ale#statusline#Count() to write your own function.''' + let g:ale_deprecation_ale_statusline_status = 1 + endif + if !exists('g:ale_statusline_format') return 'OK' endif diff --git a/autoload/ale/toggle.vim b/autoload/ale/toggle.vim index e9cc29b6..6b1affc4 100644 --- a/autoload/ale/toggle.vim +++ b/autoload/ale/toggle.vim @@ -1,93 +1,11 @@ -function! ale#toggle#InitAuGroups() abort - " This value used to be a Boolean as a Number, and is now a String. - let l:text_changed = '' . g:ale_lint_on_text_changed - - augroup ALEPatternOptionsGroup - autocmd! - autocmd BufEnter,BufRead * call ale#pattern_options#SetOptions(str2nr(expand('<abuf>'))) - augroup END - - augroup ALERunOnTextChangedGroup - autocmd! - if g:ale_enabled - 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 is? 'normal' - autocmd TextChanged * call ale#Queue(g:ale_lint_delay) - elseif l:text_changed is? 'insert' - autocmd TextChangedI * call ale#Queue(g:ale_lint_delay) - endif - endif - augroup END - - 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>'))) - endif - augroup END - - augroup ALERunOnFiletypeChangeGroup - autocmd! - if g:ale_enabled && g:ale_lint_on_filetype_changed - " Only start linting if the FileType actually changes after - " opening a buffer. The FileType will fire when buffers are opened. - autocmd FileType * call ale#events#FileTypeEvent( - \ str2nr(expand('<abuf>')), - \ expand('<amatch>') - \) - endif - augroup END - - augroup ALERunOnSaveGroup - autocmd! - autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand('<abuf>'))) - augroup END - - augroup ALERunOnInsertLeave - autocmd! - if g:ale_enabled && g:ale_lint_on_insert_leave - autocmd InsertLeave * call ale#Queue(0) - endif - augroup END - - augroup ALECursorGroup - autocmd! - if g:ale_enabled && g:ale_echo_cursor - autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() - " Look for a warning to echo as soon as we leave Insert mode. - " The script's position variable used when moving the cursor will - " not be changed here. - autocmd InsertLeave * call ale#cursor#EchoCursorWarning() - endif - augroup END - - if !g:ale_enabled - augroup! ALERunOnTextChangedGroup - augroup! ALERunOnEnterGroup - augroup! ALERunOnInsertLeave - augroup! ALECursorGroup - endif -endfunction - function! s:EnablePreamble() abort " Set pattern options again, if enabled. - if g:ale_pattern_options_enabled + if get(g:, 'ale_pattern_options_enabled', 0) call ale#pattern_options#SetOptions(bufnr('')) endif " Lint immediately, including running linters against the file. call ale#Queue(0, 'lint_file') - - if g:ale_set_balloons - call ale#balloon#Enable() - endif endfunction function! s:DisablePostamble() abort @@ -95,10 +13,6 @@ function! s:DisablePostamble() abort if g:ale_set_highlights call ale#highlight#UpdateHighlights() endif - - if g:ale_set_balloons - call ale#balloon#Disable() - endif endfunction function! s:CleanupEveryBuffer() abort @@ -121,21 +35,24 @@ function! ale#toggle#Toggle() abort if g:ale_enabled call s:EnablePreamble() + + if g:ale_set_balloons + call ale#balloon#Enable() + endif else call s:CleanupEveryBuffer() call s:DisablePostamble() + + if has('balloon_eval') + call ale#balloon#Disable() + endif endif - call ale#toggle#InitAuGroups() + call ale#events#Init() endfunction function! ale#toggle#Enable() abort if !g:ale_enabled - " Set pattern options again, if enabled. - if g:ale_pattern_options_enabled - call ale#pattern_options#SetOptions(bufnr('')) - endif - call ale#toggle#Toggle() endif endfunction diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index b94a11b7..28ab8231 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -11,6 +11,32 @@ function! ale#util#FeedKeys(...) abort return call('feedkeys', a:000) endfunction +" Show a message in as small a window as possible. +" +" Vim 8 does not support echoing long messages from asynchronous callbacks, +" but NeoVim does. Small messages can be echoed in Vim 8, and larger messages +" have to be shown in preview windows. +function! ale#util#ShowMessage(string) abort + if !has('nvim') + call ale#preview#CloseIfTypeMatches('ale-preview.message') + endif + + " We have to assume the user is using a monospace font. + if has('nvim') || (a:string !~? "\n" && len(a:string) < &columns) + execute 'echo a:string' + else + call ale#preview#Show(split(a:string, "\n"), { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \}) + endif +endfunction + +" A wrapper function for execute, so we can test executing some commands. +function! ale#util#Execute(expr) abort + execute a:expr +endfunction + if !exists('g:ale#util#nul_file') " A null file for sending output to nothing. let g:ale#util#nul_file = '/dev/null' @@ -33,6 +59,45 @@ function! ale#util#GetFunction(string_or_ref) abort return a:string_or_ref endfunction +function! ale#util#Open(filename, line, column, options) abort + if get(a:options, 'open_in_tab', 0) + call ale#util#Execute('tabedit ' . fnameescape(a:filename)) + else + " Open another file only if we need to. + if bufnr(a:filename) isnot bufnr('') + call ale#util#Execute('edit ' . fnameescape(a:filename)) + endif + endif + + call cursor(a:line, a:column) +endfunction + +let g:ale#util#error_priority = 5 +let g:ale#util#warning_priority = 4 +let g:ale#util#info_priority = 3 +let g:ale#util#style_error_priority = 2 +let g:ale#util#style_warning_priority = 1 + +function! ale#util#GetItemPriority(item) abort + if a:item.type is# 'I' + return g:ale#util#info_priority + endif + + if a:item.type is# 'W' + if get(a:item, 'sub_type', '') is# 'style' + return g:ale#util#style_warning_priority + endif + + return g:ale#util#warning_priority + endif + + if get(a:item, 'sub_type', '') is# 'style' + return g:ale#util#style_error_priority + endif + + return g:ale#util#error_priority +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 @@ -70,6 +135,23 @@ function! ale#util#LocItemCompare(left, right) abort return 1 endif + " When either of the items lacks a problem type, then the two items should + " be considered equal. This is important for loclist jumping. + if !has_key(a:left, 'type') || !has_key(a:right, 'type') + return 0 + endif + + let l:left_priority = ale#util#GetItemPriority(a:left) + let l:right_priority = ale#util#GetItemPriority(a:right) + + if l:left_priority < l:right_priority + return -1 + endif + + if l:left_priority > l:right_priority + return 1 + endif + return 0 endfunction @@ -139,6 +221,17 @@ function! ale#util#BinarySearch(loclist, buffer, line, column) abort let l:index += 1 endwhile + " Scan forwards to find the last item on the column for the item + " we found, which will have the most serious problem. + let l:item_column = a:loclist[l:index].col + + 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 == l:item_column + let l:index += 1 + endwhile + return l:index endif endwhile @@ -158,16 +251,6 @@ function! ale#util#InSandbox() abort return 0 endfunction -" Get the number of milliseconds since some vague, but consistent, point in -" the past. -" -" This function can be used for timing execution, etc. -" -" The time will be returned as a Number. -function! ale#util#ClockMilliseconds() abort - return float2nr(reltimefloat(reltime()) * 1000) -endfunction - " Given a single line, or a List of lines, and a single pattern, or a List " of patterns, return all of the matches for the lines(s) from the given " patterns, using matchlist(). @@ -287,8 +370,10 @@ if !exists('s:patial_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) + if has_key(s:partial_timers, a:timer_id) + let [l:Callback, l:args] = remove(s:partial_timers, a:timer_id) + call call(l:Callback, [a:timer_id] + l:args) + endif endfunction " Given a delay, a callback, a List of arguments, start a timer with @@ -311,3 +396,13 @@ function! ale#util#StopPartialTimer(timer_id) abort call remove(s:partial_timers, a:timer_id) endif endfunction + +" Given a possibly multi-byte string and a 1-based character position on a +" line, return the 1-based byte position on that line. +function! ale#util#Col(str, chr) abort + if a:chr < 2 + return a:chr + endif + + return strlen(join(split(a:str, '\zs')[0:a:chr - 2], '')) + 1 +endfunction diff --git a/doc/ale-c.txt b/doc/ale-c.txt index fc2c45c7..c41f3bc8 100644 --- a/doc/ale-c.txt +++ b/doc/ale-c.txt @@ -31,7 +31,18 @@ g:ale_c_build_dir *g:ale_c_build_dir* 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) + around LibTooling (namely clang-tidy here) + + +g:ale_c_parse_makefile *g:ale_c_parse_makefile* + *b:ale_c_parse_makefile* + Type: |Number| + Default: `0` + + If set to `1`, ALE will run `make -n` to automatically determine flags to + set for C or C++ compilers. This can make it easier to determine the correct + build flags to use for different files. + =============================================================================== clang *ale-c-clang* @@ -144,6 +155,41 @@ g:ale_c_cppcheck_options *g:ale_c_cppcheck_options* =============================================================================== +flawfinder *ale-c-flawfinder* + +g:ale_c_flawfinder_executable *g:ale_c_flawfinder_executable* + *b:ale_c_flawfinder_executable* + Type: |String| + Default: `'flawfinder'` + + This variable can be changed to use a different executable for flawfinder. + + +g:ale_c_flawfinder_minlevel *g:ale_c_flawfinder_minlevel* + *b:ale_c_flawfinder_minlevel* + Type: |Number| + Default: `1` + + This variable can be changed to ignore risks under the given risk threshold. + + +g:ale_c_flawfinder_options *g:ale-c-flawfinder* + *b:ale-c-flawfinder* + Type: |String| + Default: `''` + + This variable can be used to pass extra options into the flawfinder command. + +g:ale_c_flawfinder_error_severity *g:ale_c_flawfinder_error_severity* + *b:ale_c_flawfinder_error_severity* + Type: |Number| + Default: `6` + + This variable can be changed to set the minimum severity to be treated as an + error. This setting also applies to flawfinder for c++. + + +=============================================================================== gcc *ale-c-gcc* g:ale_c_gcc_executable *g:ale_c_gcc_executable* diff --git a/doc/ale-cloudformation.txt b/doc/ale-cloudformation.txt new file mode 100644 index 00000000..59c6af06 --- /dev/null +++ b/doc/ale-cloudformation.txt @@ -0,0 +1,14 @@ +=============================================================================== +ALE CloudFormation Integration *ale-cloudformation-options* + + +=============================================================================== +cfn-python-lint *ale-cloudformation-cfn-python-lint* + +cfn-python-lint is a linter for AWS CloudFormation template file. + +https://github.com/awslabs/cfn-python-lint + +=============================================================================== + 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 cda5768b..8bd111e4 100644 --- a/doc/ale-cpp.txt +++ b/doc/ale-cpp.txt @@ -5,8 +5,11 @@ ALE C++ Integration *ale-cpp-options* =============================================================================== Global Options -The |g:ale_c_build_dir_names| and |g:ale_c_build_dir| also apply to some C++ -linters too. +The following C options also apply to some C++ linters too. + +* |g:ale_c_build_dir_names| +* |g:ale_c_build_dir| +* |g:ale_c_parse_makefile| =============================================================================== @@ -154,6 +157,53 @@ g:ale_cpp_cpplint_options *g:ale_cpp_cpplint_options* =============================================================================== +cquery *ale-cpp-cquery* + +g:ale_cpp_cquery_executable *g:ale_cpp_cquery_executable* + *b:ale_cpp_cquery_executable* + Type: |String| + Default: `'cquery'` + + This variable can be changed to use a different executable for cquery. + + +g:ale_cpp_cquery_cache_directory *g:ale_cpp_cquery_cache_directory* + *b:ale_cpp_cquery_cache_directory* + Type: |String| + Default: `'~/.cache/cquery'` + + This variable can be changed to decide which directory cquery uses for its + cache. + + +=============================================================================== +flawfinder *ale-cpp-flawfinder* + +g:ale_cpp_flawfinder_executable *g:ale_cpp_flawfinder_executable* + *b:ale_cpp_flawfinder_executable* + Type: |String| + Default: `'flawfinder'` + + This variable can be changed to use a different executable for flawfinder. + + +g:ale_cpp_flawfinder_minlevel *g:ale_cpp_flawfinder_minlevel* + *b:ale_cpp_flawfinder_minlevel* + Type: |Number| + Default: `1` + + This variable can be changed to ignore risks under the given risk threshold. + + +g:ale_cpp_flawfinder_options *g:ale-cpp-flawfinder* + *b:ale-cpp-flawfinder* + Type: |String| + Default: `''` + + This variable can be used to pass extra options into the flawfinder command. + + +=============================================================================== gcc *ale-cpp-gcc* g:ale_cpp_gcc_executable *g:ale_cpp_gcc_executable* diff --git a/doc/ale-cs.txt b/doc/ale-cs.txt index 237e8484..f65b9f39 100644 --- a/doc/ale-cs.txt +++ b/doc/ale-cs.txt @@ -38,8 +38,8 @@ mcsc *ale-cs-mcsc* The paths to search for additional assembly files can be specified using the |g:ale_cs_mcsc_assembly_path| or |b:ale_cs_mcsc_assembly_path| variables. - NOTE: ALE will not any errors in files apart from syntax errors if any one - of the source files contains a syntax error. Syntax errors must be fixed + NOTE: ALE will not find any errors in files apart from syntax errors if any + one of the source files contains a syntax error. Syntax errors must be fixed first before other errors will be shown. @@ -94,6 +94,7 @@ g:ale_cs_mcsc_assemblies *g:ale_cs_mcsc_assemblies* " Compile C# programs with the Unity engine DLL file on Mac. let g:ale_cs_mcsc_assemblies = [ \ '/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll', + \ 'path-to-unityproject/obj/Debug', \] < diff --git a/doc/ale-css.txt b/doc/ale-css.txt index 474445b8..f3cae385 100644 --- a/doc/ale-css.txt +++ b/doc/ale-css.txt @@ -30,7 +30,7 @@ g:ale_css_stylelint_options *g:ale_css_stylelint_options* g:ale_css_stylelint_use_global *g:ale_css_stylelint_use_global* *b:ale_css_stylelint_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-elixir.txt b/doc/ale-elixir.txt index b7d4922e..ac0ec605 100644 --- a/doc/ale-elixir.txt +++ b/doc/ale-elixir.txt @@ -14,6 +14,18 @@ g:ale_elixir_mix_options *g:ale_elixir_mix_options* This variable can be changed to specify the mix executable. =============================================================================== +mix_format *ale-elixir-mix-format* + +g:ale_elixir_mix_format_options *g:ale_elixir_mix_format_options* + *b:ale_elixir_mix_format_options* + Type: |String| + Default: `''` + + + This variable can be changed to specify the mix options passed to the + mix_format fixer + +=============================================================================== dialyxir *ale-elixir-dialyxir* Dialyzer, a DIscrepancy AnaLYZer for ERlang programs. diff --git a/doc/ale-elm.txt b/doc/ale-elm.txt index e96205df..de7d8939 100644 --- a/doc/ale-elm.txt +++ b/doc/ale-elm.txt @@ -16,7 +16,7 @@ g:ale_elm_format_executable *g:ale_elm_format_executable* g:ale_elm_format_use_global *g:ale_elm_format_use_global* *b:ale_elm_format_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -34,7 +34,7 @@ elm-make *ale-elm-elm-make* g:ale_elm_make_executable *g:ale_elm_make_executable* *b:ale_elm_make_executable* Type: |String| - Default: `'elm-make'` + Default: `'elm'` See |ale-integrations-local-executables| @@ -42,7 +42,7 @@ g:ale_elm_make_executable *g:ale_elm_make_executable* g:ale_elm_make_use_global *g:ale_elm_make_use_global* *b:ale_elm_make_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-gitcommit.txt b/doc/ale-gitcommit.txt index 71813dd0..38f3fd90 100644 --- a/doc/ale-gitcommit.txt +++ b/doc/ale-gitcommit.txt @@ -19,18 +19,20 @@ g:ale_gitcommit_gitlint_options *g:ale_gitcommit_gitlint_options* Default: `''` This variable can be changed to add command-line arguments to the gitlint - invocation. + invocation. For example, you can specify the path to a configuration file. > - For example, to dinamically set the gitlint configuration file path, you - may want to set > + let g:ale_gitcommit_gitlint_options = '-C /home/user/.config/gitlint.ini' +< + You can also disable particular error codes using this option. For example, + you can ignore errors for git commits with a missing body. > - let g:ale_gitcommit_gitlint_options = '-C /home/user/.config/gitlint.ini' + let g:ale_gitcommit_gitlint_options = '--ignore B6' < g:ale_gitcommit_gitlint_use_global *g:ale_gitcommit_gitlint_use_global* *b:ale_gitcommit_gitlint_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` This variable controls whether or not ALE will search for gitlint in a virtualenv directory first. If this variable is set to `1`, then ALE will diff --git a/doc/ale-go.txt b/doc/ale-go.txt index 502f2376..60d6cb84 100644 --- a/doc/ale-go.txt +++ b/doc/ale-go.txt @@ -20,6 +20,7 @@ the benefit of running a number of linters, more than ALE would by default, while ensuring it doesn't run any linters known to be slow or resource intensive. + =============================================================================== gobuild *ale-go-gobuild* @@ -42,6 +43,7 @@ g:ale_go_gofmt_options *g:ale_go_gofmt_options* This variable can be set to pass additional options to the gofmt fixer. + =============================================================================== gometalinter *ale-go-gometalinter* @@ -71,5 +73,35 @@ g:ale_go_gometalinter_options *g:ale_go_gometalinter_options* number of linters known to be slow or consume a lot of resources. +g:ale_go_gometalinter_lint_package *g:ale_go_gometalinter_lint_package* + *b:ale_go_gometalinter_lint_package* + Type: |Number| + Default: `0` + + When set to `1`, the whole Go package will be checked instead of only the + current file. + + +=============================================================================== +staticcheck *ale-go-staticcheck* + +g:ale_go_staticcheck_options *g:ale_go_staticcheck_options* + *b:ale_go_staticcheck_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the staticcheck + linter. + + +g:ale_go_staticcheck_lint_package *g:ale_go_staticcheck_lint_package* + *b:ale_go_staticcheck_lint_package* + Type: |Number| + Default: `0` + + When set to `1`, the whole Go package will be checked instead of only the + current file. + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-handlebars.txt b/doc/ale-handlebars.txt index 6908aac6..061c5d3c 100644 --- a/doc/ale-handlebars.txt +++ b/doc/ale-handlebars.txt @@ -16,7 +16,7 @@ g:ale_handlebars_embertemplatelint_executable g:ale_handlebars_embertemplatelint_use_global *g:ale_handlebars_embertemplatelint_use_global* Type: |Number| *b:ale_handlebars_embertemplatelint_use_global* - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-haskell.txt b/doc/ale-haskell.txt index 9fab39bf..09ecd92d 100644 --- a/doc/ale-haskell.txt +++ b/doc/ale-haskell.txt @@ -36,10 +36,16 @@ g:ale_haskell_hdevtools_executable *g:ale_haskell_hdevtools_executable* g:ale_haskell_hdevtools_options *g:ale_haskell_hdevtools_options* *b:ale_haskell_hdevtools_options* Type: |String| - Default: `'-g -Wall'` + Default: `get(g:, 'hdevtools_options', '-g -Wall')` This variable can be changed to modify flags given to hdevtools. + The hdevtools documentation recommends setting GHC options for `hdevtools` + with `g:hdevtools_options`. ALE will use the value of `g:hdevtools_options` + for the value of `g:ale_haskell_hdevtools_options` by default, so this + option can be respected and overridden specifically for ALE. + + =============================================================================== hfmt *ale-haskell-hfmt* diff --git a/doc/ale-html.txt b/doc/ale-html.txt index 416e932e..98fddc58 100644 --- a/doc/ale-html.txt +++ b/doc/ale-html.txt @@ -24,7 +24,7 @@ g:ale_html_htmlhint_options *g:ale_html_htmlhint_options* g:ale_html_htmlhint_use_global *g:ale_html_htmlhint_use_global* *b:ale_html_htmlhint_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -71,6 +71,14 @@ g:ale_html_tidy_options *g:ale_html_tidy_options* (mac), sjis (shiftjis), utf-16le, utf-16, utf-8 +g:ale_html_tidy_use_global *g:html_tidy_use_global* + + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + =============================================================================== write-good *ale-html-write-good* diff --git a/doc/ale-java.txt b/doc/ale-java.txt index 0d2011f8..4481e823 100644 --- a/doc/ale-java.txt +++ b/doc/ale-java.txt @@ -25,6 +25,14 @@ g:ale_java_javac_classpath *g:ale_java_javac_classpath* This variable can be set to change the global classpath for Java. +g:ale_java_javac_executable *g:ale_java_javac_executable* + *b:ale_java_javac_executable* + Type: |String| + Default: `'javac'` + + This variable can be set to change the executable path used for javac. + + g:ale_java_javac_options *g:ale_java_javac_options* *b:ale_java_javac_options* Type: |String| @@ -53,5 +61,19 @@ g:ale_java_google_java_format_options *g:ale_java_google_java_format_options* This variable can be set to pass additional options + +=============================================================================== +pmd *ale-java-pmd* + +g:ale_java_pmd_options *g:ale_java_pmd_options* + *b:ale_java_pmd_options* + + Type: String + Default: '-R category/java/bestpractices' + + This variable can be changed to modify flags given to PMD. Do not specify -f + and -d. They are added automatically. + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-javascript.txt b/doc/ale-javascript.txt index f625fd7f..53a70fd7 100644 --- a/doc/ale-javascript.txt +++ b/doc/ale-javascript.txt @@ -45,7 +45,7 @@ g:ale_javascript_eslint_options *g:ale_javascript_eslint_options* g:ale_javascript_eslint_use_global *g:ale_javascript_eslint_use_global* *b:ale_javascript_eslint_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -98,11 +98,22 @@ g:ale_javascript_flow_use_home_config *g:ale_javascript_flow_use_home_config* g:ale_javascript_flow_use_global *g:ale_javascript_flow_use_global* *b:ale_javascript_flow_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| +g:ale_javascript_flow_use_respect_pragma + *g:ale_javascript_flow_use_respect_pragma* + *b:ale_javascript_flow_use_respect_pragma* + Type: |Number| + Default: `1` + + By default, ALE will use the `--respect-pragma` option for `flow`, so only + files with the `@flow` pragma are checked by ALE. This option can be set to + `0` to disable that behaviour, so all files can be checked by `flow`. + + =============================================================================== importjs *ale-javascript-importjs* @@ -126,7 +137,7 @@ g:ale_javascript_jscs_executable *g:ale_javascript_jscs_executable* g:ale_javascript_jscs_use_global *g:ale_javascript_jscs_use_global* *b:ale_javascript_jscs_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -145,7 +156,7 @@ g:ale_javascript_jshint_executable *g:ale_javascript_jshint_executable* g:ale_javascript_jshint_use_global *g:ale_javascript_jshint_use_global* *b:ale_javascript_jshint_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -172,7 +183,7 @@ g:ale_javascript_prettier_options *g:ale_javascript_prettier_options* g:ale_javascript_prettier_use_global *g:ale_javascript_prettier_use_global* *b:ale_javascript_prettier_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -202,7 +213,7 @@ g:ale_javascript_prettier_eslint_use_global *g:ale_javascript_prettier_eslint_use_global* *b:ale_javascript_prettier_eslint_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -233,7 +244,7 @@ g:ale_javascript_prettier_standard_use_global *g:ale_javascript_prettier_standard_use_global* *b:ale_javascript_prettier_standard_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -262,7 +273,7 @@ g:ale_javascript_standard_options *g:ale_javascript_standard_options* g:ale_javascript_standard_use_global *g:ale_javascript_standard_use_global* *b:ale_javascript_standard_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -289,7 +300,7 @@ g:ale_javascript_xo_options *g:ale_javascript_xo_options* g:ale_javascript_xo_use_global *g:ale_javascript_xo_use_global* *b:ale_javascript_xo_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-json.txt b/doc/ale-json.txt index 1e97abce..0ec7932d 100644 --- a/doc/ale-json.txt +++ b/doc/ale-json.txt @@ -44,7 +44,7 @@ g:ale_json_fixjson_use_global *g:ale_json_fixjson_use_global* *b:ale_json_fixjson_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-less.txt b/doc/ale-less.txt index 05f56e2b..040e511e 100644 --- a/doc/ale-less.txt +++ b/doc/ale-less.txt @@ -24,7 +24,7 @@ g:ale_less_lessc_options *g:ale_less_lessc_options* g:ale_less_lessc_use_global *g:ale_less_lessc_use_global* *b:ale_less_lessc_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -57,7 +57,7 @@ g:ale_less_stylelint_options *g:ale_less_stylelint_options* g:ale_less_stylelint_use_global *g:ale_less_stylelint_use_global* *b:ale_less_stylelint_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-markdown.txt b/doc/ale-markdown.txt index b59f0183..63ddcae4 100644 --- a/doc/ale-markdown.txt +++ b/doc/ale-markdown.txt @@ -3,12 +3,38 @@ ALE Markdown Integration *ale-markdown-options* =============================================================================== +mdl *ale-markdown-mdl* + +g:ale_markdown_mdl_executable *g:ale_markdown_mdl_executable* + *b:ale_markdown_mdl_executable* + Type: |String| + Default: `'mdl'` + + Override the invoked mdl binary. This is useful for running mdl from + binstubs or a bundle. + + +g:ale_markdown_mdl_options *g:ale_markdown_mdl_options* + *b:ale_markdown_mdl_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to mdl. + + +=============================================================================== prettier *ale-markdown-prettier* See |ale-javascript-prettier| for information about the available options. =============================================================================== +textlint *ale-markdown-textlint* + +See |ale-text-textlint| + + +=============================================================================== write-good *ale-markdown-write-good* See |ale-write-good-options| diff --git a/doc/ale-mercury.txt b/doc/ale-mercury.txt new file mode 100644 index 00000000..ca06a0a7 --- /dev/null +++ b/doc/ale-mercury.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE Mercury Integration *ale-mercury-options* + + +=============================================================================== +mmc *ale-mercury-mmc* + + +g:ale_mercury_mmc_executable *g:ale_mercury_mmc_executable* + *b:ale_mercury_mmc_executable* + Type: |String| + Default: `'mmc'` + + This variable can be changed to use a different executable for mmc. + + +g:ale_mercury_mmc_options *g:ale_mercury_mmc_options* + *b:ale_mercury_mmc_options* + Type: |String| + Default: `'--make --output-compile-error-lines 100'` + + This variable can be set to pass additional options to mmc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nasm.txt b/doc/ale-nasm.txt new file mode 100644 index 00000000..16c024a1 --- /dev/null +++ b/doc/ale-nasm.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE NASM Integration *ale-nasm-options* + + +=============================================================================== +nasm *ale-nasm-nasm* + +g:ale_nasm_nasm_executable *g:ale_nasm_nasm_executable* + *b:ale_nasm_nasm_executable* + + Type: |String| + Default `'nasm'` + + This variable can be changed to use different executable for NASM. + + +g:ale_nasm_nasm_options *g:ale_nasm_nasm_options* + *b:ale_nasm_nasm_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to NASM. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-ocaml.txt b/doc/ale-ocaml.txt index cfa365af..a7ef8d29 100644 --- a/doc/ale-ocaml.txt +++ b/doc/ale-ocaml.txt @@ -28,7 +28,7 @@ g:ale_ocaml_ols_executable *g:ale_ocaml_ols_executable* g:ale_ocaml_ols_use_global *g:ale_ocaml_ols_use_global* *b:ale_ocaml_ols_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` This variable can be set to `1` to always use the globally installed executable. See also |ale-integrations-local-executables|. diff --git a/doc/ale-perl.txt b/doc/ale-perl.txt index 414856bd..761c2735 100644 --- a/doc/ale-perl.txt +++ b/doc/ale-perl.txt @@ -3,6 +3,9 @@ ALE Perl Integration *ale-perl-options* ALE offers a few ways to check Perl code. Checking code with `perl` is disabled by default, as `perl` code cannot be checked without executing it. +Specifically, we use the `-c` flag to see if `perl` code compiles. This does +not execute all of the code in a file, but it does run `BEGIN` and `CHECK` +blocks. See `perl --help` and https://stackoverflow.com/a/12908487/406224 See |g:ale_linters|. @@ -73,7 +76,16 @@ g:ale_perl_perlcritic_showrules *g:ale_perl_perlcritic_showrules* Controls whether perlcritic rule names are shown after the error message. Defaults to off to reduce length of message. +=============================================================================== +perltidy *ale-perl-perltidy* + +g:ale_perl_perltidy_options *g:ale_perl_perltidy_options* + *b:ale_perl_perltidy_options* + Type: |String| + Default: `''` + This variable can be changed to alter the command-line arguments to + the perltidy invocation. =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-php.txt b/doc/ale-php.txt index 455472f7..33796f7c 100644 --- a/doc/ale-php.txt +++ b/doc/ale-php.txt @@ -37,7 +37,7 @@ g:ale_php_langserver_executable *g:ale_php_langserver_executable* g:ale_php_langserver_use_global *g:ale_php_langserver_use_global* *b:ale_php_langserver_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 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|. @@ -48,7 +48,7 @@ g:ale_php_langserver_use_global *g:ale_php_langserver_use_global* =============================================================================== phan *ale-php-phan* -WARNING: please do not use this linter if you have an configuration file +WARNING: please use the phan_client linter if you have an configuration file for your project because the phan will look into your entirely project and ale will display in the current buffer warnings that may belong to other file. @@ -57,8 +57,22 @@ g:ale_php_phan_minimum_severity *g:ale_php_phan_minimum_severity* Type: |Number| Default: `0` - This variable defines the minimum severity level + This variable defines the minimum severity level. + +g:ale_php_phan_executable *g:ale_php_phan_executable* + *b:ale_php_phan_executable* + Type: |String| + Default: `'phan'` + + This variable sets executable used for phan or phan_client. + +g:ale_php_phan_use_client *g:ale_php_phan_use_client* + *b:ale_php_phan_use_client* + Type: |Number| + Default: `get(g:, 'ale_php_phan_use_client', 0)` + This variable can be set to 1 to use the phan_client with phan daemon mode + instead of the phan standalone. =============================================================================== phpcbf *ale-php-phpcbf* @@ -84,7 +98,7 @@ g:ale_php_phpcbf_standard *g:ale_php_phpcbf_standard* g:ale_php_phpcbf_use_global *g:ale_php_phpcbf_use_global* *b:ale_php_phpcbf_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -113,7 +127,7 @@ g:ale_php_phpcs_standard *g:ale_php_phpcs_standard* g:ale_php_phpcs_use_global *g:ale_php_phpcs_use_global* *b:ale_php_phpcs_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -167,4 +181,28 @@ g:ale_php_phpstan_configuration *g:ale_php_phpstan_configuration* =============================================================================== +php-cs-fixer *ale-php-php-cs-fixer* + +g:ale_php_cs_fixer_executable *g:ale_php_cs_fixer_executable* + *b:ale_php_cs_fixer_executable* + Type: |String| + Default: `'php-cs-fixer'` + + This variable sets executable used for php-cs-fixer. + +g:ale_php_cs_fixer_use_global *g:ale_php_cs_fixer_use_global* + *b:ale_php_cs_fixer_use_global* + Type: |Boolean| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable force globally installed fixer. + +g:ale_php_cs_fixer_options *g:ale_php_cs_fixer_options* + *b:ale_php_cs_fixer_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to php-cs-fixer. + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pony.txt b/doc/ale-pony.txt new file mode 100644 index 00000000..3b32168e --- /dev/null +++ b/doc/ale-pony.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Pony Integration *ale-pony-options* + + +=============================================================================== +ponyc *ale-pony-ponyc* + +g:ale_pony_ponyc_executable *g:ale_pony_ponyc_executable* + *b:ale_pony_ponyc_executable* + Type: |String| + Default: `'ponyc'` + + See |ale-integrations-local-executables| + + +g:ale_pony_ponyc_options *g:ale_pony_ponyc_options* + *b:ale_pony_ponyc_options* + Type: |String| + Default: `'--pass paint'` + + This variable can be set to pass options to ponyc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pug.txt b/doc/ale-pug.txt index 01071485..e2836f85 100644 --- a/doc/ale-pug.txt +++ b/doc/ale-pug.txt @@ -35,7 +35,7 @@ g:ale_pug_puglint_options *g:ale_pug_puglint_options* g:ale_pug_puglint_use_global *g:ale_pug_puglint_use_global* *b:ale_pug_puglint_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-python.txt b/doc/ale-python.txt index 4d55e751..b24b531d 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -3,6 +3,31 @@ ALE Python Integration *ale-python-options* =============================================================================== +ALE Python Project Root Behavior *ale-python-root* + +For some linters, ALE will search for a Python project root by looking at the +files in directories on or above where a file being checked is. ALE applies +the following methods, in order: + +1. Find the first directory containing a common Python configuration file. +2. If no configuration file can be found, use the first directory which does + not contain a readable file named `__init__.py`. + +ALE will look for configuration files with the following filenames. > + + MANIFEST.in + setup.cfg + pytest.ini + tox.ini + mypy.ini + pycodestyle.cfg + flake8.cfg +< + +The first directory containing any of the files named above will be used. + + +=============================================================================== autopep8 *ale-python-autopep8* g:ale_python_autopep8_executable *g:ale_python_autopep8_executable* @@ -24,7 +49,34 @@ g:ale_python_autopep8_options *g:ale_python_autopep8_options* g:ale_python_autopep8_use_global *g:ale_python_autopep8_use_global* *b:ale_python_autopep8_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +black *ale-python-black* + +g:ale_python_black_executable *g:ale_python_black_executable* + *b:ale_python_black_executable* + Type: |String| + Default: `'black'` + + See |ale-integrations-local-executables| + +autopep8 +g:ale_python_black_options *g:ale_python_black_options* + *b:ale_python_black_options* + Type: |String| + Default: `''` + + This variable can be set to pass extra options to black. + + +g:ale_python_black_use_global *g:ale_python_black_use_global* + *b:ale_python_black_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -32,12 +84,24 @@ g:ale_python_autopep8_use_global *g:ale_python_autopep8_use_global* =============================================================================== flake8 *ale-python-flake8* +g:ale_python_flake8_change_directory *g:ale_python_flake8_change_directory* + *b:ale_python_flake8_change_directory* + Type: |Number| + Default: `1` + + If set to `1`, ALE will switch to the directory the Python file being + checked with `flake8` is in before checking it. This helps `flake8` find + configuration files more easily. This option can be turned off if you want + to control the directory Python is executed from yourself. + + g:ale_python_flake8_executable *g:ale_python_flake8_executable* *b:ale_python_flake8_executable* Type: |String| Default: `'flake8'` - This variable can be changed to modify the executable used for flake8. + This variable can be changed to modify the executable used for flake8. Set + this to `'pipenv'` to invoke `'pipenv` `run` `flake8'`. g:ale_python_flake8_options *g:ale_python_flake8_options* @@ -61,7 +125,7 @@ g:ale_python_flake8_options *g:ale_python_flake8_options* g:ale_python_flake8_use_global *g:ale_python_flake8_use_global* *b:ale_python_flake8_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` This variable controls whether or not ALE will search for flake8 in a virtualenv directory first. If this variable is set to `1`, then ALE will @@ -84,7 +148,7 @@ g:ale_python_isort_executable *g:ale_python_isort_executable* g:ale_python_isort_use_global *g:ale_python_isort_use_global* *b:ale_python_isort_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -96,6 +160,8 @@ The minimum supported version of mypy that ALE supports is v0.4.4. This is the first version containing the `--shadow-file` option ALE needs to be able to check for errors while you type. +`mypy` will be run from a detected project root, per |ale-python-root|. + g:ale_python_mypy_executable *g:ale_python_mypy_executable* *b:ale_python_mypy_executable* @@ -104,6 +170,8 @@ g:ale_python_mypy_executable *g:ale_python_mypy_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `mypy'`. + g:ale_python_mypy_ignore_invalid_syntax *g:ale_python_mypy_ignore_invalid_syntax* *b:ale_python_mypy_ignore_invalid_syntax* @@ -127,7 +195,7 @@ g:ale_python_mypy_options *g:ale_python_mypy_options* g:ale_python_mypy_use_global *g:ale_python_mypy_use_global* *b:ale_python_mypy_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -142,6 +210,8 @@ g:ale_python_prospector_executable *g:ale_python_prospector_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `prospector'`. + g:ale_python_prospector_options *g:ale_python_prospector_options* *b:ale_python_prospector_options* @@ -167,7 +237,7 @@ g:ale_python_prospector_options *g:ale_python_prospector_options* g:ale_python_prospector_use_global *g:ale_python_prospector_use_global* *b:ale_python_prospector_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -183,6 +253,8 @@ g:ale_python_pycodestyle_executable *g:ale_python_pycodestyle_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `pycodestyle'`. + g:ale_python_pycodestyle_options *g:ale_python_pycodestyle_options* *b:ale_python_pycodestyle_options* @@ -196,14 +268,39 @@ g:ale_python_pycodestyle_options *g:ale_python_pycodestyle_options* g:ale_python_pycodestyle_use_global *g:ale_python_pycodestyle_use_global* *b:ale_python_pycodestyle_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| =============================================================================== +pyflakes *ale-python-pyflakes* + + +g:ale_python_pyflakes_executable *g:ale_python_pyflakes_executable* + *b:ale_python_pyflakes_executable* + Type: |String| + Default: `'pyflakes'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pyflakes'`. + + +=============================================================================== pylint *ale-python-pylint* +g:ale_python_pylint_change_directory *g:ale_python_pylint_change_directory* + *b:ale_python_pylint_change_directory* + Type: |Number| + Default: `1` + + If set to `1`, ALE will switch to the directory the Python file being + checked with `pylint` is in before checking it. This helps `pylint` find + configuration files more easily. This option can be turned off if you want + to control the directory Python is executed from yourself. + + g:ale_python_pylint_executable *g:ale_python_pylint_executable* *b:ale_python_pylint_executable* Type: |String| @@ -211,6 +308,8 @@ g:ale_python_pylint_executable *g:ale_python_pylint_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `pylint'`. + g:ale_python_pylint_options *g:ale_python_pylint_options* *b:ale_python_pylint_options* @@ -235,7 +334,7 @@ g:ale_python_pylint_options *g:ale_python_pylint_options* g:ale_python_pylint_use_global *g:ale_python_pylint_use_global* *b:ale_python_pylint_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -243,6 +342,9 @@ g:ale_python_pylint_use_global *g:ale_python_pylint_use_global* =============================================================================== pyls *ale-python-pyls* +`pyls` will be run from a detected project root, per |ale-python-root|. + + g:ale_python_pyls_executable *g:ale_python_pyls_executable* *b:ale_python_pyls_executable* Type: |String| @@ -250,11 +352,13 @@ g:ale_python_pyls_executable *g:ale_python_pyls_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `pyls'`. + g:ale_python_pyls_use_global *g:ale_python_pyls_use_global* *b:ale_python_pyls_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -273,7 +377,7 @@ g:ale_python_yapf_executable *g:ale_python_yapf_executable* g:ale_python_yapf_use_global *g:ale_python_yapf_use_global* *b:ale_python_yapf_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-qml.txt b/doc/ale-qml.txt new file mode 100644 index 00000000..f6d715a1 --- /dev/null +++ b/doc/ale-qml.txt @@ -0,0 +1,18 @@ +=============================================================================== +ALE QML Integration *ale-qml-options* + + +=============================================================================== +qmlfmt *ale-qml-qmlfmt* + +g:ale_qml_qmlfmt_executable *g:ale_qml_qmlfmt_executable* + *b:ale_qml_qmlfmt_executable* + Type: |String| + Default: `'qmlfmt'` + + This variable can be set to change the path to qmlfmt. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: + diff --git a/doc/ale-reasonml.txt b/doc/ale-reasonml.txt index 36ddd75d..426d4c46 100644 --- a/doc/ale-reasonml.txt +++ b/doc/ale-reasonml.txt @@ -28,7 +28,7 @@ g:ale_reason_ols_executable *g:ale_reason_ols_executable* g:ale_reason_ols_use_global *g:ale_reason_ols_use_global* *b:ale_reason_ols_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` This variable can be set to `1` to always use the globally installed executable. See also |ale-integrations-local-executables|. diff --git a/doc/ale-ruby.txt b/doc/ale-ruby.txt index 94181ed4..85a3e137 100644 --- a/doc/ale-ruby.txt +++ b/doc/ale-ruby.txt @@ -87,4 +87,16 @@ g:ale_ruby_ruby_executable *g:ale_ruby_ruby_executable* =============================================================================== +rufo *ale-ruby-rufo* + +g:ale_ruby_rufo_executable *g:ale_ruby_rufo_executable* + *b:ale_ruby_rufo_executable* + Type: String + Default: `'rufo'` + + Override the invoked rufo binary. This is useful for running rufo from + binstubs or a bundle. + + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-rust.txt b/doc/ale-rust.txt index dad9ae66..f9afe17d 100644 --- a/doc/ale-rust.txt +++ b/doc/ale-rust.txt @@ -59,6 +59,26 @@ g:ale_rust_cargo_check_all_targets *g:ale_rust_cargo_check_all_targets* is used. See |g:ale_rust_cargo_use_check|, +g:ale_rust_cargo_check_tests *g:ale_rust_cargo_check_tests* + *b:ale_rust_cargo_check_tests* + Type: |Number| + Default: `0` + + When set to `1`, ALE will set the `--tests` option when `cargo check` + is used. This allows for linting of tests which are normally excluded. + See |g:ale_rust_cargo_use_check|, + + +g:ale_rust_cargo_check_examples *g:ale_rust_cargo_check_examples* + *b:ale_rust_cargo_check_examples* + Type: |Number| + Default: `0` + + When set to `1`, ALE will set the `--examples` option when `cargo check` + is used. This allows for linting of examples which are normally excluded. + See |g:ale_rust_cargo_use_check|, + + g:ale_rust_cargo_default_feature_behavior *g:ale_rust_cargo_default_feature_behavior* *b:ale_rust_cargo_default_feature_behavior* diff --git a/doc/ale-sass.txt b/doc/ale-sass.txt index 5465957a..08e9a381 100644 --- a/doc/ale-sass.txt +++ b/doc/ale-sass.txt @@ -16,7 +16,7 @@ g:ale_sass_stylelint_executable *g:ale_sass_stylelint_executable* g:ale_sass_stylelint_use_global *g:ale_sass_stylelint_use_global* *b:ale_sass_stylelint_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-scala.txt b/doc/ale-scala.txt index 9c9472f6..b5474f3b 100644 --- a/doc/ale-scala.txt +++ b/doc/ale-scala.txt @@ -3,6 +3,31 @@ ALE Scala Integration *ale-scala-options* =============================================================================== +scalafmt *ale-scala-scalafmt* + +If Nailgun is used, override `g:ale_scala_scalafmt_executable` like so: > + let g:ale_scala_scalafmt_executable = 'ng' + + +g:ale_scala_scalafmt_executable *g:ale_scala_scalafmt_executable* + *b:ale_scala_scalafmt_executable* + Type: |String| + Default: `'scalafmt'` + + Override the invoked `scalafmt` binary. This is useful for running `scalafmt` + with Nailgun. + + +g:ale_scala_scalafmt_options *g:ale_scala_scalafmt_options* + *b:ale_scala_scalafmt_options* + Type: |String| + Default: `''` + + A string containing additional options to pass to `'scalafmt'`, or + `'ng scalafmt'` if Nailgun is used. + + +=============================================================================== scalastyle *ale-scala-scalastyle* `scalastyle` requires a configuration file for a project to run. When no diff --git a/doc/ale-scss.txt b/doc/ale-scss.txt index 19695a76..14e6feb7 100644 --- a/doc/ale-scss.txt +++ b/doc/ale-scss.txt @@ -22,7 +22,7 @@ g:ale_scss_stylelint_executable *g:ale_scss_stylelint_executable* g:ale_scss_stylelint_use_global *g:ale_scss_stylelint_use_global* *b:ale_scss_stylelint_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-sh.txt b/doc/ale-sh.txt index 941dc59b..7557e522 100644 --- a/doc/ale-sh.txt +++ b/doc/ale-sh.txt @@ -3,6 +3,25 @@ ALE Shell Integration *ale-sh-options* =============================================================================== +sh-language-server *ale-sh-language-server* + +g:ale_sh_language_server_executable *g:ale_sh_language_server_executable* + *b:ale_sh_language_server_executable* + Type: |String| + Default: `'bash-language-server'` + + See |ale-integrations-local-executables| + + +g:ale_sh_language_server_use_global *g:ale_sh_language_server_use_global* + *b:ale_sh_language_server_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== shell *ale-sh-shell* g:ale_sh_shell_default_shell *g:ale_sh_shell_default_shell* diff --git a/doc/ale-stylus.txt b/doc/ale-stylus.txt index 59d90551..3e6ba906 100644 --- a/doc/ale-stylus.txt +++ b/doc/ale-stylus.txt @@ -24,7 +24,7 @@ g:ale_stylus_stylelint_options *g:ale_stylus_stylelint_options* g:ale_stylus_stylelint_use_global *g:ale_stylus_stylelint_use_global* *b:ale_stylus_stylelint_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-text.txt b/doc/ale-text.txt index a4dfa5e2..913d7e61 100644 --- a/doc/ale-text.txt +++ b/doc/ale-text.txt @@ -3,6 +3,36 @@ ALE Text Integration *ale-text-options* =============================================================================== +textlint *ale-text-textlint* + +The options for the textlint linter are global because it does not make +sense to have them specified on a per-language basis. + +g:ale_textlint_executable *g:ale_textlint_executable* + *b:ale_textlint_executable* + Type: |String| + Default: `'textlint'` + + See |ale-integrations-local-executables| + + +g:ale_textlint_options *g:ale_textlint_options* + *b:ale_textlint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to textlint. + + +g:ale_textlint_use_global *g:ale_textlint_use_global* + *b:ale_textlint_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== write-good *ale-text-write-good* See |ale-write-good-options| diff --git a/doc/ale-typescript.txt b/doc/ale-typescript.txt index d83a2dfe..1bccf5a3 100644 --- a/doc/ale-typescript.txt +++ b/doc/ale-typescript.txt @@ -59,7 +59,7 @@ g:ale_typescript_tslint_rules_dir *g:ale_typescript_tslint_rules_dir* g:ale_typescript_tslint_use_global *g:ale_typescript_tslint_use_global* *b:ale_typescript_tslint_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -91,7 +91,7 @@ g:ale_typescript_tsserver_config_path *g:ale_typescript_tsserver_config_path* g:ale_typescript_tsserver_use_global *g:ale_typescript_tsserver_use_global* *b:ale_typescript_tsserver_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` This variable controls whether or not ALE will search for a local path for tsserver first. If this variable is set to `1`, then ALE will always use the diff --git a/doc/ale-vim.txt b/doc/ale-vim.txt index 30ac3a16..772bad23 100644 --- a/doc/ale-vim.txt +++ b/doc/ale-vim.txt @@ -5,6 +5,14 @@ ALE Vim Integration *ale-vim-options* =============================================================================== vint *ale-vim-vint* +g:ale_vim_vint_executable *g:ale_vim_vint_executable* + *b:ale_vim_vint_executable* + Type: |String| + Default: `'vint'` + + This option can be set to change the executable path for Vint. + + g:ale_vim_vint_show_style_issues *g:ale_vim_vint_show_style_issues* *b:ale_vim_vint_show_style_issues* Type: |Number| diff --git a/doc/ale-yaml.txt b/doc/ale-yaml.txt index a902f25d..044d0b3d 100644 --- a/doc/ale-yaml.txt +++ b/doc/ale-yaml.txt @@ -31,7 +31,7 @@ g:ale_yaml_swaglint_executable *g:ale_yaml_swaglint_executable* g:ale_yaml_swaglint_use_global *g:ale_yaml_swaglint_use_global* *b:ale_yaml_swaglint_use_global* Type: |String| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale.txt b/doc/ale.txt index 6ace9d55..6c0b3ccf 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -13,6 +13,7 @@ CONTENTS *ale-contents* 5. Language Server Protocol Support.....|ale-lsp| 5.1 Completion........................|ale-completion| 5.2 Go To Definition..................|ale-go-to-definition| + 5.3 Find References...................|ale-find-references| 6. Global Options.......................|ale-options| 6.1 Highlights........................|ale-highlights| 6.2 Options for write-good Linter.....|ale-write-good-options| @@ -28,11 +29,14 @@ CONTENTS *ale-contents* clang-format........................|ale-c-clangformat| clangtidy...........................|ale-c-clangtidy| cppcheck............................|ale-c-cppcheck| + flawfinder..........................|ale-c-flawfinder| gcc.................................|ale-c-gcc| chef..................................|ale-chef-options| foodcritic..........................|ale-chef-foodcritic| clojure...............................|ale-clojure-options| joker...............................|ale-clojure-joker| + cloudformation........................|ale-cloudformation-options| + cfn-python-lint.....................|ale-cloudformation-cfn-python-lint| cmake.................................|ale-cmake-options| cmakelint...........................|ale-cmake-cmakelint| cpp...................................|ale-cpp-options| @@ -42,6 +46,8 @@ CONTENTS *ale-contents* clangtidy...........................|ale-cpp-clangtidy| cppcheck............................|ale-cpp-cppcheck| cpplint.............................|ale-cpp-cpplint| + cquery..............................|ale-cpp-cquery| + flawfinder..........................|ale-cpp-flawfinder| gcc.................................|ale-cpp-gcc| c#....................................|ale-cs-options| mcs.................................|ale-cs-mcs| @@ -57,6 +63,7 @@ CONTENTS *ale-contents* hadolint............................|ale-dockerfile-hadolint| elixir................................|ale-elixir-options| mix.................................|ale-elixir-mix| + mix_format..........................|ale-elixir-mix-format| dialyxir............................|ale-elixir-dialyxir| elm...................................|ale-elm-options| elm-format..........................|ale-elm-elm-format| @@ -80,6 +87,7 @@ CONTENTS *ale-contents* gobuild.............................|ale-go-gobuild| gofmt...............................|ale-go-gofmt| gometalinter........................|ale-go-gometalinter| + staticcheck.........................|ale-go-staticcheck| graphql...............................|ale-graphql-options| eslint..............................|ale-graphql-eslint| gqlint..............................|ale-graphql-gqlint| @@ -102,6 +110,7 @@ CONTENTS *ale-contents* checkstyle..........................|ale-java-checkstyle| javac...............................|ale-java-javac| google-java-format..................|ale-java-google-java-format| + pmd.................................|ale-java-pmd| javascript............................|ale-javascript-options| eslint..............................|ale-javascript-eslint| flow................................|ale-javascript-flow| @@ -133,8 +142,14 @@ CONTENTS *ale-contents* luac................................|ale-lua-luac| luacheck............................|ale-lua-luacheck| markdown..............................|ale-markdown-options| + mdl.................................|ale-markdown-mdl| prettier............................|ale-markdown-prettier| + textlint............................|ale-markdown-textlint| write-good..........................|ale-markdown-write-good| + mercury...............................|ale-mercury-options| + mmc.................................|ale-mercury-mmc| + nasm..................................|ale-nasm-options| + nasm................................|ale-nasm-nasm| nroff.................................|ale-nroff-options| write-good..........................|ale-nroff-write-good| objc..................................|ale-objc-options| @@ -147,6 +162,7 @@ CONTENTS *ale-contents* perl..................................|ale-perl-options| perl................................|ale-perl-perl| perlcritic..........................|ale-perl-perlcritic| + perltidy............................|ale-perl-perltidy| php...................................|ale-php-options| hack................................|ale-php-hack| hackfmt.............................|ale-php-hackfmt| @@ -156,10 +172,13 @@ CONTENTS *ale-contents* phpcs...............................|ale-php-phpcs| phpmd...............................|ale-php-phpmd| phpstan.............................|ale-php-phpstan| + php-cs-fixer........................|ale-php-php-cs-fixer| po....................................|ale-po-options| write-good..........................|ale-po-write-good| pod...................................|ale-pod-options| write-good..........................|ale-pod-write-good| + pony..................................|ale-pony-options| + ponyc...............................|ale-pony-ponyc| proto.................................|ale-proto-options| protoc-gen-lint.....................|ale-proto-protoc-gen-lint| pug...................................|ale-pug-options| @@ -168,14 +187,18 @@ CONTENTS *ale-contents* puppetlint..........................|ale-puppet-puppetlint| python................................|ale-python-options| autopep8............................|ale-python-autopep8| + black...............................|ale-python-black| flake8..............................|ale-python-flake8| isort...............................|ale-python-isort| mypy................................|ale-python-mypy| prospector..........................|ale-python-prospector| pycodestyle.........................|ale-python-pycodestyle| + pyflakes............................|ale-python-pyflakes| pylint..............................|ale-python-pylint| pyls................................|ale-python-pyls| yapf................................|ale-python-yapf| + qml...................................|ale-qml-options| + qmlfmt..............................|ale-qml-qmlfmt| r.....................................|ale-r-options| lintr...............................|ale-r-lintr| reasonml..............................|ale-reasonml-options| @@ -190,6 +213,7 @@ CONTENTS *ale-contents* reek................................|ale-ruby-reek| rubocop.............................|ale-ruby-rubocop| ruby................................|ale-ruby-ruby| + rufo................................|ale-ruby-rufo| rust..................................|ale-rust-options| cargo...............................|ale-rust-cargo| rls.................................|ale-rust-rls| @@ -198,11 +222,13 @@ CONTENTS *ale-contents* sass..................................|ale-sass-options| stylelint...........................|ale-sass-stylelint| scala.................................|ale-scala-options| + scalafmt............................|ale-scala-scalafmt| scalastyle..........................|ale-scala-scalastyle| scss..................................|ale-scss-options| prettier............................|ale-scss-prettier| stylelint...........................|ale-scss-stylelint| sh....................................|ale-sh-options| + sh-language-server..................|ale-sh-language-server| shell...............................|ale-sh-shell| shellcheck..........................|ale-sh-shellcheck| shfmt...............................|ale-sh-shfmt| @@ -225,6 +251,7 @@ CONTENTS *ale-contents* texinfo...............................|ale-texinfo-options| write-good..........................|ale-texinfo-write-good| text..................................|ale-text-options| + textlint............................|ale-text-textlint| write-good..........................|ale-text-write-good| thrift................................|ale-thrift-options| thrift..............................|ale-thrift-thrift| @@ -260,7 +287,7 @@ CONTENTS *ale-contents* ALE provides the means to run linters asynchronously in Vim in a variety of languages and tools. ALE sends the contents of buffers to linter programs using the |job-control| features available in Vim 8 and NeoVim. For Vim 8, -Vim must be compiled with the |job| and |channel| and |timer| features +Vim must be compiled with the |job| and |channel| and |timers| features as a minimum. ALE supports the following key features for linting: @@ -293,24 +320,26 @@ Notes: * API Blueprint: `drafter` * AsciiDoc: `alex`!!, `proselint`, `redpen`, `write-good` * Awk: `gawk` -* Bash: `shell` (-n flag), `shellcheck`, `shfmt` +* Bash: `language-server`, `shell` (-n flag), `shellcheck`, `shfmt` * Bourne Shell: `shell` (-n flag), `shellcheck`, `shfmt` -* C: `cppcheck`, `cpplint`!!, `gcc`, `clang`, `clangtidy`!!, `clang-format` -* C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `gcc` +* C: `cppcheck`, `cpplint`!!, `clang`, `clangtidy`!!, `clang-format`, `flawfinder`, `gcc` +* C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `cquery`, `flawfinder`, `gcc` * CUDA: `nvcc`!! * C#: `mcs`, `mcsc`!! * Chef: `foodcritic` * Clojure: `joker` +* CloudFormation: `cfn-python-lint` * CMake: `cmakelint` * CoffeeScript: `coffee`, `coffeelint` * Crystal: `crystal`!! * CSS: `csslint`, `prettier`, `stylelint` +* Cucumber: `cucumber` * Cython (pyrex filetype): `cython` * D: `dmd` * Dafny: `dafny`!! * Dart: `dartanalyzer`!!, `language_server` * Dockerfile: `hadolint` -* Elixir: `credo`, `dialyxir`, `dogma`!! +* Elixir: `credo`, `dialyxir`, `dogma`, `mix`!! * Elm: `elm-format, elm-make` * Erb: `erb`, `erubi`, `erubis` * Erlang: `erlc`, `SyntaxErl` @@ -320,14 +349,14 @@ Notes: * FusionScript: `fusion-lint` * Git Commit Messages: `gitlint` * GLSL: glslang, `glslls` -* Go: `gofmt`, `goimports`, `go vet`, `golint`, `gotype`, `gometalinter`!!, `go build`!!, `gosimple`!!, `staticcheck`!! +* Go: `gofmt`, `goimports`, `go vet`!!, `golint`, `gotype`!!, `gometalinter`!!, `go build`!!, `gosimple`!!, `staticcheck`!! * GraphQL: `eslint`, `gqlint`, `prettier` * Haml: `haml-lint` * Handlebars: `ember-template-lint` * Haskell: `brittany`, `ghc`, `stack-ghc`, `stack-build`!!, `ghc-mod`, `stack-ghc-mod`, `hlint`, `hdevtools`, `hfmt` * HTML: `alex`!!, `HTMLHint`, `proselint`, `tidy`, `write-good` * Idris: `idris` -* Java: `checkstyle`, `javac`, `google-java-format` +* Java: `checkstyle`, `javac`, `google-java-format`, `PMD` * JavaScript: `eslint`, `flow`, `jscs`, `jshint`, `prettier`, `prettier-eslint`, `prettier-standard`, `standard`, `xo` * JSON: `fixjson`, `jsonlint`, `jq`, `prettier` * Kotlin: `kotlinc`, `ktlint` @@ -337,42 +366,46 @@ Notes: * Lua: `luac`, `luacheck` * Mail: `alex`!!, `proselint`, `vale` * Make: `checkmake` -* Markdown: `alex`!!, `mdl`, `prettier`, `proselint`, `redpen`, `remark-lint`, `vale`, `write-good` +* Markdown: `alex`!!, `markdownlint`!!, `mdl`, `prettier`, `proselint`, `redpen`, `remark-lint`, `textlint`, `vale`, `write-good` * MATLAB: `mlint` +* Mercury: `mmc`!! +* NASM: `nasm`!! * Nim: `nim check`!! * nix: `nix-instantiate` * nroff: `alex`!!, `proselint`, `write-good` * Objective-C: `clang` * Objective-C++: `clang` * OCaml: `merlin` (see |ale-ocaml-merlin|), `ols` -* Perl: `perl -c`, `perl-critic` -* PHP: `hack`, `hackfmt`, `langserver`, `phan`, `php -l`, `phpcs`, `phpmd`, `phpstan`, `phpcbf` +* Perl: `perl -c`, `perl-critic`, `perltidy` +* PHP: `hack`, `hackfmt`, `langserver`, `phan`, `php -l`, `phpcs`, `phpmd`, `phpstan`, `phpcbf`, `php-cs-fixer` * PO: `alex`!!, `msgfmt`, `proselint`, `write-good` * Pod: `alex`!!, `proselint`, `write-good` +* Pony: `ponyc` * proto: `protoc-gen-lint` * Pug: `pug-lint` * Puppet: `puppet`, `puppet-lint` -* Python: `autopep8`, `flake8`, `isort`, `mypy`, `prospector`, `pycodestyle`, `pyls`, `pylint`!!, `yapf` +* Python: `autopep8`, `black`, `flake8`, `isort`, `mypy`, `prospector`, `pycodestyle`, `pyls`, `pylint`!!, `yapf` +* QML: `qmlfmt`, `qmllint` * R: `lintr` * ReasonML: `merlin`, `ols`, `refmt` * reStructuredText: `alex`!!, `proselint`, `redpen`, `rstcheck`, `vale`, `write-good` * Re:VIEW: `redpen` * RPM spec: `rpmlint` -* Ruby: `brakeman`, `rails_best_practices`!!, `reek`, `rubocop`, `ruby` +* Ruby: `brakeman`, `rails_best_practices`!!, `reek`, `rubocop`, `ruby`, `rufo` * Rust: `cargo`!!, `rls`, `rustc` (see |ale-integration-rust|), `rustfmt` * SASS: `sass-lint`, `stylelint` * SCSS: `prettier`, `sass-lint`, `scss-lint`, `stylelint` -* Scala: `scalac`, `scalastyle` +* Scala: `fsc`, `scalac`, `scalafmt`, `scalastyle` * Slim: `slim-lint` * SML: `smlnj` -* Solidity: `solhint, solium` +* Solidity: `solhint`, `solium` * Stylus: `stylelint` * SQL: `sqlint` * Swift: `swiftlint`, `swiftformat` * Tcl: `nagelfar`!! * Terraform: `tflint` * Texinfo: `alex`!!, `proselint`, `write-good` -* Text^: `alex`!!, `proselint`, `vale`, `write-good`, `redpen` +* Text^: `alex`!!, `proselint`, `redpen`, `textlint`, `vale`, `write-good` * Thrift: `thrift` * TypeScript: `eslint`, `prettier`, `tslint`, `tsserver`, `typecheck` * Verilog: `iverilog`, `verilator` @@ -449,14 +482,22 @@ the buffer-local options can be used with external plugins for reading Vim project configuration files. Buffer-local settings can also be used in ftplugin files for different filetypes. +ALE offers several options for controlling which linters are run. + +* Selecting linters to run. - |g:ale_linters| +* Aliasing filetypes for linters - |g:ale_linter_aliases| +* Only running linters you asked for. - |g:ale_linters_explicit| + =============================================================================== 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. +ALE can fix problems with files with the |ALEFix| command. |ALEFix| +accepts names of fixers to be applied as arguments. Alternatively, +when no arguments are provided, 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. @@ -606,6 +647,9 @@ 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|. +If you don't like some of the suggestions you see, you can filter them out +with |g:ale_completion_excluded_words| or |b:ale_completion_excluded_words|. + ------------------------------------------------------------------------------- 5.2 Go To Definition *ale-go-to-definition* @@ -618,6 +662,32 @@ information returned by LSP servers. The following commands are supported: |ALEGoToDefinitionInTab| - The same, but for opening the file in a new tab. +------------------------------------------------------------------------------- +5.3 Find References *ale-find-references* + +ALE supports finding references for symbols though any enabled LSP linters. +ALE will display a preview window showing the places where a symbol is +referenced in a codebase when a command is run. The following commands are +supported: + +|ALEFindReferences| - Find references for the word under the cursor. + + +------------------------------------------------------------------------------- +5.4 Hovering *ale-hover* + +ALE supports "hover" information for printing brief information about symbols +at the cursor taken from LSP linters. The following commands are supported: + +|ALEHover| - Print information about the symbol at the cursor. + +If |b:ale_set_balloons| is set to `1` and your version of Vim supports the +|balloon_show()| function, then "hover" information also show up when you move +the mouse over a symbol in a buffer. Diagnostic information will take priority +over hover information for balloons. If a line contains a problem, that +problem will be displayed in a balloon instead of hover information. + + =============================================================================== 6. Global Options *ale-options* @@ -635,7 +705,7 @@ g:airline#extensions#ale#enabled *g:airline#extensions#ale#enabled* g:ale_cache_executable_check_failures *g:ale_cache_executable_check_failures* Type: |Number| - Default: `0` + Default: undefined When set to `1`, ALE will cache failing executable checks for linters. By default, only executable checks which succeed will be cached. @@ -718,6 +788,24 @@ g:ale_completion_enabled *g:ale_completion_enabled* See |ale-completion| +g:ale_completion_excluded_words *g:ale_completion_excluded_words* + *b:ale_completion_excluded_words* + Type: |List| + Default: `[]` + + This option can be set to a list of |String| values for "words" to exclude + from completion results, as in the words for |complete-items|. The strings + will be matched exactly in a case-sensitive manner. (|==#|) + + This setting can be configured in ftplugin files with buffer variables, so + that different lists can be used for different filetypes. For example: > + + " In ~/.vim/ftplugin/typescript.vim + + " Don't suggest `it` or `describe` so we can use snippets for those words. + let b:ale_completion_excluded_words = ['it', 'describe'] +< + g:ale_completion_max_suggestions *g:ale_completion_max_suggestions* Type: |Number| @@ -817,20 +905,6 @@ g:ale_echo_msg_warning_str *g:ale_echo_msg_warning_str* The string used for `%severity%` for warnings. See |g:ale_echo_msg_format| -g:ale_emit_conflict_warnings *g:ale_emit_conflict_warnings* - - Type: |Number| - Default: `1` - - When set to `0`, ALE will not emit any warnings on startup about conflicting - plugins. ALE will probably not work if other linting plugins are installed. - - When this option is set to `1`, ALE will add its `after` directory to - |runtimepath| automatically, so the checks can be applied. Setting this - option to `0` before ALE is loaded will prevent ALE from modifying - |runtimepath|. - - g:ale_enabled *g:ale_enabled* *b:ale_enabled* @@ -865,6 +939,14 @@ g:ale_fixers *g:ale_fixers* `b:ale_fixers` can be set to a |List| of callbacks instead, which can be more convenient. + A special `'*'` key be used as a wildcard filetype for configuring fixers + for every other type of file. For example: > + + " Fix Python files with 'bar'. + " Don't fix 'html' files. + " Fix everything else with 'foo'. + let g:ale_fixers = {'python': ['bar'], 'html': [], '*': ['foo']} +< g:ale_fix_on_save *g:ale_fix_on_save* b:ale_fix_on_save *b:ale_fix_on_save* @@ -917,10 +999,10 @@ g:ale_keep_list_window_open *g:ale_keep_list_window_open* Type: |Number| Default: `0` - When set to `1`, this option will keep the loclist or quickfix windows - event after all warnings/errors have been removed for files. By default - the loclist or quicfix windows will be closed automatically when there - are no warnings or errors. + When set to `1`, this option will keep the loclist or quickfix windows event + after all warnings/errors have been removed for files. By default the + loclist or quickfix windows will be closed automatically when there are no + warnings or errors. See |g:ale_open_list| @@ -1040,8 +1122,13 @@ g:ale_linter_aliases *g:ale_linter_aliases* following values: > { - \ 'zsh': 'sh', + \ 'Dockerfile': 'dockerfile', \ 'csh': 'sh', + \ 'plaintex': 'tex', + \ 'systemverilog': 'verilog', + \ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'], + \ 'vimwiki': 'markdown', + \ 'zsh': 'sh', \} < For example, if you wish to map a new filetype `'foobar'` to run the `'php'` @@ -1145,6 +1232,42 @@ g:ale_linters_explicit *g:ale_linters_explicit* as possible, unless otherwise specified. +g:ale_linters_ignore *g:ale_linters_ignore* + *b:ale_linters_ignore* + + Type: |Dictionary| or |List| + Default: `{}` + + Linters to ignore. Commands for ignored linters will not be run, and + diagnostics for LSP linters will be ignored. (See |ale-lsp|) + + This setting can be set to a |Dictionary| mapping filetypes to linter names, + just like |g:ale_linters|, to list linters to ignore. Ignore lists will be + applied after everything else. > + + " Select flake8 and pylint, and ignore pylint, so only flake8 is run. + let g:ale_linters = {'python': ['flake8', 'pylint']} + let g:ale_linters_ignore = {'python': ['pylint']} +< + This setting can be set to simply a |List| of linter names, which is + especially more convenient when using the setting in ftplugin files for + particular buffers. > + + " The same as above, in a ftplugin/python.vim. + let b:ale_linters = ['flake8', 'pylint'] + let b:ale_linters_ignore = ['pylint'] +< + +g:ale_list_vertical *g:ale_list_vertical* + *b:ale_list_vertical* + Type: |Number| + Default: `0` + + When set to `1`, this will cause ALE to open any windows (loclist or + quickfix) vertically instead of horizontally (|vert| |lopen|) or (|vert| + |copen|) + + g:ale_loclist_msg_format *g:ale_loclist_msg_format* b:ale_loclist_msg_format *b:ale_loclist_msg_format* @@ -1190,7 +1313,7 @@ g:ale_max_signs *g:ale_max_signs* g:ale_maximum_file_size *g:ale_maximum_file_size* *b:ale_maximum_file_size* Type: |Number| - Default: `0` + Default: undefined A maximum file size in bytes for ALE to check. If set to any positive number, ALE will skip checking files larger than the given size. @@ -1211,10 +1334,21 @@ g:ale_open_list *g:ale_open_list* 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 - to `1`, in which case the window will be kept open until closed manually. + to `1`, in which case the window will be kept open when no problems are + found. The window size can be configured with |g:ale_list_window_size|. + Windows can be opened vertically with |g:ale_list_vertical|. + + If you want to close the loclist window automatically when the buffer is + closed, you can set up the following |autocmd| command: > + + augroup CloseLoclistWindowGroup + autocmd! + autocmd QuitPre * if empty(&buftype) | lclose | endif + augroup END +< g:ale_pattern_options *g:ale_pattern_options* @@ -1229,15 +1363,15 @@ g:ale_pattern_options *g:ale_pattern_options* let g:ale_pattern_options = { \ '\.foo\.js$': { \ 'ale_linters': ['eslint'], - \ 'ale_fixers: ['eslint'], + \ 'ale_fixers': ['eslint'], \ }, \} < See |b:ale_linters| and |b:ale_fixers| for information for those options. Filenames are matched with |match()|, and patterns depend on the |magic| - setting, unless prefixed with the special escape sequences like `'\v'`, - etc.The patterns can match any part of a filename. The absolute path of the + setting, unless prefixed with the special escape sequences like `'\v'`, etc. + The patterns can match any part of a filename. The absolute path of the filename will be used for matching, taken from `expand('%:p')`. The options for every match for the filename will be applied, with the @@ -1257,13 +1391,40 @@ g:ale_pattern_options_enabled *g:ale_pattern_options_enabled* g:ale_set_balloons *g:ale_set_balloons* + *b:ale_set_balloons* Type: |Number| - Default: `has('balloon_eval')` + Default: `(has('balloon_eval') && has('gui_running'))` + `|| (has('balloon_eval_term') && !has('gui_running'))` When this option is set to `1`, balloon messages will be displayed for - problems. Problems nearest to the cursor on the line the cursor is over will - be displayed. + problems or hover information if available. + + Problems nearest to the line the mouse cursor is over will be displayed. If + there are no problems to show, and one of the linters is an LSP linter + supporting "Hover" information, per |ale-hover|, then brief information + about the symbol under the cursor will be displayed in a balloon. + + `b:ale_set_balloons` can be set to `0` to disable balloons for a buffer. + Balloons cannot be enabled for a specific buffer when not initially enabled + globally. + + Balloons will not be shown when |g:ale_enabled| or |b:ale_enabled| is `0`. + + +g:ale_set_balloons_legacy_echo *g:ale_set_balloons_legacy_echo* + *b:ale_set_balloons_legacy_echo* + Type: |Number| + Default: undefined + + If set to `1`, moving your mouse over documents in Vim will make ALE ask + `tsserver` or `LSP` servers for information about the symbol where the mouse + cursor is, and print that information into Vim's echo line. This is an + option for supporting older versions of Vim which do not properly support + balloons in an asynchronous manner. + + If your version of Vim supports the |balloon_show| function, then this + option does nothing meaningful. g:ale_set_highlights *g:ale_set_highlights* @@ -1452,6 +1613,18 @@ g:ale_type_map *g:ale_type_map* Type maps can be set per-buffer with `b:ale_type_map`. +g:ale_use_global_executables *g:ale_use_global_executables* + + Type: |Number| + Default: not set + + This option can be set to change the default for all `_use_global` options. + This option must be set before ALE is loaded, preferably in a vimrc file. + + See |ale-integrations-local-executables| for more information on those + options. + + g:ale_virtualenv_dir_names *g:ale_virtualenv_dir_names* b:ale_virtualenv_dir_names *b:ale_virtualenv_dir_names* @@ -1639,7 +1812,7 @@ g:ale_writegood_options *g:ale_writegood_options* g:ale_writegood_use_global *g:ale_writegood_use_global* *b:ale_writegood_use_global* Type: |Number| - Default: `0` + Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| @@ -1673,6 +1846,9 @@ If you prefer to use global executables for those tools, set the relevant let g:ale_python_flake8_use_global = 1 < +|g:ale_use_global_executables| can be set to `1` in your vimrc file to make +ALE use global executables for all linters by default. + The option |g:ale_virtualenv_dir_names| controls the local virtualenv paths ALE will use to search for Python executables. @@ -1680,6 +1856,21 @@ ALE will use to search for Python executables. =============================================================================== 8. Commands/Keybinds *ale-commands* +ALEFindReferences *ALEFindReferences* + + Find references in the codebase for the symbol under the cursor using the + enabled LSP linters for the buffer. ALE will display a preview window + containing the results if some references are found. + + The window can be navigated using the usual Vim navigation commands. The + Enter key (`<CR>`) can be used to jump to a referencing location, or the `t` + key can be used to jump to the location in a new tab. + + You can jump back to the position you were at before going to a reference of + something with jump motions like CTRL-O. See |jump-motions|. + + A plug mapping `<Plug>(ale_find_references)` is defined for this command. + ALEFix *ALEFix* Fix problems with the current buffer. See |ale-fix| for more information. @@ -1700,6 +1891,9 @@ ALEGoToDefinition *ALEGoToDefinition* linters for the buffer. ALE will jump to a definition if an LSP server provides a location to jump to. Otherwise, ALE will do nothing. + You can jump back to the position you were at before going to the definition + of something with jump motions like CTRL-O. See |jump-motions|. + A plug mapping `<Plug>(ale_go_to_definition)` is defined for this command. @@ -1710,6 +1904,19 @@ ALEGoToDefinitionInTab *ALEGoToDefinitionInTab* A plug mapping `<Plug>(ale_go_to_definition_in_tab)` is defined for this command. + +ALEHover *ALEHover* + + Print brief information about the symbol under the cursor, taken from any + available LSP linters. There may be a small non-blocking delay before + information is printed. + + NOTE: In Vim 8, long messages will be shown in a preview window, as Vim 8 + does not support showing a prompt to press enter to continue for long + messages from asynchronous callbacks. + + A plug mapping `<Plug>(ale_hover)` is defined for this command. + *:ALELint* ALELint *ALELint* @@ -1799,9 +2006,13 @@ ALEDisableBuffer *ALEDisableBuffer* *:ALEDetail* ALEDetail *ALEDetail* - Show the full linter message for the current line in the preview window. - This will only have an effect on lines that contain a linter message. The - preview window can be easily closed with the `q` key. + Show the full linter message for the problem nearest to the cursor on the + given line in the preview window. The preview window can be easily closed + with the `q` key. If there is no message to show, the window will not be + opened. + + If a loclist item has a `detail` key set, the message for that key will be + preferred over `text`. See |ale-loclist-format|. A plug mapping `<Plug>(ale_detail)` is defined for this command. @@ -1823,6 +2034,9 @@ ALEInfoToClipboard *ALEInfoToClipboard* The command `:ALEInfoToClipboard` can be used to output ALEInfo directly to your clipboard. This might not work on every machine. + `:ALEInfoToFile` will write the ALE runtime information to a given filename. + The filename works just like |:w|. + ALEReset *ALEReset* ALEResetBuffer *ALEResetBuffer* @@ -1842,6 +2056,15 @@ ALEResetBuffer *ALEResetBuffer* |ALEDisableBuffer|. +ALEStopAllLSPs *ALEStopAllLSPs* + + `ALEStopAllLSPs` will close and stop all channels and jobs for all LSP-like + clients, including tsserver, remove all of the data stored for them, and + delete all of the problems found for them, updating every linted buffer. + + This command can be used when LSP clients mess up and need to be restarted. + + =============================================================================== 9. API *ale-api* @@ -1894,6 +2117,13 @@ ale#engine#GetLoclist(buffer) *ale#engine#GetLoclist()* be copied before applying |map()| or |filter()|. +ale#engine#IsCheckingBuffer(buffer) *ale#engine#IsCheckingBuffer()* + + Given a buffer number, returns `1` when ALE is busy checking that buffer. + + This function can be used for status lines, tab names, etc. + + ale#engine#ManageFile(buffer, filename) *ale#engine#ManageFile()* Given a buffer number for a buffer currently running some linting tasks @@ -1973,6 +2203,9 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* the following manner: *ale-loclist-format* `text` - This error message is required. + `detail` - An optional, more descriptive message. + This message can be displayed with the |ALEDetail| + command instead of the message for `text`, if set. `lnum` - The line number is required. Any strings will be automatically converted to numbers by using `str2nr()`. @@ -2010,6 +2243,13 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* key is also set with a valid number for some other buffer. `vcol` - Defaults to `0`. + + If set to `1`, ALE will convert virtual column + positions for `col` and `end_col` to byte column + positions. If the buffer is changed in-between + checking it and displaying the results, the + calculated byte column positions will probably be + wrong. `type` - Defaults to `'E'`. `nr` - Defaults to `-1`. @@ -2128,14 +2368,21 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* 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. + When this argument is not empty, only one of either + `language` or `language_callback` must be defined, + and `project_root_callback` must be defined. LSP linters handle diagnostics automatically, so the `callback` argument must not be defined. - `project_callback` A |String| or |Funcref| for a callback function + An optional `completion_filter` callback may be + defined for filtering completion results. + + An optional `initialization_options` or + `initialization_options_callback` may be defined to + pass initialization options to the LSP. + + `project_root_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 @@ -2145,13 +2392,29 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* This argument must only be set if the `lsp` argument is also set to a non-empty string. + `language` A |String| representing the name of the language + being checked. This string will be sent to the LSP to + tell it what type of language is being checked. + + 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. + This option can be used instead of `language` if a + linter can check multiple languages. + + `completion_filter` A |String| or |Funcref| for a callback function + accepting a buffer number and a completion item. + + The completion item will be a |Dictionary| following + the Language Server Protocol `CompletionItem` + interface as described in the specification, + available online here: + https://microsoft.github.io/language-server-protocol `aliases` A |List| of aliases for the linter name. @@ -2160,6 +2423,17 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* setting can make it easier to guess the linter name by offering a few alternatives. + `initialization_options` A |Dictionary| of initialization options for LSPs. + This will be fed (as JSON) to the LSP in the + initialize command. + + `initialization_options_callback` + A |String| or |Funcref| for a callback function + accepting a buffer number. A |Dictionary| should be + returned for initialization options to pass the LSP. + This can be used in place of `initialization_options` + when more complicated processing is needed. + Only one of `command`, `command_callback`, or `command_chain` should be specified. `command_callback` is generally recommended when a command string needs to be generated dynamically, or any global options are used. @@ -2203,6 +2477,7 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* is not desired, the |ale#engine#EscapeCommandPart()| function can be used to replace those characters to avoid formatting issues. + *ale-linter-loading-behavior* *ale-linter-loading-behaviour* Linters for ALE will be loaded by searching |runtimepath| in the following @@ -2215,6 +2490,21 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* containing `.` characters will be split into individual parts, and files will be loaded for each filetype between the `.` characters. + Linters can be defined from vimrc and other files as long as this function + is loaded first. For example, the following code will define a Hello World + linter in vimrc in Vim 8: > + + " Plugins have to be loaded first. + " If you are using a plugin manager, run that first. + packloadall + + call ale#linter#Define('vim', { + \ 'name': 'echo-test', + \ 'executable': 'echo', + \ 'command': 'echo hello world', + \ 'callback': {buffer, lines -> map(lines, '{"text": v:val, "lnum": 1}')}, + \}) +< ale#linter#Get(filetype) *ale#linter#Get()* @@ -2230,6 +2520,13 @@ ale#linter#Get(filetype) *ale#linter#Get()* components. +ale#linter#PreventLoading(filetype) *ale#linter#PreventLoading()* + + Given a `filetype`, prevent any more linters from being loaded from + |runtimepath| for that filetype. This function can be called from vimrc or + similar to prevent ALE from loading linters. + + ale#statusline#Count(buffer) *ale#statusline#Count()* Given the number of a buffer which may have problems, return a |Dictionary| @@ -2244,11 +2541,26 @@ ale#statusline#Count(buffer) *ale#statusline#Count()* `total` -> The total number of problems. +b:ale_linted *b:ale_linted* + + `b:ale_linted` is set to the number of times a buffer has been checked by + ALE after all linters for one lint cycle have finished checking a buffer. + This variable may not be defined until ALE first checks a buffer, so it + should be accessed with |get()| or |getbufvar()|. For example: > + + " Print a message indicating how many times ALE has checked this buffer. + echo 'ALE has checked this buffer ' . get(b:, 'ale_linted') . ' time(s).' + " Print 'checked' using getbufvar() if a buffer has been checked. + echo getbufvar(bufnr(''), 'ale_linted', 0) > 0 ? 'checked' : 'not checked' +< + ALELintPre *ALELintPre-autocmd* ALELintPost *ALELintPost-autocmd* +ALEFixPre *ALEFixPre-autocmd* +ALEFixPost *ALEFixPost-autocmd* - These |User| autocommands are triggered before and after every lint cycle. - They can be used to update statuslines, send notifications, etc. + These |User| autocommands are triggered before and after every lint or fix + cycle. They can be used to update statuslines, send notifications, etc. The autocmd commands are run with |:silent|, so |:unsilent| is required for echoing messges. @@ -2270,7 +2582,15 @@ ALELintPost *ALELintPost-autocmd* autocmd User ALELintPre let s:ale_running = 1 | redrawstatus autocmd User ALELintPost let s:ale_running = 0 | redrawstatus augroup end + < +ALEJobStarted *ALEJobStarted-autocmd* + + This |User| autocommand is triggered immediately after a job is successfully + run. This provides better accuracy for checking linter status with + |ale#engine#IsCheckingBuffer()| over |ALELintPre-autocmd|, which is actually + triggered before any linters are executed. + =============================================================================== 10. Special Thanks *ale-special-thanks* diff --git a/ftplugin/ale-preview-selection.vim b/ftplugin/ale-preview-selection.vim new file mode 100644 index 00000000..d77b4f98 --- /dev/null +++ b/ftplugin/ale-preview-selection.vim @@ -0,0 +1,16 @@ +" Close the ALEPreviewWindow window with the q key. +noremap <buffer> q :q!<CR> +" Disable some keybinds for the selection window. +noremap <buffer> v <NOP> +noremap <buffer> i <NOP> +noremap <buffer> I <NOP> +noremap <buffer> <C-q> <NOP> +noremap <buffer> <C-v> <NOP> +noremap <buffer> <S-v> <NOP> +noremap <buffer> a <NOP> +noremap <buffer> A <NOP> +noremap <buffer> o <NOP> +noremap <buffer> O <NOP> +" Keybinds for opening selection items. +noremap <buffer> <CR> :call ale#preview#OpenSelectionInBuffer()<CR> +noremap <buffer> t :call ale#preview#OpenSelectionInTab()<CR> diff --git a/plugin/ale.vim b/plugin/ale.vim index d75d33b0..2ea30a30 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -32,14 +32,10 @@ if !s:has_features finish endif -" This flag can be set to 0 to disable emitting conflict warnings. -let g:ale_emit_conflict_warnings = get(g:, 'ale_emit_conflict_warnings', 1) - -if g:ale_emit_conflict_warnings -\&& match(&runtimepath, '[/\\]ale[/\\]after') < 0 - " Add the after directory to the runtimepath - " This is only done if the after directory isn't already in runtimepath - let &runtimepath .= ',' . expand('<sfile>:p:h:h') . '/after' +" remove in 2.0 +if has('nvim') && !has('nvim-0.2.0') && !get(g:, 'ale_use_deprecated_neovim') + execute 'echom ''ALE support for NeoVim versions below 0.2.0 is deprecated.''' + execute 'echom ''Use `let g:ale_use_deprecated_neovim = 1` to silence this warning for now.''' endif " Set this flag so that other plugins can use it, like airline. @@ -68,9 +64,9 @@ let g:ale_filetype_blacklist = [ \] " This Dictionary configures which linters are enabled for which languages. -call ale#Set('linters', {}) +let g:ale_linters = get(g:, 'ale_linters', {}) " This option can be changed to only enable explicitly selected linters. -call ale#Set('linters_explicit', 0) +let g:ale_linters_explicit = get(g:, 'ale_linters_explicit', 0) " This Dictionary configures which functions will be used for fixing problems. let g:ale_fixers = get(g:, 'ale_fixers', {}) @@ -101,7 +97,8 @@ let g:ale_lint_on_save = get(g:, 'ale_lint_on_save', 1) " This flag can be set to 1 to enable linting when the filetype is changed. let g:ale_lint_on_filetype_changed = get(g:, 'ale_lint_on_filetype_changed', 1) -call ale#Set('fix_on_save', 0) +" This flag can be set to 1 to enable automatically fixing files on save. +let g:ale_fix_on_save = get(g:, 'ale_fix_on_save', 0) " This flag may be set to 0 to disable ale. After ale is loaded, :ALEToggle " should be used instead. @@ -112,75 +109,26 @@ let g:ale_enabled = get(g:, 'ale_enabled', 1) let g:ale_set_loclist = get(g:, 'ale_set_loclist', 1) let g:ale_set_quickfix = get(g:, 'ale_set_quickfix', 0) -" This flag dictates if ale open the configured loclist -let g:ale_open_list = get(g:, 'ale_open_list', 0) - -" This flag dictates if ale keeps open loclist even if there is no error in loclist -let g:ale_keep_list_window_open = get(g:, 'ale_keep_list_window_open', 0) - -" The window size to set for the quickfix and loclist windows -call ale#Set('list_window_size', 10) - " This flag can be set to 0 to disable setting signs. " This is enabled by default only if the 'signs' feature exists. let g:ale_set_signs = get(g:, 'ale_set_signs', has('signs')) -" This flag can be set to some integer to control the maximum number of signs -" that ALE will set. -let g:ale_max_signs = get(g:, 'ale_max_signs', -1) - -" This flag can be set to 1 to enable changing the sign column colors when -" there are errors. -call ale#Set('change_sign_column_color', 0) " This flag can be set to 0 to disable setting error highlights. let g:ale_set_highlights = get(g:, 'ale_set_highlights', has('syntax')) -" These variables dictate what sign is used to indicate errors and warnings. -call ale#Set('sign_error', '>>') -call ale#Set('sign_style_error', g:ale_sign_error) -call ale#Set('sign_warning', '--') -call ale#Set('sign_style_warning', g:ale_sign_warning) -call ale#Set('sign_info', g:ale_sign_warning) - -" This variable sets an offset which can be set for sign IDs. -" This ID can be changed depending on what IDs are set for other plugins. -" The dummy sign will use the ID exactly equal to the offset. -let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) - -" This flag can be set to 1 to keep sign gutter always open -let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0) - -" A string format for the echoed message -call ale#Set('echo_msg_format', '%code: %%s') -" The same for the loclist. -call ale#Set('loclist_msg_format', g:ale_echo_msg_format) - -" Strings used for severity in the echoed message -let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error') -let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info') -let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') - " This flag can be set to 0 to disable echoing when the cursor moves. let g:ale_echo_cursor = get(g:, 'ale_echo_cursor', 1) -" Controls the milliseconds delay before echoing a message. -let g:ale_echo_delay = get(g:, 'ale_echo_delay', 10) " This flag can be set to 0 to disable balloon support. -call ale#Set('set_balloons', has('balloon_eval')) - -" A deprecated setting for ale#statusline#Status() -" See :help ale#statusline#Count() for getting status reports. -let g:ale_statusline_format = get(g:, 'ale_statusline_format', -\ ['%d error(s)', '%d warning(s)', 'OK'] +let g:ale_set_balloons = get(g:, 'ale_set_balloons', +\ (has('balloon_eval') && has('gui_running')) +\ || (has('balloon_eval_term') && !has('gui_running')) \) " This flag can be set to 0 to disable warnings for trailing whitespace -call ale#Set('warn_about_trailing_whitespace', 1) +let g:ale_warn_about_trailing_whitespace = get(g:, 'ale_warn_about_trailing_whitespace', 1) " This flag can be set to 0 to disable warnings for trailing blank lines -call ale#Set('warn_about_trailing_blank_lines', 1) - -" A flag for controlling the maximum size of the command history to store. -let g:ale_max_buffer_history_size = get(g:, 'ale_max_buffer_history_size', 20) +let g:ale_warn_about_trailing_blank_lines = get(g:, 'ale_warn_about_trailing_blank_lines', 1) " A flag for enabling or disabling the command history. let g:ale_history_enabled = get(g:, 'ale_history_enabled', 1) @@ -188,29 +136,8 @@ let g:ale_history_enabled = get(g:, 'ale_history_enabled', 1) " A flag for storing the full output of commands in the history. let g:ale_history_log_output = get(g:, 'ale_history_log_output', 1) -" A flag for caching failed executable checks. -" This is off by default, because it will cause problems. -call ale#Set('cache_executable_check_failures', 0) - -" A dictionary mapping regular expression patterns to arbitrary buffer -" variables to be set. Useful for configuration ALE based on filename -" patterns. -call ale#Set('pattern_options', {}) -call ale#Set('pattern_options_enabled', !empty(g:ale_pattern_options)) - -" A maximum file size for checking for errors. -call ale#Set('maximum_file_size', 0) - -" Remapping of linter problems. -call ale#Set('type_map', {}) - " Enable automatic completion with LSP servers and tsserver -call ale#Set('completion_enabled', 0) -call ale#Set('completion_delay', 100) -call ale#Set('completion_max_suggestions', 50) - -" A setting for wrapping commands. -call ale#Set('command_wrapper', '') +let g:ale_completion_enabled = get(g:, 'ale_completion_enabled', 0) if g:ale_set_balloons call ale#balloon#Enable() @@ -241,6 +168,8 @@ command! -bar ALEToggleBuffer :call ale#toggle#ToggleBuffer(bufnr('')) command! -bar ALEEnableBuffer :call ale#toggle#EnableBuffer(bufnr('')) command! -bar ALEDisableBuffer :call ale#toggle#DisableBuffer(bufnr('')) command! -bar ALEResetBuffer :call ale#toggle#ResetBuffer(bufnr('')) +" A command to stop all LSP-like clients, including tsserver. +command! -bar ALEStopAllLSPs :call ale#lsp#reset#StopAllLSPs() " A command for linting manually. command! -bar ALELint :call ale#Queue(0, 'lint_file') @@ -249,9 +178,11 @@ command! -bar ALELint :call ale#Queue(0, 'lint_file') command! -bar ALEInfo :call ale#debugging#Info() " The same, but copy output to your clipboard. command! -bar ALEInfoToClipboard :call ale#debugging#InfoToClipboard() +" Copy ALE information to a file. +command! -bar -nargs=1 ALEInfoToFile :call ale#debugging#InfoToFile(<f-args>) " Fix problems in files. -command! -bar ALEFix :call ale#fix#Fix() +command! -bar -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '', <f-args>) " Suggest registered functions to use for fixing problems. command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype) @@ -259,6 +190,13 @@ command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype) command! -bar ALEGoToDefinition :call ale#definition#GoTo({}) command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in_tab': 1}) +" Find references for tsserver and LSP +command! -bar ALEFindReferences :call ale#references#Find() + +" Get information for the cursor. +command! -bar ALEHover :call ale#hover#Show(bufnr(''), getcurpos()[1], + \ getcurpos()[2], {}) + " <Plug> mappings for commands nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return> nnoremap <silent> <Plug>(ale_previous_wrap) :ALEPreviousWrap<Return> @@ -279,25 +217,39 @@ nnoremap <silent> <Plug>(ale_detail) :ALEDetail<Return> nnoremap <silent> <Plug>(ale_fix) :ALEFix<Return> nnoremap <silent> <Plug>(ale_go_to_definition) :ALEGoToDefinition<Return> nnoremap <silent> <Plug>(ale_go_to_definition_in_tab) :ALEGoToDefinitionInTab<Return> +nnoremap <silent> <Plug>(ale_find_references) :ALEFindReferences<Return> +nnoremap <silent> <Plug>(ale_hover) :ALEHover<Return> " Set up autocmd groups now. -call ale#toggle#InitAuGroups() +call ale#events#Init() " Housekeeping augroup ALECleanupGroup autocmd! " Clean up buffers automatically when they are unloaded. - autocmd BufDelete * call ale#engine#Cleanup(str2nr(expand('<abuf>'))) + autocmd BufDelete * if exists('*ale#engine#Cleanup') | call ale#engine#Cleanup(str2nr(expand('<abuf>'))) | endif autocmd QuitPre * call ale#events#QuitEvent(str2nr(expand('<abuf>'))) augroup END " Backwards Compatibility +" remove in 2.0 function! ALELint(delay) abort + if !get(g:, 'ale_deprecation_ale_lint', 0) + execute 'echom ''ALELint() is deprecated, use ale#Queue() instead.''' + let g:ale_deprecation_ale_lint = 1 + endif + call ale#Queue(a:delay) endfunction +" remove in 2.0 function! ALEGetStatusLine() abort + if !get(g:, 'ale_deprecation_ale_get_status_line', 0) + execute 'echom ''ALEGetStatusLine() is deprecated.''' + let g:ale_deprecation_ale_get_status_line = 1 + endif + return ale#statusline#Status() endfunction @@ -1,4 +1,7 @@ -#!/bin/bash -eu +#!/usr/bin/env bash + +set -e +set -u # Author: w0rp <devw0rp@gmail.com> # diff --git a/syntax/ale-preview-selection.vim b/syntax/ale-preview-selection.vim new file mode 100644 index 00000000..879ba096 --- /dev/null +++ b/syntax/ale-preview-selection.vim @@ -0,0 +1,11 @@ +if exists('b:current_syntax') + finish +endif + +syn match alePreviewSelectionFilename /\v^([a-zA-Z]?:?[^:]+)/ +syn match alPreviewNumber /\v:\d+:\d+$/ + +hi def link alePreviewSelectionFilename String +hi def link alePreviewNumber Number + +let b:current_syntax = 'ale-preview-selection' diff --git a/test/command_callback/mix_paths/wrapped_project/mix.exs b/test/command_callback/mix_paths/wrapped_project/mix.exs new file mode 100644 index 00000000..d2d855e6 --- /dev/null +++ b/test/command_callback/mix_paths/wrapped_project/mix.exs @@ -0,0 +1 @@ +use Mix.Config diff --git a/test/elm-test-files/app/node_modules/.bin/elm-make b/test/command_callback/php_paths/project-with-php-cs-fixer/test.php index e69de29b..e69de29b 100644 --- a/test/elm-test-files/app/node_modules/.bin/elm-make +++ b/test/command_callback/php_paths/project-with-php-cs-fixer/test.php diff --git a/test/command_callback/php_paths/project-with-php-cs-fixer/vendor/bin/php-cs-fixer b/test/command_callback/php_paths/project-with-php-cs-fixer/vendor/bin/php-cs-fixer new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php_paths/project-with-php-cs-fixer/vendor/bin/php-cs-fixer diff --git a/test/command_callback/php_paths/project-without-php-cs-fixer/test.php b/test/command_callback/php_paths/project-without-php-cs-fixer/test.php new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php_paths/project-without-php-cs-fixer/test.php diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/black.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/black.exe new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_virtualenv/env/Scripts/black.exe diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/black b/test/command_callback/python_paths/with_virtualenv/env/bin/black new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_virtualenv/env/bin/black diff --git a/test/command_callback/scala_paths/dummy.scala b/test/command_callback/scala_paths/dummy.scala new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/scala_paths/dummy.scala diff --git a/test/command_callback/test_c_clang_command_callbacks.vader b/test/command_callback/test_c_clang_command_callbacks.vader index d6fc8ca6..2f6d4dd0 100644 --- a/test/command_callback/test_c_clang_command_callbacks.vader +++ b/test/command_callback/test_c_clang_command_callbacks.vader @@ -30,10 +30,10 @@ Execute(The executable should be configurable): Execute(The executable should be used in the command): AssertEqual \ ale#Escape('clang') . b:command_tail, - \ ale_linters#c#clang#GetCommand(bufnr('')) + \ ale_linters#c#clang#GetCommand(bufnr(''), []) let b:ale_c_clang_executable = 'foobar' AssertEqual \ ale#Escape('foobar') . b:command_tail, - \ ale_linters#c#clang#GetCommand(bufnr('')) + \ ale_linters#c#clang#GetCommand(bufnr(''), []) diff --git a/test/command_callback/test_c_flawfinder_command_callbacks.vader b/test/command_callback/test_c_flawfinder_command_callbacks.vader new file mode 100644 index 00000000..38a602dd --- /dev/null +++ b/test/command_callback/test_c_flawfinder_command_callbacks.vader @@ -0,0 +1,51 @@ +Before: + Save g:ale_c_flawfinder_executable + Save g:ale_c_flawfinder_options + Save g:ale_c_flawfinder_minlevel + + unlet! g:ale_c_flawfinder_executable + unlet! b:ale_c_flawfinder_executable + unlet! g:ale_c_flawfinder_options + unlet! b:ale_c_flawfinder_options + unlet! g:ale_c_flawfinder_minlevel + unlet! b:ale_c_flawfinder_minlevel + + runtime ale_linters/c/flawfinder.vim + +After: + unlet! b:ale_c_flawfinder_executable + unlet! b:ale_c_flawfinder_options + unlet! b:ale_c_flawfinder_minlevel + + Restore + call ale#linter#Reset() + +Execute(The flawfinder command should be correct): + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) + +Execute(The minlevel of flawfinder should be configurable): + let b:ale_c_flawfinder_minlevel = 8 + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=8 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) + +Execute(Additional flawfinder options should be configurable): + let b:ale_c_flawfinder_options = ' --foobar' + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --foobar --minlevel=1 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) + +Execute(The flawfinder exectable should be configurable): + let b:ale_c_flawfinder_executable = 'foo/bar' + + AssertEqual + \ ale#Escape('foo/bar') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) diff --git a/test/command_callback/test_c_gcc_command_callbacks.vader b/test/command_callback/test_c_gcc_command_callbacks.vader index 8038f410..3557576e 100644 --- a/test/command_callback/test_c_gcc_command_callbacks.vader +++ b/test/command_callback/test_c_gcc_command_callbacks.vader @@ -30,10 +30,10 @@ Execute(The executable should be configurable): Execute(The executable should be used in the command): AssertEqual \ ale#Escape('gcc') . b:command_tail, - \ ale_linters#c#gcc#GetCommand(bufnr('')) + \ ale_linters#c#gcc#GetCommand(bufnr(''), []) let b:ale_c_gcc_executable = 'foobar' AssertEqual \ ale#Escape('foobar') . b:command_tail, - \ ale_linters#c#gcc#GetCommand(bufnr('')) + \ ale_linters#c#gcc#GetCommand(bufnr(''), []) diff --git a/test/command_callback/test_cargo_command_callbacks.vader b/test/command_callback/test_cargo_command_callbacks.vader index 9c06f27d..f674645f 100644 --- a/test/command_callback/test_cargo_command_callbacks.vader +++ b/test/command_callback/test_cargo_command_callbacks.vader @@ -1,11 +1,15 @@ Before: Save g:ale_rust_cargo_use_check Save g:ale_rust_cargo_check_all_targets + Save g:ale_rust_cargo_check_tests + Save g:ale_rust_cargo_check_examples Save g:ale_rust_cargo_default_feature_behavior Save g:ale_rust_cargo_include_features unlet! g:ale_rust_cargo_use_check - unlet! g:ale_cargo_check_all_targets + unlet! g:ale_rust_cargo_check_all_targets + unlet! g:ale_rust_cargo_check_tests + unlet! g:ale_rust_cargo_check_examples unlet! g:ale_rust_cargo_default_feature_behavior unlet! g:ale_rust_cargo_include_features @@ -119,6 +123,38 @@ Execute(--all-targets should be used when g:ale_rust_cargo_check_all_targets is AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) +Execute(--tests should be used when g:ale_rust_cargo_check_tests is set to 1): + let g:ale_rust_cargo_check_tests = 1 + + AssertEqual + \ 'cargo check --tests' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo check --tests' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + +Execute(--examples should be used when g:ale_rust_cargo_check_examples is set to 1): + let g:ale_rust_cargo_check_examples = 1 + + AssertEqual + \ 'cargo check --examples' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo check --examples' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + Execute(--no-default-features should be used when g:ale_rust_cargo_default_feature_behavior is none): let g:ale_rust_cargo_default_feature_behavior = 'none' diff --git a/test/command_callback/test_cpp_clang_command_callbacks.vader b/test/command_callback/test_cpp_clang_command_callbacks.vader index 67d6898c..8c671115 100644 --- a/test/command_callback/test_cpp_clang_command_callbacks.vader +++ b/test/command_callback/test_cpp_clang_command_callbacks.vader @@ -30,10 +30,10 @@ Execute(The executable should be configurable): Execute(The executable should be used in the command): AssertEqual \ ale#Escape('clang++') . b:command_tail, - \ ale_linters#cpp#clang#GetCommand(bufnr('')) + \ ale_linters#cpp#clang#GetCommand(bufnr(''), []) let b:ale_cpp_clang_executable = 'foobar' AssertEqual \ ale#Escape('foobar') . b:command_tail, - \ ale_linters#cpp#clang#GetCommand(bufnr('')) + \ ale_linters#cpp#clang#GetCommand(bufnr(''), []) diff --git a/test/command_callback/test_cpp_cquery_command_callbacks.vader b/test/command_callback/test_cpp_cquery_command_callbacks.vader new file mode 100644 index 00000000..89a3e225 --- /dev/null +++ b/test/command_callback/test_cpp_cquery_command_callbacks.vader @@ -0,0 +1,48 @@ +" Author: Ben Falconer <ben@falconers.me.uk> +" Description: A language server for C++ + +Before: + Save g:ale_cpp_cquery_executable + Save g:ale_cpp_cquery_cache_directory + + unlet! g:ale_cpp_cquery_executable + unlet! b:ale_cpp_cquery_executable + unlet! g:ale_cpp_cquery_cache_directory + unlet! b:ale_cpp_cquery_cache_directory + + runtime ale_linters/cpp/cquery.vim + +After: + Restore + unlet! b:ale_cpp_cquery_executable + unlet! b:ale_cpp_cquery_cache_directory + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'cquery', ale_linters#cpp#cquery#GetExecutable(bufnr('')) + + let b:ale_cpp_cquery_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cpp#cquery#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('cquery'), + \ ale_linters#cpp#cquery#GetCommand(bufnr('')) + + let b:ale_cpp_cquery_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar'), + \ ale_linters#cpp#cquery#GetCommand(bufnr('')) + +Execute(The cache directory should be configurable): + AssertEqual + \ {'cacheDirectory': expand('$HOME/.cache/cquery')}, + \ ale_linters#cpp#cquery#GetInitializationOptions(bufnr('')) + + let b:ale_cpp_cquery_cache_directory = '/foo/bar' + + AssertEqual + \ {'cacheDirectory': '/foo/bar'}, + \ ale_linters#cpp#cquery#GetInitializationOptions(bufnr('')) diff --git a/test/command_callback/test_cpp_flawfinder_command_callbacks.vader b/test/command_callback/test_cpp_flawfinder_command_callbacks.vader new file mode 100644 index 00000000..8769ec96 --- /dev/null +++ b/test/command_callback/test_cpp_flawfinder_command_callbacks.vader @@ -0,0 +1,51 @@ +Before: + Save g:ale_cpp_flawfinder_executable + Save g:ale_cpp_flawfinder_options + Save g:ale_cpp_flawfinder_minlevel + + unlet! g:ale_cpp_flawfinder_executable + unlet! b:ale_cpp_flawfinder_executable + unlet! g:ale_cpp_flawfinder_options + unlet! b:ale_cpp_flawfinder_options + unlet! g:ale_cpp_flawfinder_minlevel + unlet! b:ale_cpp_flawfinder_minlevel + + runtime ale_linters/cpp/flawfinder.vim + +After: + unlet! b:ale_cpp_flawfinder_executable + unlet! b:ale_cpp_flawfinder_options + unlet! b:ale_cpp_flawfinder_minlevel + + Restore + call ale#linter#Reset() + +Execute(The flawfinder command should be correct): + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) + +Execute(The minlevel of flawfinder should be configurable): + let b:ale_cpp_flawfinder_minlevel = 8 + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=8 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) + +Execute(Additional flawfinder options should be configurable): + let b:ale_cpp_flawfinder_options = ' --foobar' + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --foobar --minlevel=1 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) + +Execute(The flawfinder exectable should be configurable): + let b:ale_cpp_flawfinder_executable = 'foo/bar' + + AssertEqual + \ ale#Escape('foo/bar') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cpp_gcc_command_callbacks.vader b/test/command_callback/test_cpp_gcc_command_callbacks.vader index 9ab4d5cb..7abebf4c 100644 --- a/test/command_callback/test_cpp_gcc_command_callbacks.vader +++ b/test/command_callback/test_cpp_gcc_command_callbacks.vader @@ -30,10 +30,10 @@ Execute(The executable should be configurable): Execute(The executable should be used in the command): AssertEqual \ ale#Escape('gcc') . b:command_tail, - \ ale_linters#cpp#gcc#GetCommand(bufnr('')) + \ ale_linters#cpp#gcc#GetCommand(bufnr(''), []) let b:ale_cpp_gcc_executable = 'foobar' AssertEqual \ ale#Escape('foobar') . b:command_tail, - \ ale_linters#cpp#gcc#GetCommand(bufnr('')) + \ ale_linters#cpp#gcc#GetCommand(bufnr(''), []) diff --git a/test/command_callback/test_cucumber_command_callback.vader b/test/command_callback/test_cucumber_command_callback.vader new file mode 100644 index 00000000..d09a5712 --- /dev/null +++ b/test/command_callback/test_cucumber_command_callback.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/ruby/rubocop.vim + call ale#test#SetDirectory('/testplugin/test/') + +After: + Restore + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(Should require the nearest features dir, if one is found): + call ale#test#SetFilename('cucumber_fixtures/features/cuke.feature') + + AssertEqual + \ 'cucumber --dry-run --quiet --strict --format=json ' + \ . '-r ' . ale#Escape(ale#path#Simplify(g:dir . '/cucumber_fixtures/features/')) . ' %t', + \ ale_linters#cucumber#cucumber#GetCommand(bufnr('')) + +Execute(Should require nothing if no features dir is found): + call ale#test#SetFilename('something/without/a/features/dir') + + AssertEqual + \ 'cucumber --dry-run --quiet --strict --format=json ' + \ . ' %t', + \ ale_linters#cucumber#cucumber#GetCommand(bufnr('')) diff --git a/test/command_callback/test_elixir_mix_command_callbacks.vader b/test/command_callback/test_elixir_mix_command_callbacks.vader new file mode 100644 index 00000000..67785881 --- /dev/null +++ b/test/command_callback/test_elixir_mix_command_callbacks.vader @@ -0,0 +1,37 @@ +Before: + runtime ale_linters/elixir/mix.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + + let g:project_root = ale#path#Simplify(g:dir . '/mix_paths/wrapped_project') + + let g:env_prefix = has('win32') + \ ? 'set MIX_BUILD_PATH=TEMP && ' + \ : 'MIX_BUILD_PATH=TEMP ' + + + function! GetCommand(buffer) abort + let l:command = ale_linters#elixir#mix#GetCommand(a:buffer) + + return substitute(l:command, 'MIX_BUILD_PATH=[^ ]\+', 'MIX_BUILD_PATH=TEMP', '') + endfunction + +After: + Restore + + unlet! g:env_prefix + unlet! g:project_root + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + + delfunction GetCommand + +Execute(The default mix command should be correct): + call ale#test#SetFilename('mix_paths/wrapped_project/lib/app.ex') + + AssertEqual + \ GetCommand(bufnr('')), + \ ale#path#CdString(g:project_root) + \ . g:env_prefix + \ . 'mix compile %s' diff --git a/test/command_callback/test_flake8_command_callback.vader b/test/command_callback/test_flake8_command_callback.vader index 1784b81f..1cc50836 100644 --- a/test/command_callback/test_flake8_command_callback.vader +++ b/test/command_callback/test_flake8_command_callback.vader @@ -2,11 +2,13 @@ Before: Save g:ale_python_flake8_executable Save g:ale_python_flake8_options Save g:ale_python_flake8_use_global + Save g:ale_python_flake8_change_directory unlet! g:ale_python_flake8_executable unlet! g:ale_python_flake8_args unlet! g:ale_python_flake8_options unlet! g:ale_python_flake8_use_global + unlet! g:ale_python_flake8_change_directory let b:bin_dir = has('win32') ? 'Scripts' : 'bin' @@ -33,20 +35,30 @@ Execute(The flake8 callbacks should return the correct default values): \ ale#Escape('flake8') . ' --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) " Try with older versions. call ale#semver#ResetVersionCache() AssertEqual - \ ale#Escape('flake8') . ' --format=default -', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('flake8') . ' --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) +Execute(The option for disabling changing directories should work): + let g:ale_python_flake8_change_directory = 0 + + AssertEqual + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) + Execute(The flake8 command callback should let you set options): let g:ale_python_flake8_options = '--some-option' AssertEqual - \ ale#Escape('flake8') + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('flake8') \ . ' --some-option --format=default' \ . ' --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.4']) @@ -54,7 +66,8 @@ Execute(The flake8 command callback should let you set options): call ale#semver#ResetVersionCache() AssertEqual - \ ale#Escape('flake8') + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('flake8') \ . ' --some-option --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) @@ -68,7 +81,8 @@ Execute(You should be able to set a custom executable and it should be escaped): \ ale#Escape('executable with spaces') . ' --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ ale#Escape('executable with spaces') + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('executable with spaces') \ . ' --format=default' \ . ' --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) @@ -87,7 +101,8 @@ Execute(The flake8 callbacks should detect virtualenv directories): \ ale#Escape(b:executable) . ' --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ ale#Escape(b:executable) + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape(b:executable) \ . ' --format=default --stdin-display-name %s -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) @@ -140,7 +155,8 @@ Execute(Using `python -m flake8` should be supported for running flake8): \ ale#Escape('python') . ' -m flake8 --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ ale#Escape('python') . ' -m flake8 --some-option --format=default -', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('python') . ' -m flake8 --some-option --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) call ale#semver#ResetVersionCache() @@ -155,27 +171,14 @@ Execute(Using `python -m flake8` should be supported for running flake8): \ ale#Escape('python') . ' -m flake8 --version', \ ale_linters#python#flake8#VersionCheck(bufnr('')) AssertEqual - \ ale#Escape('python') . ' -m flake8 --some-option --format=default -', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('python') . ' -m flake8 --some-option --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) -Execute(Using `python2 -m flake8` should be supported with the old args option): - let g:ale_python_flake8_executable = 'python2' - let g:ale_python_flake8_args = '-m flake8' - let g:ale_python_flake8_use_global = 0 - - unlet! g:ale_python_flake8_options +Execute(Setting executable to 'pipenv' appends 'run flake8'): + let g:ale_python_flake8_executable = 'path/to/pipenv' - call ale#linter#Reset() - runtime ale_linters/python/flake8.vim - - silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') - - AssertEqual - \ 'python2', - \ ale_linters#python#flake8#GetExecutable(bufnr('')) AssertEqual - \ ale#Escape('python2') . ' -m flake8 --version', - \ ale_linters#python#flake8#VersionCheck(bufnr('')) - AssertEqual - \ ale#Escape('python2') . ' -m flake8 --format=default -', - \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run flake8 --format=default --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) diff --git a/test/command_callback/test_fsc_command_callback.vader b/test/command_callback/test_fsc_command_callback.vader new file mode 100644 index 00000000..451fa108 --- /dev/null +++ b/test/command_callback/test_fsc_command_callback.vader @@ -0,0 +1,17 @@ +Before: + runtime ale_linters/scala/fsc.vim + +After: + call ale#linter#Reset() + +Given scala(An empty Scala file): +Execute(The default executable and command should be correct): + AssertEqual 'fsc', ale_linters#scala#fsc#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('fsc') . ' -Ystop-after:parser %t', + \ ale_linters#scala#fsc#GetCommand(bufnr('')) + +Given scala.sbt(An empty SBT file): +Execute(fsc should not be run for sbt files): + AssertEqual '', ale_linters#scala#fsc#GetExecutable(bufnr('')) + AssertEqual '', ale_linters#scala#fsc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_gawk_command_callback.vader b/test/command_callback/test_gawk_command_callback.vader new file mode 100644 index 00000000..ae128fe5 --- /dev/null +++ b/test/command_callback/test_gawk_command_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_awk_gawk_executable + Save g:ale_awk_gawk_options + unlet! g:ale_awk_gawk_executable + unlet! g:ale_awk_gawk_options + + runtime ale_linters/awk/gawk.vim + +After: + Restore + unlet! b:command_tail + unlet! b:ale_awk_gawk_executable + unlet! b:ale_awk_gawk_options + + call ale#linter#Reset() + +Execute(The executable should be used in the command): + AssertEqual + \ 'gawk' + \ . " --source 'BEGIN { exit } END { exit 1 }'" + \ . ' ' . '-f %t --lint /dev/null', + \ ale_linters#awk#gawk#GetCommand(bufnr('')) + + let b:ale_awk_gawk_executable = '/other/gawk' + + AssertEqual + \ '/other/gawk' + \ . " --source 'BEGIN { exit } END { exit 1 }'" + \ . ' ' . '-f %t --lint /dev/null', + \ ale_linters#awk#gawk#GetCommand(bufnr('')) + + let b:ale_awk_gawk_executable = 'gawk' + let b:ale_awk_gawk_options = '--something' + + AssertEqual + \ 'gawk' + \ . " --source 'BEGIN { exit } END { exit 1 }'" + \ . ' --something' + \ . ' ' . '-f %t --lint /dev/null', + \ ale_linters#awk#gawk#GetCommand(bufnr('')) diff --git a/test/command_callback/test_gosimple_command_callback.vader b/test/command_callback/test_gosimple_command_callback.vader new file mode 100644 index 00000000..a0b1f468 --- /dev/null +++ b/test/command_callback/test_gosimple_command_callback.vader @@ -0,0 +1,12 @@ +Before: + runtime ale_linters/go/gosimple.vim + call ale#test#SetFilename('../go_files/testfile2.go') + +After: + call ale#linter#Reset() + +Execute(The default gosimple command should be correct): + AssertEqual 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ' gosimple .', + \ ale_linters#go#gosimple#GetCommand(bufnr('')) + diff --git a/test/command_callback/test_gotype_command_callback.vader b/test/command_callback/test_gotype_command_callback.vader index f95e8423..4fba3344 100644 --- a/test/command_callback/test_gotype_command_callback.vader +++ b/test/command_callback/test_gotype_command_callback.vader @@ -5,15 +5,11 @@ Before: After: call ale#linter#Reset() - -Execute(The gotype callback should include other files from the directory but exclude the file itself): - let dir = expand('#' . bufnr('') . ':p:h') - AssertEqual - \ "gotype %t ". ale#Escape(ale#path#Simplify(dir . "/testfile.go")), +Execute(The default gotype command should be correct): + AssertEqual 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ' gotype .', \ ale_linters#go#gotype#GetCommand(bufnr('')) Execute(The gotype callback should ignore test files): call ale#test#SetFilename('bla_test.go') - AssertEqual - \ 0, - \ ale_linters#go#gotype#GetCommand(bufnr('')) + AssertEqual 0, ale_linters#go#gotype#GetCommand(bufnr('')) diff --git a/test/command_callback/test_govet_command_callback.vader b/test/command_callback/test_govet_command_callback.vader new file mode 100644 index 00000000..a9b29605 --- /dev/null +++ b/test/command_callback/test_govet_command_callback.vader @@ -0,0 +1,16 @@ +Before: + runtime ale_linters/go/govet.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The default command should be correct): + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ' go vet .', + \ ale_linters#go#govet#GetCommand(bufnr('')) diff --git a/test/command_callback/test_javac_command_callback.vader b/test/command_callback/test_javac_command_callback.vader index 7823d030..b0d0e7c7 100644 --- a/test/command_callback/test_javac_command_callback.vader +++ b/test/command_callback/test_javac_command_callback.vader @@ -1,9 +1,11 @@ Before: call ale#test#SetDirectory('/testplugin/test/command_callback') + Save g:ale_java_javac_executable Save g:ale_java_javac_options Save g:ale_java_javac_classpath + unlet! g:ale_java_javac_executable unlet! g:ale_java_javac_options unlet! g:ale_java_javac_classpath @@ -28,7 +30,8 @@ Before: call ale#test#SetFilename('dummy.java') - let g:prefix = 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + let g:prefix = 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('javac') . ' -Xlint' After: call ale#test#RestoreDirectory() @@ -57,6 +60,17 @@ Execute(The javac callback should use g:ale_java_javac_classpath correctly): \ . ' -d TEMP %t', \ GetCommand([]) +Execute(The executable should be configurable): + let g:ale_java_javac_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#java#javac#GetExecutable(bufnr('')) + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('foobar') . ' -Xlint' + \ . ' -d TEMP %t', + \ GetCommand([]) + Execute(The javac callback should include discovered classpaths): AssertEqual \ g:prefix @@ -120,7 +134,7 @@ Execute(The javac callback should detect source directories): call ale#engine#InitBufferInfo(bufnr('')) AssertEqual - \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' . ale#Escape('javac') . ' -Xlint' \ . ' -sourcepath ' . ale#Escape( \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/') \ ) @@ -133,7 +147,7 @@ Execute(The javac callback should combine detected source directories and classp call ale#engine#InitBufferInfo(bufnr('')) AssertEqual - \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' . ale#Escape('javac') . ' -Xlint' \ . ' -cp ' . ale#Escape(join(['/foo/bar.jar', '/xyz/abc.jar'], g:cp_sep)) \ . ' -sourcepath ' . ale#Escape( \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/') @@ -164,7 +178,7 @@ Execute(The javac callback should include src/test/java for test paths): call ale#engine#InitBufferInfo(bufnr('')) AssertEqual - \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' . ale#Escape('javac') . ' -Xlint' \ . ' -sourcepath ' . ale#Escape(join([ \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/'), \ ale#path#Simplify(g:dir . '/java_paths/src/test/java/'), @@ -178,7 +192,7 @@ Execute(The javac callback should include src/main/jaxb when available): call ale#engine#InitBufferInfo(bufnr('')) AssertEqual - \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' . ale#Escape('javac') . ' -Xlint' \ . ' -sourcepath ' . ale#Escape(join([ \ ale#path#Simplify(g:dir . '/java_paths_with_jaxb/src/main/java/'), \ ale#path#Simplify(g:dir . '/java_paths_with_jaxb/src/main/jaxb/'), diff --git a/test/command_callback/test_lintr_command_callback.vader b/test/command_callback/test_lintr_command_callback.vader index e655328b..2f7dfb1d 100644 --- a/test/command_callback/test_lintr_command_callback.vader +++ b/test/command_callback/test_lintr_command_callback.vader @@ -16,7 +16,7 @@ After: Execute(The default lintr command should be correct): AssertEqual \ 'cd ' . ale#Escape(getcwd()) . ' && ' - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint(cache = FALSE, commandArgs(TRUE), ' \ . 'with_defaults())') @@ -28,7 +28,7 @@ Execute(The lintr options should be configurable): AssertEqual \ 'cd ' . ale#Escape(getcwd()) . ' && ' - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint(cache = FALSE, commandArgs(TRUE), ' \ . 'with_defaults(object_usage_linter = NULL))') @@ -40,7 +40,7 @@ Execute(If the lint_package flag is set, lintr::lint_package should be called): AssertEqual \ 'cd ' . ale#Escape(getcwd()) . ' && ' - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint_package(cache = FALSE, ' \ . 'linters = with_defaults())') diff --git a/test/command_callback/test_markdown_mdl_command_callback.vader b/test/command_callback/test_markdown_mdl_command_callback.vader new file mode 100644 index 00000000..2fb9a206 --- /dev/null +++ b/test/command_callback/test_markdown_mdl_command_callback.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_markdown_mdl_executable + Save g:ale_markdown_mdl_options + + unlet! g:ale_markdown_mdl_executable + unlet! g:ale_markdown_mdl_options + + runtime ale_linters/markdown/mdl.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual ale_linters#markdown#mdl#GetExecutable(bufnr('')), 'mdl' + AssertEqual + \ ale_linters#markdown#mdl#GetCommand(bufnr('')), + \ ale#Escape('mdl') + +Execute(The executable and options should be configurable): + let g:ale_markdown_mdl_executable = 'foo bar' + let g:ale_markdown_mdl_options = '--wat' + + AssertEqual ale_linters#markdown#mdl#GetExecutable(bufnr('')), 'foo bar' + AssertEqual + \ ale_linters#markdown#mdl#GetCommand(bufnr('')), + \ ale#Escape('foo bar') . ' --wat' + +Execute(Setting bundle appends 'exec mdl'): + let g:ale_markdown_mdl_executable = 'path to/bundle' + + AssertEqual + \ ale#Escape('path to/bundle') . ' exec mdl', + \ ale_linters#markdown#mdl#GetCommand(bufnr('')) diff --git a/test/command_callback/test_mercury_mmc_command_callback.vader b/test/command_callback/test_mercury_mmc_command_callback.vader new file mode 100644 index 00000000..7ebf49c3 --- /dev/null +++ b/test/command_callback/test_mercury_mmc_command_callback.vader @@ -0,0 +1,42 @@ +Before: + Save g:ale_mercury_mmc_executable + Save g:ale_mercury_mmc_options + + unlet! g:ale_mercury_mmc_executable + unlet! b:ale_mercury_mmc_executable + unlet! g:ale_mercury_mmc_options + unlet! b:ale_mercury_mmc_options + + runtime ale_linters/mercury/mmc.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + unlet! b:ale_mercury_mmc_executable + unlet! b:ale_mercury_mmc_options + + Restore + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mmc --errorcheck-only --make --output-compile-error-lines 100 dummy', + \ + \ ale_linters#mercury#mmc#GetCommand(bufnr('')) + +Execute(The executable should be configurable): + let b:ale_mercury_mmc_executable = 'foo' + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'foo --errorcheck-only --make --output-compile-error-lines 100 dummy', + \ + \ ale_linters#mercury#mmc#GetCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_mercury_mmc_options = '--bar' + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mmc --errorcheck-only --bar dummy', + \ + \ ale_linters#mercury#mmc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_mypy_command_callback.vader b/test/command_callback/test_mypy_command_callback.vader index 6a0add52..0fb4cc54 100644 --- a/test/command_callback/test_mypy_command_callback.vader +++ b/test/command_callback/test_mypy_command_callback.vader @@ -95,3 +95,12 @@ Execute(You should able able to use the global mypy instead): \ . ' --show-column-numbers ' \ . '--shadow-file %s %t %s', \ ale_linters#python#mypy#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run mypy'): + let g:ale_python_mypy_executable = 'path/to/pipenv' + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run mypy' + \ . ' --show-column-numbers --shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) diff --git a/test/command_callback/test_nasm_nasm_command_callbacks.vader b/test/command_callback/test_nasm_nasm_command_callbacks.vader new file mode 100644 index 00000000..5053e536 --- /dev/null +++ b/test/command_callback/test_nasm_nasm_command_callbacks.vader @@ -0,0 +1,52 @@ +Before: + Save g:ale_nasm_nasm_executable + Save g:ale_nasm_nasm_options + + unlet! g:ale_nasm_nasm_executable + unlet! b:ale_nasm_nasm_executable + unlet! g:ale_nasm_nasm_options + unlet! b:ale_nasm_nasm_options + + runtime ale_linters/nasm/nasm.vim + + let b:command_tail = + \ ' -X gnu -I ' . ale#Escape(getcwd() . (has('win32') ? '\' : '/')) . ' %s' + let b:command_tail_opt = + \ ' -X gnu -I ' . ale#Escape(getcwd() . (has('win32') ? '\' : '/')) . ' -w+orphan-labels %s' + +After: + Restore + unlet! b:command_tail + unlet! b:command_tail_opt + unlet! b:ale_nasm_nasm_executable + unlet! b:ale_nasm_nasm_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'nasm', ale_linters#nasm#nasm#GetExecutable(bufnr('')) + + let b:ale_nasm_nasm_executable = '/opt/nasm/nasm' + + AssertEqual '/opt/nasm/nasm', ale_linters#nasm#nasm#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('nasm') . b:command_tail, + \ ale_linters#nasm#nasm#GetCommand(bufnr('')) + + let b:ale_nasm_nasm_executable = '~/nasm' + + AssertEqual + \ ale#Escape('~/nasm') . b:command_tail, + \ ale_linters#nasm#nasm#GetCommand(bufnr('')) + +Execute(The options should be configurable): + AssertEqual '', ale_linters#nasm#nasm#GetOptions(bufnr('')) + let b:ale_nasm_nasm_options = '-w-macro-params' + AssertEqual '-w-macro-params', ale_linters#nasm#nasm#GetOptions(bufnr('')) + +Execute(The options should be used in command): + let b:ale_nasm_nasm_options = '-w+orphan-labels' + AssertEqual + \ ale#Escape('nasm') . b:command_tail_opt, + \ ale_linters#nasm#nasm#GetCommand(bufnr('')) diff --git a/test/command_callback/test_perlcritic_command_callback.vader b/test/command_callback/test_perlcritic_command_callback.vader index 6507868b..e8d8cc18 100644 --- a/test/command_callback/test_perlcritic_command_callback.vader +++ b/test/command_callback/test_perlcritic_command_callback.vader @@ -30,14 +30,18 @@ Execute(The command should be correct with g:ale_perl_perlcritic_showrules off): let b:ale_perl_perlcritic_showrules = 0 AssertEqual - \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m\n'' --nocolor', + \ ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m\n') + \ . ' --nocolor', \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) Execute(The command should be correct with g:ale_perl_perlcritic_showrules on): let b:ale_perl_perlcritic_showrules = 1 AssertEqual - \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m [%p]\n'' --nocolor', + \ ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m [%p]\n') + \ . ' --nocolor', \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) Execute(The command search for the profile file when set): @@ -46,7 +50,9 @@ Execute(The command search for the profile file when set): let b:readme_path = ale#path#Simplify(expand('%:p:h:h:h') . '/README.md') AssertEqual - \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m\n'' --nocolor' + \ ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m\n') + \ . ' --nocolor' \ . ' --profile ' . ale#Escape(b:readme_path), \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) @@ -54,6 +60,8 @@ Execute(Extra options should be set appropriately): let b:ale_perl_perlcritic_options = 'beep boop' AssertEqual - \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m\n'' --nocolor' + \ ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m\n') + \ . ' --nocolor' \ . ' beep boop', \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) diff --git a/test/command_callback/test_php_langserver_callbacks.vader b/test/command_callback/test_php_langserver_callbacks.vader index 0dc30630..ebcae0e7 100644 --- a/test/command_callback/test_php_langserver_callbacks.vader +++ b/test/command_callback/test_php_langserver_callbacks.vader @@ -42,9 +42,6 @@ Execute(Vendor executables should be detected): \ )), \ ale_linters#php#langserver#GetCommand(bufnr('')) -Execute(The language string should be correct): - AssertEqual 'php', ale_linters#php#langserver#GetLanguage(bufnr('')) - Execute(The project path should be correct for .git directories): call ale#test#SetFilename('php-langserver-project/test.php') call mkdir(g:dir . '/.git') diff --git a/test/command_callback/test_pony_ponyc_command_callbacks.vader b/test/command_callback/test_pony_ponyc_command_callbacks.vader new file mode 100644 index 00000000..7acbfa9d --- /dev/null +++ b/test/command_callback/test_pony_ponyc_command_callbacks.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_pony_ponyc_options + + unlet! g:ale_pony_ponyc_options + unlet! b:ale_pony_ponyc_options + + runtime ale_linters/pony/ponyc.vim + +After: + Restore + unlet! b:ale_pony_ponyc_options + call ale#linter#Reset() + +Execute(The options should be used in the command): + AssertEqual + \ ale#Escape('ponyc') . ' --pass paint', + \ ale_linters#pony#ponyc#GetCommand(bufnr('')) + + let b:ale_pony_ponyc_options = 'foobar' + + AssertEqual + \ ale#Escape('ponyc') . ' foobar', + \ ale_linters#pony#ponyc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_prospector_command_callback.vader b/test/command_callback/test_prospector_command_callback.vader new file mode 100644 index 00000000..04cd58ed --- /dev/null +++ b/test/command_callback/test_prospector_command_callback.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_python_mypy_executable + Save g:ale_python_mypy_options + + unlet! g:ale_python_mypy_executable + unlet! g:ale_python_mypy_options + + runtime ale_linters/python/prospector.vim + +After: + Restore + + unlet! b:executable + + call ale#linter#Reset() + +Execute(Setting executable to 'pipenv' appends 'run prospector'): + let g:ale_python_prospector_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run prospector' + \ . ' --messages-only --absolute-paths --zero-exit --output-format json %s', + \ ale_linters#python#prospector#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pycodestyle_command_callback.vader b/test/command_callback/test_pycodestyle_command_callback.vader index 5b309e19..90b07a24 100644 --- a/test/command_callback/test_pycodestyle_command_callback.vader +++ b/test/command_callback/test_pycodestyle_command_callback.vader @@ -25,3 +25,10 @@ Execute(The pycodestyle executable should be configurable): AssertEqual ale#Escape('~/.local/bin/pycodestyle') . ' -', \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pycodestyle'): + let g:ale_python_pycodestyle_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run pycodestyle -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pyflakes_command_callback.vader b/test/command_callback/test_pyflakes_command_callback.vader index e8486ca8..491432e9 100644 --- a/test/command_callback/test_pyflakes_command_callback.vader +++ b/test/command_callback/test_pyflakes_command_callback.vader @@ -47,3 +47,10 @@ Execute(You should be able to override the pyflakes virtualenv lookup): AssertEqual ale#Escape('pyflakes') . ' %t', \ ale_linters#python#pyflakes#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pyflakes'): + let g:ale_python_pyflakes_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run pyflakes %t', + \ ale_linters#python#pyflakes#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pylint_command_callback.vader b/test/command_callback/test_pylint_command_callback.vader index 1ff8e354..f8cb5800 100644 --- a/test/command_callback/test_pylint_command_callback.vader +++ b/test/command_callback/test_pylint_command_callback.vader @@ -2,10 +2,12 @@ Before: Save g:ale_python_pylint_executable Save g:ale_python_pylint_options Save g:ale_python_pylint_use_global + Save g:ale_python_pylint_change_directory unlet! g:ale_python_pylint_executable unlet! g:ale_python_pylint_options unlet! g:ale_python_pylint_use_global + unlet! g:ale_python_pylint_change_directory runtime ale_linters/python/pylint.vim call ale#test#SetDirectory('/testplugin/test/command_callback') @@ -28,6 +30,18 @@ Execute(The pylint callbacks should return the correct default values): \ 'pylint', \ ale_linters#python#pylint#GetExecutable(bufnr('')) AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('pylint') . ' ' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(The option for disabling changing directories should work): + let g:ale_python_pylint_change_directory = 0 + + AssertEqual + \ 'pylint', + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + \ + AssertEqual \ ale#Escape('pylint') . ' ' . b:command_tail, \ ale_linters#python#pylint#GetCommand(bufnr('')) @@ -38,14 +52,16 @@ Execute(The pylint executable should be configurable, and escaped properly): \ 'executable with spaces', \ ale_linters#python#pylint#GetExecutable(bufnr('')) AssertEqual - \ ale#Escape('executable with spaces') . ' ' . b:command_tail, + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('executable with spaces') . ' ' . b:command_tail, \ ale_linters#python#pylint#GetCommand(bufnr('')) Execute(The pylint command callback should let you set options): let g:ale_python_pylint_options = '--some-option' AssertEqual - \ ale#Escape('pylint') . ' --some-option' . b:command_tail, + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('pylint') . ' --some-option' . b:command_tail, \ ale_linters#python#pylint#GetCommand(bufnr('')) Execute(The pylint callbacks shouldn't detect virtualenv directories where they don't exist): @@ -55,7 +71,8 @@ Execute(The pylint callbacks shouldn't detect virtualenv directories where they \ 'pylint', \ ale_linters#python#pylint#GetExecutable(bufnr('')) AssertEqual - \ ale#Escape('pylint') . ' ' . b:command_tail, + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('pylint') . ' ' . b:command_tail, \ ale_linters#python#pylint#GetCommand(bufnr('')) Execute(The pylint callbacks should detect virtualenv directories): @@ -70,7 +87,8 @@ Execute(The pylint callbacks should detect virtualenv directories): \ ale_linters#python#pylint#GetExecutable(bufnr('')) AssertEqual - \ ale#Escape(b:executable) . ' ' . b:command_tail, + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape(b:executable) . ' ' . b:command_tail, \ ale_linters#python#pylint#GetCommand(bufnr('')) Execute(You should able able to use the global pylint instead): @@ -81,5 +99,15 @@ Execute(You should able able to use the global pylint instead): \ 'pylint', \ ale_linters#python#pylint#GetExecutable(bufnr('')) AssertEqual - \ ale#Escape('pylint') . ' ' . b:command_tail, + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('pylint') . ' ' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pylint'): + let g:ale_python_pylint_executable = 'path/to/pipenv' + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run pylint' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s', \ ale_linters#python#pylint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pyls_command_callback.vader b/test/command_callback/test_pyls_command_callback.vader index 06ea718f..4bef4742 100644 --- a/test/command_callback/test_pyls_command_callback.vader +++ b/test/command_callback/test_pyls_command_callback.vader @@ -47,3 +47,10 @@ Execute(You should be able to override the pyls virtualenv lookup): AssertEqual ale#Escape('pyls'), \ ale_linters#python#pyls#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pyls'): + let g:ale_python_pyls_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run pyls', + \ ale_linters#python#pyls#GetCommand(bufnr('')) diff --git a/test/command_callback/test_qmlfmt_command_callback.vader b/test/command_callback/test_qmlfmt_command_callback.vader new file mode 100644 index 00000000..263caea7 --- /dev/null +++ b/test/command_callback/test_qmlfmt_command_callback.vader @@ -0,0 +1,18 @@ +Before: + runtime ale_linters/qml/qmlfmt.vim + +After: + let g:ale_qml_qmlfmt_executable = 'qmlfmt' + + call ale#linter#Reset() + +Execute(The qml qmlfmt command callback should return the correct default string): + AssertEqual ale#Escape('qmlfmt') . ' -e', + \ join(split(ale_linters#qml#qmlfmt#GetCommand(1))) + +Execute(The qmlfmt executable should be configurable): + let g:ale_qml_qmlfmt_executable = '~/.local/bin/qmlfmt' + + AssertEqual '~/.local/bin/qmlfmt', ale_linters#qml#qmlfmt#GetExecutable(1) + AssertEqual ale#Escape('~/.local/bin/qmlfmt') . ' -e', + \ join(split(ale_linters#qml#qmlfmt#GetCommand(1))) diff --git a/test/command_callback/test_rust_rls_callbacks.vader b/test/command_callback/test_rust_rls_callbacks.vader index 693d6e9f..16bde98a 100644 --- a/test/command_callback/test_rust_rls_callbacks.vader +++ b/test/command_callback/test_rust_rls_callbacks.vader @@ -28,8 +28,12 @@ Execute(The toolchain should be configurable): \ ale#Escape('rls') . ' +' . ale#Escape('stable'), \ ale_linters#rust#rls#GetCommand(bufnr('')) -Execute(The language string should be correct): - AssertEqual 'rust', ale_linters#rust#rls#GetLanguage(bufnr('')) +Execute(The toolchain should be ommitted if not given): + let g:ale_rust_rls_toolchain = '' + + AssertEqual + \ ale#Escape('rls'), + \ ale_linters#rust#rls#GetCommand(bufnr('')) Execute(The project root should be detected correctly): AssertEqual '', ale_linters#rust#rls#GetProjectRoot(bufnr('')) diff --git a/test/command_callback/test_sasslint_command_callback.vader b/test/command_callback/test_sasslint_command_callback.vader new file mode 100644 index 00000000..1db7e5fb --- /dev/null +++ b/test/command_callback/test_sasslint_command_callback.vader @@ -0,0 +1,12 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.sass') + +After: + call ale#test#RestoreDirectory() + +Execute(The default sasslint command should be correct): + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('sass-lint') . ' -v -q -f compact %t', + \ ale#handlers#sasslint#GetCommand(bufnr('')) diff --git a/test/handler/test_scalac_handler.vader b/test/command_callback/test_scalac_command_callback.vader index fd222f67..6bb31b3f 100644 --- a/test/handler/test_scalac_handler.vader +++ b/test/command_callback/test_scalac_command_callback.vader @@ -5,7 +5,6 @@ After: call ale#linter#Reset() Given scala(An empty Scala file): - Execute(The default executable and command should be correct): AssertEqual 'scalac', ale_linters#scala#scalac#GetExecutable(bufnr('')) AssertEqual diff --git a/test/command_callback/test_staticcheck_command_callback.vader b/test/command_callback/test_staticcheck_command_callback.vader new file mode 100644 index 00000000..e9628eb6 --- /dev/null +++ b/test/command_callback/test_staticcheck_command_callback.vader @@ -0,0 +1,41 @@ +Before: + Save b:ale_go_staticcheck_options + Save b:ale_go_staticcheck_lint_package + + let b:ale_go_staticcheck_options = '' + let b:ale_go_staticcheck_lint_package = 0 + + runtime ale_linters/go/staticcheck.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.go') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The staticcheck callback should return the right defaults): + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'staticcheck ' + \ . ale#Escape(expand('%' . ':t')), + \ ale_linters#go#staticcheck#GetCommand(bufnr('')) + +Execute(The staticcheck callback should use configured options): + let b:ale_go_staticcheck_options = '-test' + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'staticcheck ' + \ . '-test ' . ale#Escape(expand('%' . ':t')), + \ ale_linters#go#staticcheck#GetCommand(bufnr('')) + +Execute(The staticcheck `lint_package` option should use the correct command): + let b:ale_go_staticcheck_lint_package = 1 + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'staticcheck .', + \ ale_linters#go#staticcheck#GetCommand(bufnr('')) diff --git a/test/command_callback/test_textlint_command_callbacks.vader b/test/command_callback/test_textlint_command_callbacks.vader new file mode 100644 index 00000000..212f34d3 --- /dev/null +++ b/test/command_callback/test_textlint_command_callbacks.vader @@ -0,0 +1,85 @@ +" Author: januswel, w0rp + +Before: + Save g:ale_textlint_executable + Save g:ale_textlint_use_global + Save g:ale_textlint_options + + unlet! g:ale_textlint_executable + unlet! b:ale_textlint_executable + unlet! g:ale_textlint_use_global + unlet! b:ale_textlint_use_global + unlet! g:ale_textlint_options + unlet! b:ale_textlint_options + + runtime autoload/ale/handlers/textlint.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + unlet! b:command_tail + unlet! b:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'textlint', ale#handlers#textlint#GetExecutable(bufnr('')) + + let b:ale_textlint_executable = 'foobar' + + AssertEqual 'foobar', ale#handlers#textlint#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('textlint') . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#textlint#GetCommand(bufnr('')) + + let b:ale_textlint_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#textlint#GetCommand(bufnr('')) + \ + +Execute(The options should be configurable): + let b:ale_textlint_options = '--something' + + AssertEqual + \ ale#Escape('textlint') . ' --something -f json --stdin --stdin-filename %s', + \ ale#handlers#textlint#GetCommand(bufnr('')) + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_bin_path/foo.txt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint'), + \ ale#handlers#textlint#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint')) + \ . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#textlint#GetCommand(bufnr('')) + +Execute(The local executable from textlint/bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_textlint_bin_path/foo.txt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#handlers#textlint#GetExecutable(bufnr('')) + + if has('win32') + AssertEqual + \ ale#Escape('node.exe') . ' ' . ale#Escape(ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#textlint#GetCommand(bufnr('')) + else + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#textlint#GetCommand(bufnr('')) + endif diff --git a/test/command_callback/test_tslint_command_callback.vader b/test/command_callback/test_tslint_command_callback.vader index 4ad42fa5..edab72c8 100644 --- a/test/command_callback/test_tslint_command_callback.vader +++ b/test/command_callback/test_tslint_command_callback.vader @@ -17,7 +17,10 @@ Before: After: Restore + unlet! b:ale_typescript_tslint_executable + unlet! b:ale_typescript_tslint_config_path unlet! b:ale_typescript_tslint_rules_dir + unlet! b:ale_typescript_tslint_use_global call ale#test#RestoreDirectory() call ale#linter#Reset() @@ -25,7 +28,7 @@ After: Execute(The default tslint command should be correct): AssertEqual \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' - \ . 'tslint --format json %t', + \ . ale#Escape('tslint') . ' --format json %t', \ ale_linters#typescript#tslint#GetCommand(bufnr('')) Execute(The rules directory option should be included if set): @@ -33,7 +36,16 @@ Execute(The rules directory option should be included if set): AssertEqual \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' - \ . 'tslint --format json' + \ . ale#Escape('tslint') . ' --format json' \ . ' -r ' . ale#Escape('/foo/bar') \ . ' %t', \ ale_linters#typescript#tslint#GetCommand(bufnr('')) + +Execute(The executable should be configurable and escaped): + let b:ale_typescript_tslint_executable = 'foo bar' + + AssertEqual 'foo bar', ale_linters#typescript#tslint#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('foo bar') . ' --format json %t', + \ ale_linters#typescript#tslint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_vint_command_callback.vader b/test/command_callback/test_vint_command_callback.vader new file mode 100644 index 00000000..ddf39f31 --- /dev/null +++ b/test/command_callback/test_vint_command_callback.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_vim_vint_executable + + unlet! g:ale_vim_vint_executable + + runtime ale_linters/vim/vint.vim + + let b:command_tail = (has('nvim') ? ' --enable-neovim' : '') + \ . ' -f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})" %t' + call ale#semver#ResetVersionCache() + +After: + Restore + + call ale#linter#Reset() + call ale#semver#ResetVersionCache() + + unlet! b:bin_dir + unlet! b:executable + +Execute(The default command should be correct): + AssertEqual 'vint', ale_linters#vim#vint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('vint') .' --version', + \ ale_linters#vim#vint#VersionCommand(bufnr('')) + AssertEqual + \ ale#Escape('vint') .' -s --no-color' . b:command_tail, + \ ale_linters#vim#vint#GetCommand(bufnr(''), []) + +Execute(The executable should be configurable): + let g:ale_vim_vint_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#vim#vint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foobar') .' --version', + \ ale_linters#vim#vint#VersionCommand(bufnr('')) + AssertEqual + \ ale#Escape('foobar') .' -s --no-color' . b:command_tail, + \ ale_linters#vim#vint#GetCommand(bufnr(''), []) diff --git a/test/command_callback/textlint_paths/with_bin_path/node_modules/.bin/textlint b/test/command_callback/textlint_paths/with_bin_path/node_modules/.bin/textlint new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/textlint_paths/with_bin_path/node_modules/.bin/textlint diff --git a/test/command_callback/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js b/test/command_callback/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js diff --git a/test/command_callback/tidy_paths/.tidyrc b/test/command_callback/tidy_paths/.tidyrc new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tidy_paths/.tidyrc diff --git a/test/command_callback/tidy_paths/test.html b/test/command_callback/tidy_paths/test.html new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tidy_paths/test.html diff --git a/test/command_callback/tidy_paths/tidy b/test/command_callback/tidy_paths/tidy new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tidy_paths/tidy diff --git a/test/command_callback/tidy_paths/tidy.exe b/test/command_callback/tidy_paths/tidy.exe new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tidy_paths/tidy.exe diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader index 49d485f6..fbe18bb6 100644 --- a/test/completion/test_completion_events.vader +++ b/test/completion/test_completion_events.vader @@ -2,12 +2,9 @@ Before: Save g:ale_completion_enabled Save g:ale_completion_delay Save g:ale_completion_max_suggestions - Save g:ale_completion_experimental_lsp_support Save &l:omnifunc Save &l:completeopt - unlet! g:ale_completion_experimental_lsp_support - let g:ale_completion_enabled = 1 let g:get_completions_called = 0 let g:feedkeys_calls = [] @@ -43,7 +40,6 @@ After: unlet! b:ale_completion_response unlet! b:ale_completion_parser unlet! b:ale_complete_done_time - unlet! g:ale_completion_experimental_lsp_support delfunction CheckCompletionCalled diff --git a/test/completion/test_completion_filtering.vader b/test/completion/test_completion_filtering.vader index 3e461aef..ae91a952 100644 --- a/test/completion/test_completion_filtering.vader +++ b/test/completion/test_completion_filtering.vader @@ -1,15 +1,27 @@ +Before: + Save g:ale_completion_excluded_words + + let g:ale_completion_excluded_words = [] + +After: + Restore + + unlet! b:ale_completion_excluded_words + unlet! b:suggestions + Execute(Prefix filtering should work for Lists of strings): AssertEqual \ ['FooBar', 'foo'], - \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], 'foo') + \ ale#completion#Filter(bufnr(''), ['FooBar', 'FongBar', 'baz', 'foo'], 'foo') AssertEqual \ ['FooBar', 'FongBar', 'baz', 'foo'], - \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], '.') + \ ale#completion#Filter(bufnr(''), ['FooBar', 'FongBar', 'baz', 'foo'], '.') Execute(Prefix filtering should work for completion items): AssertEqual \ [{'word': 'FooBar'}, {'word': 'foo'}], \ ale#completion#Filter( + \ bufnr(''), \ [ \ {'word': 'FooBar'}, \ {'word': 'FongBar'}, @@ -18,6 +30,7 @@ Execute(Prefix filtering should work for completion items): \ ], \ 'foo' \ ) + AssertEqual \ [ \ {'word': 'FooBar'}, @@ -26,6 +39,7 @@ Execute(Prefix filtering should work for completion items): \ {'word': 'foo'}, \ ], \ ale#completion#Filter( + \ bufnr(''), \ [ \ {'word': 'FooBar'}, \ {'word': 'FongBar'}, @@ -34,3 +48,61 @@ Execute(Prefix filtering should work for completion items): \ ], \ '.' \ ) + +Execute(Excluding words from completion results should work): + let b:ale_completion_excluded_words = ['it', 'describe'] + + AssertEqual + \ [{'word': 'Italian'}], + \ ale#completion#Filter( + \ bufnr(''), + \ [ + \ {'word': 'Italian'}, + \ {'word': 'it'}, + \ ], + \ 'it' + \ ) + + AssertEqual + \ [{'word': 'Deutsch'}], + \ ale#completion#Filter( + \ bufnr(''), + \ [ + \ {'word': 'describe'}, + \ {'word': 'Deutsch'}, + \ ], + \ 'de' + \ ) + + AssertEqual + \ [{'word': 'Deutsch'}], + \ ale#completion#Filter( + \ bufnr(''), + \ [ + \ {'word': 'describe'}, + \ {'word': 'Deutsch'}, + \ ], + \ '.' + \ ) + +Execute(Excluding words from completion results should work with lists of Strings): + let b:ale_completion_excluded_words = ['it', 'describe'] + + AssertEqual + \ ['Italian'], + \ ale#completion#Filter(bufnr(''), ['Italian', 'it'], 'it') + AssertEqual + \ ['Deutsch'], + \ ale#completion#Filter(bufnr(''), ['describe', 'Deutsch'], 'de') + AssertEqual + \ ['Deutsch'], + \ ale#completion#Filter(bufnr(''), ['describe', 'Deutsch'], '.') + +Execute(Filtering shouldn't modify the original list): + let b:ale_completion_excluded_words = ['it', 'describe'] + let b:suggestions = [{'word': 'describe'}] + + AssertEqual [], ale#completion#Filter(bufnr(''), b:suggestions, '.') + AssertEqual b:suggestions, [{'word': 'describe'}] + AssertEqual [], ale#completion#Filter(bufnr(''), b:suggestions, 'de') + AssertEqual b:suggestions, [{'word': 'describe'}] diff --git a/test/completion/test_completion_prefixes.vader b/test/completion/test_completion_prefixes.vader index 8ac29326..0b2cfeaf 100644 --- a/test/completion/test_completion_prefixes.vader +++ b/test/completion/test_completion_prefixes.vader @@ -2,6 +2,8 @@ Given typescript(): let abc = y. let foo = ab let foo = (ab) + let string1 = ' + let string2 = " Execute(Completion should be done after dots in TypeScript): AssertEqual '.', ale#completion#GetPrefix(&filetype, 1, 13) @@ -15,5 +17,29 @@ 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(Completion should be done after strings in TypeScript): + AssertEqual '''', ale#completion#GetPrefix(&filetype, 4, 16) + AssertEqual '"', ale#completion#GetPrefix(&filetype, 5, 16) + +Execute(Completion prefixes should work for other filetypes): + AssertEqual 'ab', ale#completion#GetPrefix('xxxyyyzzz', 3, 14) + Execute(Completion prefixes should work for other filetypes): AssertEqual 'ab', ale#completion#GetPrefix('xxxyyyzzz', 3, 14) + +Given rust(): + let abc = y. + let abc = String:: + let foo = (ab) + +Execute(Completion should be done after dots in Rust): + AssertEqual '.', ale#completion#GetPrefix(&filetype, 1, 13) + +Execute(Completion should be done after colons in Rust): + AssertEqual '::', ale#completion#GetPrefix(&filetype, 2, 19) + +Execute(Completion should be done after words in parens in Rust): + AssertEqual 'ab', ale#completion#GetPrefix(&filetype, 3, 14) + +Execute(Completion should not be done after parens in Rust): + AssertEqual '', ale#completion#GetPrefix(&filetype, 3, 15) diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader index f21acfb9..8ba2ad38 100644 --- a/test/completion/test_lsp_completion_messages.vader +++ b/test/completion/test_lsp_completion_messages.vader @@ -2,12 +2,9 @@ Before: Save g:ale_completion_delay Save g:ale_completion_max_suggestions Save g:ale_completion_info - Save g:ale_completion_experimental_lsp_support Save &l:omnifunc Save &l:completeopt - unlet! g:ale_completion_experimental_lsp_support - let g:ale_completion_enabled = 1 call ale#test#SetDirectory('/testplugin/test/completion') @@ -18,12 +15,18 @@ Before: let g:message_list = [] let g:Callback = '' - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + return { + \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', + \ 'language_id': 'python', \} endfunction @@ -44,8 +47,9 @@ After: unlet! b:ale_completion_parser unlet! b:ale_complete_done_time unlet! b:ale_linters - unlet! g:ale_completion_experimental_lsp_support + unlet! b:ale_tsserver_completion_names + call ale#lsp#RemoveConnectionWithID(347) call ale#test#RestoreDirectory() call ale#linter#Reset() @@ -116,6 +120,12 @@ Execute(The right message sent to the tsserver LSP when the first completion mes \ ], \}) + " We should save the names we got in the buffer, as TSServer doesn't return + " details for every name. + AssertEqual + \ ['Foo', 'FooBar', 'frazzle'], + \ get(b:, 'ale_tsserver_completion_names', []) + " The entry details messages should have been sent. AssertEqual \ [[ @@ -136,8 +146,6 @@ Given python(Some Python file): bazxyzxyzxyz Execute(The right message should be sent for the initial LSP request): - let g:ale_completion_experimental_lsp_support = 1 - runtime ale_linters/python/pyls.vim let b:ale_linters = ['pyls'] " The cursor position needs to match what was saved before. diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader new file mode 100644 index 00000000..736353e3 --- /dev/null +++ b/test/completion/test_lsp_completion_parsing.vader @@ -0,0 +1,451 @@ +After: + unlet! b:ale_completion_info + +Execute(Should handle Rust completion results correctly): + AssertEqual + \ [ + \ {'word': 'new', 'menu': 'pub fn new() -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'with_capacity', 'menu': 'pub fn with_capacity(capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_utf8', 'menu': 'pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_utf8_lossy', 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_utf16', 'menu': 'pub fn from_utf16(v: &[u16]) -> Result<String, FromUtf16Error>', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_utf16_lossy', 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_raw_parts', 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_utf8_unchecked', 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a str>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = Cow<''a, str>>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Searcher', 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'default', 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Output', 'menu': 'type Output = String;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'Err', 'menu': 'type Err = ParseError;', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from_str', 'menu': 'fn from_str(s: &str) -> Result<String, ParseError>', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from', 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from', 'menu': 'fn from(s: Box<str>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \], + \ ale#completion#ParseLSPCompletions({ + \ "jsonrpc":"2.0", + \ "id":65, + \ "result":[ + \ { + \ "label":"new", + \ "kind":3, + \ "detail":"pub fn new() -> String" + \ }, + \ { + \ "label":"with_capacity", + \ "kind":3, + \ "detail":"pub fn with_capacity(capacity: usize) -> String" + \ }, + \ { + \ "label":"from_utf8", + \ "kind":3, + \ "detail":"pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>" + \ }, + \ { + \ "label":"from_utf8_lossy", + \ "kind":3, + \ "detail":"pub fn from_utf8_lossy<'a>(v: &'a [u8]) -> Cow<'a, str>" + \ }, + \ { + \ "label":"from_utf16", + \ "kind":3, + \ "detail":"pub fn from_utf16(v: &[u16]) -> Result<String, FromUtf16Error>" + \ }, + \ { + \ "label":"from_utf16_lossy", + \ "kind":3, + \ "detail":"pub fn from_utf16_lossy(v: &[u16]) -> String" + \ }, + \ { + \ "label":"from_raw_parts", + \ "kind":3, + \ "detail":"pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String" + \ }, + \ { + \ "label":"from_utf8_unchecked", + \ "kind":3, + \ "detail":"pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter<I: IntoIterator<Item = &'a char>>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter<I: IntoIterator<Item = Cow<'a, str>>>(iter: I) -> String" + \ }, + \ { + \ "label":"Searcher", + \ "kind":8, + \ "detail":"type Searcher = <&'b str as Pattern<'a>>::Searcher;" + \ }, + \ { + \ "label":"default", + \ "kind":3, + \ "detail":"fn default() -> String" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = String;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Target", + \ "kind":8, + \ "detail":"type Target = str;" + \ }, + \ { + \ "label":"Err", + \ "kind":8, + \ "detail":"type Err = ParseError;" + \ }, + \ { + \ "label":"from_str", + \ "kind":3, + \ "detail":"fn from_str(s: &str) -> Result<String, ParseError>" + \ }, + \ { + \ "label":"from", + \ "kind":3, + \ "detail":"fn from(s: &'a str) -> String" + \ }, + \ { + \ "label":"from", + \ "kind":3, + \ "detail":"fn from(s: Box<str>) -> String" + \ }, + \ { + \ "label":"from", + \ "kind":3, + \ "detail":"fn from(s: Cow<'a, str>) -> String" + \ } + \ ] + \ }) + +Execute(Should handle Python completion results correctly): + let b:ale_completion_info = { + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \} + + AssertEqual + \ [ + \ {'word': 'what', 'menu': 'example-python-project.bar.Bar', 'info': "what()\n\n", 'kind': 'f', 'icase': 1}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ "jsonrpc":"2.0", + \ "id":6, + \ "result":{ + \ "isIncomplete":v:false, + \ "items":[ + \ { + \ "label":"what()", + \ "kind":3, + \ "detail":"example-python-project.bar.Bar", + \ "documentation":"what()\n\n", + \ "sortText":"awhat", + \ "insertText":"what" + \ }, + \ { + \ "label":"__class__", + \ "kind":7, + \ "detail":"object", + \ "documentation":"type(object_or_name, bases, dict)\ntype(object) -> the object's type\ntype(name, bases, dict) -> a new type", + \ "sortText":"z__class__", + \ "insertText":"__class__" + \ }, + \ { + \ "label":"__delattr__(name)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Implement delattr(self, name).", + \ "sortText":"z__delattr__", + \ "insertText":"__delattr__" + \ }, + \ { + \ "label":"__dir__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"__dir__() -> list\ndefault dir() implementation", + \ "sortText":"z__dir__", + \ "insertText":"__dir__" + \ }, + \ { + \ "label":"__doc__", + \ "kind":18, + \ "detail":"object", + \ "documentation":"str(object='') -> str\nstr(bytes_or_buffer[, encoding[, errors]]) -> str\n\nCreate a new string object from the given object. If encoding or\nerrors is specified, then the object must expose a data buffer\nthat will be decoded using the given encoding and error handler.\nOtherwise, returns the result of object.__str__() (if defined)\nor repr(object).\nencoding defaults to sys.getdefaultencoding().\nerrors defaults to 'strict'.", + \ "sortText":"z__doc__", + \ "insertText":"__doc__" + \ }, + \ { + \ "label":"__eq__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self==value.", + \ "sortText":"z__eq__", + \ "insertText":"__eq__" + \ }, + \ { + \ "label":"__format__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"default object formatter", + \ "sortText":"z__format__", + \ "insertText":"__format__" + \ }, + \ { + \ "label":"__ge__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self>=value.", + \ "sortText":"z__ge__", + \ "insertText":"__ge__" + \ }, + \ { + \ "label":"__getattribute__(name)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return getattr(self, name).", + \ "sortText":"z__getattribute__", + \ "insertText":"__getattribute__" + \ }, + \ { + \ "label":"__gt__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self>value.", + \ "sortText":"z__gt__", + \ "insertText":"__gt__" + \ }, + \ { + \ "label":"__hash__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return hash(self).", + \ "sortText":"z__hash__", + \ "insertText":"__hash__" + \ }, + \ { + \ "label":"__init__(args, kwargs)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Initialize self.\u00a0\u00a0See help(type(self)) for accurate signature.", + \ "sortText":"z__init__", + \ "insertText":"__init__" + \ }, + \ { + \ "label":"__init_subclass__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + \ "sortText":"z__init_subclass__", + \ "insertText":"__init_subclass__" + \ }, + \ { + \ "label":"__le__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self<=value.", + \ "sortText":"z__le__", + \ "insertText":"__le__" + \ }, + \ { + \ "label":"__lt__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self<value.", + \ "sortText":"z__lt__", + \ "insertText":"__lt__" + \ }, + \ { + \ "label":"__ne__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self!=value.", + \ "sortText":"z__ne__", + \ "insertText":"__ne__" + \ }, + \ { + \ "label":"__new__(kwargs)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Create and return a new object.\u00a0\u00a0See help(type) for accurate signature.", + \ "sortText":"z__new__", + \ "insertText":"__new__" + \ }, + \ { + \ "label":"__reduce__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"helper for pickle", + \ "sortText":"z__reduce__", + \ "insertText":"__reduce__" + \ }, + \ { + \ "label":"__reduce_ex__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"helper for pickle", + \ "sortText":"z__reduce_ex__", + \ "insertText":"__reduce_ex__" + \ }, + \ { + \ "label":"__repr__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return repr(self).", + \ "sortText":"z__repr__", + \ "insertText":"__repr__" + \ }, + \ { + \ "label":"__setattr__(name, value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Implement setattr(self, name, value).", + \ "sortText":"z__setattr__", + \ "insertText":"__setattr__" + \ }, + \ { + \ "label":"__sizeof__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"__sizeof__() -> int\nsize of object in memory, in bytes", + \ "sortText":"z__sizeof__", + \ "insertText":"__sizeof__" + \ }, + \ { + \ "label":"__str__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return str(self).", + \ "sortText":"z__str__", + \ "insertText":"__str__" + \ }, + \ { + \ "label":"__subclasshook__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented.\u00a0\u00a0If it returns\nNotImplemented, the normal algorithm is used.\u00a0\u00a0Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + \ "sortText":"z__subclasshook__", + \ "insertText":"__subclasshook__" + \ } + \ ] + \ } + \ }) + +Execute(Should handle Python completion results correctly): + let b:ale_completion_info = { + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ 'prefix': 'mig', + \} + + AssertEqual + \ [ + \ {'word': 'migrations', 'menu': 'xxx', 'info': 'migrations', 'kind': 'f', 'icase': 1}, + \ {'word': 'MigEngine', 'menu': 'xxx', 'info': 'mig engine', 'kind': 'f', 'icase': 1}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'jsonrpc': '2.0', + \ 'id': 6, + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'label': 'migrations', + \ 'kind': 3, + \ 'detail': 'xxx', + \ 'documentation': 'migrations', + \ }, + \ { + \ 'label': 'MigEngine', + \ 'kind': 3, + \ 'detail': 'xxx', + \ 'documentation': 'mig engine', + \ }, + \ { + \ 'label': 'ignore me', + \ 'kind': 3, + \ 'detail': 'nope', + \ 'documentation': 'nope', + \ }, + \ ] + \ } + \ }) + +Execute(Should handle missing detail keys): + AssertEqual + \ [ + \ {'word': 'x', 'menu': '', 'info': 'y', 'kind': 'f', 'icase': 1}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'jsonrpc': '2.0', + \ 'id': 6, + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'label': 'x', + \ 'kind': 3, + \ 'documentation': 'y', + \ }, + \ ] + \ } + \ }) diff --git a/test/completion/test_tsserver_completion_parsing.vader b/test/completion/test_tsserver_completion_parsing.vader index b663ef40..c8e2c993 100644 --- a/test/completion/test_tsserver_completion_parsing.vader +++ b/test/completion/test_tsserver_completion_parsing.vader @@ -1,3 +1,6 @@ +After: + unlet! b:ale_tsserver_completion_names + Execute(TypeScript completions responses should be parsed correctly): AssertEqual [], \ ale#completion#ParseTSServerCompletions({ @@ -73,3 +76,74 @@ Execute(TypeScript completion details responses should be parsed correctly): \ }, \ ], \}) + +Execute(Entries without details should be included in the responses): + let b:ale_tsserver_completion_names = ['xyz'] + + AssertEqual + \ [ + \ { + \ 'word': 'abc', + \ 'menu': '(property) Foo.abc: number', + \ 'info': '', + \ 'kind': 'f', + \ 'icase': 1, + \ }, + \ { + \ 'word': 'def', + \ 'menu': '(property) Foo.def: number', + \ 'info': 'foo bar baz', + \ 'kind': 'f', + \ 'icase': 1, + \ }, + \ { + \ 'word': 'xyz', + \ 'menu': '', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ }, + \ ], + \ ale#completion#ParseTSServerCompletionEntryDetails({ + \ 'body': [ + \ { + \ 'name': 'abc', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'abc'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ }, + \ { + \ 'name': 'def', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'def'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ 'documentation': [ + \ {'text': 'foo'}, + \ {'text': ' '}, + \ {'text': 'bar'}, + \ {'text': ' '}, + \ {'text': 'baz'}, + \ ], + \ }, + \ ], + \}) diff --git a/test/cucumber_fixtures/features/cuke.feature b/test/cucumber_fixtures/features/cuke.feature new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/cucumber_fixtures/features/cuke.feature diff --git a/test/cucumber_fixtures/features/step_definitions/base_steps.rb b/test/cucumber_fixtures/features/step_definitions/base_steps.rb new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/cucumber_fixtures/features/step_definitions/base_steps.rb diff --git a/test/elm-test-files/app/node_modules/.bin/elm b/test/elm-test-files/app/node_modules/.bin/elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/app/node_modules/.bin/elm diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader index 817c243d..417394c3 100644 --- a/test/fix/test_ale_fix.vader +++ b/test/fix/test_ale_fix.vader @@ -17,6 +17,14 @@ Before: \ 'testft': [], \} + let g:pre_success = 0 + let g:post_success = 0 + augroup VaderTest + autocmd! + autocmd User ALEFixPre let g:pre_success = 1 + autocmd User ALEFixPost let g:post_success = 1 + augroup end + if !has('win32') let &shell = '/bin/bash' endif @@ -171,6 +179,7 @@ After: unlet! g:ale_emulate_job_failure unlet! b:ale_fixers unlet! b:ale_fix_on_save + augroup! VaderTest delfunction AddCarets delfunction AddDollars delfunction DoNothing @@ -248,6 +257,25 @@ Expect(Only the second function should be applied): $b $c +Execute(The * fixers shouldn't be used if an empty list is set for fixers): + let g:ale_fixers.testft = [] + let g:ale_fixers['*'] = ['AddDollars'] + ALEFix + +Expect(Nothing should be changed): + a + b + c + +Execute(* fixers should be used if no filetype is matched): + let g:ale_fixers = {'*': ['AddDollars']} + ALEFix + +Expect(The file should be changed): + $a + $b + $c + Execute(ALEFix should allow commands to be run): if has('win32') " Just skip this test on Windows, we can't run it. @@ -263,6 +291,15 @@ Expect(An extra line should be added): c d +Execute(ALEFix should use fixers passed in commandline when provided): + let g:ale_fixers.testft = ['RemoveLastLine'] + ALEFix AddCarets AddDollars + +Expect(Only fixers passed via command line should be run): + $^a + $^b + $^c + Execute(ALEFix should allow temporary files to be read): if has('win32') " Just skip this test on Windows, we can't run it. @@ -574,6 +611,16 @@ Execute(ALE should print a message telling you something isn't a valid fixer whe AssertEqual 'There is no fixer named `invalidname`. Check :ALEFixSuggest', GetLastMessage() +Execute(ALE should complain about invalid fixers with minuses in the name): + let g:ale_fixers.testft = ['foo-bar'] + ALEFix + + AssertEqual 'There is no fixer named `foo-bar`. Check :ALEFixSuggest', GetLastMessage() + +Execute(ALE should tolerate valid fixers with minuses in the name): + let g:ale_fixers.testft = ['prettier-standard'] + ALEFix + Execute(Test fixing with chained callbacks): let g:ale_fixers.testft = ['FirstChainCallback'] ALEFix @@ -654,3 +701,9 @@ Expect(The lines in the JSON should be used): x y z + +Execute(ALEFix should apply autocmds): + let g:ale_fixers.testft = ['AddCarets'] + ALEFix + AssertEqual g:pre_success, 1 + AssertEqual g:post_success, 1 diff --git a/test/fix/test_ale_fix_completion.vader b/test/fix/test_ale_fix_completion.vader new file mode 100644 index 00000000..6c38bb8d --- /dev/null +++ b/test/fix/test_ale_fix_completion.vader @@ -0,0 +1,23 @@ +Execute (List of available fixers is empty): + call ale#fix#registry#Clear() + +Then (List of applicable fixers for python file is empty): + AssertEqual [], ale#fix#registry#GetApplicableFixers('python') + +Execute (Add ruby fixer): + call ale#fix#registry#Add('ruby_fixer', 'fixer_fun', ['ruby'], 'ruby fixer') + +Then (List of applicable fixers for python file is still empty): + AssertEqual [], ale#fix#registry#GetApplicableFixers('python') + +Execute (Add generic fixer): + call ale#fix#registry#Add('generic_fixer', 'fixer_fun', [], 'generic fixer') + +Then (Generic fixer should be returned as applicable for python file): + AssertEqual ['generic_fixer'], ale#fix#registry#GetApplicableFixers('python') + +Execute (Add python fixer): + call ale#fix#registry#Add('python_fixer', 'fixer_func', ['python'], 'python fixer') + +Then (List of fixers should contain both generic and python fixers): + AssertEqual ['generic_fixer', 'python_fixer'], ale#fix#registry#GetApplicableFixers('python') diff --git a/test/fix/test_ale_fix_completion_filter.vader b/test/fix/test_ale_fix_completion_filter.vader new file mode 100644 index 00000000..536b7138 --- /dev/null +++ b/test/fix/test_ale_fix_completion_filter.vader @@ -0,0 +1,14 @@ +Before: + call ale#fix#registry#Clear() + call ale#test#SetFilename('test.js') + call ale#fix#registry#Add('prettier', '', ['javascript'], 'prettier') + call ale#fix#registry#Add('eslint', '', ['javascript'], 'eslint') + setfiletype javascript + +Execute(completeFixers returns all of the applicable fixers without an arglead): + AssertEqual ['eslint', 'prettier'], + \ ale#fix#registry#CompleteFixers('', 'ALEFix ', 7) + +Execute(completeFixers returns all of the applicable fixers without an arglead): + AssertEqual ['prettier'], + \ ale#fix#registry#CompleteFixers('pre', 'ALEFix ', 10) diff --git a/test/fixers/test_black_fixer_callback.vader b/test/fixers/test_black_fixer_callback.vader new file mode 100644 index 00000000..365b0fa6 --- /dev/null +++ b/test/fixers/test_black_fixer_callback.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_python_black_executable + Save g:ale_python_black_options + + " Use an invalid global executable, so we don't match it. + let g:ale_python_black_executable = 'xxxinvalid' + let g:ale_python_black_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The black callback should return the correct default values): + AssertEqual + \ 0, + \ ale#fixers#black#Fix(bufnr('')) + + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/black')) . ' -'}, + \ ale#fixers#black#Fix(bufnr('')) + +Execute(The black callback should include options): + let g:ale_python_black_options = '--some-option' + + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/black')) . ' --some-option -' }, + \ ale#fixers#black#Fix(bufnr('')) diff --git a/test/fixers/test_brittany_fixer_callback.vader b/test/fixers/test_brittany_fixer_callback.vader index a0182b52..073e368c 100644 --- a/test/fixers/test_brittany_fixer_callback.vader +++ b/test/fixers/test_brittany_fixer_callback.vader @@ -18,6 +18,7 @@ Execute(The brittany callback should return the correct default values): \ { \ 'read_temporary_file': 1, \ 'command': ale#Escape('xxxinvalid') + \ . ' --write-mode inplace' \ . ' %t', \ }, \ ale#fixers#brittany#Fix(bufnr('')) diff --git a/test/fixers/test_elm_format_fixer_callback.vader b/test/fixers/test_elm_format_fixer_callback.vader index d613aa84..682c22ca 100644 --- a/test/fixers/test_elm_format_fixer_callback.vader +++ b/test/fixers/test_elm_format_fixer_callback.vader @@ -18,7 +18,7 @@ Execute(The elm-format command should have default params): \ ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/node_modules/.bin/elm-format')) \ . ' %t --yes', \ }, - \ ale#fixers#format#Fix(bufnr('')) + \ ale#fixers#elm_format#Fix(bufnr('')) Execute(The elm-format command should manage use_global = 1 param): call ale#test#SetFilename('../elm-test-files/src/subdir/testfile.elm') @@ -31,7 +31,7 @@ Execute(The elm-format command should manage use_global = 1 param): \ ale#Escape('elm-format') \ . ' %t --yes', \ }, - \ ale#fixers#format#Fix(bufnr('')) + \ ale#fixers#elm_format#Fix(bufnr('')) Execute(The elm-format command should manage executable param): call ale#test#SetFilename('../elm-test-files/src/subdir/testfile.elm') @@ -45,7 +45,7 @@ Execute(The elm-format command should manage executable param): \ ale#Escape('elmformat') \ . ' %t --yes', \ }, - \ ale#fixers#format#Fix(bufnr('')) + \ ale#fixers#elm_format#Fix(bufnr('')) Execute(The elm-format command should manage empty options): call ale#test#SetFilename('../elm-test-files/src/subdir/testfile.elm') @@ -58,7 +58,7 @@ Execute(The elm-format command should manage empty options): \ ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/node_modules/.bin/elm-format')) \ . ' %t', \ }, - \ ale#fixers#format#Fix(bufnr('')) + \ ale#fixers#elm_format#Fix(bufnr('')) Execute(The elm-format command should manage custom options): call ale#test#SetFilename('../elm-test-files/src/subdir/testfile.elm') @@ -71,4 +71,4 @@ Execute(The elm-format command should manage custom options): \ ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/node_modules/.bin/elm-format')) \ . ' %t --param1 --param2', \ }, - \ ale#fixers#format#Fix(bufnr('')) + \ ale#fixers#elm_format#Fix(bufnr('')) diff --git a/test/fixers/test_mix_format_fixer_callback.vader b/test/fixers/test_mix_format_fixer_callback.vader index c6c97c57..365fbecf 100644 --- a/test/fixers/test_mix_format_fixer_callback.vader +++ b/test/fixers/test_mix_format_fixer_callback.vader @@ -1,10 +1,15 @@ Before: - call ale#test#SetDirectory('/testplugin/test/fixers') Save g:ale_elixir_mix_executable + Save g:ale_elixir_mix_format_options let g:ale_elixir_mix_executable = 'xxxinvalid' + let g:ale_elixir_mix_format_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') After: + Restore + call ale#test#RestoreDirectory() Execute(The mix_format callback should return the correct default values): @@ -18,3 +23,14 @@ Execute(The mix_format callback should return the correct default values): \ }, \ ale#fixers#mix_format#Fix(bufnr('')) +Execute(The mix_format callback should include the correct format options): + let g:ale_elixir_mix_format_options = 'invalid_options' + call ale#test#SetFilename('../elixir-test-files/testfile.ex') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format invalid_options %t', + \ }, + \ ale#fixers#mix_format#Fix(bufnr('')) diff --git a/test/fixers/test_perltidy_fixer_callback.vader b/test/fixers/test_perltidy_fixer_callback.vader new file mode 100644 index 00000000..c7430bfa --- /dev/null +++ b/test/fixers/test_perltidy_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_perl_perltidy_executable + Save g:ale_perl_perltidy_options + + " Use an invalid global executable, so we don't match it. + let g:ale_perl_perltidy_executable = 'xxxinvalid' + let g:ale_perl_perltidy_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The perltidy callback should return the correct default values): + call ale#test#SetFilename('../pl_files/testfile.pl') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -b' + \ . ' %t', + \ }, + \ ale#fixers#perltidy#Fix(bufnr('')) + +Execute(The perltidy callback should include custom perltidy options): + let g:ale_perl_perltidy_options = "-r '(a) -> a'" + call ale#test#SetFilename('../pl_files/testfile.pl') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -b' + \ . ' ' . g:ale_perl_perltidy_options + \ . ' %t', + \ }, + \ ale#fixers#perltidy#Fix(bufnr('')) diff --git a/test/fixers/test_php_cs_fixer.vader b/test/fixers/test_php_cs_fixer.vader new file mode 100644 index 00000000..b47c190c --- /dev/null +++ b/test/fixers/test_php_cs_fixer.vader @@ -0,0 +1,65 @@ +Before: + Save g:ale_php_cs_fixer_executable + Save g:ale_php_cs_fixer_options + let g:ale_php_cs_fixer_executable = 'php-cs-fixer' + let g:ale_php_cs_fixer_options = '' + + 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 php-cs-fixer should use local by default): + call ale#test#SetFilename('php_paths/project-with-php-cs-fixer/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/php_paths/project-with-php-cs-fixer/vendor/bin/php-cs-fixer'), + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_cs_fixer_use_global = 1 + call ale#test#SetFilename('php_paths/project-with-php-cs-fixer/test.php') + + AssertEqual + \ 'php-cs-fixer', + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + +Execute(project without php-cs-fixer should use global): + call ale#test#SetFilename('php_paths/project-without-php-cs-fixer/test.php') + + AssertEqual + \ 'php-cs-fixer', + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + + + + +Execute(The php-cs-fixer callback should return the correct default values): + call ale#test#SetFilename('php_paths/project-without-php-cs-fixer/foo/test.php') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('php-cs-fixer') + \ . ' ' . g:ale_php_cs_fixer_options + \ . ' fix %t' + \ }, + \ ale#fixers#php_cs_fixer#Fix(bufnr('')) + +Execute(The php-cs-fixer callback should include custom php-cs-fixer options): + let g:ale_php_cs_fixer_options = '--config="$HOME/.php_cs"' + call ale#test#SetFilename('php_paths/project-without-php-cs-fixer/test.php') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_php_cs_fixer_executable) + \ . ' --config="$HOME/.php_cs" fix %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#php_cs_fixer#Fix(bufnr('')) diff --git a/test/fixers/test_prettier_fixer_callback.vader b/test/fixers/test_prettier_fixer_callback.vader index c4f36f52..2018c3a6 100644 --- a/test/fixers/test_prettier_fixer_callback.vader +++ b/test/fixers/test_prettier_fixer_callback.vader @@ -95,3 +95,143 @@ Execute(The version number should be cached): \ . ' --stdin-filepath %s --stdin', \ }, \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + +Execute(Should set --parser based on filetype, TypeScript): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=typescript + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser typescript' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, CSS): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=css + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser css' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, LESS): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=less + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser less' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, SCSS): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=scss + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser scss' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, JSON): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=json + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser json' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, JSON5): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=json5 + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser json5' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, GraphQL): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=graphql + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser graphql' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, Markdown): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=markdown + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser markdown' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on filetype, Vue): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=vue + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser vue' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(Should set --parser based on first filetype of multiple filetypes): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=css.scss + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser css' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) diff --git a/test/fixers/test_qmlfmt_fixer_callback.vader b/test/fixers/test_qmlfmt_fixer_callback.vader new file mode 100644 index 00000000..e216f2e1 --- /dev/null +++ b/test/fixers/test_qmlfmt_fixer_callback.vader @@ -0,0 +1,12 @@ +Before: + Save g:ale_qml_qmlfmt_executable + +After: + Restore + +Execute(The qmlfmt fixer should use the options you set): + let g:ale_qml_qmlfmt_executable = 'foo-exe' + + AssertEqual + \ {'command': ale#Escape('foo-exe')}, + \ ale#fixers#qmlfmt#Fix(bufnr('')) diff --git a/test/fixers/test_rufo_fixer_callback.vader b/test/fixers/test_rufo_fixer_callback.vader new file mode 100644 index 00000000..a0828406 --- /dev/null +++ b/test/fixers/test_rufo_fixer_callback.vader @@ -0,0 +1,33 @@ +Before: + Save g:ale_ruby_rufo_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_rufo_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 rufo command should contain `bundle exec` when executable is `bundle`): + let g:ale_ruby_rufo_executable = 'bundle' + call ale#test#SetFilename('ruby_paths/dummy.rb') + + AssertEqual + \ ale#Escape('bundle') . ' exec rufo %t', + \ ale#fixers#rufo#GetCommand(bufnr('')) + +Execute(The rufo callback should return the correct default values): + call ale#test#SetFilename('ruby_paths/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') . ' %t' + \ }, + \ ale#fixers#rufo#Fix(bufnr('')) diff --git a/test/fixers/test_scalafmt_fixer_callback.vader b/test/fixers/test_scalafmt_fixer_callback.vader new file mode 100644 index 00000000..d82fda43 --- /dev/null +++ b/test/fixers/test_scalafmt_fixer_callback.vader @@ -0,0 +1,69 @@ +Before: + Save g:ale_scala_scalafmt_executable + Save g:ale_scala_scalafmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_scala_scalafmt_executable = 'xxxinvalid' + let g:ale_scala_scalafmt_options = '' + + 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 scalafmt callback should return the correct default values): + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_scala_scalafmt_executable) + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should use ng with scalafmt automatically): + let g:ale_scala_scalafmt_executable = 'ng' + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('ng') + \ . ' scalafmt' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should include custom scalafmt options): + let g:ale_scala_scalafmt_options = '--diff' + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_scala_scalafmt_executable) + \ . ' --diff' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should include custom scalafmt options and use ng with scalafmt): + let g:ale_scala_scalafmt_options = '--diff' + let g:ale_scala_scalafmt_executable = 'ng' + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('ng') + \ . ' scalafmt' + \ . ' --diff' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) diff --git a/test/fixers/test_tidy_fixer_callback.vader b/test/fixers/test_tidy_fixer_callback.vader new file mode 100644 index 00000000..5677d8fd --- /dev/null +++ b/test/fixers/test_tidy_fixer_callback.vader @@ -0,0 +1,29 @@ +Before: + Save g:ale_html_tidy_executable + + let g:ale_html_tidy_executable = 'tidy_paths/tidy' + + 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 tidy callback should return 0 if tidy not found): + let g:ale_html_tidy_executable = 'xxxinvalidpath' + AssertEqual + \ 0, + \ ale#fixers#tidy#Fix(bufnr('')) + +Execute(The tidy callback should return the correct default command): + AssertEqual + \ { + \ 'command': ale#Escape('tidy_paths/tidy') + \ . ' -q --tidy-mark no --show-errors 0 --show-warnings 0' + \ }, + \ ale#fixers#tidy#Fix(bufnr('')) diff --git a/test/handler/test_cfn_python_lint_handler.vader b/test/handler/test_cfn_python_lint_handler.vader new file mode 100644 index 00000000..2c7ddc62 --- /dev/null +++ b/test/handler/test_cfn_python_lint_handler.vader @@ -0,0 +1,33 @@ +Before: + runtime! ale_linters/cloudformation/cfn_python_lint.vim + call ale#test#SetFilename('sample.template.yaml') + +After: + call ale#linter#Reset() + +Execute(The cfn_python_lint handler should parse items correctly): + AssertEqual + \ [ + \ { + \ 'lnum': '96', + \ 'col': '7', + \ 'end_lnum': '96', + \ 'end_col': '15', + \ 'text': 'Property Resources/Sample/Properties/FromPort should be of type Integer', + \ 'code': 'E3012', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': '97', + \ 'col': '7', + \ 'end_lnum': '97', + \ 'end_col': '15', + \ 'text': 'AllowedPattern and/or AllowedValues for Parameter should be specified at Parameters/SampleIpAddress. Example for AllowedPattern "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"', + \ 'code': 'W2509', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#cloudformation#cfn_python_lint#Handle(bufnr(''), [ + \ fnamemodify(tempname(), ':h') . '/sample.template.yaml:96:7:96:15:E3012:Property Resources/Sample/Properties/FromPort should be of type Integer', + \ fnamemodify(tempname(), ':h') . '/sample.template.yaml:97:7:97:15:W2509:AllowedPattern and/or AllowedValues for Parameter should be specified at Parameters/SampleIpAddress. Example for AllowedPattern "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"', + \ ]) diff --git a/test/handler/test_cucumber_handler.vader b/test/handler/test_cucumber_handler.vader new file mode 100644 index 00000000..2b69a784 --- /dev/null +++ b/test/handler/test_cucumber_handler.vader @@ -0,0 +1,18 @@ +Before: + runtime ale_linters/cucumber/cucumber.vim + +After: + call ale#linter#Reset() + +Execute(The cucumber handler parses JSON correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 13, + \ 'code': 'E', + \ 'text': 'Undefined step' + \ } + \ ], + \ ale_linters#cucumber#cucumber#Handle(bufnr(''), [ + \ '[{"elements": [{"steps": [{"result": {"status": "undefined"},"match": {"location": "features/cuke.feature:13"},"line": 13,"name": "Something undefined","keyword": "Given "},{"result": {"status": "skipped"},"match": {"location": "/var/lib/gems/2.3.0/gems/cucumber-3.1.0/lib/cucumber/step_match.rb:103"},"line": 14,"name": "I visit the profile page for Alice","keyword": "When "}],"type": "scenario","line": 12,"description": "","name": "Another scenario","keyword": "Scenario","id": "a-user-can-view-another-users-profile;another-scenario"}],"line": 1,"description": "","name": "A user can view another users profile","keyword": "Feature","id": "a-user-can-view-another-users-profile","uri": "features/cuke.feature"}]' + \ ]) diff --git a/test/handler/test_elmmake_handler.vader b/test/handler/test_elmmake_handler.vader index f3424b4b..41c9646f 100644 --- a/test/handler/test_elmmake_handler.vader +++ b/test/handler/test_elmmake_handler.vader @@ -9,7 +9,139 @@ After: call ale#linter#Reset() -Execute(The elm-make handler should parse lines correctly): + +" Elm 0.19 + +Execute(The elm-make handler should parse Elm 0.19 general problems correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "error details\n\nstyled details" + \ } + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ '{ + \ "type": "error", + \ "path": "' . b:tmp . '/Module.elm", + \ "title": "UNKNOWN IMPORT", + \ "message": ["error details\n\n", { "string": "styled details" }] + \ }' + \ ]) + +Execute(The elm-make handler should parse Elm 0.19 compilation errors correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 404, + \ 'col': 1, + \ 'end_lnum': 408, + \ 'end_col': 18, + \ 'type': 'E', + \ 'text': "error details 1\n\nstyled details" + \ }, + \ { + \ 'lnum': 406, + \ 'col': 5, + \ 'end_lnum': 407, + \ 'end_col': 17, + \ 'type': 'E', + \ 'text': "error details 2", + \ }, + \ { + \ 'lnum': 406, + \ 'col': 5, + \ 'end_lnum': 406, + \ 'end_col': 93, + \ 'type': 'E', + \ 'text': "error details 3", + \ }, + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ '{ + \ "type": "compile-errors", + \ "errors": [ + \ { + \ "path": "' . b:tmp . '/Module.elm", + \ "problems": [ + \ { + \ "title": "TYPE MISMATCH", + \ "message": ["error details 1\n\n", { "string": "styled details" }], + \ "region": { "start": { "line": 404, "column": 1 }, "end": { "line": 408, "column": 18 } } + \ }, + \ { + \ "title": "TYPE MISMATCH", + \ "message": ["error details 2"], + \ "region": { "start": {"line": 406, "column": 5}, "end": {"line": 407, "column": 17 } } + \ }, + \ { + \ "title": "TYPE MISMATCH", + \ "message": ["error details 3"], + \ "region": { "start": { "line": 406, "column": 5}, "end": {"line": 406, "column": 93 } } + \ } + \ ] + \ } + \ ] + \ }' + \ ]) + +Execute(The elm-make handler should handle errors in Elm 0.19 imported modules): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "src/Module.elm - error details\n\nstyled details", + \ 'detail': "src/Module.elm ----------\n\nerror details\n\nstyled details" + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "Elm - error details\n\nstyled details", + \ 'detail': "Elm ----------\n\nerror details\n\nstyled details" + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "src/Module.elm:404 - error details\n\nstyled details", + \ 'detail': "src/Module.elm:404 ----------\n\nerror details\n\nstyled details" + \ }, + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ '{ + \ "type": "error", + \ "path": "src/Module.elm", + \ "title": "UNKNOWN IMPORT", + \ "message": ["error details\n\n", { "string": "styled details" }] + \ }', + \ '{ + \ "type": "error", + \ "path": null, + \ "title": "UNKNOWN IMPORT", + \ "message": ["error details\n\n", { "string": "styled details" }] + \ }', + \ '{ + \ "type": "compile-errors", + \ "errors": [ + \ { + \ "path": "src/Module.elm", + \ "problems": [ + \ { + \ "title": "TYPE MISMATCH", + \ "message": ["error details\n\n", { "string": "styled details" }], + \ "region": { "start": { "line": 404, "column": 1 }, "end": { "line": 408, "column": 18 } } + \ } + \ ] + \ } + \ ] + \ }' + \ ]) + + +" Elm 0.18 + +Execute(The elm-make handler should parse Elm 0.18 compilation errors correctly): AssertEqual \ [ \ { @@ -50,21 +182,90 @@ Execute(The elm-make handler should parse lines correctly): \ }, \ ], \ ale_linters#elm#make#Handle(347, [ - \ '[{"tag":"unused import","overview":"warning overview","details":"warning details","region":{"start":{"line":33,"column":1},"end":{"line":33,"column":19}},"type":"warning","file":"' . b:tmp . 'Module.elm"}]', - \ '[{"tag":"TYPE MISMATCH","overview":"error overview 1","subregion":{"start":{"line":406,"column":5},"end":{"line":408,"column":18}},"details":"error details 1","region":{"start":{"line":404,"column":1},"end":{"line":408,"column":18}},"type":"error","file":"' . b:tmp . 'Module.elm"},{"tag":"TYPE MISMATCH","overview":"error overview 2","subregion":{"start":{"line":407,"column":12},"end":{"line":407,"column":17}},"details":"error details 2","region":{"start":{"line":406,"column":5},"end":{"line":407,"column":17}},"type":"error","file":"' . b:tmp . 'Module.elm"},{"tag":"TYPE MISMATCH","overview":"error overview 3","subregion":{"start":{"line":406,"column":88},"end":{"line":406,"column":93}},"details":"error details 3","region":{"start":{"line":406,"column":5},"end":{"line":406,"column":93}},"type":"error","file":"' . b:tmp . 'Module.elm"}]' + \ '[ + \ { + \ "tag": "unused import", + \ "overview": "warning overview", + \ "details": "warning details", + \ "region": {"start": { "line": 33, "column": 1 }, "end": { "line": 33, "column": 19 } }, + \ "type": "warning", + \ "file": "' . b:tmp . '/Module.elm" + \ } + \ ]', + \ '[ + \ { + \ "tag": "TYPE MISMATCH", + \ "overview": "error overview 1", + \ "subregion": { "start": { "line": 406, "column": 5 }, "end": { "line": 408, "column": 18 } }, + \ "details": "error details 1", + \ "region": { "start": { "line": 404, "column": 1 }, "end": { "line": 408, "column": 18 } }, + \ "type": "error", + \ "file":"' . b:tmp . '/Module.elm" + \ }, + \ { + \ "tag": "TYPE MISMATCH", + \ "overview": "error overview 2", + \ "subregion": { "start": { "line": 407, "column": 12 }, "end": { "line": 407, "column": 17 } }, + \ "details": "error details 2", + \ "region": { "start": { "line": 406, "column": 5}, "end": { "line": 407, "column": 17 } }, + \ "type":"error", + \ "file":"' . b:tmp . '/Module.elm" + \ }, + \ { + \ "tag": "TYPE MISMATCH", + \ "overview": "error overview 3", + \ "subregion": { "start": { "line": 406, "column": 88 }, "end": { "line": 406, "column": 93 } }, + \ "details": "error details 3", + \ "region": { "start": { "line": 406, "column": 5 }, "end": { "line": 406, "column": 93 } }, + \ "type":"error", + \ "file":"' . b:tmp . '/Module.elm" + \ } + \ ]' \ ]) +Execute(The elm-make handler should handle errors in Elm 0.18 imported modules): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "src/Module.elm:33 - error overview", + \ 'detail': "src/Module.elm:33 ----------\n\nerror overview\n\nerror details" + \ } + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ '[ + \ { + \ "tag": "unused import", + \ "overview": "warning overview", + \ "details": "warning details", + \ "region": {"start": { "line": 33, "column": 1 }, "end": { "line": 33, "column": 19 } }, + \ "type": "warning", + \ "file": "src/Module.elm" + \ }, + \ { + \ "tag": "type error", + \ "overview": "error overview", + \ "details": "error details", + \ "region": {"start": { "line": 33, "column": 1 }, "end": { "line": 33, "column": 19 } }, + \ "type": "error", + \ "file": "src/Module.elm" + \ } + \ ]', + \ ]) + +" Generic + Execute(The elm-make handler should put an error on the first line if a line cannot be parsed): AssertEqual \ [ \ { - \ 'lnum': 33, + \ 'lnum': 404, \ 'col': 1, - \ 'end_lnum': 33, - \ 'end_col': 19, - \ 'type': 'W', - \ 'text': 'warning overview', - \ 'detail': "warning overview\n\nwarning details", + \ 'end_lnum': 408, + \ 'end_col': 18, + \ 'type': 'E', + \ 'text': "error details 1\n\nstyled details" \ }, \ { \ 'lnum': 1, @@ -74,7 +275,28 @@ Execute(The elm-make handler should put an error on the first line if a line can \ }, \ ], \ ale_linters#elm#make#Handle(347, [ - \ '[{"tag":"unused import","overview":"warning overview","details":"warning details","region":{"start":{"line":33,"column":1},"end":{"line":33,"column":19}},"type":"warning","file":"' . b:tmp . 'Module.elm"}]', - \ "Not JSON", - \ "Also not JSON", + \ '{ + \ "type": "compile-errors", + \ "errors": [ + \ { + \ "path": "' . b:tmp . '/Module.elm", + \ "problems": [ + \ { + \ "title": "TYPE MISMATCH", + \ "message": ["error details 1\n\n", { "string": "styled details" }], + \ "region": { "start": { "line": 404, "column": 1 }, "end": { "line": 408, "column": 18 } } + \ } + \ ] + \ } + \ ] + \ }', + \ 'Not JSON', + \ 'Also not JSON', + \ ]) + +Execute(The elm-make handler should ignore success lines): + AssertEqual + \ [], + \ ale_linters#elm#make#Handle(347, [ + \ 'Successfully generated /dev/null', \ ]) diff --git a/test/handler/test_fish_handler.vader b/test/handler/test_fish_handler.vader index 567952e4..ad3a963c 100644 --- a/test/handler/test_fish_handler.vader +++ b/test/handler/test_fish_handler.vader @@ -37,3 +37,25 @@ Execute(The fish handler should handle basic warnings and syntax errors): \ "abbr --add p 'cd ~/Projects'", \ '^', \ ]) + +Execute(The fish handler should handle problems where the problem before before the line with the line number): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 23, + \ 'text': 'Unsupported use of ''||''. In fish, please use ''COMMAND; or COMMAND''.', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 1, + \ 'text': 'wat', + \ }, + \ ], + \ ale_linters#fish#fish#Handle(bufnr(''), [ + \ 'Unsupported use of ''||''. In fish, please use ''COMMAND; or COMMAND''.', + \ '/tmp/vLz620o/258/test.fish (line 2): if set -q SSH_CLIENT || set -q SSH_TTY', + \ ' ^', + \ '/tmp/vLz620o/258/test.fish (line 5): wat', + \ ' ^', + \ ]) diff --git a/test/handler/test_flake8_handler.vader b/test/handler/test_flake8_handler.vader index 492941c9..efacdfb2 100644 --- a/test/handler/test_flake8_handler.vader +++ b/test/handler/test_flake8_handler.vader @@ -214,3 +214,33 @@ Execute(Disabling trailing blank line warnings should work): \ ale_linters#python#flake8#Handle(bufnr(''), [ \ 'foo.py:6:1: W391 blank line at end of file', \ ]) + +Execute(F401 should be a warning): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'F401', + \ 'type': 'W', + \ 'text': 'module imported but unused', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: F401 module imported but unused', + \ ]) + +Execute(E112 should be a syntax error): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'E112', + \ 'type': 'E', + \ 'text': 'expected an indented block', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: E112 expected an indented block', + \ ]) diff --git a/test/handler/test_flawfinder_handler.vader b/test/handler/test_flawfinder_handler.vader new file mode 100644 index 00000000..708bac2a --- /dev/null +++ b/test/handler/test_flawfinder_handler.vader @@ -0,0 +1,57 @@ +Before: + Save g:ale_c_flawfinder_error_severity + + unlet! g:ale_c_flawfinder_error_severity + unlet! b:ale_c_flawfinder_error_severity + + runtime ale_linters/c/flawfinder.vim + +After: + unlet! g:ale_c_flawfinder_error_severity + Restore + +Execute(The Flawfinder handler should ignore other lines of output): + AssertEqual + \ [], + \ ale#handlers#flawfinder#HandleFlawfinderFormat(347, [ + \ 'foo', + \ 'bar', + \ 'baz', + \ ]) + +Execute(The Flawfinder handler should work): + AssertEqual + \ [ + \ { + \ 'lnum': 31, + \ 'col': 4, + \ 'type': 'W', + \ 'text': "(buffer) strncpy: Easily used incorrectly", + \ }, + \ ], + \ ale#handlers#flawfinder#HandleFlawfinderFormat(347, [ + \ "<stdin>:31:4: [1] (buffer) strncpy:Easily used incorrectly", + \ ]) + +Execute(The Flawfinder error severity level should be configurable): + let b:ale_c_flawfinder_error_severity = 2 + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col': 4, + \ 'type': 'E', + \ 'text': "(buffer) char: Statically-sized arrays can be bad", + \ }, + \ { + \ 'lnum': 31, + \ 'col': 4, + \ 'type': 'W', + \ 'text': "(buffer) strncpy: Easily used incorrectly", + \ }, + \ ], + \ ale#handlers#flawfinder#HandleFlawfinderFormat(bufnr(''), [ + \ "<stdin>:12:4: [2] (buffer) char:Statically-sized arrays can be bad", + \ "<stdin>:31:4: [1] (buffer) strncpy:Easily used incorrectly", + \ ]) diff --git a/test/handler/test_gawk_handler.vader b/test/handler/test_gawk_handler.vader new file mode 100644 index 00000000..3a7b5457 --- /dev/null +++ b/test/handler/test_gawk_handler.vader @@ -0,0 +1,39 @@ +Before: + runtime ale_linters/awk/gawk.vim + +After: + call ale#linter#Reset() + +Execute(gawk syntax errors should be parsed correctly): + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'text': "invalid char ''' in expression", + \ 'code': 0, + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 0, + \ 'text': 'unterminated string', + \ 'code': 0, + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 0, + \ 'text': "escape sequence `\u' treated as plain `u'", + \ 'code': 0, + \ 'type': 'W', + \ }, + \ ], + \ ale#handlers#gawk#HandleGawkFormat(347, [ + \ "gawk: something.awk:1: BEGIN { system('touch aaaaaaaaa') }", + \ "gawk: something.awk:1: ^ invalid char ''' in expression", + \ 'gawk: something.awk:5: { x = "aaaaaaaaaaa', + \ 'gawk: something.awk:5: ^ unterminated string', + \ "gawk: something.awk:10: warning: escape sequence `\u' treated as plain `u'", + \ ]) diff --git a/test/handler/test_gcc_handler.vader b/test/handler/test_gcc_handler.vader index 79f17899..678d3f42 100644 --- a/test/handler/test_gcc_handler.vader +++ b/test/handler/test_gcc_handler.vader @@ -148,3 +148,17 @@ Execute(The GCC handler should interpret - as being the current file): \ ale#handlers#gcc#HandleGCCFormat(347, [ \ '-:6:12: error: Some error', \ ]) + +Execute(The GCC handler should handle fatal error messages due to missing files): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 12, + \ 'type': 'E', + \ 'text': 'foo.h: No such file or directory' + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ '<stdin>:3:12: fatal error: foo.h: No such file or directory', + \ ]) diff --git a/test/handler/test_gitlint_handler.vader b/test/handler/test_gitlint_handler.vader index 73ee988f..60d632a0 100644 --- a/test/handler/test_gitlint_handler.vader +++ b/test/handler/test_gitlint_handler.vader @@ -1,8 +1,16 @@ Before: - runtime ale_linters/gitcommit/gitlint.vim + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/gitcommit/gitlint.vim After: - call ale#linter#Reset() + Restore + + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() Execute(The gitlint handler should handle basic warnings and syntax errors): AssertEqual @@ -39,3 +47,24 @@ Execute(The gitlint handler should handle basic warnings and syntax errors): \ '8: T1 Title exceeds max length (92>72): "some very long commit subject line where the author can''t wait to explain what he just fixed"' \ ]) +Execute(Disabling trailing whitespace warnings should work): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'type': 'E', + \ 'text': 'Trailing whitespace', + \ 'code': 'T2', + \ }, + \ ], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: T2 Trailing whitespace', + \]) + + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: T2 Trailing whitespace', + \ ]) diff --git a/test/handler/test_go_generic_handler.vader b/test/handler/test_go_generic_handler.vader new file mode 100644 index 00000000..624e56c1 --- /dev/null +++ b/test/handler/test_go_generic_handler.vader @@ -0,0 +1,22 @@ +Execute(The golang handler should return the correct filenames): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'text': 'some error', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.go'), + \ }, + \ { + \ 'lnum': 27, + \ 'col': 5, + \ 'text': 'some error with a column', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/other.go'), + \ }, + \ ], + \ ale#handlers#go#Handler(bufnr(''), [ + \ 'test.go:27: some error', + \ 'other.go:27:5: some error with a column', + \ ]) diff --git a/test/handler/test_markdownlint_handler.vader b/test/handler/test_markdownlint_handler.vader new file mode 100644 index 00000000..db6acc66 --- /dev/null +++ b/test/handler/test_markdownlint_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/markdown/markdownlint.vim + +After: + call ale#linter#Reset() + +Execute(The Markdownlint handler should parse output correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': '(MD002/first-header-h1) First header should be a top level header [Expected: h1; Actual: h2]', + \ 'type': 'W' + \ }, + \ { + \ 'lnum': 298, + \ 'text': '(MD033/no-inline-html) Inline HTML [Element: p]', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md: 1: MD002/first-header-h1 First header should be a top level header [Expected: h1; Actual: h2]', + \ 'README.md: 298: MD033/no-inline-html Inline HTML [Element: p]' + \ ]) diff --git a/test/handler/test_mcsc_handler.vader b/test/handler/test_mcsc_handler.vader index ac55ee81..8ae47357 100644 --- a/test/handler/test_mcsc_handler.vader +++ b/test/handler/test_mcsc_handler.vader @@ -3,10 +3,15 @@ Before: unlet! g:ale_cs_mcsc_source + call ale#test#SetDirectory('/testplugin/test/handler') + call ale#test#SetFilename('Test.cs') + runtime ale_linters/cs/mcsc.vim After: unlet! g:ale_cs_mcsc_source + + call ale#test#RestoreDirectory() call ale#linter#Reset() Execute(The mcs handler should work with the default of the buffer's directory): @@ -18,10 +23,10 @@ Execute(The mcs handler should work with the default of the buffer's directory): \ 'text': '; expected', \ 'code': 'CS1001', \ 'type': 'E', - \ 'filename': ale#path#Simplify(expand('%:p:h') . '/Test.cs'), + \ 'filename': ale#path#Simplify(g:dir . '/Test.cs'), \ }, \ ], - \ ale_linters#cs#mcsc#Handle(347, [ + \ ale_linters#cs#mcsc#Handle(bufnr(''), [ \ 'Test.cs(12,29): error CS1001: ; expected', \ 'Compilation failed: 2 error(s), 1 warnings', \ ]) @@ -56,7 +61,7 @@ Execute(The mcs handler should handle cannot find symbol errors): \ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'), \ }, \ ], - \ ale_linters#cs#mcsc#Handle(347, [ + \ ale_linters#cs#mcsc#Handle(bufnr(''), [ \ 'Test.cs(12,29): error CS1001: ; expected', \ 'Test.cs(101,0): error CS1028: Unexpected processor directive (no #if for this #endif)', \ 'Test.cs(10,12): warning CS0123: some warning', diff --git a/test/handler/test_mercury_mmc_handler.vader b/test/handler/test_mercury_mmc_handler.vader new file mode 100644 index 00000000..e862f287 --- /dev/null +++ b/test/handler/test_mercury_mmc_handler.vader @@ -0,0 +1,58 @@ +Before: + runtime ale_linters/mercury/mmc.vim + +After: + call ale#linter#Reset() + +Execute(The mmc handler should handle syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': "Syntax error at token ',': operator precedence error." + \ } + \ ], + \ ale_linters#mercury#mmc#Handle(1, [ + \ "file_name.m:003: Syntax error at token ',': operator precedence error." + \ ]) + +Execute(The mmc handler should handle warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'type': 'W', + \ 'text': 'Warning: reference to uninitialized state variable !.X.' + \ }, + \ { + \ 'lnum': 12, + \ 'type': 'W', + \ 'text': 'warning: determinism declaration could be tighter.' + \ } + \ ], + \ ale_linters#mercury#mmc#Handle(1, [ + \ 'file_name.m:010: Warning: reference to uninitialized state variable !.X.', + \ "file_name.m:012: In `some_predicate':", + \ 'file_name.m:012: warning: determinism declaration could be tighter.' + \ ]) + +Execute(The mmc handler should handle semantic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'type': 'E', + \ 'text': "error: undefined type `bar'/0." + \ }, + \ { + \ 'lnum': 15, + \ 'type': 'E', + \ 'text': "Error: circular equivalence type `file_name.foo'/0." + \ } + \ ], + \ ale_linters#mercury#mmc#Handle(1, [ + \ "file_name.m:007: In clause for predicate `foldit'/4:", + \ "file_name.m:007: error: undefined type `bar'/0.", + \ "file_name.m:015: Error: circular equivalence type `file_name.foo'/0." + \ ]) diff --git a/test/handler/test_mix_handler.vader b/test/handler/test_mix_handler.vader new file mode 100644 index 00000000..a5549b5d --- /dev/null +++ b/test/handler/test_mix_handler.vader @@ -0,0 +1,21 @@ +Before: + runtime ale_linters/elixir/mix.vim + +After: + call ale#linter#Reset() + +Execute(The mix handler should parse lines correctly): + + AssertEqual + \ [ + \ { + \ 'bufnr': 347, + \ 'lnum': 87, + \ 'col': 0, + \ 'text': 'undefined function update_in/4', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#elixir#mix#Handle(347, [ + \ '** (CompileError) apps/sim/lib/sim/server.ex:87: undefined function update_in/4' + \ ]) diff --git a/test/handler/test_msgfmt_hander.vader b/test/handler/test_msgfmt_hander.vader new file mode 100644 index 00000000..1a67dbc6 --- /dev/null +++ b/test/handler/test_msgfmt_hander.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/po/msgfmt.vim + +After: + call ale#linter#Reset() + +Execute(Duplicate messages should be made easier to navigate): + AssertEqual + \ [ + \ {'lnum': 14, 'col': 0, 'type': 'W', 'text': 'some other thing'}, + \ {'lnum': 1746, 'col': 0, 'type': 'W', 'text': 'duplicate of message at line 262'}, + \ {'lnum': 262, 'col': 0, 'type': 'W', 'text': 'first location of duplicate of message at line 1746'}, + \ {'lnum': 666, 'col': 0, 'type': 'W', 'text': 'duplicate message definition...'}, + \ {'lnum': 888, 'col': 0, 'type': 'W', 'text': 'some other thing'}, + \ {'lnum': 999, 'col': 0, 'type': 'W', 'text': '...this is the location of the first definition'}, + \ ], + \ ale_linters#po#msgfmt#Handle(bufnr(''), [ + \ '/tmp/v6GMUFf/16/foo.po:14: some other thing', + \ '/tmp/v6GMUFf/16/foo.po:1746: duplicate message definition...', + \ '/tmp/v6GMUFf/16/foo.po:262: ...this is the location of the first definition', + \ '/tmp/v6GMUFf/16/foo.po:666: duplicate message definition...', + \ '/tmp/v6GMUFf/16/foo.po:888: some other thing', + \ '/tmp/v6GMUFf/16/foo.po:999: ...this is the location of the first definition', + \ ]) diff --git a/test/handler/test_nasm_handler.vader b/test/handler/test_nasm_handler.vader new file mode 100644 index 00000000..9c7d9650 --- /dev/null +++ b/test/handler/test_nasm_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/nasm/nasm.vim + +After: + call ale#linter#Reset() + +Execute(The nasm handler should parse GCC style output from nasm correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'text': "label alone on a line without a colon might be in error", + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'text': "invalid combination of opcode and operands", + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 7, + \ 'text': "unable to open include file `bar.asm'", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#nasm#nasm#Handle(bufnr(''), [ + \ "tmp.asm:2: warning: label alone on a line without a colon might be in error", + \ "tmp.asm:4: error: invalid combination of opcode and operands", + \ "tmp.asm:7: fatal: unable to open include file `bar.asm'" + \ ]) diff --git a/test/handler/test_perl_handler.vader b/test/handler/test_perl_handler.vader index 75e8f226..c5791d76 100644 --- a/test/handler/test_perl_handler.vader +++ b/test/handler/test_perl_handler.vader @@ -47,3 +47,42 @@ Execute(The Perl linter should complain about failing to locate modules): \ 'Unable to build `ro` accessor for slot `path` in `App::CPANFileUpdate` because the slot cannot be found. at /extlib/Method/Traits.pm line 189.', \ 'BEGIN failed--compilation aborted at - line 10.', \ ]) + +Execute(The Perl linter should not report warnings as errors): + AssertEqual + \ [ + \ {'lnum': '5', 'type': 'W', 'text': '"my" variable $foo masks earlier declaration in same scope'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ '"my" variable $foo masks earlier declaration in same scope at - line 5.', + \ 't.pl syntax OK', + \ ]) + +Execute(The Perl linter does not default to reporting generic error): + AssertEqual + \ [ + \ {'lnum': '8', 'type': 'E', 'text': 'Missing right curly or square bracket'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ 'Missing right curly or square bracket at - line 8, at end of line', + \ 'syntax error at - line 8, at EOF', + \ 'Execution of t.pl aborted due to compilation errors.', + \ ]) + +" The first "error" is actually a warning, but the current implementation +" doesn't have a good way of teasing out the warnings from amongst the +" errors. If we're able to do this in future, then we'll want to switch +" the first "E" to a "W". + +Execute(The Perl linter reports errors even when mixed with warnings): + AssertEqual + \ [ + \ {'lnum': '5', 'type': 'E', 'text': '"my" variable $foo masks earlier declaration in same scope'}, + \ {'lnum': '8', 'type': 'E', 'text': 'Missing right curly or square bracket'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ '"my" variable $foo masks earlier declaration in same scope at - line 5.', + \ 'Missing right curly or square bracket at - line 8, at end of line', + \ 'syntax error at - line 8, at EOF', + \ 'Execution of t.pl aborted due to compilation errors.', + \ ]) diff --git a/test/handler/test_pmd_handler.vader b/test/handler/test_pmd_handler.vader new file mode 100644 index 00000000..0c95fb2a --- /dev/null +++ b/test/handler/test_pmd_handler.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/java/pmd.vim + +After: + call ale#linter#Reset() + +Execute(The pmd handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 18, + \ 'text': 'Each class should declare at least one constructor', + \ 'code': 'Code Style - AtLeastOneConstructor', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 36, + \ 'text': 'Local variable ''node'' could be declared final', + \ 'code': 'Code Style - LocalVariableCouldBeFinal', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#java#pmd#Handle(666, [ + \ '"Problem","Package","File","Priority","Line","Description","Rule set","Rule"', + \ '"1","rsb.performance.test.ros","/home/languitar/src/rsb-performance-test-api-ros/src/main/java/rsb/performance/test/ros/NodeHolder.java","3","18","Each class should declare at least one constructor","Code Style","AtLeastOneConstructor"', + \ '"2","rsb.performance.test.ros","/home/languitar/src/rsb-performance-test-api-ros/src/main/java/rsb/performance/test/ros/NodeHolder.java","1","36","Local variable ''node'' could be declared final","Code Style","LocalVariableCouldBeFinal"' + \ ]) diff --git a/test/handler/test_pony_handler.vader b/test/handler/test_pony_handler.vader new file mode 100644 index 00000000..25a8254b --- /dev/null +++ b/test/handler/test_pony_handler.vader @@ -0,0 +1,21 @@ +Execute(The pony handler should handle ponyc output): + call ale#test#SetFilename('foo.pony') + + AssertEqual + \ [ + \ { + \ 'filename': '/home/projects/Wombat.pony', + \ 'lnum': 22, + \ 'type': 'E', + \ 'col': 30, + \ 'text': 'can''t lookup private fields from outside the type', + \ }, + \ ], + \ ale#handlers#pony#HandlePonycFormat(bufnr(''), [ + \ 'Building builtin -> /usr/lib/pony/0.21.3/packages/builtin', + \ 'Building . -> /home/projects', + \ 'Error:', + \ '/home/projects/Wombat.pony:22:30: can''t lookup private fields from outside the type', + \ ' env.out.print(defaultWombat._hunger_level)', + \ ' ^', + \ ]) diff --git a/test/handler/test_puppet_handler.vader b/test/handler/test_puppet_handler.vader index 0d274fd2..e73c9dc7 100644 --- a/test/handler/test_puppet_handler.vader +++ b/test/handler/test_puppet_handler.vader @@ -37,9 +37,15 @@ Execute(The puppet handler should parse lines and column correctly): \ 'lnum': 54, \ 'col': 9, \ 'text': "Syntax error at ':'" - \ } + \ }, + \ { + \ 'lnum': 45, + \ 'col': 12, + \ 'text': "Syntax error at 'parameter1'" + \ }, \ ], \ ale_linters#puppet#puppet#Handle(255, [ \ "Error: Could not parse for environment production: Syntax error at ':' at /root/puppetcode/modules/nginx/manifests/init.pp:43:12", \ "Error: Could not parse for environment production: Syntax error at ':' at C:/puppet/modules/nginx/manifests/init.pp:54:9", + \ "Error: Could not parse for environment production: Syntax error at 'parameter1' (file: /tmp/modules/mariadb/manifests/slave.pp, line: 45, column: 12)", \ ]) diff --git a/test/handler/test_pycodestyle_handler.vader b/test/handler/test_pycodestyle_handler.vader index 0fd885d6..3664455e 100644 --- a/test/handler/test_pycodestyle_handler.vader +++ b/test/handler/test_pycodestyle_handler.vader @@ -137,3 +137,18 @@ Execute(Disabling trailing blank line warnings should work): \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ \ 'foo.py:6:1: W391 blank line at end of file', \ ]) + +Execute(E112 should be a syntax error): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'E112', + \ 'type': 'E', + \ 'text': 'expected an indented block', + \ }, + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'foo.py:6:1: E112 expected an indented block', + \ ]) diff --git a/test/handler/test_pylint_handler.vader b/test/handler/test_pylint_handler.vader index aff40845..18f66526 100644 --- a/test/handler/test_pylint_handler.vader +++ b/test/handler/test_pylint_handler.vader @@ -94,3 +94,22 @@ Execute(Ignoring trailing whitespace messages should work): \ '------------------------------------------------------------------', \ 'Your code has been rated at 0.00/10 (previous run: 2.50/10, -2.50)', \ ]) + +Execute(The pylint handler should parse Windows filenames): + AssertEqual + \ [ + \ { + \ 'lnum': 13, + \ 'col': 6, + \ 'text': 'Undefined variable ''x''', + \ 'code': 'undefined-variable', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#python#pylint#Handle(bufnr(''), [ + \ '************* Module test', + \ 'D:\acm\github\vim\tools\test.py:13:5: E0602 (undefined-variable) Undefined variable ''x''', + \ '', + \ '------------------------------------------------------------------', + \ 'Your code has been rated at 5.83/10 (previous run: 5.83/10, +0.00)', + \ ]) diff --git a/test/handler/test_qmlfmt_handler.vader b/test/handler/test_qmlfmt_handler.vader new file mode 100644 index 00000000..fc8ef355 --- /dev/null +++ b/test/handler/test_qmlfmt_handler.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/qml/qmlfmt.vim + +After: + call ale#linter#Reset() + +Execute(The qmlfmt handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 22, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Expected token ''}''.' + \ } + \ ], + \ ale_linters#qml#qmlfmt#Handle(1, [ + \ 'Error:22:1: Expected token ''}''.' + \ ]) diff --git a/test/handler/test_qmllint_handler.vader b/test/handler/test_qmllint_handler.vader new file mode 100644 index 00000000..fcc65eb5 --- /dev/null +++ b/test/handler/test_qmllint_handler.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/qml/qmllint.vim + +After: + call ale#linter#Reset() + +Execute(The qmllint handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Expected token ''}''' + \ } + \ ], + \ ale_linters#qml#qmllint#Handle(1, [ + \ '/tmp/ab34cd56/Test.qml:2 : Expected token ''}''' + \ ]) diff --git a/test/handler/test_redpen_handler.vader b/test/handler/test_redpen_handler.vader index f28d6923..4490bcba 100644 --- a/test/handler/test_redpen_handler.vader +++ b/test/handler/test_redpen_handler.vader @@ -23,6 +23,15 @@ Execute(redpen handler should handle errors output): \ 'type': 'W', \ 'code': 'Spelling', \ }, + \ { + \ 'lnum': 1, + \ 'col': 35, + \ 'end_lnum': 1, + \ 'end_col': 55, + \ 'text': 'Found possibly misspelled word "コードチェック".', + \ 'type': 'W', + \ 'code': 'Spelling', + \ }, \ ], \ ale#handlers#redpen#HandleRedpenOutput(bufnr(''), [ \ '[', @@ -50,6 +59,21 @@ Execute(redpen handler should handle errors output): \ ' "lineNum": 1,', \ ' "sentenceStartColumnNum": 0,', \ ' "message": "Found possibly misspelled word \"NeoVim\"."', + \ ' },', + \ ' {', + \ ' "sentence": "ALEはNeoVimとVim8で非同期のコードチェックを実現するプラグインです。",', + \ ' "endPosition": {', + \ ' "offset": 27,', + \ ' "lineNum": 1', + \ ' },', + \ ' "validator": "Spelling",', + \ ' "lineNum": 1,', + \ ' "sentenceStartColumnNum": 0,', + \ ' "message": "Found possibly misspelled word \"コードチェック\".",', + \ ' "startPosition": {', + \ ' "offset": 20,', + \ ' "lineNum": 1', + \ ' }', \ ' }', \ ' ]', \ ' }', diff --git a/test/handler/test_remark_lint_handler.vader b/test/handler/test_remark_lint_handler.vader index f61da199..0794d51c 100644 --- a/test/handler/test_remark_lint_handler.vader +++ b/test/handler/test_remark_lint_handler.vader @@ -19,12 +19,21 @@ Execute(Warning and error messages should be handled correctly): \ 'type': 'E', \ 'text': 'Incorrect list-item indent: remove 1 space list-item-indent remark-lint', \ }, + \ { + \ 'lnum': 18, + \ 'col': 71, + \ 'end_lnum': 19, + \ 'end_col': 1, + \ 'type': 'E', + \ 'text': 'Missing new line after list item list-item-spacing remark-lint', + \ }, \ ], \ ale_linters#markdown#remark_lint#Handle(1, [ \ 'foo.md', \ ' 1:4 warning Incorrect list-item indent: add 1 space list-item-indent remark-lint', \ ' 3:5 error Incorrect list-item indent: remove 1 space list-item-indent remark-lint', + \ ' 18:71-19:1 error Missing new line after list item list-item-spacing remark-lint', \ '', \ '⚠ 1 warnings', - \ '✘ 1 errors', + \ '✘ 2 errors', \]) diff --git a/test/handler/test_scala_handler.vader b/test/handler/test_scala_handler.vader new file mode 100644 index 00000000..3214bdbc --- /dev/null +++ b/test/handler/test_scala_handler.vader @@ -0,0 +1,32 @@ +After: + call ale#linter#Reset() + +Execute(The handler should return an empty list with empty input): + AssertEqual [], ale#handlers#scala#HandleScalacLintFormat(bufnr(''), []) + +Execute(The handler should correctly parse error messages): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 8, + \ 'text': ''':'' expected but identifier found.', + \ 'type': 'E' + \ }, + \ { + \ 'lnum': 6, + \ 'col': 2, + \ 'text': 'identifier expected but eof found.', + \ 'type': 'E' + \ } + \ ], + \ ale#handlers#scala#HandleScalacLintFormat(bufnr(''), + \ [ + \ "hi.scala:4: error: ':' expected but identifier found.", + \ " Some stupid scala code", + \ " ^", + \ "hi.scala:6: error: identifier expected but eof found.", + \ ")", + \ " ^", + \ "two errors found", + \ ]) diff --git a/test/handler/test_textlint_handler.vader b/test/handler/test_textlint_handler.vader new file mode 100644 index 00000000..c00d54de --- /dev/null +++ b/test/handler/test_textlint_handler.vader @@ -0,0 +1,41 @@ +Before: + runtime! ale_linters/markdown/textlint.vim + +After: + call ale#linter#Reset() + +Execute(textlint handler should handle errors output): + AssertEqual + \ [ + \ { + \ 'lnum': 16, + \ 'col': 50, + \ 'text': 'Found possibly misspelled word "NeoVim".', + \ 'type': 'W', + \ 'code': 'preset-japanese/no-doubled-joshi', + \ }, + \ ], + \ ale#handlers#textlint#HandleTextlintOutput(bufnr(''), [ + \ '[', + \ ' {', + \ ' "filePath": "test.md",', + \ ' "messages": [', + \ ' {', + \ ' "type": "lint",', + \ ' "ruleId": "preset-japanese/no-doubled-joshi",', + \ ' "index": 1332,', + \ ' "line": 16,', + \ ' "column": 50,', + \ ' "severity": 2,', + \ ' "message": "Found possibly misspelled word \"NeoVim\"."', + \ ' }', + \ ' ]', + \ ' }', + \ ']', + \ ]) + +Execute(textlint handler should no error output): + AssertEqual + \ [], + \ ale#handlers#textlint#HandleTextlintOutput(bufnr(''), [ + \ ]) diff --git a/test/lsp/test_did_save_event.vader b/test/lsp/test_did_save_event.vader index 042a3ce2..97774372 100644 --- a/test/lsp/test_did_save_event.vader +++ b/test/lsp/test_did_save_event.vader @@ -34,12 +34,18 @@ Before: \ }) let g:ale_linters = {'foobar': ['dummy_linter']} - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + return { + \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', + \ 'language_id': 'foobar', \} endfunction @@ -59,6 +65,7 @@ After: delfunction LanguageCallback delfunction ProjectRootCallback + call ale#lsp#RemoveConnectionWithID(347) call ale#test#RestoreDirectory() call ale#linter#Reset() diff --git a/test/lsp/test_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader index 053da803..dc28c2e9 100644 --- a/test/lsp/test_lsp_client_messages.vader +++ b/test/lsp/test_lsp_client_messages.vader @@ -16,9 +16,11 @@ Execute(ale#lsp#message#Initialize() should return correct messages): \ 'processId': getpid(), \ 'rootPath': '/foo/bar', \ 'capabilities': {}, + \ 'initializationOptions': {'foo': 'bar'}, + \ 'rootUri': 'file:///foo/bar', \ } \ ], - \ ale#lsp#message#Initialize('/foo/bar') + \ ale#lsp#message#Initialize('/foo/bar', {'foo': 'bar'}) Execute(ale#lsp#message#Initialized() should return correct messages): AssertEqual [1, 'initialized'], ale#lsp#message#Initialized() @@ -144,6 +146,35 @@ Execute(ale#lsp#message#Definition() should return correct messages): \ ], \ ale#lsp#message#Definition(bufnr(''), 12, 34) +Execute(ale#lsp#message#References() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/references', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 34}, + \ 'context': {'includeDeclaration': v:false}, + \ } + \ ], + \ ale#lsp#message#References(bufnr(''), 12, 34) + +Execute(ale#lsp#message#Hover() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/hover', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 34}, + \ } + \ ], + \ ale#lsp#message#Hover(bufnr(''), 12, 34) + Execute(ale#lsp#tsserver_message#Open() should return correct messages): AssertEqual \ [ @@ -233,3 +264,29 @@ Execute(ale#lsp#tsserver_message#Definition() should return correct messages): \ } \ ], \ ale#lsp#tsserver_message#Definition(bufnr(''), 347, 12) + +Execute(ale#lsp#tsserver_message#References() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@references', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ } + \ ], + \ ale#lsp#tsserver_message#References(bufnr(''), 347, 12) + +Execute(ale#lsp#tsserver_message#Quickinfo() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@quickinfo', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ } + \ ], + \ ale#lsp#tsserver_message#Quickinfo(bufnr(''), 347, 12) diff --git a/test/lsp/test_lsp_error_parsing.vader b/test/lsp/test_lsp_error_parsing.vader new file mode 100644 index 00000000..7464b0e7 --- /dev/null +++ b/test/lsp/test_lsp_error_parsing.vader @@ -0,0 +1,65 @@ +Execute(Invalid responses should be handled): + AssertEqual '', ale#lsp#response#GetErrorMessage({}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': 0}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {}}) + AssertEqual '', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': 0, + \ 'message': 'x', + \ }, + \}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {'code': -32602}}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {'code': -32603}}) + +Execute(Messages without tracebacks should be handled): + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ }, + \}) + AssertEqual 'abc', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32603, + \ 'message': 'abc', + \ }, + \}) + +Execute(Invalid traceback data should be tolerated): + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ }, + \ }, + \}) + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': 0, + \ }, + \ }, + \}) + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': [], + \ }, + \ }, + \}) + +Execute(Messages with tracebacks should be handled): + AssertEqual "xyz\n123\n456", ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': ['123', '456'], + \ }, + \ }, + \}) diff --git a/test/lsp/test_read_lsp_diagnostics.vader b/test/lsp/test_read_lsp_diagnostics.vader index 3e637418..444272aa 100644 --- a/test/lsp/test_read_lsp_diagnostics.vader +++ b/test/lsp/test_read_lsp_diagnostics.vader @@ -121,7 +121,8 @@ Execute(ale#lsp#response#ReadDiagnostics() should handle multiple messages): \ ]}}) Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle tsserver responses): - AssertEqual [ + AssertEqual + \ [ \ { \ 'type': 'E', \ 'nr': 2365, @@ -131,5 +132,35 @@ Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle tsserver respon \ 'end_lnum': 1, \ 'end_col': 17, \ }, - \], + \ ], \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"/bar/foo.ts","diagnostics":[{"start":{"line":1,"offset":11},"end":{"line":1,"offset":17},"text":"Operator ''+'' cannot be applied to types ''3'' and ''{}''.","code":2365}]}}) + +Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle warnings from tsserver): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 3, + \ 'nr': 2515, + \ 'end_lnum': 27, + \ 'type': 'W', + \ 'end_col': 14, + \ 'text': 'Calls to ''console.log'' are not allowed. (no-console)', + \ } + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"<removed>","diagnostics":[{"start":{"line":27,"offset":3},"end":{"line":27,"offset":14},"text":"Calls to 'console.log' are not allowed. (no-console)","code":2515,"category":"warning","source":"tslint"}]}}) + +Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle suggestions from tsserver): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 3, + \ 'nr': 2515, + \ 'end_lnum': 27, + \ 'type': 'I', + \ 'end_col': 14, + \ 'text': 'Some info', + \ } + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"<removed>","diagnostics":[{"start":{"line":27,"offset":3},"end":{"line":27,"offset":14},"text":"Some info","code":2515,"category":"suggestion","source":"tslint"}]}}) diff --git a/test/lsp/test_reset_lsp.vader b/test/lsp/test_reset_lsp.vader new file mode 100644 index 00000000..2bec13dc --- /dev/null +++ b/test/lsp/test_reset_lsp.vader @@ -0,0 +1,90 @@ +Before: + Save g:ale_enabled + Save g:ale_set_signs + Save g:ale_set_quickfix + Save g:ale_set_loclist + Save g:ale_set_highlights + Save g:ale_echo_cursor + + let g:ale_enabled = 0 + let g:ale_set_signs = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + + function EmptyString() abort + return '' + endfunction + + call ale#engine#InitBufferInfo(bufnr('')) + + call ale#linter#Define('testft', { + \ 'name': 'lsplinter', + \ 'lsp': 'tsserver', + \ 'executable_callback': 'EmptyString', + \ 'command_callback': 'EmptyString', + \ 'project_root_callback': 'EmptyString', + \ 'language_callback': 'EmptyString', + \}) + + call ale#linter#Define('testft', { + \ 'name': 'otherlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + +After: + Restore + + unlet! b:ale_save_event_fired + + delfunction EmptyString + call ale#linter#Reset() + +Given testft(Some file with an imaginary filetype): +Execute(ALEStopAllLSPs should clear the loclist): + let g:ale_buffer_info[bufnr('')].loclist = [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'lsplinter', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'otherlinter', + \ }, + \] + let g:ale_buffer_info[bufnr('')].active_linter_list = ['lsplinter', 'otherlinter'] + + ALEStopAllLSPs + + " The loclist should be updated. + AssertEqual g:ale_buffer_info[bufnr('')].loclist, [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'otherlinter', + \ }, + \] + + " The LSP linter should be removed from the active linter list. + AssertEqual g:ale_buffer_info[bufnr('')].active_linter_list, ['otherlinter'] diff --git a/test/prettier-test-files/testfile b/test/prettier-test-files/testfile new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/prettier-test-files/testfile diff --git a/test/script/check-supported-tools-tables b/test/script/check-supported-tools-tables index 32cebb2d..220c7427 100755 --- a/test/script/check-supported-tools-tables +++ b/test/script/check-supported-tools-tables @@ -1,4 +1,7 @@ -#!/bin/bash -eu +#!/usr/bin/env bash + +set -e +set -u # 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. diff --git a/test/script/check-toc b/test/script/check-toc index cc2d2b9c..8e411589 100755 --- a/test/script/check-toc +++ b/test/script/check-toc @@ -1,4 +1,7 @@ -#!/bin/bash -eu +#!/usr/bin/env bash + +set -e +set -u # This script checks that the table of contents for the supported tools is # sorted, and that the table matches the files. diff --git a/test/script/custom-checks b/test/script/custom-checks index 791053d4..76b9bc86 100755 --- a/test/script/custom-checks +++ b/test/script/custom-checks @@ -1,4 +1,7 @@ -#!/bin/bash -eu +#!/usr/bin/env bash + +set -e +set -u exit_code=0 image=w0rp/ale diff --git a/test/script/custom-linting-rules b/test/script/custom-linting-rules index ef6d792f..0d1a0fd1 100755 --- a/test/script/custom-linting-rules +++ b/test/script/custom-linting-rules @@ -1,4 +1,7 @@ -#!/bin/bash -eu +#!/usr/bin/env bash + +set -e +set -u # This Bash script implements custom sanity checks for scripts beyond what # Vint covers, which are easy to check with regex. diff --git a/test/script/run-vader-tests b/test/script/run-vader-tests index a10b8baf..3e7e815c 100755 --- a/test/script/run-vader-tests +++ b/test/script/run-vader-tests @@ -1,4 +1,7 @@ -#!/bin/bash -eu +#!/usr/bin/env bash + +set -e +set -u image=w0rp/ale docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$image") diff --git a/test/script/run-vint b/test/script/run-vint index e1140307..0d5b6e11 100755 --- a/test/script/run-vint +++ b/test/script/run-vint @@ -1,4 +1,7 @@ -#!/bin/bash -eu +#!/usr/bin/env bash + +set -e +set -u exit_code=0 image=w0rp/ale diff --git a/test/sign/test_sign_placement.vader b/test/sign/test_sign_placement.vader index 36f34e16..19267fe7 100644 --- a/test/sign/test_sign_placement.vader +++ b/test/sign/test_sign_placement.vader @@ -1,7 +1,9 @@ Before: Save g:ale_set_signs + Save g:ale_buffer_info let g:ale_set_signs = 1 + let g:ale_buffer_info = {} call ale#linter#Reset() sign unplace * diff --git a/test/smoke_test.vader b/test/smoke_test.vader index f6d0be56..843bddab 100644 --- a/test/smoke_test.vader +++ b/test/smoke_test.vader @@ -93,7 +93,7 @@ Execute(Linters should run in PowerShell too): \}) call ale#Lint() - call ale#engine#WaitForJobs(2000) + call ale#engine#WaitForJobs(4000) AssertEqual [ \ { diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index e20125a3..39a2a85a 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -1,30 +1,50 @@ Before: Save g:ale_buffer_info Save g:ale_cache_executable_check_failures + Save g:ale_completion_delay Save g:ale_completion_enabled + Save g:ale_completion_max_suggestions Save g:ale_fixers Save g:ale_history_log_output Save g:ale_lint_on_insert_leave Save g:ale_lint_on_text_changed Save g:ale_linters + Save g:ale_lsp_error_messages Save g:ale_maximum_file_size Save g:ale_pattern_options Save g:ale_pattern_options_enabled Save g:ale_set_balloons + Save g:ale_sign_error + Save g:ale_sign_info + Save g:ale_sign_style_error + Save g:ale_sign_style_warning + Save g:ale_sign_warning + Save g:ale_statusline_format + Save g:ale_type_map Save g:ale_warn_about_trailing_whitespace unlet! b:ale_history let g:ale_buffer_info = {} let g:ale_cache_executable_check_failures = 0 + let g:ale_completion_delay = 100 let g:ale_completion_enabled = 0 + let g:ale_completion_max_suggestions = 50 let g:ale_history_log_output = 1 let g:ale_lint_on_insert_leave = 0 let g:ale_lint_on_text_changed = 'always' + let g:ale_lsp_error_messages = {} let g:ale_maximum_file_size = 0 let g:ale_pattern_options = {} let g:ale_pattern_options_enabled = 0 let g:ale_set_balloons = 0 + let g:ale_sign_error = '>>' + let g:ale_sign_info = '--' + let g:ale_sign_style_error = '>>' + let g:ale_sign_style_warning = '--' + let g:ale_sign_warning = '--' + let g:ale_statusline_format = ['%d error(s)', '%d warning(s)', 'OK'] + let g:ale_type_map = {} let g:ale_warn_about_trailing_whitespace = 1 let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout'} @@ -32,10 +52,19 @@ Before: call ale#engine#ResetExecutableCache() call ale#linter#Reset() + call ale#linter#PreventLoading('testft') let g:ale_linters = {} let g:ale_fixers = {} let g:ale_linter_aliases = {} let g:ale_buffer_info = {} + let g:fixer_lines = [ + \ ' Suggested Fixers: ', + \ ' ''foo'' - Fix things the foo way', + \] + let g:variables_lines = [ + \ ' Linter Variables:', + \ '', + \] let g:globals_lines = [ \ ' Global Variables:', \ '', @@ -66,6 +95,7 @@ Before: \ 'let g:ale_linters = {}', \ 'let g:ale_linters_explicit = 0', \ 'let g:ale_list_window_size = 10', + \ 'let g:ale_list_vertical = 0', \ 'let g:ale_loclist_msg_format = ''%code: %%s''', \ 'let g:ale_max_buffer_history_size = 20', \ 'let g:ale_max_signs = -1', @@ -87,6 +117,7 @@ Before: \ 'let g:ale_sign_warning = ''--''', \ 'let g:ale_statusline_format = [''%d error(s)'', ''%d warning(s)'', ''OK'']', \ 'let g:ale_type_map = {}', + \ 'let g:ale_use_global_executables = v:null', \ 'let g:ale_warn_about_trailing_blank_lines = 1', \ 'let g:ale_warn_about_trailing_whitespace = 1', \] @@ -104,6 +135,11 @@ Before: AssertEqual a:expected_list, split(l:output, "\n") endfunction + call ale#test#SetDirectory('/testplugin/test') + + call ale#fix#registry#Clear() + call ale#fix#registry#Add('foo', 'x', [], 'Fix things the foo way') + After: Restore @@ -113,6 +149,8 @@ After: unlet! b:ale_history unlet! b:ale_linters unlet! g:output + unlet! g:fixer_lines + unlet! g:variables_lines unlet! g:globals_string unlet! g:command_header unlet! g:ale_testft_testlinter1_foo @@ -122,15 +160,22 @@ After: unlet! g:ale_testft2_testlinter2_bar delfunction CheckInfo + call ale#test#RestoreDirectory() + call ale#fix#registry#ResetToDefaults() + Given nolintersft (Empty buffer with no linters): Execute (ALEInfo with no linters should return the right output): - call CheckInfo([ - \ ' Current Filetype: nolintersft', - \ 'Available Linters: []', - \ ' Enabled Linters: []', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: nolintersft', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given (Empty buffer with no filetype): Execute (ALEInfo should return buffer-local global ALE settings): @@ -142,48 +187,64 @@ Execute (ALEInfo should return buffer-local global ALE settings): \ index(g:globals_lines, 'let g:ale_linters = {}') + 1 \) - call CheckInfo([ - \ ' Current Filetype: ', - \ 'Available Linters: []', - \ ' Enabled Linters: []', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: ', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given (Empty buffer with no filetype): Execute (ALEInfo with no filetype should return the right output): - call CheckInfo([ - \ ' Current Filetype: ', - \ 'Available Linters: []', - \ ' Enabled Linters: []', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: ', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given testft (Empty buffer): Execute (ALEInfo with a single linter should return the right output): call ale#linter#Define('testft', g:testlinter1) - call CheckInfo([ - \ ' Current Filetype: testft', - \ 'Available Linters: [''testlinter1'']', - \ ' Enabled Linters: [''testlinter1'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given testft (Empty buffer): Execute (ALEInfo with two linters should return the right output): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft', g:testlinter2) - call CheckInfo([ - \ ' Current Filetype: testft', - \ 'Available Linters: [''testlinter1'', ''testlinter2'']', - \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given testft (Empty buffer): Execute (ALEInfo should calculate enabled linters correctly): @@ -194,39 +255,51 @@ Execute (ALEInfo should calculate enabled linters correctly): let g:globals_lines[index(g:globals_lines, 'let g:ale_linters = {}')] \ = 'let g:ale_linters = {''testft'': [''testlinter2'']}' - call CheckInfo([ - \ ' Current Filetype: testft', - \ 'Available Linters: [''testlinter1'', ''testlinter2'']', - \ ' Enabled Linters: [''testlinter2'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given testft (Empty buffer): Execute (ALEInfo should only return linters for current filetype): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ - \ ' Current Filetype: testft', - \ 'Available Linters: [''testlinter1'']', - \ ' Enabled Linters: [''testlinter1'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo with compound filetypes should return linters for both of them): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ - \ ' Current Filetype: testft.testft2', - \ 'Available Linters: [''testlinter1'', ''testlinter2'']', - \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo should return appropriately named global variables): @@ -238,17 +311,58 @@ Execute (ALEInfo should return appropriately named global variables): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ - \ ' Current Filetype: testft.testft2', - \ 'Available Linters: [''testlinter1'', ''testlinter2'']', - \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \ 'let g:ale_testft2_testlinter2_bar = {''x'': ''y''}', - \ 'let g:ale_testft2_testlinter2_foo = 123', - \ 'let g:ale_testft_testlinter1_bar = [''abc'']', - \ 'let g:ale_testft_testlinter1_foo = ''abc''', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ '', + \ 'let g:ale_testft2_testlinter2_bar = {''x'': ''y''}', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let g:ale_testft_testlinter1_bar = [''abc'']', + \ 'let g:ale_testft_testlinter1_foo = ''abc''', + \ ] + \ + g:globals_lines + \ + g:command_header + \) + +Execute (ALEInfoToFile should write to a file correctly): + if filereadable(g:dir . '/ale-info-test-file') + call delete(g:dir . '/ale-info-test-file') + endif + + let g:ale_testft_testlinter1_foo = 'abc' + let g:ale_testft_testlinter1_bar = ['abc'] + let g:ale_testft2_testlinter2_foo = 123 + let g:ale_testft2_testlinter2_bar = {'x': 'y'} + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + execute 'ALEInfoToFile ' . fnameescape(g:dir . '/ale-info-test-file') + + AssertEqual + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ '', + \ 'let g:ale_testft2_testlinter2_bar = {''x'': ''y''}', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let g:ale_testft_testlinter1_bar = [''abc'']', + \ 'let g:ale_testft_testlinter1_foo = ''abc''', + \ ] + \ + g:globals_lines + \ + g:command_header, + \ readfile(g:dir . '/ale-info-test-file') Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo should buffer-local linter variables): @@ -258,15 +372,22 @@ Execute (ALEInfo should buffer-local linter variables): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ - \ ' Current Filetype: testft.testft2', - \ 'Available Linters: [''testlinter1'', ''testlinter2'']', - \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \ 'let g:ale_testft2_testlinter2_foo = 123', - \ 'let b:ale_testft2_testlinter2_foo = 456', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ '', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let b:ale_testft2_testlinter2_foo = 456', + \ ] + \ + g:globals_lines + \ + g:command_header + \) Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo should output linter aliases): @@ -279,18 +400,25 @@ Execute (ALEInfo should output linter aliases): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ - \ ' Current Filetype: testft.testft2', - \ 'Available Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Aliases:', - \ '''testlinter1'' -> [''testftalias1'', ''testftalias2'']', - \ '''testlinter2'' -> [''testftalias3'', ''testftalias4'']', - \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \ 'let g:ale_testft2_testlinter2_foo = 123', - \ 'let b:ale_testft2_testlinter2_foo = 456', - \] + g:globals_lines + g:command_header) + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Linter Aliases:', + \ '''testlinter1'' -> [''testftalias1'', ''testftalias2'']', + \ '''testlinter2'' -> [''testftalias3'', ''testftalias4'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ '', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let b:ale_testft2_testlinter2_foo = 456', + \ ] + \ + g:globals_lines + \ + g:command_header + \) Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo should return command history): @@ -302,17 +430,22 @@ Execute (ALEInfo should return command history): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ + call CheckInfo( + \ [ \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header + [ - \ '', - \ '(started) ''first command''', - \ '(started) [''/bin/bash'', ''\c'', ''last command'']', - \]) + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(started) ''first command''', + \ '(started) [''/bin/bash'', ''\c'', ''last command'']', + \ ] + \) Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo command history should print exit codes correctly): @@ -324,17 +457,22 @@ Execute (ALEInfo command history should print exit codes correctly): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ + call CheckInfo( + \ [ \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header + [ - \ '', - \ '(finished - exit code 0) ''first command''', - \ '(finished - exit code 1) [''/bin/bash'', ''\c'', ''last command'']', - \]) + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(finished - exit code 0) ''first command''', + \ '(finished - exit code 1) [''/bin/bash'', ''\c'', ''last command'']', + \ ] + \) Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo command history should print command output if logging is on): @@ -367,31 +505,36 @@ Execute (ALEInfo command history should print command output if logging is on): call ale#linter#Define('testft', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) - call CheckInfo([ + call CheckInfo( + \ [ \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header + [ - \ '', - \ '(finished - exit code 0) ''first command''', - \ '', - \ '<<<OUTPUT STARTS>>>', - \ 'some', - \ 'first command output', - \ '<<<OUTPUT ENDS>>>', - \ '', - \ '(finished - exit code 1) [''/bin/bash'', ''\c'', ''last command'']', - \ '', - \ '<<<OUTPUT STARTS>>>', - \ 'different second command output', - \ '<<<OUTPUT ENDS>>>', - \ '', - \ '(finished - exit code 0) ''command with no output''', - \ '', - \ '<<<NO OUTPUT RETURNED>>>', - \]) + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(finished - exit code 0) ''first command''', + \ '', + \ '<<<OUTPUT STARTS>>>', + \ 'some', + \ 'first command output', + \ '<<<OUTPUT ENDS>>>', + \ '', + \ '(finished - exit code 1) [''/bin/bash'', ''\c'', ''last command'']', + \ '', + \ '<<<OUTPUT STARTS>>>', + \ 'different second command output', + \ '<<<OUTPUT ENDS>>>', + \ '', + \ '(finished - exit code 0) ''command with no output''', + \ '', + \ '<<<NO OUTPUT RETURNED>>>', + \ ] + \) Execute (ALEInfo should include executable checks in the history): call ale#linter#Define('testft', g:testlinter1) @@ -400,18 +543,23 @@ Execute (ALEInfo should include executable checks in the history): call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') - call CheckInfo([ + call CheckInfo( + \ [ \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header + [ - \ '', - \ '(executable check - success) ' . (has('win32') ? 'cmd' : 'echo'), - \ '(executable check - failure) TheresNoWayThisIsExecutable', - \ '(executable check - failure) TheresNoWayThisIsExecutable', - \]) + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(executable check - success) ' . (has('win32') ? 'cmd' : 'echo'), + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \ ] + \) Execute (The option for caching failing executable checks should work): let g:ale_cache_executable_check_failures = 1 @@ -424,14 +572,60 @@ Execute (The option for caching failing executable checks should work): call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') - call CheckInfo([ + call CheckInfo( + \ [ \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', - \ ' Linter Variables:', - \ '', - \] + g:globals_lines + g:command_header + [ - \ '', - \ '(executable check - success) ' . (has('win32') ? 'cmd' : 'echo'), - \ '(executable check - failure) TheresNoWayThisIsExecutable', - \]) + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(executable check - success) ' . (has('win32') ? 'cmd' : 'echo'), + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \ ] + \) + +Given testft (Empty buffer): +Execute (LSP errors for a linter should be outputted): + let g:ale_lsp_error_messages = {'testlinter1': ['foo', 'bar']} + call ale#linter#Define('testft', g:testlinter1) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + [ + \ ' LSP Error Messages:', + \ '', + \ '(Errors for testlinter1)', + \ 'foo', + \ 'bar', + \ ] + \ + g:command_header + \) + +Given testft (Empty buffer): +Execute (LSP errors for other linters shouldn't appear): + let g:ale_lsp_error_messages = {'testlinter2': ['foo']} + call ale#linter#Define('testft', g:testlinter1) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) diff --git a/test/test_ale_lint_command.vader b/test/test_ale_lint_command.vader index d36b2177..7b4f7613 100644 --- a/test/test_ale_lint_command.vader +++ b/test/test_ale_lint_command.vader @@ -14,13 +14,6 @@ Before: \ 'pattern': '', \ 'valid': 1, \}] - let g:expected_groups = [ - \ 'ALECleanupGroup', - \ 'ALECursorGroup', - \ 'ALEHighlightBufferGroup', - \ 'ALERunOnEnterGroup', - \ 'ALERunOnTextChangedGroup', - \] function! ToggleTestCallback(buffer, output) return [{ @@ -45,7 +38,6 @@ After: Restore unlet! g:expected_loclist - unlet! g:expected_groups let g:ale_buffer_info = {} call ale#linter#Reset() diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader index d56f8c2b..cac762b4 100644 --- a/test/test_ale_toggle.vader +++ b/test/test_ale_toggle.vader @@ -5,12 +5,16 @@ Before: Save g:ale_run_synchronously Save g:ale_pattern_options Save g:ale_pattern_options_enabled + Save g:ale_set_balloons let g:ale_set_signs = 1 let g:ale_set_lists_synchronously = 1 let g:ale_run_synchronously = 1 let g:ale_pattern_options = {} let g:ale_pattern_options_enabled = 1 + let g:ale_set_balloons = + \ has('balloon_eval') && has('gui_running') || + \ has('balloon_eval_term') && !has('gui_running') unlet! b:ale_enabled @@ -28,12 +32,8 @@ Before: \}] let g:expected_groups = [ \ 'ALECleanupGroup', - \ 'ALECursorGroup', + \ 'ALEEvents', \ 'ALEHighlightBufferGroup', - \ 'ALERunOnEnterGroup', - \ 'ALERunOnFiletypeChangeGroup', - \ 'ALERunOnSaveGroup', - \ 'ALERunOnTextChangedGroup', \] function! ToggleTestCallback(buffer, output) @@ -56,7 +56,7 @@ Before: let l:results = [] for l:line in split(l:output, "\n") - let l:match = matchlist(l:line, '^ALE[a-zA-Z]\+Group') + let l:match = matchlist(l:line, '^ALE[a-zA-Z]\+') " We don't care about some groups here. if !empty(l:match) @@ -135,13 +135,7 @@ Execute(ALEToggle should reset everything and then run again): AssertEqual [], getloclist(0), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' AssertEqual [], getmatches(), 'The highlights were not cleared' - AssertEqual - \ [ - \ 'ALECleanupGroup', - \ 'ALEHighlightBufferGroup', - \ 'ALERunOnSaveGroup', - \ ], - \ ParseAuGroups() + AssertEqual g:expected_groups, ParseAuGroups() " Toggle ALE on, everything should be set up and run again. ALEToggle @@ -344,3 +338,43 @@ Execute(ALEResetBuffer should reset everything for a buffer): AssertEqual 1, g:ale_enabled AssertEqual 1, get(b:, 'ale_enabled', 1) + +Execute(Disabling ALE should disable balloons): + " These tests won't run in the console, but we can run them manually in GVim. + if has('balloon_eval') && has('gui_running') || + \ has('balloon_eval_term') && !has('gui_running') + call ale#linter#Reset() + + " Enable balloons, so we can check the expr value. + call ale#balloon#Enable() + + AssertEqual 1, &ballooneval + AssertEqual 'ale#balloon#Expr()', &balloonexpr + + " Toggle ALE off. + ALEToggle + + " The balloon settings should be reset. + AssertEqual 0, &ballooneval + AssertEqual '', &balloonexpr + endif + +Execute(Enabling ALE should enable balloons if the setting is on): + if has('balloon_eval') && has('gui_running') || + \ has('balloon_eval_term') && !has('gui_running') + call ale#linter#Reset() + call ale#balloon#Disable() + ALEDisable + let g:ale_set_balloons = 0 + ALEEnable + + AssertEqual 0, &ballooneval + AssertEqual '', &balloonexpr + + ALEDisable + let g:ale_set_balloons = 1 + ALEEnable + + AssertEqual 1, &ballooneval + AssertEqual 'ale#balloon#Expr()', &balloonexpr + endif diff --git a/test/test_alejobstarted_autocmd.vader b/test/test_alejobstarted_autocmd.vader new file mode 100644 index 00000000..51a57881 --- /dev/null +++ b/test/test_alejobstarted_autocmd.vader @@ -0,0 +1,42 @@ +Given testft (An empty file): + +Before: + let g:job_started_success = 0 + let g:ale_run_synchronously = 1 + + unlet! b:ale_linted + + function! TestCallback(buffer, output) + return [] + endfunction + + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'true', + \}) + +After: + let g:ale_run_synchronously = 0 + let g:ale_buffer_info = {} + + try + augroup! VaderTest + catch + endtry + + unlet! g:job_started_success + + delfunction TestCallback + call ale#linter#Reset() + +Execute(Run a lint cycle with an actual job to check for ALEJobStarted): + augroup VaderTest + autocmd! + autocmd User ALEJobStarted let g:job_started_success = 1 + augroup end + + call ale#Lint() + + AssertEqual g:job_started_success, 1 diff --git a/test/test_alelint_autocmd.vader b/test/test_alelint_autocmd.vader index b19e6b4e..5af1cd47 100644 --- a/test/test_alelint_autocmd.vader +++ b/test/test_alelint_autocmd.vader @@ -3,12 +3,19 @@ Before: let g:post_success = 0 let g:ale_run_synchronously = 1 + unlet! b:ale_linted + After: let g:ale_run_synchronously = 0 let g:ale_buffer_info = {} - augroup! VaderTest -Execute (Run a lint cycle, and check that a variable is set in the autocmd): + try + augroup! VaderTest + catch + endtry + +Given foobar(An empty file): +Execute(Run a lint cycle, and check that a variable is set in the autocmd): augroup VaderTest autocmd! autocmd User ALELintPre let g:pre_success = 1 @@ -19,3 +26,14 @@ Execute (Run a lint cycle, and check that a variable is set in the autocmd): AssertEqual g:pre_success, 1 AssertEqual g:post_success, 1 + +Execute(b:ale_linted should be increased after each lint cycle): + AssertEqual get(b:, 'ale_linted'), 0 + + call ale#Lint() + + AssertEqual get(b:, 'ale_linted'), 1 + + call ale#Lint() + + AssertEqual get(b:, 'ale_linted'), 2 diff --git a/test/test_autocmd_commands.vader b/test/test_autocmd_commands.vader index c03e8fb7..01646606 100644 --- a/test/test_autocmd_commands.vader +++ b/test/test_autocmd_commands.vader @@ -1,6 +1,6 @@ Before: function! CheckAutocmd(group) - call ale#toggle#InitAuGroups() + call ale#events#Init() redir => l:output execute 'silent! autocmd ' . a:group @@ -26,8 +26,9 @@ Before: " for the one matching the current buffer. if l:line =~# '<buffer=' . bufnr('') . '>' let l:header .= ' <buffer>' - else + elseif l:line[:0] is# ' ' call add(l:matches, join(split(l:header . l:line))) + else let l:header = '' endif endif @@ -38,16 +39,28 @@ Before: return l:matches endfunction + Save g:ale_completion_enabled + Save g:ale_echo_cursor Save g:ale_enabled - Save g:ale_lint_on_text_changed - Save g:ale_lint_on_insert_leave - Save g:ale_pattern_options_enabled + Save g:ale_fix_on_save Save g:ale_lint_on_enter Save g:ale_lint_on_filetype_changed + Save g:ale_lint_on_insert_leave Save g:ale_lint_on_save - Save g:ale_echo_cursor - Save g:ale_fix_on_save - Save g:ale_completion_enabled + Save g:ale_lint_on_text_changed + Save g:ale_pattern_options_enabled + + " Turn everything on by defaul for these tests. + let g:ale_completion_enabled = 1 + let g:ale_echo_cursor = 1 + let g:ale_enabled = 1 + let g:ale_fix_on_save = 1 + let g:ale_lint_on_enter = 1 + let g:ale_lint_on_filetype_changed = 1 + let g:ale_lint_on_insert_leave = 1 + let g:ale_lint_on_save = 1 + let g:ale_lint_on_text_changed = 1 + let g:ale_pattern_options_enabled = 1 After: delfunction CheckAutocmd @@ -59,100 +72,97 @@ After: call ale#completion#Disable() endif - call ale#toggle#InitAuGroups() + call ale#events#Init() -Execute (g:ale_lint_on_text_changed = 0 should bind no events): +Execute (All events should be set up when everything is on): + let g:ale_echo_cursor = 1 + + AssertEqual + \ [ + \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', + \ 'BufEnter call ale#events#EnterEvent(str2nr(expand(''<abuf>'')))', + \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', + \ 'BufReadPost call ale#Queue(0, ''lint_file'', str2nr(expand(''<abuf>'')))', + \ 'BufWinEnter * call ale#Queue(0, ''lint_file'', str2nr(expand(''<abuf>'')))', + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))', + \ 'CursorHold * call ale#cursor#EchoCursorWarningWithDelay()', + \ 'CursorMoved * call ale#cursor#EchoCursorWarningWithDelay()', + \ 'FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand(''<abuf>'')))', + \ 'FileType * call ale#events#FileTypeEvent( str2nr(expand(''<abuf>'')), expand(''<amatch>''))', + \ 'InsertLeave * call ale#Queue(0)', + \ 'InsertLeave call ale#cursor#EchoCursorWarning()', + \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ ], + \ CheckAutocmd('ALEEvents') + +Execute (Only the required events should be bound even if various settings are off): + let g:ale_completion_enabled = 0 + let g:ale_echo_cursor = 0 + let g:ale_enabled = 0 + let g:ale_fix_on_save = 0 + let g:ale_lint_on_enter = 0 + let g:ale_lint_on_filetype_changed = 0 + let g:ale_lint_on_insert_leave = 0 + let g:ale_lint_on_save = 0 let g:ale_lint_on_text_changed = 0 + let g:ale_pattern_options_enabled = 0 - AssertEqual [], CheckAutocmd('ALERunOnTextChangedGroup') + AssertEqual + \ [ + \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', + \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))', + \ ], + \ CheckAutocmd('ALEEvents') Execute (g:ale_lint_on_text_changed = 1 bind both events): let g:ale_lint_on_text_changed = 1 - AssertEqual [ - \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', - \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)' - \], CheckAutocmd('ALERunOnTextChangedGroup') + AssertEqual + \ [ + \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') Execute (g:ale_lint_on_text_changed = 'always' should bind both events): let g:ale_lint_on_text_changed = 'always' - AssertEqual [ - \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', - \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)' - \], CheckAutocmd('ALERunOnTextChangedGroup') + AssertEqual + \ [ + \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') Execute (g:ale_lint_on_text_changed = 'normal' should bind only TextChanged): let g:ale_lint_on_text_changed = 'normal' - AssertEqual [ - \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', - \], CheckAutocmd('ALERunOnTextChangedGroup') + AssertEqual + \ [ + \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') Execute (g:ale_lint_on_text_changed = 'insert' should bind only TextChangedI): let g:ale_lint_on_text_changed = 'insert' - AssertEqual [ - \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', - \], CheckAutocmd('ALERunOnTextChangedGroup') + AssertEqual + \ [ + \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') Execute (g:ale_lint_on_insert_leave = 1 should bind InsertLeave): let g:ale_lint_on_insert_leave = 1 - - AssertEqual [ - \ 'InsertLeave * call ale#Queue(0)', - \], CheckAutocmd('ALERunOnInsertLeave') - -Execute (g:ale_lint_on_insert_leave = 0 should bind no events): - let g:ale_lint_on_insert_leave = 0 - - AssertEqual [], CheckAutocmd('ALERunOnInsertLeave') - -Execute (g:ale_pattern_options_enabled = 1 should bind BufReadPost and BufEnter): - let g:ale_pattern_options_enabled = 1 - - AssertEqual [ - \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', - \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', - \], CheckAutocmd('ALEPatternOptionsGroup') - -Execute (g:ale_pattern_options_enabled = 0 should still bind events): - let g:ale_pattern_options_enabled = 0 - - AssertEqual [ - \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', - \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', - \], CheckAutocmd('ALEPatternOptionsGroup') - -Execute (g:ale_enabled = 0 should still bind pattern events): - let g:ale_enabled = 0 - - AssertEqual [ - \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', - \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))', - \], CheckAutocmd('ALEPatternOptionsGroup') - -Execute (g:ale_lint_on_enter = 0 should bind only the BufEnter event): - let g:ale_lint_on_enter = 0 + let g:ale_echo_cursor = 0 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 - - AssertEqual [ - \ 'BufEnter * call ale#events#EnterEvent(str2nr(expand(''<abuf>'')))', - \ 'BufReadPost * call ale#Queue(0, ''lint_file'', str2nr(expand(''<abuf>'')))', - \ 'BufWinEnter * call ale#Queue(0, ''lint_file'', str2nr(expand(''<abuf>'')))', - \ 'FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand(''<abuf>'')))', - \], CheckAutocmd('ALERunOnEnterGroup') - -Execute (g:ale_lint_on_filetype_changed = 0 should bind no events): - let g:ale_lint_on_filetype_changed = 0 - - AssertEqual [], CheckAutocmd('ALERunOnFiletypeChangeGroup') + \ [ + \ 'InsertLeave * call ale#Queue(0)', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^InsertLeave''') Execute (g:ale_lint_on_filetype_changed = 1 should bind the FileType event): let g:ale_lint_on_filetype_changed = 1 @@ -164,34 +174,11 @@ Execute (g:ale_lint_on_filetype_changed = 1 should bind the FileType event): \ . 'expand(''<amatch>'')' \ . ')', \ ], - \ CheckAutocmd('ALERunOnFiletypeChangeGroup') - -Execute (The SaveEvent should always be bound): - let g:ale_enabled = 0 - let g:ale_lint_on_save = 0 - let g:ale_fix_on_save = 0 - - AssertEqual [ - \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))', - \], CheckAutocmd('ALERunOnSaveGroup') - -Execute (g:ale_echo_cursor = 0 should bind no events): - let g:ale_echo_cursor = 0 - - AssertEqual [], CheckAutocmd('ALECursorGroup') - -Execute (g:ale_echo_cursor = 1 should bind cursor events): - let g:ale_echo_cursor = 1 - - AssertEqual [ - \ 'CursorHold * call ale#cursor#EchoCursorWarningWithDelay()', - \ 'CursorMoved * call ale#cursor#EchoCursorWarningWithDelay()', - \ 'InsertLeave * call ale#cursor#EchoCursorWarning()', - \], CheckAutocmd('ALECursorGroup') + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''\v^FileType''') Execute (ALECleanupGroup should include the right commands): AssertEqual [ - \ 'BufDelete * call ale#engine#Cleanup(str2nr(expand(''<abuf>'')))', + \ 'BufDelete * if exists(''*ale#engine#Cleanup'') | call ale#engine#Cleanup(str2nr(expand(''<abuf>''))) | endif', \ 'QuitPre * call ale#events#QuitEvent(str2nr(expand(''<abuf>'')))', \], CheckAutocmd('ALECleanupGroup') diff --git a/test/test_balloon_messages.vader b/test/test_balloon_messages.vader index ec09fe29..d0724c21 100644 --- a/test/test_balloon_messages.vader +++ b/test/test_balloon_messages.vader @@ -1,21 +1,34 @@ Before: Save g:ale_buffer_info + Save g:ale_enabled + Save g:ale_set_balloons - let g:ale_buffer_info[347] = {'loclist': [ + let g:ale_set_balloons = 1 + + let g:ale_buffer_info[bufnr('')] = {'loclist': [ + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'col': 10, + \ 'linter_name': 'eslint', + \ 'type': 'W', + \ 'text': 'Ignore me.', + \ }, \ { - \ 'bufnr': 347, + \ 'bufnr': bufnr(''), \ 'lnum': 1, \ 'col': 10, \ 'text': 'Missing semicolon. (semi)', + \ 'type': 'E', \ }, \ { - \ 'bufnr': 347, + \ 'bufnr': bufnr(''), \ 'lnum': 2, \ 'col': 10, \ 'text': 'Infix operators must be spaced. (space-infix-ops)' \ }, \ { - \ 'bufnr': 347, + \ 'bufnr': bufnr(''), \ 'lnum': 2, \ 'col': 15, \ 'text': 'Missing radix parameter (radix)' @@ -25,17 +38,50 @@ Before: After: Restore + unlet! b:ale_enabled + unlet! b:ale_set_balloons + Execute(Balloon messages should be shown for the correct lines): AssertEqual \ 'Missing semicolon. (semi)', - \ ale#balloon#MessageForPos(347, 1, 1) + \ ale#balloon#MessageForPos(bufnr(''), 1, 1) Execute(Balloon messages should be shown for earlier columns): AssertEqual \ 'Infix operators must be spaced. (space-infix-ops)', - \ ale#balloon#MessageForPos(347, 2, 1) + \ ale#balloon#MessageForPos(bufnr(''), 2, 1) Execute(Balloon messages should be shown for later columns): AssertEqual \ 'Missing radix parameter (radix)', - \ ale#balloon#MessageForPos(347, 2, 16) + \ ale#balloon#MessageForPos(bufnr(''), 2, 16) + +Execute(Balloon messages should be disabled if ALE is disabled globally): + let g:ale_enabled = 0 + " Enabling the buffer should not make a difference. + let b:ale_enabled = 1 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(Balloon messages should be disabled if ALE is disabled for a buffer): + let b:ale_enabled = 0 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(Balloon messages should be disabled if the global setting is off): + let g:ale_set_balloons = 0 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(Balloon messages should be disabled if the buffer setting is off): + let b:ale_set_balloons = 0 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(The balloon buffer setting should override the global one): + let g:ale_set_balloons = 0 + let b:ale_set_balloons = 1 + + AssertEqual + \ 'Missing semicolon. (semi)', + \ ale#balloon#MessageForPos(bufnr(''), 1, 1) diff --git a/test/test_c_import_paths.vader b/test/test_c_import_paths.vader index 6080779f..f2a06781 100644 --- a/test/test_c_import_paths.vader +++ b/test/test_c_import_paths.vader @@ -42,7 +42,7 @@ Execute(The C GCC handler should include 'include' directories for projects with \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' - \ , ale_linters#c#gcc#GetCommand(bufnr('')) + \ , ale_linters#c#gcc#GetCommand(bufnr(''), []) Execute(The C GCC handler should include 'include' directories for projects with a configure file): runtime! ale_linters/c/gcc.vim @@ -55,7 +55,7 @@ Execute(The C GCC handler should include 'include' directories for projects with \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/include')) . ' ' \ . ' -' - \ , ale_linters#c#gcc#GetCommand(bufnr('')) + \ , ale_linters#c#gcc#GetCommand(bufnr(''), []) Execute(The C GCC handler should include root directories for projects with .h files in them): runtime! ale_linters/c/gcc.vim @@ -68,7 +68,7 @@ Execute(The C GCC handler should include root directories for projects with .h f \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' \ . ' -' - \ , ale_linters#c#gcc#GetCommand(bufnr('')) + \ , ale_linters#c#gcc#GetCommand(bufnr(''), []) Execute(The C GCC handler should include root directories for projects with .hpp files in them): runtime! ale_linters/c/gcc.vim @@ -81,7 +81,7 @@ Execute(The C GCC handler should include root directories for projects with .hpp \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' \ . ' -' - \ , ale_linters#c#gcc#GetCommand(bufnr('')) + \ , ale_linters#c#gcc#GetCommand(bufnr(''), []) Execute(The C Clang handler should include 'include' directories for projects with a Makefile): runtime! ale_linters/c/clang.vim @@ -94,7 +94,7 @@ Execute(The C Clang handler should include 'include' directories for projects wi \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' - \ , ale_linters#c#clang#GetCommand(bufnr('')) + \ , ale_linters#c#clang#GetCommand(bufnr(''), []) Execute(The C Clang handler should include 'include' directories for projects with a configure file): runtime! ale_linters/c/clang.vim @@ -107,7 +107,7 @@ Execute(The C Clang handler should include 'include' directories for projects wi \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' \ . ' -' - \ , ale_linters#c#clang#GetCommand(bufnr('')) + \ , ale_linters#c#clang#GetCommand(bufnr(''), []) Execute(The C Clang handler should include root directories for projects with .h files in them): runtime! ale_linters/c/clang.vim @@ -120,7 +120,7 @@ Execute(The C Clang handler should include root directories for projects with .h \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' \ . ' -' - \ , ale_linters#c#clang#GetCommand(bufnr('')) + \ , ale_linters#c#clang#GetCommand(bufnr(''), []) Execute(The C Clang handler should include root directories for projects with .hpp files in them): runtime! ale_linters/c/clang.vim @@ -133,7 +133,7 @@ Execute(The C Clang handler should include root directories for projects with .h \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' \ . ' -' - \ , ale_linters#c#clang#GetCommand(bufnr('')) + \ , ale_linters#c#clang#GetCommand(bufnr(''), []) Execute(The C++ GCC handler should include 'include' directories for projects with a Makefile): runtime! ale_linters/cpp/gcc.vim @@ -146,7 +146,7 @@ Execute(The C++ GCC handler should include 'include' directories for projects wi \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' - \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + \ , ale_linters#cpp#gcc#GetCommand(bufnr(''), []) Execute(The C++ GCC handler should include 'include' directories for projects with a configure file): runtime! ale_linters/cpp/gcc.vim @@ -159,7 +159,7 @@ Execute(The C++ GCC handler should include 'include' directories for projects wi \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/include')) . ' ' \ . ' -' - \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + \ , ale_linters#cpp#gcc#GetCommand(bufnr(''), []) Execute(The C++ GCC handler should include root directories for projects with .h files in them): runtime! ale_linters/cpp/gcc.vim @@ -172,7 +172,7 @@ Execute(The C++ GCC handler should include root directories for projects with .h \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' \ . ' -' - \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + \ , ale_linters#cpp#gcc#GetCommand(bufnr(''), []) Execute(The C++ GCC handler should include root directories for projects with .hpp files in them): runtime! ale_linters/cpp/gcc.vim @@ -185,7 +185,7 @@ Execute(The C++ GCC handler should include root directories for projects with .h \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' \ . ' -' - \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + \ , ale_linters#cpp#gcc#GetCommand(bufnr(''), []) Execute(The C++ Clang handler should include 'include' directories for projects with a Makefile): runtime! ale_linters/cpp/clang.vim @@ -198,7 +198,7 @@ Execute(The C++ Clang handler should include 'include' directories for projects \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' - \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) Execute(The C++ Clang handler should include 'include' directories for projects with a configure file): runtime! ale_linters/cpp/clang.vim @@ -211,7 +211,7 @@ Execute(The C++ Clang handler should include 'include' directories for projects \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/include')) . ' ' \ . ' -' - \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) Execute(The C++ Clang handler should include root directories for projects with .h files in them): runtime! ale_linters/cpp/clang.vim @@ -224,7 +224,7 @@ Execute(The C++ Clang handler should include root directories for projects with \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' \ . ' -' - \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) Execute(The C++ Clang handler should include root directories for projects with .hpp files in them): runtime! ale_linters/cpp/clang.vim @@ -237,7 +237,7 @@ Execute(The C++ Clang handler should include root directories for projects with \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' \ . ' -' - \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) Execute(The C++ Clang handler shoud use the include directory based on the .git location): runtime! ale_linters/cpp/clang.vim @@ -258,7 +258,7 @@ Execute(The C++ Clang handler shoud use the include directory based on the .git \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/src')) . ' ' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/include')) . ' ' \ . ' -' - \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + \ , ale_linters#cpp#clang#GetCommand(bufnr(''), []) Execute(The C++ ClangTidy handler should include json folders for projects with suitable build directory in them): runtime! ale_linters/cpp/clangtidy.vim diff --git a/test/test_c_parse_makefile.vader b/test/test_c_parse_makefile.vader new file mode 100644 index 00000000..7c2fc21e --- /dev/null +++ b/test/test_c_parse_makefile.vader @@ -0,0 +1,184 @@ +Before: + Save g:ale_c_parse_makefile + Save g:ale_c_gcc_options + Save g:ale_c_clang_options + Save g:ale_cpp_gcc_options + Save g:ale_cpp_clang_options + + call ale#test#SetDirectory('/testplugin/test') + + let g:ale_c_parse_makefile=1 + let g:ale_c_gcc_options = '' + let g:ale_c_clang_options = '' + let g:ale_cpp_gcc_options = '' + let g:ale_cpp_clang_options = '' + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The CFlags parser should be able to parse include directives): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ [ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir'))] + \ , ale#c#ParseCFlags(bufnr(''), 'gcc -Isubdir -c file.c') + +Execute(The CFlags parser should be able to parse macro directives): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ [ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ '-DTEST=1'] + \ , ale#c#ParseCFlags(bufnr(''), 'gcc -Isubdir -DTEST=1 -c file.c') + +Execute(The CFlags parser should be able to parse macro directives with spaces): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ [ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ '-DTEST=$(( 2 * 4 ))'] + \ , ale#c#ParseCFlags(bufnr(''), 'gcc -Isubdir -DTEST=$(( 2 * 4 )) -c file.c') + +Execute(The CFlags parser should be able to parse shell directives with spaces): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ [ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlags(bufnr(''), 'gcc -Isubdir -DTEST=`date +%s` -c file.c') + +Execute(The CFlagsToList parser should be able to parse multiple cflags): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ [ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Isubdir -DTEST=`date +%s` -c file.c', '-')) + +Execute(The CFlagsToList parser should be able to parse multiple cflags #2): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ [ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Isubdir ' . + \ '-I'. ale#path#Simplify('kernel/include') . + \ ' -DTEST=`date +%s` -c file.c', '-')) + +Execute(The CFlagsToList parser should be able to parse multiple cflags #3): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ['-Dgoal=9', + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Dgoal=9 -Isubdir ' . + \ '-I'. ale#path#Simplify('kernel/include') . + \ ' -DTEST=`date +%s` -c file.c', '-')) + +Execute(The CFlagsToList parser should be able to parse multiple cflags #4): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ['-Dgoal=9', + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' . + \ '-I'. ale#path#Simplify('kernel/include') . + \ ' -DTEST=`date +%s` -c file.c', '-')) + +Execute(The CFlagsToList parser should be able to parse multiple cflags #5): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ['-Dgoal=9', + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' . + \ '-I"dir with spaces"' . ' -I'. ale#path#Simplify('kernel/include') . + \ ' -DTEST=`date +%s` -c file.c', '-')) + +Execute(The CFlagsToList parser should be able to parse multiple cflags #6): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ['-Dgoal=9', + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' . + \ '-I''dir with spaces''' . ' -I'. ale#path#Simplify('kernel/include') . + \ ' -DTEST=`date +%s` -c file.c', '-')) + +Execute(The CFlagsToList parser should be able to parse multiple cflags #7): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ['-Dgoal=9', + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' . + \ '-I''dir with spaces''' . ' -Idir-with-dash' . + \ ' -I'. ale#path#Simplify('kernel/include') . + \ ' -DTEST=`date +%s` -c file.c', '-')) + +Execute(The CFlagsToList parser should be able to parse multiple cflags #8): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ['-Dgoal=9', + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), + \ '-Dmacro-with-dash', + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash')), + \ ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), + \ '-DTEST=`date +%s`'] + \ , ale#c#ParseCFlagsToList(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ split('gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' . + \ '-Dmacro-with-dash ' . + \ '-I''dir with spaces''' . ' -Idir-with-dash' . + \ ' -I'. ale#path#Simplify('kernel/include') . + \ ' -DTEST=`date +%s` -c file.c', '-')) diff --git a/test/test_c_projects/makefile_project/subdir/file.c b/test/test_c_projects/makefile_project/subdir/file.c new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/test_c_projects/makefile_project/subdir/file.c diff --git a/test/test_checkingbuffer_autocmd.vader b/test/test_checkingbuffer_autocmd.vader new file mode 100644 index 00000000..1cbfa342 --- /dev/null +++ b/test/test_checkingbuffer_autocmd.vader @@ -0,0 +1,57 @@ +Given testft (An empty file): + +Before: + Save g:ale_run_synchronously + Save g:ale_buffer_info + + let g:ale_run_synchronously = 1 + let g:ale_buffer_info = {} + + let g:checking_buffer = 0 + + unlet! b:ale_linted + + function! TestCallback(buffer, output) + return [] + endfunction + + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'true', + \}) + +After: + Restore + + unlet! g:checking_buffer + + delfunction TestCallback + call ale#linter#Reset() + + augroup VaderTest + autocmd! + augroup end + + augroup! VaderTest + +Execute(ALELintPre should not return success on ale#engine#IsCheckingBuffer): + augroup VaderTest + autocmd! + autocmd User ALELintPre let g:checking_buffer = ale#engine#IsCheckingBuffer(bufnr('')) ? 1 : 0 + augroup end + + call ale#Lint() + + AssertEqual g:checking_buffer, 0 + +Execute(ALEJobStarted should return success on ale#engine#IsCheckingBuffer): + augroup VaderTest + autocmd! + autocmd User ALEJobStarted let g:checking_buffer = ale#engine#IsCheckingBuffer(bufnr('')) ? 1 : 0 + augroup end + + call ale#Lint() + + AssertEqual g:checking_buffer, 1 diff --git a/test/test_conflicting_plugin_warnings.vader b/test/test_conflicting_plugin_warnings.vader deleted file mode 100644 index 08a4c412..00000000 --- a/test/test_conflicting_plugin_warnings.vader +++ /dev/null @@ -1,74 +0,0 @@ -Execute(The after file should have been loaded for real): - " FIXME: Fix these tests in NeoVim. - if !has('nvim') - Assert has_key(g:, 'loaded_ale_after'), 'g:loaded_ale_after was not set!' - Assert g:loaded_ale_after - endif - -Before: - silent! cd /testplugin/test - cd .. - unlet! g:loaded_ale_after - -After: - cd test - let g:loaded_ale_after = 1 - let g:ale_emit_conflict_warnings = 1 - unlet! g:loaded_syntastic_plugin - unlet! g:loaded_neomake - unlet! g:loaded_validator_plugin - -Execute(ALE should not warn when nothing extra is installed): - " Nothing should be thrown when loading the after file. - source after/plugin/ale.vim - -Execute(ALE should warn users when Syntastic is installed): - let g:loaded_syntastic_plugin = 1 - - AssertThrows source after/plugin/ale.vim - AssertEqual - \ 'ALE conflicts with Syntastic' - \ . '. Uninstall it, or disable this warning with ' - \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' - \ . '*before* plugins are loaded.', - \ g:vader_exception - -Execute(ALE should not warn about Syntastic when the flag is set): - let g:loaded_syntastic_plugin = 1 - let g:ale_emit_conflict_warnings = 0 - - source after/plugin/ale.vim - -Execute(ALE should warn users when Neomake is installed): - let g:loaded_neomake = 1 - - AssertThrows source after/plugin/ale.vim - AssertEqual - \ 'ALE conflicts with Neomake' - \ . '. Uninstall it, or disable this warning with ' - \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' - \ . '*before* plugins are loaded.', - \ g:vader_exception - -Execute(ALE should not warn about Neomake when the flag is set): - let g:loaded_neomake = 1 - let g:ale_emit_conflict_warnings = 0 - - source after/plugin/ale.vim - -Execute(ALE should warn users when Validator is installed): - let g:loaded_validator_plugin = 1 - - AssertThrows source after/plugin/ale.vim - AssertEqual - \ 'ALE conflicts with Validator' - \ . '. Uninstall it, or disable this warning with ' - \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' - \ . '*before* plugins are loaded.', - \ g:vader_exception - -Execute(ALE should not warn about Validator when the flag is set): - let g:loaded_validator_plugin = 1 - let g:ale_emit_conflict_warnings = 0 - - source after/plugin/ale.vim diff --git a/test/test_cursor_warnings.vader b/test/test_cursor_warnings.vader index 19592217..24652909 100644 --- a/test/test_cursor_warnings.vader +++ b/test/test_cursor_warnings.vader @@ -2,6 +2,7 @@ Before: Save g:ale_echo_msg_format Save g:ale_echo_cursor + " We should prefer the error message at column 10 instead of the warning. let g:ale_buffer_info = { \ bufnr('%'): { \ 'loclist': [ @@ -12,6 +13,17 @@ Before: \ 'vcol': 0, \ 'linter_name': 'eslint', \ 'nr': -1, + \ 'type': 'W', + \ 'code': 'semi', + \ 'text': 'Ignore me.', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, \ 'type': 'E', \ 'code': 'semi', \ 'text': 'Missing semicolon.', diff --git a/test/test_elm_executable_detection.vader b/test/test_elm_executable_detection.vader index 4227cbfa..9146eea6 100644 --- a/test/test_elm_executable_detection.vader +++ b/test/test_elm_executable_detection.vader @@ -12,7 +12,7 @@ Execute(should get valid executable with default params): call ale#test#SetFilename('elm-test-files/app/testfile.elm') AssertEqual - \ ale#path#Simplify(g:dir . '/elm-test-files/app/node_modules/.bin/elm-make'), + \ ale#path#Simplify(g:dir . '/elm-test-files/app/node_modules/.bin/elm'), \ ale_linters#elm#make#GetExecutable(bufnr('')) Execute(should get valid executable with 'use_global' params): @@ -21,16 +21,16 @@ Execute(should get valid executable with 'use_global' params): call ale#test#SetFilename('elm-test-files/app/testfile.elm') AssertEqual - \ 'elm-make', + \ 'elm', \ ale_linters#elm#make#GetExecutable(bufnr('')) Execute(should get valid executable with 'use_global' and 'executable' params): - let g:ale_elm_make_executable = 'other-elm-make' + let g:ale_elm_make_executable = 'other-elm' let g:ale_elm_make_use_global = 1 call ale#test#SetFilename('elm-test-files/app/testfile.elm') AssertEqual - \ 'other-elm-make', + \ 'other-elm', \ ale_linters#elm#make#GetExecutable(bufnr('')) diff --git a/test/test_engine_lsp_response_handling.vader b/test/test_engine_lsp_response_handling.vader index b3a45b14..18bad0a1 100644 --- a/test/test_engine_lsp_response_handling.vader +++ b/test/test_engine_lsp_response_handling.vader @@ -1,5 +1,9 @@ Before: Save g:ale_buffer_info + Save g:ale_lsp_error_messages + + unlet! g:ale_lsp_error_messages + call ale#test#SetDirectory('/testplugin/test') After: @@ -7,7 +11,9 @@ After: call ale#test#RestoreDirectory() call ale#linter#Reset() + call ale#lsp_linter#ClearLSPData() +Given foobar(An empty file): Execute(tsserver syntax error responses should be handled correctly): runtime ale_linters/typescript/tsserver.vim call ale#test#SetFilename('filename.ts') @@ -15,7 +21,7 @@ Execute(tsserver syntax error responses should be handled correctly): " When we get syntax errors and no semantic errors, we should keep the " syntax errors. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -37,7 +43,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ ], \ }, \}) - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -65,7 +71,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ getloclist(0) " After we get empty syntax errors, we should clear them. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -88,7 +94,7 @@ Execute(tsserver semantic error responses should be handled correctly): " When we get syntax errors and no semantic errors, we should keep the " syntax errors. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -98,7 +104,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ ], \ }, \}) - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -138,7 +144,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ getloclist(0) " After we get empty syntax errors, we should clear them. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -153,3 +159,20 @@ Execute(tsserver semantic error responses should be handled correctly): \ [ \ ], \ getloclist(0) + +Execute(LSP errors should be logged in the history): + call ale#lsp_linter#SetLSPLinterMap({'347': 'foobar'}) + call ale#lsp_linter#HandleLSPResponse(347, { + \ 'jsonrpc': '2.0', + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': ['123', '456'], + \ }, + \ }, + \}) + + AssertEqual + \ {'foobar': ["xyz\n123\n456"]}, + \ get(g:, 'ale_lsp_error_messages', {}) diff --git a/test/test_find_references.vader b/test/test_find_references.vader new file mode 100644 index 00000000..c2290ca3 --- /dev/null +++ b/test/test_find_references.vader @@ -0,0 +1,250 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:old_filename = expand('%:p') + let g:Callback = 0 + let g:expr_list = [] + let g:message_list = [] + let g:preview_called = 0 + let g:item_list = [] + + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/preview.vim + + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort + let g:Callback = a:callback + + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + + return { + \ 'buffer': a:buffer, + \ 'connection_id': 347, + \ 'project_root': '/foo/bar', + \ 'language_id': 'python', + \} + endfunction + + function! ale#lsp#Send(conn_id, message, root) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + function! ale#preview#ShowSelection(item_list) abort + let g:preview_called = 1 + let g:item_list = a:item_list + endfunction + +After: + call ale#lsp#RemoveConnectionWithID(347) + call ale#references#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:old_filename + unlet! g:Callback + unlet! g:message_list + unlet! g:expr_list + unlet! b:ale_linters + unlet! g:item_list + unlet! g:preview_called + + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/preview.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#references#HandleTSServerResponse(1, {'command': 'foo'}) + +Execute(Failed reference responses should be handled correctly): + call ale#references#SetMap({3: {}}) + call ale#references#HandleTSServerResponse( + \ 1, + \ {'command': 'references', 'request_seq': 3} + \) + AssertEqual {}, ale#references#GetMap() + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Results should be shown for tsserver responses): + call ale#references#SetMap({3: {}}) + call ale#references#HandleTSServerResponse(1, { + \ 'command': 'references', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'symbolStartOffset': 9, + \ 'refs': [ + \ { + \ 'file': '/foo/bar/app.ts', + \ 'isWriteAccess': v:true, + \ 'lineText': 'import {doSomething} from ''./whatever''', + \ 'end': {'offset': 24, 'line': 9}, + \ 'start': {'offset': 9, 'line': 9}, + \ 'isDefinition': v:true, + \ }, + \ { + \ 'file': '/foo/bar/app.ts', + \ 'isWriteAccess': v:false, + \ 'lineText': ' doSomething()', + \ 'end': {'offset': 18, 'line': 804}, + \ 'start': {'offset': 3, 'line': 804}, + \ 'isDefinition': v:false, + \ }, + \ { + \ 'file': '/foo/bar/other/app.ts', + \ 'isWriteAccess': v:false, + \ 'lineText': ' doSomething()', + \ 'end': {'offset': 18, 'line': 51}, + \ 'start': {'offset': 3, 'line': 51}, + \ 'isDefinition': v:false, + \ }, + \ ], + \ 'symbolDisplayString': 'import doSomething', + \ 'symbolName': 'doSomething()', + \ }, + \}) + + AssertEqual + \ [ + \ {'filename': '/foo/bar/app.ts', 'column': 9, 'line': 9}, + \ {'filename': '/foo/bar/app.ts', 'column': 3, 'line': 804}, + \ {'filename': '/foo/bar/other/app.ts', 'column': 3, 'line': 51}, + \ ], + \ g:item_list + AssertEqual {}, ale#references#GetMap() + +Execute(The preview window should not be opened for empty tsserver responses): + call ale#references#SetMap({3: {}}) + call ale#references#HandleTSServerResponse(1, { + \ 'command': 'references', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'symbolStartOffset': 9, + \ 'refs': [ + \ ], + \ 'symbolDisplayString': 'import doSomething', + \ 'symbolName': 'doSomething()', + \ }, + \}) + + Assert !g:preview_called + AssertEqual {}, ale#references#GetMap() + AssertEqual ['echom ''No references found.'''], g:expr_list + +Execute(tsserver reference requests should be sent): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences + + AssertEqual + \ 'function(''ale#references#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [[0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], + \ g:message_list + AssertEqual {'42': {}}, ale#references#GetMap() + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(LSP reference responses should be handled): + call ale#references#SetMap({3: {}}) + call ale#references#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ { + \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ { + \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/other_file')), + \ 'range': { + \ 'start': {'line': 7, 'character': 15}, + \ }, + \ }, + \ ], + \ } + \) + + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(g:dir . '/completion_dummy_file'), + \ 'line': 3, + \ 'column': 8, + \ }, + \ { + \ 'filename': ale#path#Simplify(g:dir . '/other_file'), + \ 'line': 8, + \ 'column': 16, + \ }, + \ ], + \ g:item_list + AssertEqual {}, ale#references#GetMap() + +Execute(Preview windows should not be opened for empty LSP reference responses): + call ale#references#SetMap({3: {}}) + call ale#references#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ ], + \ } + \) + + Assert !g:preview_called + AssertEqual {}, ale#references#GetMap() + AssertEqual ['echom ''No references found.'''], g:expr_list + +Execute(LSP reference requests should be sent): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEFindReferences + + AssertEqual + \ 'function(''ale#references#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/references', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 3}, + \ 'context': {'includeDeclaration': v:false}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {}}, ale#references#GetMap() diff --git a/test/test_flow_command.vader b/test/test_flow_command.vader index 49546e94..c673ce0a 100644 --- a/test/test_flow_command.vader +++ b/test/test_flow_command.vader @@ -1,8 +1,11 @@ Before: runtime ale_linters/javascript/flow.vim + call ale#test#SetDirectory('/testplugin/test') After: + unlet! b:ale_javascript_flow_use_respect_pragma + call ale#test#RestoreDirectory() call ale#linter#Reset() call ale#semver#ResetVersionCache() @@ -15,6 +18,16 @@ Execute(flow should return a command to run if a .flowconfig file exists): \ . ' check-contents --respect-pragma --json --from ale %s', \ ale_linters#javascript#flow#GetCommand(bufnr('%'), []) +Execute(flow should not use the respect pragma argument if the option is off): + call ale#test#SetFilename('flow/a/sub/dummy') + + let b:ale_javascript_flow_use_respect_pragma = 0 + + AssertEqual + \ ale#Escape('flow') + \ . ' check-contents --json --from ale %s', + \ ale_linters#javascript#flow#GetCommand(bufnr('%'), []) + Execute(flow should should not use --respect-pragma for old versions): call ale#test#SetFilename('flow/a/sub/dummy') diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader index b77a75ac..78373a65 100644 --- a/test/test_go_to_definition.vader +++ b/test/test_go_to_definition.vader @@ -7,16 +7,22 @@ Before: let g:message_list = [] let g:expr_list = [] - runtime autoload/ale/definition.vim runtime autoload/ale/linter.vim runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + return { + \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', + \ 'language_id': 'python', \} endfunction @@ -26,11 +32,13 @@ Before: return 42 endfunction - function! ale#definition#Execute(expr) abort + function! ale#util#Execute(expr) abort call add(g:expr_list, a:expr) endfunction After: + call ale#lsp#RemoveConnectionWithID(347) + call ale#definition#SetMap({}) call ale#test#RestoreDirectory() call ale#linter#Reset() @@ -40,9 +48,9 @@ After: unlet! g:expr_list unlet! b:ale_linters - runtime autoload/ale/definition.vim runtime autoload/ale/linter.vim runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim Execute(Other messages for the tsserver handler should be ignored): call ale#definition#HandleTSServerResponse(1, {'command': 'foo'}) @@ -55,6 +63,19 @@ Execute(Failed definition responses should be handled correctly): \) AssertEqual {}, ale#definition#GetMap() +Execute(Failed definition responses with no files should be handled correctly): + call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'definition', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [], + \ } + \) + AssertEqual {}, ale#definition#GetMap() + Given typescript(Some typescript file): foo somelongerline @@ -166,6 +187,28 @@ Execute(Other files should be jumped to for LSP definition responses): AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() +Execute(Locations inside the same file should be jumped to without using :edit): + call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': { + \ 'uri': ale#path#ToURI(ale#path#Simplify(expand('%:p'))), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ } + \) + + AssertEqual + \ [ + \ ], + \ g:expr_list + AssertEqual [3, 7], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + Execute(Other files should be jumped to in tabs for LSP definition responses): call ale#definition#SetMap({3: {'open_in_tab': 1}}) call ale#definition#HandleLSPResponse( diff --git a/test/test_hover.vader b/test/test_hover.vader new file mode 100644 index 00000000..15f164f0 --- /dev/null +++ b/test/test_hover.vader @@ -0,0 +1,151 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:Callback = 0 + let g:message_list = [] + let g:item_list = [] + let g:echo_list = [] + + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort + let g:Callback = a:callback + + return { + \ 'connection_id': 347, + \ 'project_root': '/foo/bar', + \} + endfunction + + function! ale#lsp#Send(conn_id, message, root) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#ShowMessage(string) abort + call add(g:echo_list, a:string) + endfunction + + function! HandleValidLSPResult(result) abort + " The cursor is beyond the length of the line. + " We will clamp the cursor position with the line length. + call setpos('.', [bufnr(''), 1, 5, 0]) + + call ale#hover#SetMap({3: { + \ 'buffer': bufnr(''), + \ 'line': 1, + \ 'column': 5, + \}}) + call ale#hover#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': a:result, + \ } + \) + endfunction + +After: + call ale#hover#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:Callback + unlet! g:message_list + unlet! b:ale_linters + unlet! g:echo_list + + delfunction HandleValidLSPResult + + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Other messages for the tsserver handler should be ignored): + call ale#hover#HandleTSServerResponse(1, {'command': 'foo'}) + +Execute(Failed hover responses should be handled correctly): + call ale#hover#SetMap({3: {}}) + call ale#hover#HandleTSServerResponse( + \ 1, + \ {'command': 'quickinfo', 'request_seq': 3} + \) + AssertEqual {}, ale#hover#GetMap() + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(tsserver quickinfo responses will null missing bodies should be handled): + call ale#hover#SetMap({3: {}}) + call ale#hover#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'quickinfo', + \ 'request_seq': 3, + \ 'success': v:true, + \ } + \) + + AssertEqual {}, ale#hover#GetMap() + +Execute(tsserver quickinfo displayString values should be displayed): + call ale#hover#SetMap({3: {}}) + call ale#hover#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'quickinfo', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': {'displayString': 'foo bar'}, + \ } + \) + + AssertEqual ['foo bar'], g:echo_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover responses with just a string should be handled): + call HandleValidLSPResult({'contents': 'foobar'}) + + AssertEqual ['foobar'], g:echo_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover null responses should be handled): + call HandleValidLSPResult(v:null) + + AssertEqual [], g:echo_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover responses with markup content should be handled): + call HandleValidLSPResult({'contents': {'kind': 'something', 'value': 'markup'}}) + + AssertEqual ['markup'], g:echo_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover response with lists of strings should be handled): + call HandleValidLSPResult({'contents': [ + \ "foo\n", + \ "bar\n", + \]}) + + AssertEqual ["foo\n\nbar\n"], g:echo_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover response with lists of strings and marked strings should be handled): + call HandleValidLSPResult({'contents': [ + \ {'language': 'rust', 'value': 'foo'}, + \ "bar\n", + \]}) + + AssertEqual ["foo\nbar\n"], g:echo_list + AssertEqual {}, ale#hover#GetMap() diff --git a/test/test_ignoring_linters.vader b/test/test_ignoring_linters.vader new file mode 100644 index 00000000..af31fce3 --- /dev/null +++ b/test/test_ignoring_linters.vader @@ -0,0 +1,250 @@ +Execute(GetList should ignore some invalid values): + AssertEqual [], ale#engine#ignore#GetList('', 'foo') + AssertEqual [], ale#engine#ignore#GetList('', 0) + AssertEqual [], ale#engine#ignore#GetList('', v:null) + +Execute(GetList should handle Lists): + AssertEqual ['foo', 'bar'], ale#engine#ignore#GetList('', ['foo', 'bar']) + +Execute(GetList should handle Dictionaries): + AssertEqual + \ ['linter1', 'linter2'], + \ uniq(sort(ale#engine#ignore#GetList('x.y.z', { + \ 'x': ['linter1'], + \ 'abc': ['linter3'], + \ 'z': ['linter2'], + \ }))) + +Execute(Exclude should ignore some invalid values): + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ 'foo', + \ ) + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ 0, + \ ) + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ v:null, + \ ) + +Execute(Exclude should handle Lists): + AssertEqual + \ [ + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ['linter1', 'alias1'], + \ ) + +Execute(Exclude should handle Dictionaries): + AssertEqual + \ [ + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ {'foo': ['linter1'], 'bar': ['alias1']}, + \ ) + +Before: + Save g:ale_linters_ignore + Save g:ale_buffer_info + + let g:linters = [] + let g:loclist = [] + let g:run_linters_called = 0 + + runtime autoload/ale/engine.vim + + " Mock the engine function so we can set it up. + function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort + let g:linters = a:linters + let g:run_linters_called = 1 + endfunction + + function! ale#engine#HandleLoclist(linter_name, buffer, loclist) abort + let g:loclist = a:loclist + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': has('win32') ? 'echo' : 'true', + \}) + call ale#test#SetDirectory('/testplugin/test') + +After: + Restore + + unlet! b:ale_linted + unlet! b:ale_linters_ignore + unlet! b:ale_quitting + unlet! b:ale_save_event_fired + unlet! g:linters + unlet! g:loclist + unlet! g:lsp_message + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + call ale#lsp_linter#ClearLSPData() + runtime autoload/ale/engine.vim + +Given foobar(An empty file): +Execute(Global ignore lists should be applied for linters): + ALELint + Assert g:run_linters_called, "The mock callback wasn't called" + AssertEqual ['testlinter'], map(g:linters, 'v:val.name') + + let g:ale_linters_ignore = ['testlinter'] + ALELint + AssertEqual [], g:linters + +Execute(buffer ignore lists should be applied for linters): + ALELint + Assert g:run_linters_called, "The mock callback wasn't called" + AssertEqual ['testlinter'], map(g:linters, 'v:val.name') + + let b:ale_linters_ignore = ['testlinter'] + ALELint + AssertEqual [], g:linters + +Execute(Buffer ignore lists should be applied for tsserver): + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:lsp_message = { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': ''','' expected.', + \ "code":1005 + \ }, + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 14, + \ 'nr': 1005, + \ 'type': 'E', + \ 'end_col': 15, + \ 'end_lnum': 2, + \ 'text': ''','' expected.', + \ }, + \ ], + \ g:loclist + + let g:loclist = [] + let b:ale_linters_ignore = ['tsserver'] + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(Buffer ignore lists should be applied for LSP linters): + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'347': 'lsplinter'}) + + let g:lsp_message = { + \ 'jsonrpc': '2.0', + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'diagnostics': [ + \ { + \ 'severity': 1, + \ 'message': 'x', + \ 'range': { + \ 'start': {'line': 0, 'character': 9}, + \ 'end': {'line': 0, 'character': 9}, + \ }, + \ } + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'type': 'E', + \ 'end_col': 10, + \ 'end_lnum': 1, + \ 'text': 'x', + \ } + \ ], + \ g:loclist + + let b:ale_linters_ignore = ['lsplinter'] + let g:loclist = [] + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist diff --git a/test/test_lint_error_delay.vader b/test/test_lint_error_delay.vader deleted file mode 100644 index 7f081794..00000000 --- a/test/test_lint_error_delay.vader +++ /dev/null @@ -1,22 +0,0 @@ -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 echoing messages for a while after exceptions are thrown): - AssertThrows call ale#cursor#EchoCursorWarning() - call ale#cursor#EchoCursorWarning() diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index d946a600..48a4a394 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -421,6 +421,30 @@ Execute(PreProcess should accept LSP server configurations): AssertEqual 'socket', ale#linter#PreProcess(g:linter).lsp +Execute(PreProcess should accept let you specify the language as just a string): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'foobar', + \ 'project_root_callback': 'x', + \} + + AssertEqual 'foobar', ale#linter#PreProcess(g:linter).language_callback(0) + +Execute(PreProcess should complain about using language and language_callback together): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'language_callback': 'x', + \ 'project_root_callback': 'x', + \} + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'Only one of `language` or `language_callback` should be set', g:vader_exception + Execute(PreProcess should require an address_callback for LSP socket configurations): let g:linter = { \ 'name': 'x', @@ -441,3 +465,28 @@ Execute(PreProcess should complain about address_callback for non-LSP linters): AssertThrows call ale#linter#PreProcess(g:linter) AssertEqual '`address_callback` cannot be used when lsp != ''socket''', g:vader_exception + +Execute(PreProcess should complain about using initialization_options and initialization_options_callback together): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'initialization_options': 'x', + \ 'initialization_options_callback': 'x', + \} + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'Only one of `initialization_options` or `initialization_options_callback` should be set', g:vader_exception + +Execute (PreProcess should throw when initialization_options_callback is not a callback): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'initialization_options_callback': {}, + \}) + AssertEqual '`initialization_options_callback` must be a callback if defined', g:vader_exception diff --git a/test/test_linter_retrieval.vader b/test/test_linter_retrieval.vader index 5d1ee451..6c402d54 100644 --- a/test/test_linter_retrieval.vader +++ b/test/test_linter_retrieval.vader @@ -5,6 +5,9 @@ Before: let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout', 'read_buffer': 1, 'lint_file': 0, 'aliases': [], 'lsp': '', 'add_newline': 0} let g:testlinter2 = {'name': 'testlinter2', 'executable': 'testlinter2', 'command': 'testlinter2', 'callback': 'testCB2', 'output_stream': 'stdout', 'read_buffer': 0, 'lint_file': 1, 'aliases': [], 'lsp': '', 'add_newline': 0} call ale#linter#Reset() + call ale#linter#PreventLoading('testft') + call ale#linter#PreventLoading('javascript') + call ale#linter#PreventLoading('typescript') After: Restore @@ -145,6 +148,7 @@ Execute (Buffer-local overrides for aliases should be used): AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') Execute (Linters should be loaded from disk appropriately): + call ale#linter#Reset() AssertEqual [{'name': 'testlinter', 'output_stream': 'stdout', 'executable': 'testlinter', 'command': 'testlinter', 'callback': 'testCB', 'read_buffer': 1, 'lint_file': 0, 'aliases': [], 'lsp': '', 'add_newline': 0}], ale#linter#Get('testft') diff --git a/test/test_list_opening.vader b/test/test_list_opening.vader index 63b30ef1..a24e8de9 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_list_vertical Save g:ale_buffer_info Save g:ale_set_lists_synchronously @@ -13,6 +14,7 @@ Before: let g:ale_open_list = 0 let g:ale_keep_list_window_open = 0 let g:ale_list_window_size = 10 + let g:ale_list_vertical = 0 let g:ale_set_lists_synchronously = 1 let g:loclist = [ @@ -33,16 +35,29 @@ Before: return 0 endfunction + " If the window is vertical, window size should match column size/width + function GetQuickfixIsVertical(cols) abort + for l:win in range(1, winnr('$')) + if getwinvar(l:win, '&buftype') is# 'quickfix' + return winwidth(l:win) == a:cols + endif + endfor + + return 0 + endfunction + After: Restore unlet! g:loclist + unlet! b:ale_list_vertical 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 + delfunction GetQuickfixIsVertical " Close quickfix window after every execute block lcl @@ -98,6 +113,24 @@ Execute(The quickfix window height should be correct for the loclist with buffer AssertEqual 8, GetQuickfixHeight() +Execute(The quickfix window should be vertical for the loclist with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 1, GetQuickfixIsVertical(b:ale_list_window_size) + +Execute(The quickfix window should be horizontal for the loclist with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 0 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 0, GetQuickfixIsVertical(b:ale_list_window_size) + Execute(The quickfix window should stay open for just the loclist): let g:ale_open_list = 1 let g:ale_keep_list_window_open = 1 @@ -167,6 +200,24 @@ Execute(The quickfix window height should be correct for the quickfix list with AssertEqual 8, GetQuickfixHeight() +Execute(The quickfix window should be vertical for the quickfix with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 1, GetQuickfixIsVertical(b:ale_list_window_size) + +Execute(The quickfix window should be horizontal for the quickfix with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 0 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 0, GetQuickfixIsVertical(b:ale_list_window_size) + Execute(The buffer ale_open_list option should be respected): let b:ale_open_list = 1 diff --git a/test/test_loclist_binary_search.vader b/test/test_loclist_binary_search.vader index 5558191c..219fb314 100644 --- a/test/test_loclist_binary_search.vader +++ b/test/test_loclist_binary_search.vader @@ -47,3 +47,20 @@ 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) + +Execute(Searches should return the last item on a single column): + let g:loclist = [ + \ {'bufnr': 1, 'lnum': 1, 'col': 10, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 10, 'type': 'E'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 11, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 11, 'type': 'E'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 20, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 20, 'type': 'E'}, + \] + + " We should return the index for the last item at some column to the right. + AssertEqual 1, ale#util#BinarySearch(g:loclist, 1, 1, 1) + " We should return the index for the last item at the column we are on. + AssertEqual 3, ale#util#BinarySearch(g:loclist, 1, 1, 11) + " We should prefer items to the left of the cursor, over the right. + AssertEqual 3, ale#util#BinarySearch(g:loclist, 1, 1, 19) diff --git a/test/test_loclist_corrections.vader b/test/test_loclist_corrections.vader index 6224d608..48aa1f78 100644 --- a/test/test_loclist_corrections.vader +++ b/test/test_loclist_corrections.vader @@ -52,7 +52,7 @@ Execute(FixLocList should use the values we supply): \ 'lnum': 3, \ 'col': 4, \ 'bufnr': 10000, - \ 'vcol': 1, + \ 'vcol': 0, \ 'type': 'W', \ 'nr': 42, \ 'linter_name': 'foobar', @@ -324,7 +324,7 @@ Execute(FixLocList should interpret temporary filenames as being the current buf \ 'foobar', \ [ \ {'text': 'a', 'lnum': 2, 'filename': b:temp_name}, - \ {'text': 'a', 'lnum': 3, 'filename': b:temp_name}, + \ {'text': 'a', 'lnum': 3, 'filename': substitute(b:temp_name, '\\', '/', 'g')}, \ ], \ ) @@ -348,3 +348,33 @@ Execute(The error code should be passed on): \ 'foobar', \ [{'text': 'a', 'lnum': 11, 'code': 'some-code'}], \ ) + +Given(A file with Japanese multi-byte text): + はじめまして! + -私はワープです。 +Execute(character positions should be converted to byte positions): + AssertEqual + \ [ + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 0, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 1, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 4, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 13, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 13, 'end_lnum': 1, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 17, 'end_lnum': 2, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 2, 'bufnr': bufnr(''), 'col': 17, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ [ + \ {'text': 'a', 'lnum': 1, 'col': 0, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 1, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 2, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'end_col': 5, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'end_col': 5, 'end_lnum': 1, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'end_col': 7, 'end_lnum': 2, 'vcol': 1}, + \ {'text': 'a', 'lnum': 2, 'col': 7, 'vcol': 1}, + \ ], + \ ) diff --git a/test/test_loclist_jumping.vader b/test/test_loclist_jumping.vader index 5e18499e..da9a1f57 100644 --- a/test/test_loclist_jumping.vader +++ b/test/test_loclist_jumping.vader @@ -2,15 +2,15 @@ Before: let g:ale_buffer_info = { \ bufnr(''): { \ 'loclist': [ - \ {'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}, + \ {'type': 'E', 'bufnr': bufnr('') - 1, 'lnum': 3, 'col': 2}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 1, 'col': 2}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 1, 'col': 3}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 1}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 2}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 3}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 6}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 700}, + \ {'type': 'E', 'bufnr': bufnr('') + 1, 'lnum': 3, 'col': 2}, \ ], \ }, \} @@ -81,7 +81,7 @@ 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}) + call add(g:ale_buffer_info[bufnr('%')].loclist, {'type': 'E', 'bufnr': bufnr(''), 'lnum': 3, 'col': 1}) AssertEqual 0, len(getline(3)) AssertEqual [2, 8], TestJump('before', 0, [3, 1]) diff --git a/test/test_loclist_sorting.vader b/test/test_loclist_sorting.vader index 157b2a25..376e743a 100644 --- a/test/test_loclist_sorting.vader +++ b/test/test_loclist_sorting.vader @@ -25,3 +25,19 @@ Execute(loclist item should be sorted): \ {'bufnr': 2, 'lnum': 1, 'col': 2}, \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, \], 'ale#util#LocItemCompare') + +Execute(Items should be sorted in by their problem priority when they lie on the same column): + AssertEqual [ + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'I'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E'}, + \ ], + \ sort([ + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'I'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W', 'sub_type': 'style'}, + \], 'ale#util#LocItemCompare') diff --git a/test/test_pattern_options.vader b/test/test_pattern_options.vader index 0e26eaaa..f439afbd 100644 --- a/test/test_pattern_options.vader +++ b/test/test_pattern_options.vader @@ -90,3 +90,14 @@ Execute(Patterns should be applied after the Dictionary changes): call ale#pattern_options#SetOptions(bufnr('')) AssertEqual 666, b:some_option + +Execute(SetOptions should tolerate settings being unset): + " This might happen if ALE is loaded in a weird way, so tolerate it. + unlet! g:ale_pattern_options + unlet! g:ale_pattern_options_enabled + + call ale#pattern_options#SetOptions(bufnr('')) + + let g:ale_pattern_options_enabled = 1 + + call ale#pattern_options#SetOptions(bufnr('')) diff --git a/test/test_prepare_command.vader b/test/test_prepare_command.vader index ed9272ab..75e4c0c6 100644 --- a/test/test_prepare_command.vader +++ b/test/test_prepare_command.vader @@ -22,6 +22,23 @@ Execute(sh should be used when the shell is fish): AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') endif +Execute(sh should be used when the shell is powershell): + if !has('win32') + " Set something else, so we will replace that too. + let &shellcmdflag = '-f' + let &shell = 'pwsh' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + + let &shell = '/usr/bin/pwsh' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + + let &shell = '/usr/local/bin/pwsh' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + endif + Execute(Other shells should be used when set): if !has('win32') let &shell = '/bin/bash' diff --git a/test/test_quickfix_deduplication.vader b/test/test_quickfix_deduplication.vader index 0dff3f2e..9cb8b931 100644 --- a/test/test_quickfix_deduplication.vader +++ b/test/test_quickfix_deduplication.vader @@ -9,42 +9,42 @@ Execute: " 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'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'type': 'E', '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'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'type': 'E', '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'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'another error'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 3, 'lnum': 1, 'col': 1, 'text': 'foo'}, \ ], \ ale#list#GetCombinedList() diff --git a/test/test_quitting_variable.vader b/test/test_quitting_variable.vader index bef344a8..38b43bb4 100644 --- a/test/test_quitting_variable.vader +++ b/test/test_quitting_variable.vader @@ -11,12 +11,12 @@ After: unlet! b:time_before Execute(QuitEvent should set b:ale_quitting some time from the clock): - let b:time_before = ale#util#ClockMilliseconds() + let b:time_before = ale#events#ClockMilliseconds() call ale#events#QuitEvent(bufnr('')) Assert b:ale_quitting >= b:time_before - Assert b:ale_quitting <= ale#util#ClockMilliseconds() + Assert b:ale_quitting <= ale#events#ClockMilliseconds() Execute(EnterEvent should set b:ale_quitting to 0): let b:ale_quitting = 1 @@ -29,11 +29,11 @@ Execute(The QuitRecently function should work when the variable isn't set): AssertEqual 0, ale#events#QuitRecently(bufnr('')) Execute(The QuitRecently function should return 1 when ALE quit recently): - let b:ale_quitting = ale#util#ClockMilliseconds() + let b:ale_quitting = ale#events#ClockMilliseconds() AssertEqual 1, ale#events#QuitRecently(bufnr('')) Execute(The QuitRecently function should return 0 when a second has passed): - let b:ale_quitting = ale#util#ClockMilliseconds() - 1001 + let b:ale_quitting = ale#events#ClockMilliseconds() - 1001 AssertEqual 0, ale#events#QuitRecently(bufnr('')) diff --git a/test/test_should_do_nothing_conditions.vader b/test/test_should_do_nothing_conditions.vader index 23ebd92e..062ab875 100644 --- a/test/test_should_do_nothing_conditions.vader +++ b/test/test_should_do_nothing_conditions.vader @@ -1,4 +1,7 @@ Before: + Save g:ale_filetype_blacklist + Save g:ale_maximum_file_size + Save g:ale_enabled Save &l:statusline call ale#test#SetDirectory('/testplugin/test') @@ -12,6 +15,8 @@ Before: endif After: + Restore + call ale#test#RestoreDirectory() if b:funky_command_created @@ -21,8 +26,7 @@ After: unlet! b:funky_command_created - Restore - +Given foobar(An empty file): Execute(ALE shouldn't do much of anything for ctrlp-funky buffers): Assert !ale#ShouldDoNothing(bufnr('')), 'The preliminary check failed' @@ -39,3 +43,21 @@ Execute(ALE shouldn't try to check buffers with '.' as the filename): silent! noautocmd file . Assert ale#ShouldDoNothing(bufnr('')) + +Execute(DoNothing should return 0 when the filetype is empty): + AssertEqual + \ 0, + \ ale#ShouldDoNothing(bufnr('')), + \ 'ShouldDoNothing() was 1 for some other reason' + + set filetype= + + AssertEqual 1, ale#ShouldDoNothing(bufnr('')) + +Execute(The DoNothing check should work if the ALE globals aren't defined): + unlet! g:ale_filetype_blacklist + unlet! g:ale_maximum_file_size + unlet! g:ale_enabled + + " This shouldn't throw exceptions. + call ale#ShouldDoNothing(bufnr('')) diff --git a/test/test_temporary_file_management.vader b/test/test_temporary_file_management.vader index ae2bf251..e248331c 100644 --- a/test/test_temporary_file_management.vader +++ b/test/test_temporary_file_management.vader @@ -1,4 +1,7 @@ Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = {} let g:ale_run_synchronously = 1 let g:command = 'echo test' @@ -41,6 +44,8 @@ Before: \}) After: + Restore + if !empty(g:preserved_directory) call delete(g:preserved_directory, 'rf') endif @@ -111,3 +116,17 @@ Execute(ALE should create and delete directories for ale#engine#CreateDirectory( Assert !isdirectory(b:dir), 'The directory was not deleted' Assert !isdirectory(b:dir2), 'The second directory was not deleted' + +Execute(ale#engine#ManageFile should add the file even if the buffer info hasn't be set yet): + let g:ale_buffer_info = {} + call ale#engine#ManageFile(bufnr(''), '/foo/bar') + AssertEqual + \ ['/foo/bar'], + \ g:ale_buffer_info[bufnr('')].temporary_file_list + +Execute(ale#engine#ManageDirectory should add the directory even if the buffer info hasn't be set yet): + let g:ale_buffer_info = {} + call ale#engine#ManageDirectory(bufnr(''), '/foo/bar') + AssertEqual + \ ['/foo/bar'], + \ g:ale_buffer_info[bufnr('')].temporary_directory_list diff --git a/test/test_verilog_verilator_options.vader b/test/test_verilog_verilator_options.vader index 561786ee..e53037b1 100644 --- a/test/test_verilog_verilator_options.vader +++ b/test/test_verilog_verilator_options.vader @@ -22,4 +22,3 @@ Execute(Set Verilog Verilator linter additional options to `-sv --default-langua \ g:matched , \ -1 , \ 'Additionnal arguments not found in the run command' - |