diff options
570 files changed, 13315 insertions, 4034 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a73f67ba..785da9b8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ Before creating a pull request, do the following. * Read the Contributing guide linked above first. -* Read the documentation that comes with ALE with `:help ale-development`. +* Read the documentation that comes with ALE with `:help ale-dev`. Have fun! --> @@ -10,4 +10,4 @@ Have fun! Where are the tests? Have you added tests? Have you updated the tests? Read the comment above and the documentation referenced in it first. Write tests! -Seriously, read `:help ale-development` and write tests. +Seriously, read `:help ale-dev` and write tests. @@ -1,6 +1,10 @@ !.editorconfig *.obj +# Ignore all hidden files everywhere. +# Use `git add -f` to add hidden files. .* +__pycache__ +*.pyc /doc/tags /init.vim /test/ale-info-test-file @@ -1,7 +1,7 @@ FROM tweekmonster/vim-testbed:latest RUN install_vim -tag v8.0.0027 -build \ - -tag v8.1.0204 -build \ + -tag v8.1.0519 -build \ -tag neovim:v0.2.0 -build \ -tag neovim:v0.3.0 -build @@ -3,9 +3,9 @@ ![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 -0.2.0+ and Vim 8 while you edit your text files, and acts as a Vim -[Language Server Protocol](https://langserver.org/) client. +ALE (Asynchronous Lint Engine) is a plugin providing linting (syntax checking +and semantic errors) in NeoVim 0.2.0+ and Vim 8 while you edit your text files, +and acts as a Vim [Language Server Protocol](https://langserver.org/) client. <img src="img/example.gif?raw=true" alt="A linting example with the darkspectrum color scheme in GVim." title="A linting example with the darkspectrum color scheme in GVim."> @@ -26,7 +26,7 @@ features, including: * Diagnostics (via Language Server Protocol linters) * Go To Definition (`:ALEGoToDefinition`) -* Completion (`let g:ale_completion_enabled = 1` before ALE is loaded) +* Completion (Built in completion support, or with Deoplete) * Finding references (`:ALEFindReferences`) * Hover information (`:ALEHover`) * Symbol search (`:ALESymbolSearch`) @@ -73,141 +73,15 @@ other content at [w0rp.com](https://w0rp.com). 15. [How can I configure my C or C++ project?](#faq-c-configuration) 16. [How can I configure ALE differently for different buffers?](#faq-buffer-configuration) 17. [How can I configure the height of the list in which ALE displays errors?](#faq-list-window-height) + 18. [How can I see what ALE has configured for the current file?](#faq-get-info) <a name="supported-languages"></a> ## 1. Supported Languages and Tools -This plugin supports the following languages and tools. All available -tools will be run in combination, so they can be complementary. - -<!-- -Keep the table rows sorted alphabetically by the language name, -and the tools in the tools column sorted alphabetically by the tool -name. That seems to be the fairest way to arrange this table. - -Remember to also update doc/ale.txt, which has a similar list with different -formatting. ---> - -**Notes:** - -* *^ No linters for text or Vim help filetypes are enabled by default.* -* *!! These linters check only files on disk. See `:help ale-lint-file-linters`* - -| Language | Tools | -| -------- | ----- | -| Ada | [gcc](https://gcc.gnu.org) | -| ASM | [gcc](https://gcc.gnu.org) | -| Ansible | [ansible-lint](https://github.com/willthames/ansible-lint) | -| 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), [vale](https://github.com/ValeLint/vale) | -| Awk | [gawk](https://www.gnu.org/software/gawk/)| -| 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) | -| BibTeX | [bibclean](http://ftp.math.utah.edu/pub/bibclean/) | -| 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), [clang](http://clang.llvm.org/), [clangd](https://clang.llvm.org/extra/clangd.html), [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [cquery](https://github.com/cquery-project/cquery), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/), [uncrustify](https://github.com/uncrustify/uncrustify), [ccls](https://github.com/MaskRay/ccls) | -| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangd](https://clang.llvm.org/extra/clangd.html), [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), [clazy](https://github.com/KDE/clazy) !!, [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/), [uncrustify](https://github.com/uncrustify/uncrustify), [ccls](https://github.com/MaskRay/ccls) | -| 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, [uncrustify](https://github.com/uncrustify/uncrustify) | -| 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 | [dls](https://github.com/d-language-server/dls), [dmd](https://dlang.org/dmd-linux.html), [uncrustify](https://github.com/uncrustify/uncrustify) | -| 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), [dartfmt](https://github.com/dart-lang/sdk/tree/master/utils/dartfmt) | -| Dockerfile | [dockerfile_lint](https://github.com/projectatomic/dockerfile_lint), [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), [mix](https://hexdocs.pm/mix/Mix.html) !!, [elixir-ls](https://github.com/JakeBecker/elixir-ls) | -| 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), [ruumba](https://github.com/ericqweinstein/ruumba) | -| Erlang | [erlc](http://erlang.org/doc/man/erlc.html), [SyntaxErl](https://github.com/ten0s/syntaxerl) | -| Fish | fish [-n flag](https://linux.die.net/man/1/fish) -| Fortran | [gcc](https://gcc.gnu.org/), [language_server](https://github.com/hansec/fortran-language-server) | -| Fountain | [proselint](http://proselint.com/) | -| 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 mod](https://golang.org/cmd/go/) !!, [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) !!, [golangserver](https://github.com/sourcegraph/go-langserver), [golangci-lint](https://github.com/golangci/golangci-lint) !! | -| GraphQL | [eslint](http://eslint.org/), [gqlint](https://github.com/happylinks/gqlint), [prettier](https://github.com/prettier/prettier) | -| Hack | [hack](http://hacklang.org/), [hackfmt](https://github.com/facebook/hhvm/tree/master/hphp/hack/hackfmt), [hhast](https://github.com/hhvm/hhast) (disabled by default; see `:help ale-integration-hack`) | -| 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/), [cabal-ghc](https://www.haskell.org/cabal/), [stylish-haskell](https://github.com/jaspervdj/stylish-haskell), [stack-ghc](https://haskellstack.org/), [stack-build](https://haskellstack.org/) !!, [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), [hie](https://github.com/haskell/haskell-ide-engine) | -| HCL | [terraform-fmt](https://github.com/hashicorp/terraform) | -| HTML | [alex](https://github.com/wooorm/alex) !!, [HTMLHint](http://htmlhint.com/), [proselint](http://proselint.com/), [tidy](http://www.html-tidy.org/), [prettier](https://github.com/prettier/prettier), [write-good](https://github.com/btford/write-good) | -| Idris | [idris](http://www.idris-lang.org/) | -| ISPC | [ispc](https://ispc.github.io/) !! | -| 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/), [javalsp](https://github.com/georgewfraser/vscode-javac), [uncrustify](https://github.com/uncrustify/uncrustify) | -| 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) | -| Julia | [languageserver](https://github.com/JuliaEditorSupport/LanguageServer.jl) | -| Kotlin | [kotlinc](https://kotlinlang.org) !!, [ktlint](https://ktlint.github.io) !!, [languageserver](https://github.com/fwcd/KotlinLanguageServer) 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) | -| Less | [lessc](https://www.npmjs.com/package/less), [prettier](https://github.com/prettier/prettier), [stylelint](https://github.com/stylelint/stylelint) | -| LLVM | [llc](https://llvm.org/docs/CommandGuide/llc.html) | -| 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) !!, [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/), [clangd](https://clang.llvm.org/extra/clangd.html), [uncrustify](https://github.com/uncrustify/uncrustify), [ccls](https://github.com/MaskRay/ccls) | -| Objective-C++ | [clang](http://clang.llvm.org/), [clangd](https://clang.llvm.org/extra/clangd.html), [uncrustify](https://github.com/uncrustify/uncrustify) | -| 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), [ocamlformat](https://github.com/ocaml-ppx/ocamlformat) | -| Pawn | [uncrustify](https://github.com/uncrustify/uncrustify) | -| Perl | [perl -c](https://perl.org/), [perl-critic](https://metacpan.org/pod/Perl::Critic), [perltidy](https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy) | -| Perl6 | [perl6 -c](https://perl6.org) | -| PHP | [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/), [psalm](https://getpsalm.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) | -| Prolog | [swipl](https://github.com/SWI-Prolog/swipl-devel) | -| proto | [protoc-gen-lint](https://github.com/ckaznocha/protoc-gen-lint) | -| Pug | [pug-lint](https://github.com/pugjs/pug-lint) | -| Puppet | [languageserver](https://github.com/lingua-pupuli/puppet-editor-services), [puppet](https://puppet.com), [puppet-lint](https://puppet-lint.com) | -| 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](https://github.com/PyCQA/prospector), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pydocstyle](https://www.pydocstyle.org/), [pyls](https://github.com/palantir/python-language-server), [pyre](https://github.com/facebook/pyre-check), [pylint](https://www.pylint.org/) !!, [vulture](https://github.com/jendrikseipp/vulture) !!, [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-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), [rufo](https://github.com/ruby-formatter/rufo), [solargraph](https://solargraph.org) | -| Rust | [cargo](https://github.com/rust-lang/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 | [fsc](https://www.scala-lang.org/old/sites/default/files/linuxsoft_archives/docu/files/tools/fsc.html), [sbtserver](https://www.scala-sbt.org/1.x/docs/sbt-server.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) | -| Stylus | [stylelint](https://github.com/stylelint/stylelint) | -| SQL | [sqlint](https://github.com/purcell/sqlint), [sqlfmt](https://github.com/jackc/sqlfmt) | -| Swift | [swiftlint](https://github.com/realm/SwiftLint), [swiftformat](https://github.com/nicklockwood/SwiftFormat) | -| Tcl | [nagelfar](http://nagelfar.sourceforge.net) !! | -| Terraform | [fmt](https://github.com/hashicorp/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/), [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](https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29), typecheck | -| VALA | [uncrustify](https://github.com/uncrustify/uncrustify) | -| Verilog | [iverilog](https://github.com/steveicarus/iverilog), [verilator](http://www.veripool.org/projects/verilator/wiki/Intro) | -| Vim | [vint](https://github.com/Kuniwak/vint) | -| Vim help^ | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | -| Vue | [prettier](https://github.com/prettier/prettier), [vls](https://github.com/vuejs/vetur/tree/master/server) | -| XHTML | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | -| XML | [xmllint](http://xmlsoft.org/xmllint.html) | -| YAML | [prettier](https://github.com/prettier/prettier), [swaglint](https://github.com/byCedric/swaglint), [yamllint](https://yamllint.readthedocs.io/) | -| YANG | [yang-lsp](https://github.com/theia-ide/yang-lsp) | +ALE supports a wide variety of languages and tools. See the +[full list](supported-tools.md) in the +[Supported Languages and Tools](supported-tools.md) page. <a name="usage"></a> @@ -227,7 +101,7 @@ new buffers or as you make edits to your files. The behaviour of linting can be configured with a variety of options, documented in [the Vim help file](doc/ale.txt). For more information on the options ALE offers, consult `:help ale-options` for global options and `:help -ale-linter-options` for options specified to particular linters. +ale-integration-options` for options specified to particular linters. <a name="usage-fixing"></a> @@ -285,6 +159,18 @@ 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 from `tsserver` for TypeScript. +ALE integrates with [Deoplete](https://github.com/Shougo/deoplete.nvim) as a +completion source, named `'ale'`. You can configure Deoplete to only use ALE as +the source of completion information, or mix it with other sources. + +```vim +" Use ALE and also some plugin 'foobar' as completion sources for all code. +let g:deoplete#sources = {'_': ['ale', 'foobar']} +``` + +ALE also offers its own automatic completion support, which does not require any +other plugins, and can be enabled by changing a setting before ALE is loaded. + ```vim " Enable completion where available. " This setting must be set before ALE is loaded. @@ -575,8 +461,16 @@ let g:airline#extensions#ale#enabled = 1 ``` If you don't want to use vim-airline, you can implement your own statusline -function without adding any other plugins. ALE provides a function for counting -the number of problems for this purpose, named `ale#statusline#Count`. +function without adding any other plugins. ALE provides some functions to +assist in this endeavour, including: + +* `ale#statusline#Count`: Which returns the number of problems found by ALE + for a specified buffer. +* `ale#statusline#FirstProblem`: Which returns a dictionary containing the + full loclist details of the first problem of a specified type found by ALE + in a buffer. (e.g. The first style warning in the current buffer.) + This can be useful for displaying more detailed information such as the + line number of the first problem in a file. Say you want to display all errors as one figure, and all non-errors as another figure. You can do the following: @@ -598,7 +492,8 @@ endfunction set statusline=%{LinterStatus()} ``` -See `:help ale#statusline#Count()` for more information. +See `:help ale#statusline#Count()` or `:help ale#statusline#FirstProblem()` +for more information. <a name="faq-lightline"></a> @@ -620,7 +515,7 @@ There are 3 global options that allow customizing the echoed message. * `%...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 + * `%severity%` is the severity type - `g:ale_echo_msg_error_str` is the string used for error severity. - `g:ale_echo_msg_warning_str` is the string used for warning severity. @@ -760,7 +655,7 @@ Or if you want, you can configure the linters from your vimrc file. ```vim " In ~/.vim/vimrc, or somewhere similar. -let g:ale_linter_aliases = {'jsx': ['css, 'javascript']} +let g:ale_linter_aliases = {'jsx': ['css', 'javascript']} let g:ale_linters = {'jsx': ['stylelint', 'eslint']} ``` @@ -897,3 +792,13 @@ To set a default height for the error list, use the `g:ale_list_window_size` var " Show 5 lines of errors (default: 10) let g:ale_list_window_size = 5 ``` + +<a name="faq-get-info"></a> + +### 5.xviii. How can I see what ALE has configured for the current file? + +Run the following to see what is currently configured: + +```vim +:ALEInfo +``` diff --git a/ale_linters/ada/gcc.vim b/ale_linters/ada/gcc.vim index d6f973ae..87496b81 100644 --- a/ale_linters/ada/gcc.vim +++ b/ale_linters/ada/gcc.vim @@ -12,7 +12,7 @@ function! ale_linters#ada#gcc#GetCommand(buffer) abort " the .ali file may be created even if no code generation is attempted. " The output file name must match the source file name (except for the " extension), so here we cannot use the null file as output. - let l:tmp_dir = fnamemodify(ale#engine#CreateDirectory(a:buffer), ':p') + let l:tmp_dir = fnamemodify(ale#command#CreateDirectory(a:buffer), ':p') let l:out_file = l:tmp_dir . fnamemodify(bufname(a:buffer), ':t:r') . '.o' " -gnatc: Check syntax and semantics only (no code generation attempted) @@ -48,7 +48,7 @@ endfunction call ale#linter#Define('ada', { \ 'name': 'gcc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('ada_gcc_executable'), -\ 'command_callback': 'ale_linters#ada#gcc#GetCommand', +\ 'executable': {b -> ale#Var(b, 'ada_gcc_executable')}, +\ 'command': function('ale_linters#ada#gcc#GetCommand'), \ 'callback': 'ale_linters#ada#gcc#Handle', \}) diff --git a/ale_linters/ansible/ansible_lint.vim b/ale_linters/ansible/ansible_lint.vim index 99fff6c3..c4affa31 100644 --- a/ale_linters/ansible/ansible_lint.vim +++ b/ale_linters/ansible/ansible_lint.vim @@ -50,7 +50,7 @@ endfunction call ale#linter#Define('ansible', { \ 'name': 'ansible_lint', \ 'aliases': ['ansible', 'ansible-lint'], -\ 'executable_callback': 'ale_linters#ansible#ansible_lint#GetExecutable', +\ 'executable': function('ale_linters#ansible#ansible_lint#GetExecutable'), \ 'command': '%e -p %t', \ 'callback': 'ale_linters#ansible#ansible_lint#Handle', \}) diff --git a/ale_linters/asciidoc/alex.vim b/ale_linters/asciidoc/alex.vim index 79b04fc3..97976b2c 100644 --- a/ale_linters/asciidoc/alex.vim +++ b/ale_linters/asciidoc/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for asciidoc files -call ale#linter#Define('help', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('asciidoc', '--text') diff --git a/ale_linters/asciidoc/textlint.vim b/ale_linters/asciidoc/textlint.vim new file mode 100644 index 00000000..308a3a29 --- /dev/null +++ b/ale_linters/asciidoc/textlint.vim @@ -0,0 +1,9 @@ +" Author: TANIGUCHI Masaya <ta2gch@gmail.com> +" Description: textlint for AsciiDoc files + +call ale#linter#Define('asciidoc', { +\ 'name': 'textlint', +\ 'executable': function('ale#handlers#textlint#GetExecutable'), +\ 'command': function('ale#handlers#textlint#GetCommand'), +\ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', +\}) diff --git a/ale_linters/asm/gcc.vim b/ale_linters/asm/gcc.vim index fdd0ee83..72b293c0 100644 --- a/ale_linters/asm/gcc.vim +++ b/ale_linters/asm/gcc.vim @@ -28,7 +28,7 @@ endfunction call ale#linter#Define('asm', { \ 'name': 'gcc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('asm_gcc_executable'), -\ 'command_callback': 'ale_linters#asm#gcc#GetCommand', +\ 'executable': {b -> ale#Var(b, 'asm_gcc_executable')}, +\ 'command': function('ale_linters#asm#gcc#GetCommand'), \ 'callback': 'ale_linters#asm#gcc#Handle', \}) diff --git a/ale_linters/awk/gawk.vim b/ale_linters/awk/gawk.vim index eb92e45e..f795c57d 100644 --- a/ale_linters/awk/gawk.vim +++ b/ale_linters/awk/gawk.vim @@ -15,8 +15,8 @@ endfunction call ale#linter#Define('awk', { \ 'name': 'gawk', -\ 'executable_callback': ale#VarFunc('awk_gawk_executable'), -\ 'command_callback': 'ale_linters#awk#gawk#GetCommand', +\ 'executable': {b -> ale#Var(b, 'awk_gawk_executable')}, +\ 'command': function('ale_linters#awk#gawk#GetCommand'), \ 'callback': 'ale#handlers#gawk#HandleGawkFormat', \ 'output_stream': 'both' \}) diff --git a/ale_linters/bib/bibclean.vim b/ale_linters/bib/bibclean.vim index 6750f22f..9056a9c3 100644 --- a/ale_linters/bib/bibclean.vim +++ b/ale_linters/bib/bibclean.vim @@ -11,9 +11,9 @@ endfunction function! ale_linters#bib#bibclean#get_type(str) abort if a:str is# '??' - return 'E' + return 'E' else - return 'W' + return 'W' endif endfunction @@ -36,30 +36,31 @@ function! ale_linters#bib#bibclean#Handle(buffer, lines) abort let l:msg = '' for l:line in a:lines - if empty(l:msg) - let l:mlist = ale_linters#bib#bibclean#match_msg(l:line) + if empty(l:msg) + let l:mlist = ale_linters#bib#bibclean#match_msg(l:line) - if !empty(l:mlist) - let l:msg = l:mlist[3] - let l:type = ale_linters#bib#bibclean#get_type(l:mlist[1]) - endif - else - if l:type is# 'E' - let l:mlist = ale_linters#bib#bibclean#match_entry(l:line) - else - let l:mlist = ale_linters#bib#bibclean#match_value(l:line) - endif + if !empty(l:mlist) + let l:msg = l:mlist[3] + let l:type = ale_linters#bib#bibclean#get_type(l:mlist[1]) + endif + else + if l:type is# 'E' + let l:mlist = ale_linters#bib#bibclean#match_entry(l:line) + else + let l:mlist = ale_linters#bib#bibclean#match_value(l:line) + endif - if !empty(l:mlist) - call add(l:output, { - \ 'lnum': l:mlist[1], - \ 'col': l:mlist[2], - \ 'text': l:msg, - \ 'type': l:type - \}) - let l:msg = '' - endif - endif + if !empty(l:mlist) + call add(l:output, { + \ 'lnum': l:mlist[1], + \ 'col': l:mlist[2], + \ 'text': l:msg, + \ 'type': l:type + \}) + + let l:msg = '' + endif + endif endfor return l:output @@ -67,8 +68,8 @@ endfunction call ale#linter#Define('bib', { \ 'name': 'bibclean', -\ 'executable_callback': ale#VarFunc('bib_bibclean_executable'), -\ 'command_callback': 'ale_linters#bib#bibclean#GetCommand', +\ 'executable': {b -> ale#Var(b, 'bib_bibclean_executable')}, +\ 'command': function('ale_linters#bib#bibclean#GetCommand'), \ 'output_stream': 'stderr', \ 'callback': 'ale_linters#bib#bibclean#Handle', \}) diff --git a/ale_linters/c/ccls.vim b/ale_linters/c/ccls.vim index 5dc2339f..9e3dafe9 100644 --- a/ale_linters/c/ccls.vim +++ b/ale_linters/c/ccls.vim @@ -7,8 +7,8 @@ call ale#Set('c_ccls_init_options', {}) call ale#linter#Define('c', { \ 'name': 'ccls', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('c_ccls_executable'), +\ 'executable': {b -> ale#Var(b, 'c_ccls_executable')}, \ 'command': '%e', -\ 'project_root_callback': 'ale#handlers#ccls#GetProjectRoot', -\ 'initialization_options_callback':ale#VarFunc('c_ccls_init_options'), +\ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), +\ 'initialization_options': {b -> ale#Var(b, 'c_ccls_init_options')}, \}) diff --git a/ale_linters/c/clang.vim b/ale_linters/c/clang.vim index f1bd675b..681101fc 100644 --- a/ale_linters/c/clang.vim +++ b/ale_linters/c/clang.vim @@ -18,10 +18,7 @@ endfunction call ale#linter#Define('c', { \ 'name': 'clang', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('c_clang_executable'), -\ 'command_chain': [ -\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#c#clang#GetCommand'} -\ ], +\ 'executable': {b -> ale#Var(b, 'c_clang_executable')}, +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#c#clang#GetCommand'))}, \ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', \}) diff --git a/ale_linters/c/clangd.vim b/ale_linters/c/clangd.vim index 6cad601a..918eadcc 100644 --- a/ale_linters/c/clangd.vim +++ b/ale_linters/c/clangd.vim @@ -17,7 +17,7 @@ endfunction call ale#linter#Define('c', { \ 'name': 'clangd', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('c_clangd_executable'), -\ 'command_callback': 'ale_linters#c#clangd#GetCommand', -\ 'project_root_callback': 'ale_linters#c#clangd#GetProjectRoot', +\ 'executable': {b -> ale#Var(b, 'c_clangd_executable')}, +\ 'command': function('ale_linters#c#clangd#GetCommand'), +\ 'project_root': function('ale_linters#c#clangd#GetProjectRoot'), \}) diff --git a/ale_linters/c/clangtidy.vim b/ale_linters/c/clangtidy.vim index 4f334655..6484f8af 100644 --- a/ale_linters/c/clangtidy.vim +++ b/ale_linters/c/clangtidy.vim @@ -35,8 +35,8 @@ endfunction call ale#linter#Define('c', { \ 'name': 'clangtidy', \ 'output_stream': 'stdout', -\ 'executable_callback': ale#VarFunc('c_clangtidy_executable'), -\ 'command_callback': 'ale_linters#c#clangtidy#GetCommand', +\ 'executable': {b -> ale#Var(b, 'c_clangtidy_executable')}, +\ 'command': function('ale_linters#c#clangtidy#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/c/cppcheck.vim b/ale_linters/c/cppcheck.vim index 5e8c7936..851f9f11 100644 --- a/ale_linters/c/cppcheck.vim +++ b/ale_linters/c/cppcheck.vim @@ -28,7 +28,7 @@ endfunction call ale#linter#Define('c', { \ 'name': 'cppcheck', \ 'output_stream': 'both', -\ 'executable_callback': ale#VarFunc('c_cppcheck_executable'), -\ 'command_callback': 'ale_linters#c#cppcheck#GetCommand', +\ 'executable': {b -> ale#Var(b, 'c_cppcheck_executable')}, +\ 'command': function('ale_linters#c#cppcheck#GetCommand'), \ 'callback': 'ale#handlers#cppcheck#HandleCppCheckFormat', \}) diff --git a/ale_linters/c/cquery.vim b/ale_linters/c/cquery.vim index a20782a2..d2be9cd9 100644 --- a/ale_linters/c/cquery.vim +++ b/ale_linters/c/cquery.vim @@ -21,8 +21,8 @@ endfunction call ale#linter#Define('c', { \ 'name': 'cquery', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('c_cquery_executable'), +\ 'executable': {b -> ale#Var(b, 'c_cquery_executable')}, \ 'command': '%e', -\ 'project_root_callback': 'ale_linters#c#cquery#GetProjectRoot', -\ 'initialization_options_callback': 'ale_linters#c#cquery#GetInitializationOptions', +\ 'project_root': function('ale_linters#c#cquery#GetProjectRoot'), +\ 'initialization_options': function('ale_linters#c#cquery#GetInitializationOptions'), \}) diff --git a/ale_linters/c/flawfinder.vim b/ale_linters/c/flawfinder.vim index 7e1f6769..53c36716 100644 --- a/ale_linters/c/flawfinder.vim +++ b/ale_linters/c/flawfinder.vim @@ -7,19 +7,19 @@ call ale#Set('c_flawfinder_minlevel', 1) call ale#Set('c_flawfinder_error_severity', 6) 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') + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'c_flawfinder_minlevel') - return '%e -CDQS' - \ . ale#Pad(ale#Var(a:buffer, 'c_flawfinder_options')) - \ . l:minlevel - \ . ' %t' + return '%e -CDQS' + \ . ale#Pad(ale#Var(a:buffer, 'c_flawfinder_options')) + \ . l:minlevel + \ . ' %t' endfunction call ale#linter#Define('c', { \ 'name': 'flawfinder', \ 'output_stream': 'stdout', -\ 'executable_callback': ale#VarFunc('c_flawfinder_executable'), -\ 'command_callback': 'ale_linters#c#flawfinder#GetCommand', +\ 'executable': {b -> ale#Var(b, 'c_flawfinder_executable')}, +\ 'command': function('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 60ecb712..d965965d 100644 --- a/ale_linters/c/gcc.vim +++ b/ale_linters/c/gcc.vim @@ -18,10 +18,7 @@ endfunction call ale#linter#Define('c', { \ 'name': 'gcc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('c_gcc_executable'), -\ 'command_chain': [ -\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#c#gcc#GetCommand'} -\ ], +\ 'executable': {b -> ale#Var(b, 'c_gcc_executable')}, +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#c#gcc#GetCommand'))}, \ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', \}) diff --git a/ale_linters/chef/cookstyle.vim b/ale_linters/chef/cookstyle.vim new file mode 100644 index 00000000..50bae2aa --- /dev/null +++ b/ale_linters/chef/cookstyle.vim @@ -0,0 +1,54 @@ +" Author: Raphael Hoegger - https://github.com/pfuender +" Description: Cookstyle (RuboCop based), a code style analyzer for Ruby files + +call ale#Set('chef_cookstyle_executable', 'cookstyle') +call ale#Set('chef_cookstyle_options', '') + +function! ale_linters#chef#cookstyle#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'chef_cookstyle_options') + + return '%e' . ale#Pad(escape(l:options, '~')) . ' --force-exclusion --format json --stdin ' . ' %s' +endfunction + +function! ale_linters#chef#cookstyle#Handle(buffer, lines) abort + if len(a:lines) == 0 + return [] + endif + + let l:errors = ale#util#FuzzyJSONDecode(a:lines[0], {}) + + if !has_key(l:errors, 'summary') + \|| l:errors['summary']['offense_count'] == 0 + \|| empty(l:errors['files']) + return [] + endif + + let l:output = [] + + for l:error in l:errors['files'][0]['offenses'] + let l:start_col = str2nr(l:error['location']['start_column']) + let l:end_col = str2nr(l:error['location']['last_column']) + + if !l:end_col + let l:end_col = l:start_col + 1 + endif + + call add(l:output, { + \ 'lnum': str2nr(l:error['location']['line']), + \ 'col': l:start_col, + \ 'end_col': l:end_col, + \ 'code': l:error['cop_name'], + \ 'text': l:error['message'], + \ 'type': l:error['severity'] is? 'convention' ? 'W' : 'E', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('chef', { +\ 'name': 'cookstyle', +\ 'executable': {b -> ale#Var(b, 'chef_cookstyle_executable')}, +\ 'command': function('ale_linters#chef#cookstyle#GetCommand'), +\ 'callback': 'ale_linters#chef#cookstyle#Handle', +\}) diff --git a/ale_linters/chef/foodcritic.vim b/ale_linters/chef/foodcritic.vim index c86336d6..48beba75 100644 --- a/ale_linters/chef/foodcritic.vim +++ b/ale_linters/chef/foodcritic.vim @@ -34,8 +34,8 @@ endfunction call ale#linter#Define('chef', { \ 'name': 'foodcritic', -\ 'executable_callback': ale#VarFunc('chef_foodcritic_executable'), -\ 'command_callback': 'ale_linters#chef#foodcritic#GetCommand', +\ 'executable': {b -> ale#Var(b, 'chef_foodcritic_executable')}, +\ 'command': function('ale_linters#chef#foodcritic#GetCommand'), \ 'callback': 'ale_linters#chef#foodcritic#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/clojure/clj_kondo.vim b/ale_linters/clojure/clj_kondo.vim new file mode 100644 index 00000000..5dd11c12 --- /dev/null +++ b/ale_linters/clojure/clj_kondo.vim @@ -0,0 +1,34 @@ +" Author: Masashi Iizuka <liquidz.uo@gmail.com> +" Description: linter for clojure using clj-kondo https://github.com/borkdude/clj-kondo + +function! ale_linters#clojure#clj_kondo#HandleCljKondoFormat(buffer, lines) abort + " output format + " <filename>:<line>:<column>: <issue type>: <message> + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):(\d+):? ((Exception|error|warning): ?(.+))$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = 'E' + + if l:match[4] is? 'warning' + let l:type = 'W' + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3], + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('clojure', { +\ 'name': 'clj-kondo', +\ 'output_stream': 'stdout', +\ 'executable': 'clj-kondo', +\ 'command': 'clj-kondo --lint %t', +\ 'callback': 'ale_linters#clojure#clj_kondo#HandleCljKondoFormat', +\}) diff --git a/ale_linters/clojure/joker.vim b/ale_linters/clojure/joker.vim index 2f61148b..1f17cd31 100644 --- a/ale_linters/clojure/joker.vim +++ b/ale_linters/clojure/joker.vim @@ -11,7 +11,7 @@ function! ale_linters#clojure#joker#HandleJokerFormat(buffer, lines) abort let l:type = 'E' if l:match[4] is? 'Parse warning' - let l:type = 'W' + let l:type = 'W' endif call add(l:output, { diff --git a/ale_linters/cmake/cmakelint.vim b/ale_linters/cmake/cmakelint.vim index 78676518..d955a265 100644 --- a/ale_linters/cmake/cmakelint.vim +++ b/ale_linters/cmake/cmakelint.vim @@ -18,7 +18,7 @@ endfunction call ale#linter#Define('cmake', { \ 'name': 'cmakelint', -\ 'executable_callback': 'ale_linters#cmake#cmakelint#Executable', -\ 'command_callback': 'ale_linters#cmake#cmakelint#Command', +\ 'executable': function('ale_linters#cmake#cmakelint#Executable'), +\ 'command': function('ale_linters#cmake#cmakelint#Command'), \ 'callback': 'ale#handlers#unix#HandleAsWarning', \}) diff --git a/ale_linters/coffee/coffee.vim b/ale_linters/coffee/coffee.vim index f2539281..8e891639 100644 --- a/ale_linters/coffee/coffee.vim +++ b/ale_linters/coffee/coffee.vim @@ -16,8 +16,8 @@ endfunction call ale#linter#Define('coffee', { \ 'name': 'coffee', -\ 'executable_callback': 'ale_linters#coffee#coffee#GetExecutable', -\ 'command_callback': 'ale_linters#coffee#coffee#GetCommand', +\ 'executable': function('ale_linters#coffee#coffee#GetExecutable'), +\ 'command': function('ale_linters#coffee#coffee#GetCommand'), \ 'output_stream': 'stderr', \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/coffee/coffeelint.vim b/ale_linters/coffee/coffeelint.vim index 6d3df353..b7c85fa7 100644 --- a/ale_linters/coffee/coffeelint.vim +++ b/ale_linters/coffee/coffeelint.vim @@ -37,7 +37,7 @@ endfunction call ale#linter#Define('coffee', { \ 'name': 'coffeelint', -\ 'executable_callback': 'ale_linters#coffee#coffeelint#GetExecutable', -\ 'command_callback': 'ale_linters#coffee#coffeelint#GetCommand', +\ 'executable': function('ale_linters#coffee#coffeelint#GetExecutable'), +\ 'command': function('ale_linters#coffee#coffeelint#GetCommand'), \ 'callback': 'ale_linters#coffee#coffeelint#Handle', \}) diff --git a/ale_linters/cpp/ccls.vim b/ale_linters/cpp/ccls.vim index 501fd685..b265ff70 100644 --- a/ale_linters/cpp/ccls.vim +++ b/ale_linters/cpp/ccls.vim @@ -7,8 +7,8 @@ call ale#Set('cpp_ccls_init_options', {}) call ale#linter#Define('cpp', { \ 'name': 'ccls', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('cpp_ccls_executable'), +\ 'executable': {b -> ale#Var(b, 'cpp_ccls_executable')}, \ 'command': '%e', -\ 'project_root_callback': 'ale#handlers#ccls#GetProjectRoot', -\ 'initialization_options_callback': ale#VarFunc('cpp_ccls_init_options'), +\ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), +\ 'initialization_options': {b -> ale#Var(b, 'cpp_ccls_init_options')}, \}) diff --git a/ale_linters/cpp/clang.vim b/ale_linters/cpp/clang.vim index 649c5993..e48291eb 100644 --- a/ale_linters/cpp/clang.vim +++ b/ale_linters/cpp/clang.vim @@ -18,10 +18,7 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'clang', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('cpp_clang_executable'), -\ 'command_chain': [ -\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#cpp#clang#GetCommand'}, -\ ], +\ 'executable': {b -> ale#Var(b, 'cpp_clang_executable')}, +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#cpp#clang#GetCommand'))}, \ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', \}) diff --git a/ale_linters/cpp/clangcheck.vim b/ale_linters/cpp/clangcheck.vim index c66d6702..b511a413 100644 --- a/ale_linters/cpp/clangcheck.vim +++ b/ale_linters/cpp/clangcheck.vim @@ -27,8 +27,8 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'clangcheck', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('cpp_clangcheck_executable'), -\ 'command_callback': 'ale_linters#cpp#clangcheck#GetCommand', +\ 'executable': {b -> ale#Var(b, 'cpp_clangcheck_executable')}, +\ 'command': function('ale_linters#cpp#clangcheck#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/cpp/clangd.vim b/ale_linters/cpp/clangd.vim index 9139f054..4a8ff4f6 100644 --- a/ale_linters/cpp/clangd.vim +++ b/ale_linters/cpp/clangd.vim @@ -17,7 +17,7 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'clangd', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('cpp_clangd_executable'), -\ 'command_callback': 'ale_linters#cpp#clangd#GetCommand', -\ 'project_root_callback': 'ale_linters#cpp#clangd#GetProjectRoot', +\ 'executable': {b -> ale#Var(b, 'cpp_clangd_executable')}, +\ 'command': function('ale_linters#cpp#clangd#GetCommand'), +\ 'project_root': function('ale_linters#cpp#clangd#GetProjectRoot'), \}) diff --git a/ale_linters/cpp/clangtidy.vim b/ale_linters/cpp/clangtidy.vim index 9c3da8db..841b795f 100644 --- a/ale_linters/cpp/clangtidy.vim +++ b/ale_linters/cpp/clangtidy.vim @@ -29,8 +29,8 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'clangtidy', \ 'output_stream': 'stdout', -\ 'executable_callback': ale#VarFunc('cpp_clangtidy_executable'), -\ 'command_callback': 'ale_linters#cpp#clangtidy#GetCommand', +\ 'executable': {b -> ale#Var(b, 'cpp_clangtidy_executable')}, +\ 'command': function('ale_linters#cpp#clangtidy#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/cpp/clazy.vim b/ale_linters/cpp/clazy.vim index cbbd0ccf..9b29ac9a 100644 --- a/ale_linters/cpp/clazy.vim +++ b/ale_linters/cpp/clazy.vim @@ -25,8 +25,8 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'clazy', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('cpp_clazy_executable'), -\ 'command_callback': 'ale_linters#cpp#clazy#GetCommand', +\ 'executable': {b -> ale#Var(b, 'cpp_clazy_executable')}, +\ 'command': function('ale_linters#cpp#clazy#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/cpp/cppcheck.vim b/ale_linters/cpp/cppcheck.vim index 229d6133..5173d698 100644 --- a/ale_linters/cpp/cppcheck.vim +++ b/ale_linters/cpp/cppcheck.vim @@ -28,7 +28,7 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'cppcheck', \ 'output_stream': 'both', -\ 'executable_callback': ale#VarFunc('cpp_cppcheck_executable'), -\ 'command_callback': 'ale_linters#cpp#cppcheck#GetCommand', +\ 'executable': {b -> ale#Var(b, 'cpp_cppcheck_executable')}, +\ 'command': function('ale_linters#cpp#cppcheck#GetCommand'), \ 'callback': 'ale#handlers#cppcheck#HandleCppCheckFormat', \}) diff --git a/ale_linters/cpp/cpplint.vim b/ale_linters/cpp/cpplint.vim index d135fa79..f1f6ce7f 100644 --- a/ale_linters/cpp/cpplint.vim +++ b/ale_linters/cpp/cpplint.vim @@ -13,8 +13,8 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'cpplint', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('cpp_cpplint_executable'), -\ 'command_callback': 'ale_linters#cpp#cpplint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'cpp_cpplint_executable')}, +\ 'command': function('ale_linters#cpp#cpplint#GetCommand'), \ 'callback': 'ale#handlers#cpplint#HandleCppLintFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/cpp/cquery.vim b/ale_linters/cpp/cquery.vim index b1c81989..0dd9f6ad 100644 --- a/ale_linters/cpp/cquery.vim +++ b/ale_linters/cpp/cquery.vim @@ -21,8 +21,8 @@ endfunction call ale#linter#Define('cpp', { \ 'name': 'cquery', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('cpp_cquery_executable'), +\ 'executable': {b -> ale#Var(b, 'cpp_cquery_executable')}, \ 'command': '%e', -\ 'project_root_callback': 'ale_linters#cpp#cquery#GetProjectRoot', -\ 'initialization_options_callback': 'ale_linters#cpp#cquery#GetInitializationOptions', +\ 'project_root': function('ale_linters#cpp#cquery#GetProjectRoot'), +\ 'initialization_options': function('ale_linters#cpp#cquery#GetInitializationOptions'), \}) diff --git a/ale_linters/cpp/flawfinder.vim b/ale_linters/cpp/flawfinder.vim index 4f669bff..5bfdea22 100644 --- a/ale_linters/cpp/flawfinder.vim +++ b/ale_linters/cpp/flawfinder.vim @@ -7,19 +7,19 @@ call ale#Set('cpp_flawfinder_minlevel', 1) call ale#Set('c_flawfinder_error_severity', 6) 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') + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'cpp_flawfinder_minlevel') - return '%e -CDQS' - \ . ale#Var(a:buffer, 'cpp_flawfinder_options') - \ . l:minlevel - \ . ' %t' + return '%e -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#VarFunc('cpp_flawfinder_executable'), -\ 'command_callback': 'ale_linters#cpp#flawfinder#GetCommand', +\ 'executable': {b -> ale#Var(b, 'cpp_flawfinder_executable')}, +\ 'command': function('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 9935b0bb..c427020b 100644 --- a/ale_linters/cpp/gcc.vim +++ b/ale_linters/cpp/gcc.vim @@ -19,10 +19,7 @@ call ale#linter#Define('cpp', { \ 'name': 'gcc', \ 'aliases': ['g++'], \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('cpp_gcc_executable'), -\ 'command_chain': [ -\ {'callback': 'ale#c#GetMakeCommand', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#cpp#gcc#GetCommand'}, -\ ], +\ 'executable': {b -> ale#Var(b, 'cpp_gcc_executable')}, +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#cpp#gcc#GetCommand'))}, \ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', \}) diff --git a/ale_linters/crystal/ameba.vim b/ale_linters/crystal/ameba.vim new file mode 100644 index 00000000..5dfc7f45 --- /dev/null +++ b/ale_linters/crystal/ameba.vim @@ -0,0 +1,57 @@ +" Author: Harrison Bachrach - https://github.com/HarrisonB +" Description: Ameba, a linter for crystal files + +call ale#Set('crystal_ameba_executable', 'bin/ameba') + +function! ale_linters#crystal#ameba#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'crystal_ameba_executable') + + return ale#Escape(l:executable) + \ . ' --format json ' + \ . ale#Escape(expand('#' . a:buffer . ':p')) +endfunction + +" Handle output from ameba +function! ale_linters#crystal#ameba#HandleAmebaOutput(buffer, lines) abort + if len(a:lines) == 0 + return [] + endif + + let l:errors = ale#util#FuzzyJSONDecode(a:lines[0], {}) + + if !has_key(l:errors, 'summary') + \|| l:errors['summary']['issues_count'] == 0 + \|| empty(l:errors['sources']) + return [] + endif + + let l:output = [] + + for l:error in l:errors['sources'][0]['issues'] + let l:start_col = str2nr(l:error['location']['column']) + let l:end_col = str2nr(l:error['end_location']['column']) + + if !l:end_col + let l:end_col = l:start_col + 1 + endif + + call add(l:output, { + \ 'lnum': str2nr(l:error['location']['line']), + \ 'col': l:start_col, + \ 'end_col': l:end_col, + \ 'code': l:error['rule_name'], + \ 'text': l:error['message'], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('crystal', { +\ 'name': 'ameba', +\ 'executable': {b -> ale#Var(b, 'crystal_ameba_executable')}, +\ 'command': function('ale_linters#crystal#ameba#GetCommand'), +\ 'callback': 'ale_linters#crystal#ameba#HandleAmebaOutput', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/crystal/crystal.vim b/ale_linters/crystal/crystal.vim index 81579d63..3c2fefb7 100644 --- a/ale_linters/crystal/crystal.vim +++ b/ale_linters/crystal/crystal.vim @@ -26,6 +26,6 @@ call ale#linter#Define('crystal', { \ 'executable': 'crystal', \ 'output_stream': 'both', \ 'lint_file': 1, -\ 'command_callback': 'ale_linters#crystal#crystal#GetCommand', +\ 'command': function('ale_linters#crystal#crystal#GetCommand'), \ 'callback': 'ale_linters#crystal#crystal#Handle', \}) diff --git a/ale_linters/cs/mcs.vim b/ale_linters/cs/mcs.vim index 8738026d..1b373e73 100644 --- a/ale_linters/cs/mcs.vim +++ b/ale_linters/cs/mcs.vim @@ -32,6 +32,6 @@ call ale#linter#Define('cs',{ \ 'name': 'mcs', \ 'output_stream': 'stderr', \ 'executable': 'mcs', -\ 'command_callback': 'ale_linters#cs#mcs#GetCommand', +\ 'command': function('ale_linters#cs#mcs#GetCommand'), \ 'callback': 'ale_linters#cs#mcs#Handle', \}) diff --git a/ale_linters/cs/mcsc.vim b/ale_linters/cs/mcsc.vim index 1561661e..dd067eba 100644 --- a/ale_linters/cs/mcsc.vim +++ b/ale_linters/cs/mcsc.vim @@ -30,7 +30,7 @@ function! ale_linters#cs#mcsc#GetCommand(buffer) abort " register temporary module target file with ale " register temporary module target file with ALE. - let l:out = ale#engine#CreateFile(a:buffer) + let l:out = ale#command#CreateFile(a:buffer) " The code is compiled as a module and the output is redirected to a " temporary file. @@ -75,7 +75,7 @@ call ale#linter#Define('cs',{ \ 'name': 'mcsc', \ 'output_stream': 'stderr', \ 'executable': 'mcs', -\ 'command_callback': 'ale_linters#cs#mcsc#GetCommand', +\ 'command': function('ale_linters#cs#mcsc#GetCommand'), \ 'callback': 'ale_linters#cs#mcsc#Handle', \ 'lint_file': 1 \}) diff --git a/ale_linters/css/csslint.vim b/ale_linters/css/csslint.vim index 98b7fdd4..50c21ce2 100644 --- a/ale_linters/css/csslint.vim +++ b/ale_linters/css/csslint.vim @@ -13,6 +13,6 @@ endfunction call ale#linter#Define('css', { \ 'name': 'csslint', \ 'executable': 'csslint', -\ 'command_callback': 'ale_linters#css#csslint#GetCommand', +\ 'command': function('ale_linters#css#csslint#GetCommand'), \ 'callback': 'ale#handlers#css#HandleCSSLintFormat', \}) diff --git a/ale_linters/css/fecs.vim b/ale_linters/css/fecs.vim new file mode 100644 index 00000000..511847c6 --- /dev/null +++ b/ale_linters/css/fecs.vim @@ -0,0 +1,9 @@ +" Author: harttle <yangjvn@126.com> +" Description: fecs for CSS files + +call ale#linter#Define('css', { +\ 'name': 'fecs', +\ 'executable': function('ale#handlers#fecs#GetExecutable'), +\ 'command': function('ale#handlers#fecs#GetCommand'), +\ 'callback': 'ale#handlers#fecs#Handle', +\}) diff --git a/ale_linters/css/stylelint.vim b/ale_linters/css/stylelint.vim index 6f8bef68..38cb0e0b 100644 --- a/ale_linters/css/stylelint.vim +++ b/ale_linters/css/stylelint.vim @@ -11,9 +11,9 @@ endfunction call ale#linter#Define('css', { \ 'name': 'stylelint', -\ 'executable_callback': ale#node#FindExecutableFunc('css_stylelint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'css_stylelint', [ \ 'node_modules/.bin/stylelint', -\ ]), -\ 'command_callback': 'ale_linters#css#stylelint#GetCommand', +\ ])}, +\ 'command': function('ale_linters#css#stylelint#GetCommand'), \ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/cucumber/cucumber.vim b/ale_linters/cucumber/cucumber.vim index e8ae09ff..0cfd815e 100644 --- a/ale_linters/cucumber/cucumber.vim +++ b/ale_linters/cucumber/cucumber.vim @@ -41,6 +41,6 @@ endfunction call ale#linter#Define('cucumber', { \ 'name': 'cucumber', \ 'executable': 'cucumber', -\ 'command_callback': 'ale_linters#cucumber#cucumber#GetCommand', +\ 'command': function('ale_linters#cucumber#cucumber#GetCommand'), \ 'callback': 'ale_linters#cucumber#cucumber#Handle' \}) diff --git a/ale_linters/cuda/nvcc.vim b/ale_linters/cuda/nvcc.vim index f4442cb8..f3af07b6 100644 --- a/ale_linters/cuda/nvcc.vim +++ b/ale_linters/cuda/nvcc.vim @@ -7,7 +7,7 @@ call ale#Set('cuda_nvcc_options', '-std=c++11') function! ale_linters#cuda#nvcc#GetCommand(buffer) abort " Unused: use ale#util#nul_file " let l:output_file = ale#util#Tempname() . '.ii' - " call ale#engine#ManageFile(a:buffer, l:output_file) + " call ale#command#ManageFile(a:buffer, l:output_file) return '%e -cuda' \ . ale#Pad(ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer))) \ . ale#Pad(ale#Var(a:buffer, 'cuda_nvcc_options')) @@ -42,8 +42,8 @@ endfunction call ale#linter#Define('cuda', { \ 'name': 'nvcc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('cuda_nvcc_executable'), -\ 'command_callback': 'ale_linters#cuda#nvcc#GetCommand', +\ 'executable': {b -> ale#Var(b, 'cuda_nvcc_executable')}, +\ 'command': function('ale_linters#cuda#nvcc#GetCommand'), \ 'callback': 'ale_linters#cuda#nvcc#HandleNVCCFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/cypher/cypher_lint.vim b/ale_linters/cypher/cypher_lint.vim new file mode 100644 index 00000000..408ddd6e --- /dev/null +++ b/ale_linters/cypher/cypher_lint.vim @@ -0,0 +1,26 @@ +" Author: Francisco Lopes <francisco@oblita.com> +" Description: Linting for Neo4j's Cypher + +function! ale_linters#cypher#cypher_lint#Handle(buffer, lines) abort + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\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, + \ 'text': l:match[4], + \ 'type': 'E', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('cypher', { +\ 'name': 'cypher_lint', +\ 'executable': 'cypher-lint', +\ 'command': 'cypher-lint', +\ 'output_stream': 'stderr', +\ 'callback': 'ale_linters#cypher#cypher_lint#Handle', +\}) diff --git a/ale_linters/d/dls.vim b/ale_linters/d/dls.vim index 7210d21e..78d1c152 100644 --- a/ale_linters/d/dls.vim +++ b/ale_linters/d/dls.vim @@ -16,7 +16,7 @@ endfunction call ale#linter#Define('d', { \ 'name': 'dls', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#d#dls#GetExecutable', -\ 'command_callback': 'ale_linters#d#dls#GetExecutable', -\ 'project_root_callback': 'ale_linters#d#dls#FindProjectRoot', +\ 'executable': function('ale_linters#d#dls#GetExecutable'), +\ 'command': function('ale_linters#d#dls#GetExecutable'), +\ 'project_root': function('ale_linters#d#dls#FindProjectRoot'), \}) diff --git a/ale_linters/d/dmd.vim b/ale_linters/d/dmd.vim index c816d592..14461ae6 100644 --- a/ale_linters/d/dmd.vim +++ b/ale_linters/d/dmd.vim @@ -1,7 +1,7 @@ " Author: w0rp <devw0rp@gmail.com> " Description: "dmd for D files" -function! ale_linters#d#dmd#DUBCommand(buffer) abort +function! ale_linters#d#dmd#GetDUBCommand(buffer) abort " If we can't run dub, then skip this command. if !executable('dub') " Returning an empty string skips to the DMD command. @@ -21,7 +21,18 @@ function! ale_linters#d#dmd#DUBCommand(buffer) abort \ . ' && dub describe --import-paths' endfunction -function! ale_linters#d#dmd#DMDCommand(buffer, dub_output) abort +function! ale_linters#d#dmd#RunDUBCommand(buffer) abort + let l:command = ale_linters#d#dmd#GetDUBCommand(a:buffer) + + if empty(l:command) + " If we can't run DUB, just run DMD. + return ale_linters#d#dmd#DMDCommand(a:buffer, [], {}) + endif + + return ale#command#Run(a:buffer, l:command, function('ale_linters#d#dmd#DMDCommand')) +endfunction + +function! ale_linters#d#dmd#DMDCommand(buffer, dub_output, meta) abort let l:import_list = [] " Build a list of import paths generated from DUB, if available. @@ -57,9 +68,7 @@ endfunction call ale#linter#Define('d', { \ 'name': 'dmd', \ 'executable': 'dmd', -\ 'command_chain': [ -\ {'callback': 'ale_linters#d#dmd#DUBCommand', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#d#dmd#DMDCommand', 'output_stream': 'stderr'}, -\ ], +\ 'command': function('ale_linters#d#dmd#RunDUBCommand'), \ 'callback': 'ale_linters#d#dmd#Handle', +\ 'output_stream': 'stderr', \}) diff --git a/ale_linters/dart/dartanalyzer.vim b/ale_linters/dart/dartanalyzer.vim index 26817df5..0a4d9742 100644 --- a/ale_linters/dart/dartanalyzer.vim +++ b/ale_linters/dart/dartanalyzer.vim @@ -29,8 +29,8 @@ endfunction call ale#linter#Define('dart', { \ 'name': 'dartanalyzer', -\ 'executable_callback': ale#VarFunc('dart_dartanalyzer_executable'), -\ 'command_callback': 'ale_linters#dart#dartanalyzer#GetCommand', +\ 'executable': {b -> ale#Var(b, 'dart_dartanalyzer_executable')}, +\ 'command': function('ale_linters#dart#dartanalyzer#GetCommand'), \ 'callback': 'ale_linters#dart#dartanalyzer#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/dart/language_server.vim b/ale_linters/dart/language_server.vim index 8e0c139b..d0e639c8 100644 --- a/ale_linters/dart/language_server.vim +++ b/ale_linters/dart/language_server.vim @@ -14,7 +14,7 @@ endfunction call ale#linter#Define('dart', { \ 'name': 'language_server', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('dart_language_server_executable'), +\ 'executable': {b -> ale#Var(b, 'dart_language_server_executable')}, \ 'command': '%e', -\ 'project_root_callback': 'ale_linters#dart#language_server#GetProjectRoot', +\ 'project_root': function('ale_linters#dart#language_server#GetProjectRoot'), \}) diff --git a/ale_linters/dockerfile/dockerfile_lint.vim b/ale_linters/dockerfile/dockerfile_lint.vim index a5a846f2..95768b12 100644 --- a/ale_linters/dockerfile/dockerfile_lint.vim +++ b/ale_linters/dockerfile/dockerfile_lint.vim @@ -55,7 +55,7 @@ endfunction call ale#linter#Define('dockerfile', { \ 'name': 'dockerfile_lint', -\ 'executable_callback': ale#VarFunc('dockerfile_dockerfile_lint_executable'), -\ 'command_callback': 'ale_linters#dockerfile#dockerfile_lint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'dockerfile_dockerfile_lint_executable')}, +\ 'command': function('ale_linters#dockerfile#dockerfile_lint#GetCommand'), \ 'callback': 'ale_linters#dockerfile#dockerfile_lint#Handle', \}) diff --git a/ale_linters/dockerfile/hadolint.vim b/ale_linters/dockerfile/hadolint.vim index dc0f5b9e..e57cd76d 100644 --- a/ale_linters/dockerfile/hadolint.vim +++ b/ale_linters/dockerfile/hadolint.vim @@ -93,7 +93,7 @@ endfunction call ale#linter#Define('dockerfile', { \ 'name': 'hadolint', -\ 'executable_callback': 'ale_linters#dockerfile#hadolint#GetExecutable', -\ 'command_callback': 'ale_linters#dockerfile#hadolint#GetCommand', +\ 'executable': function('ale_linters#dockerfile#hadolint#GetExecutable'), +\ 'command': function('ale_linters#dockerfile#hadolint#GetCommand'), \ 'callback': 'ale_linters#dockerfile#hadolint#Handle', \}) diff --git a/ale_linters/elixir/credo.vim b/ale_linters/elixir/credo.vim index d778471c..317ecab3 100644 --- a/ale_linters/elixir/credo.vim +++ b/ale_linters/elixir/credo.vim @@ -37,16 +37,27 @@ function! ale_linters#elixir#credo#Handle(buffer, lines) abort return l:output endfunction +function! ale_linters#elixir#credo#GetMode() abort + if get(g:, 'ale_elixir_credo_strict', 0) + return '--strict' + else + return 'suggest' + endif +endfunction + function! ale_linters#elixir#credo#GetCommand(buffer) abort let l:project_root = ale#handlers#elixir#FindMixProjectRoot(a:buffer) + let l:mode = ale_linters#elixir#credo#GetMode() return ale#path#CdString(l:project_root) - \ . ' mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' + \ . 'mix help credo && ' + \ . 'mix credo ' . ale_linters#elixir#credo#GetMode() + \ . ' --format=flycheck --read-from-stdin %s' endfunction call ale#linter#Define('elixir', { \ 'name': 'credo', \ 'executable': 'mix', -\ 'command_callback': 'ale_linters#elixir#credo#GetCommand', +\ 'command': function('ale_linters#elixir#credo#GetCommand'), \ 'callback': 'ale_linters#elixir#credo#Handle', \}) diff --git a/ale_linters/elixir/dialyxir.vim b/ale_linters/elixir/dialyxir.vim index d28d3c70..c7da7757 100644 --- a/ale_linters/elixir/dialyxir.vim +++ b/ale_linters/elixir/dialyxir.vim @@ -35,7 +35,7 @@ endfunction call ale#linter#Define('elixir', { \ 'name': 'dialyxir', \ 'executable': 'mix', -\ 'command_callback': 'ale_linters#elixir#dialyxir#GetCommand', +\ 'command': function('ale_linters#elixir#dialyxir#GetCommand'), \ 'callback': 'ale_linters#elixir#dialyxir#Handle', \}) diff --git a/ale_linters/elixir/dogma.vim b/ale_linters/elixir/dogma.vim index dcfb6f28..1c721158 100644 --- a/ale_linters/elixir/dogma.vim +++ b/ale_linters/elixir/dogma.vim @@ -39,7 +39,7 @@ endfunction call ale#linter#Define('elixir', { \ 'name': 'dogma', \ 'executable': 'mix', -\ 'command_callback': 'ale_linters#elixir#dogma#GetCommand', +\ 'command': function('ale_linters#elixir#dogma#GetCommand'), \ 'lint_file': 1, \ 'callback': 'ale_linters#elixir#dogma#Handle', \}) diff --git a/ale_linters/elixir/elixir_ls.vim b/ale_linters/elixir/elixir_ls.vim index 3b299ec6..d5517de5 100644 --- a/ale_linters/elixir/elixir_ls.vim +++ b/ale_linters/elixir/elixir_ls.vim @@ -6,7 +6,7 @@ call ale#Set('elixir_elixir_ls_config', {}) function! ale_linters#elixir#elixir_ls#GetExecutable(buffer) abort let l:dir = ale#path#Simplify(ale#Var(a:buffer, 'elixir_elixir_ls_release')) - let l:cmd = ale#Has('win32') ? '\language_server.bat' : '/language_server.sh' + let l:cmd = has('win32') ? '\language_server.bat' : '/language_server.sh' return l:dir . l:cmd endfunction @@ -14,8 +14,8 @@ endfunction call ale#linter#Define('elixir', { \ 'name': 'elixir-ls', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#elixir#elixir_ls#GetExecutable', -\ 'command_callback': 'ale_linters#elixir#elixir_ls#GetExecutable', -\ 'project_root_callback': 'ale#handlers#elixir#FindMixUmbrellaRoot', -\ 'lsp_config_callback': ale#VarFunc('elixir_elixir_ls_config'), +\ 'executable': function('ale_linters#elixir#elixir_ls#GetExecutable'), +\ 'command': function('ale_linters#elixir#elixir_ls#GetExecutable'), +\ 'project_root': function('ale#handlers#elixir#FindMixUmbrellaRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'elixir_elixir_ls_config')}, \}) diff --git a/ale_linters/elixir/mix.vim b/ale_linters/elixir/mix.vim index dc3c1818..abf5d0aa 100644 --- a/ale_linters/elixir/mix.vim +++ b/ale_linters/elixir/mix.vim @@ -32,7 +32,7 @@ endfunction function! ale_linters#elixir#mix#GetCommand(buffer) abort let l:project_root = ale#handlers#elixir#FindMixProjectRoot(a:buffer) - let l:temp_dir = ale#engine#CreateDirectory(a:buffer) + let l:temp_dir = ale#command#CreateDirectory(a:buffer) let l:mix_build_path = has('win32') \ ? 'set MIX_BUILD_PATH=' . ale#Escape(l:temp_dir) . ' &&' @@ -46,7 +46,7 @@ endfunction call ale#linter#Define('elixir', { \ 'name': 'mix', \ 'executable': 'mix', -\ 'command_callback': 'ale_linters#elixir#mix#GetCommand', +\ 'command': function('ale_linters#elixir#mix#GetCommand'), \ 'callback': 'ale_linters#elixir#mix#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/elm/elm_lsp.vim b/ale_linters/elm/elm_lsp.vim new file mode 100644 index 00000000..2259286f --- /dev/null +++ b/ale_linters/elm/elm_lsp.vim @@ -0,0 +1,22 @@ +" Author: antew - https://github.com/antew +" Description: LSP integration for elm, currently supports diagnostics (linting) + +call ale#Set('elm_lsp_executable', 'elm-lsp') +call ale#Set('elm_lsp_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! elm_lsp#GetRootDir(buffer) abort + let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json') + + return !empty(l:elm_json) ? fnamemodify(l:elm_json, ':p:h') : '' +endfunction + +call ale#linter#Define('elm', { +\ 'name': 'elm_lsp', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#node#FindExecutable(b, 'elm_lsp', [ +\ 'node_modules/.bin/elm-lsp', +\ ])}, +\ 'command': '%e --stdio', +\ 'project_root': function('elm_lsp#GetRootDir'), +\ 'language': 'elm' +\}) diff --git a/ale_linters/elm/make.vim b/ale_linters/elm/make.vim index ddea983f..6b93257f 100644 --- a/ale_linters/elm/make.vim +++ b/ale_linters/elm/make.vim @@ -137,9 +137,7 @@ function! ale_linters#elm#make#ParseMessageItem(item) abort 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 +function! ale_linters#elm#make#GetPackageFile(buffer) abort let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json') if empty(l:elm_json) @@ -147,26 +145,96 @@ function! ale_linters#elm#make#GetCommand(buffer) abort let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm-package.json') endif + return l:elm_json +endfunction + +function! ale_linters#elm#make#IsVersionGte19(buffer) abort + let l:elm_json = ale_linters#elm#make#GetPackageFile(a:buffer) + + if l:elm_json =~# '-package' + return 0 + else + return 1 + endif +endfunction + +function! ale_linters#elm#make#GetRootDir(buffer) abort + let l:elm_json = ale_linters#elm#make#GetPackageFile(a:buffer) + if empty(l:elm_json) + return '' + else + return fnamemodify(l:elm_json, ':p:h') + endif +endfunction + +function! ale_linters#elm#make#IsTest(buffer) abort + let l:root_dir = ale_linters#elm#make#GetRootDir(a:buffer) + + if empty(l:root_dir) + return 0 + endif + + let l:tests_dir = join([l:root_dir, 'tests', ''], has('win32') ? '\' : '/') + + let l:buffer_path = fnamemodify(bufname(a:buffer), ':p') + + if stridx(l:buffer_path, l:tests_dir) == 0 + return 1 + else + return 0 + 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:executable = ale_linters#elm#make#GetExecutable(a:buffer) + let l:root_dir = ale_linters#elm#make#GetRootDir(a:buffer) + let l:is_v19 = ale_linters#elm#make#IsVersionGte19(a:buffer) + let l:is_using_elm_test = l:executable =~# 'elm-test$' + + if empty(l:root_dir) let l:dir_set_cmd = '' else - let l:root_dir = fnamemodify(l:elm_json, ':p:h') let l:dir_set_cmd = 'cd ' . ale#Escape(l:root_dir) . ' && ' endif + " elm-test needs to know the path of elm-make if elm isn't installed globally. + " https://github.com/rtfeldman/node-test-runner/blob/57728f10668f2d2ab3179e7e3208bcfa9a1f19aa/README.md#--compiler + if l:is_v19 && l:is_using_elm_test + let l:elm_make_executable = ale#node#FindExecutable(a:buffer, 'elm_make', ['node_modules/.bin/elm']) + let l:elm_test_compiler_flag = ' --compiler ' . l:elm_make_executable . ' ' + else + let l:elm_test_compiler_flag = ' ' + endif + " 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. " Source: https://github.com/elm-lang/elm-compiler/blob/19d5a769b30ec0b2fc4475985abb4cd94cd1d6c3/builder/src/Generate/Output.hs#L253 - return l:dir_set_cmd . '%e make --report=json --output=/dev/null %t' + return l:dir_set_cmd . '%e make --report=json --output=/dev/null' . l:elm_test_compiler_flag . '%t' +endfunction + +function! ale_linters#elm#make#GetExecutable(buffer) abort + let l:is_test = ale_linters#elm#make#IsTest(a:buffer) + let l:is_v19 = ale_linters#elm#make#IsVersionGte19(a:buffer) + + if l:is_test && l:is_v19 + return ale#node#FindExecutable( + \ a:buffer, + \ 'elm_make', + \ ['node_modules/.bin/elm-test', 'node_modules/.bin/elm'] + \) + else + return ale#node#FindExecutable(a:buffer, 'elm_make', ['node_modules/.bin/elm']) + endif endfunction call ale#linter#Define('elm', { \ 'name': 'make', -\ 'executable_callback': ale#node#FindExecutableFunc('elm_make', [ -\ 'node_modules/.bin/elm', -\ ]), +\ 'executable': function('ale_linters#elm#make#GetExecutable'), \ 'output_stream': 'both', -\ 'command_callback': 'ale_linters#elm#make#GetCommand', +\ 'command': function('ale_linters#elm#make#GetCommand'), \ 'callback': 'ale_linters#elm#make#Handle' \}) diff --git a/ale_linters/erlang/erlc.vim b/ale_linters/erlang/erlc.vim index 0bdb4dea..a83bacc3 100644 --- a/ale_linters/erlang/erlc.vim +++ b/ale_linters/erlang/erlc.vim @@ -4,7 +4,7 @@ let g:ale_erlang_erlc_options = get(g:, 'ale_erlang_erlc_options', '') function! ale_linters#erlang#erlc#GetCommand(buffer) abort let l:output_file = ale#util#Tempname() - call ale#engine#ManageFile(a:buffer, l:output_file) + call ale#command#ManageFile(a:buffer, l:output_file) return 'erlc -o ' . ale#Escape(l:output_file) \ . ' ' . ale#Var(a:buffer, 'erlang_erlc_options') @@ -91,6 +91,6 @@ endfunction call ale#linter#Define('erlang', { \ 'name': 'erlc', \ 'executable': 'erlc', -\ 'command_callback': 'ale_linters#erlang#erlc#GetCommand', +\ 'command': function('ale_linters#erlang#erlc#GetCommand'), \ 'callback': 'ale_linters#erlang#erlc#Handle', \}) diff --git a/ale_linters/erlang/syntaxerl.vim b/ale_linters/erlang/syntaxerl.vim index 5b679743..5d555a8d 100644 --- a/ale_linters/erlang/syntaxerl.vim +++ b/ale_linters/erlang/syntaxerl.vim @@ -3,7 +3,17 @@ call ale#Set('erlang_syntaxerl_executable', 'syntaxerl') -function! ale_linters#erlang#syntaxerl#GetCommand(buffer, output) abort +function! ale_linters#erlang#syntaxerl#RunHelpCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'erlang_syntaxerl_executable') + + return ale#command#Run( + \ a:buffer, + \ ale#Escape(l:executable) . ' -h', + \ function('ale_linters#erlang#syntaxerl#GetCommand'), + \) +endfunction + +function! ale_linters#erlang#syntaxerl#GetCommand(buffer, output, meta) abort let l:use_b_option = match(a:output, '\C\V-b, --base\>') > -1 return '%e' . (l:use_b_option ? ' -b %s %t' : ' %t') @@ -26,10 +36,7 @@ endfunction call ale#linter#Define('erlang', { \ 'name': 'syntaxerl', -\ 'executable_callback': ale#VarFunc('erlang_syntaxerl_executable'), -\ 'command_chain': [ -\ {'callback': {-> '%e -h'}}, -\ {'callback': 'ale_linters#erlang#syntaxerl#GetCommand'}, -\ ], +\ 'executable': {b -> ale#Var(b, 'erlang_syntaxerl_executable')}, +\ 'command': {b -> ale_linters#erlang#syntaxerl#RunHelpCommand(b)}, \ 'callback': 'ale_linters#erlang#syntaxerl#Handle', \}) diff --git a/ale_linters/eruby/erb.vim b/ale_linters/eruby/erb.vim index 61d97032..f3438320 100644 --- a/ale_linters/eruby/erb.vim +++ b/ale_linters/eruby/erb.vim @@ -19,7 +19,7 @@ call ale#linter#Define('eruby', { \ 'aliases': ['erubylint'], \ 'executable': 'erb', \ 'output_stream': 'stderr', -\ 'command_callback': 'ale_linters#eruby#erb#GetCommand', +\ 'command': function('ale_linters#eruby#erb#GetCommand'), \ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', \}) diff --git a/ale_linters/eruby/erubi.vim b/ale_linters/eruby/erubi.vim index 6f2d3ac6..ddca3f61 100644 --- a/ale_linters/eruby/erubi.vim +++ b/ale_linters/eruby/erubi.vim @@ -1,14 +1,10 @@ " Author: Eddie Lebow https://github.com/elebow " Description: eruby checker using `erubi` -function! ale_linters#eruby#erubi#CheckErubi(buffer) abort - return 'ruby -r erubi/capture_end -e ' . ale#Escape('""') -endfunction - -function! ale_linters#eruby#erubi#GetCommand(buffer, check_erubi_output) abort +function! ale_linters#eruby#erubi#GetCommand(buffer, output, meta) abort let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) - if (!empty(a:check_erubi_output)) + if !empty(a:output) " The empty command in CheckErubi returns nothing if erubi runs and " emits an error if erubi is not present return '' @@ -27,9 +23,10 @@ endfunction call ale#linter#Define('eruby', { \ 'name': 'erubi', \ 'executable': 'ruby', -\ 'command_chain': [ -\ {'callback': 'ale_linters#eruby#erubi#CheckErubi'}, -\ {'callback': 'ale_linters#eruby#erubi#GetCommand', 'output_stream': 'stderr'}, -\ ], +\ 'command': {buffer -> ale#command#Run( +\ buffer, +\ 'ruby -r erubi/capture_end -e ' . ale#Escape('""'), +\ function('ale_linters#eruby#erubi#GetCommand'), +\ )}, \ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', \}) diff --git a/ale_linters/eruby/erubis.vim b/ale_linters/eruby/erubis.vim index 1ebd4a05..755c5803 100644 --- a/ale_linters/eruby/erubis.vim +++ b/ale_linters/eruby/erubis.vim @@ -18,6 +18,6 @@ call ale#linter#Define('eruby', { \ 'name': 'erubis', \ 'executable': 'erubis', \ 'output_stream': 'stderr', -\ 'command_callback': 'ale_linters#eruby#erubis#GetCommand', +\ 'command': function('ale_linters#eruby#erubis#GetCommand'), \ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', \}) diff --git a/ale_linters/eruby/ruumba.vim b/ale_linters/eruby/ruumba.vim index 24f112e4..e68bb51d 100644 --- a/ale_linters/eruby/ruumba.vim +++ b/ale_linters/eruby/ruumba.vim @@ -56,7 +56,7 @@ endfunction call ale#linter#Define('eruby', { \ 'name': 'ruumba', -\ 'executable_callback': ale#VarFunc('eruby_ruumba_executable'), -\ 'command_callback': 'ale_linters#eruby#ruumba#GetCommand', +\ 'executable': {b -> ale#Var(b, 'eruby_ruumba_executable')}, +\ 'command': function('ale_linters#eruby#ruumba#GetCommand'), \ 'callback': 'ale_linters#eruby#ruumba#Handle', \}) diff --git a/ale_linters/fortran/gcc.vim b/ale_linters/fortran/gcc.vim index f1595789..6e97d6fd 100644 --- a/ale_linters/fortran/gcc.vim +++ b/ale_linters/fortran/gcc.vim @@ -66,7 +66,7 @@ endfunction call ale#linter#Define('fortran', { \ 'name': 'gcc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('fortran_gcc_executable'), -\ 'command_callback': 'ale_linters#fortran#gcc#GetCommand', +\ 'executable': {b -> ale#Var(b, 'fortran_gcc_executable')}, +\ 'command': function('ale_linters#fortran#gcc#GetCommand'), \ 'callback': 'ale_linters#fortran#gcc#Handle', \}) diff --git a/ale_linters/fortran/language_server.vim b/ale_linters/fortran/language_server.vim index 4e5f5dc4..00aa0577 100644 --- a/ale_linters/fortran/language_server.vim +++ b/ale_linters/fortran/language_server.vim @@ -13,7 +13,7 @@ endfunction call ale#linter#Define('fortran', { \ 'name': 'language_server', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('fortran_language_server_executable'), +\ 'executable': {b -> ale#Var(b, 'fortran_language_server_executable')}, \ 'command': '%e', -\ 'project_root_callback': 'ale_linters#fortran#language_server#GetProjectRoot', +\ 'project_root': function('ale_linters#fortran#language_server#GetProjectRoot'), \}) diff --git a/ale_linters/fuse/fusionlint.vim b/ale_linters/fuse/fusionlint.vim index ab8f143b..ffb25d33 100644 --- a/ale_linters/fuse/fusionlint.vim +++ b/ale_linters/fuse/fusionlint.vim @@ -27,7 +27,7 @@ endfunction call ale#linter#Define('fuse', { \ 'name': 'fusionlint', -\ 'executable_callback': ale#VarFunc('fuse_fusionlint_executable'), -\ 'command_callback': 'ale_linters#fuse#fusionlint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'fuse_fusionlint_executable')}, +\ 'command': function('ale_linters#fuse#fusionlint#GetCommand'), \ 'callback': 'ale_linters#fuse#fusionlint#Handle', \}) diff --git a/ale_linters/gitcommit/gitlint.vim b/ale_linters/gitcommit/gitlint.vim index a9c4822d..4b9cec63 100644 --- a/ale_linters/gitcommit/gitlint.vim +++ b/ale_linters/gitcommit/gitlint.vim @@ -45,7 +45,7 @@ endfunction call ale#linter#Define('gitcommit', { \ 'name': 'gitlint', \ 'output_stream': 'stderr', -\ 'executable_callback': 'ale_linters#gitcommit#gitlint#GetExecutable', -\ 'command_callback': 'ale_linters#gitcommit#gitlint#GetCommand', +\ 'executable': function('ale_linters#gitcommit#gitlint#GetExecutable'), +\ 'command': function('ale_linters#gitcommit#gitlint#GetCommand'), \ 'callback': 'ale_linters#gitcommit#gitlint#Handle', \}) diff --git a/ale_linters/glsl/glslang.vim b/ale_linters/glsl/glslang.vim index d55a5e7c..bbddce90 100644 --- a/ale_linters/glsl/glslang.vim +++ b/ale_linters/glsl/glslang.vim @@ -34,7 +34,7 @@ endfunction call ale#linter#Define('glsl', { \ 'name': 'glslang', -\ 'executable_callback': ale#VarFunc('glsl_glslang_executable'), -\ 'command_callback': 'ale_linters#glsl#glslang#GetCommand', +\ 'executable': {b -> ale#Var(b, 'glsl_glslang_executable')}, +\ 'command': function('ale_linters#glsl#glslang#GetCommand'), \ 'callback': 'ale_linters#glsl#glslang#Handle', \}) diff --git a/ale_linters/glsl/glslls.vim b/ale_linters/glsl/glslls.vim index 8c6d9bd9..b62844c7 100644 --- a/ale_linters/glsl/glslls.vim +++ b/ale_linters/glsl/glslls.vim @@ -24,7 +24,7 @@ endfunction call ale#linter#Define('glsl', { \ 'name': 'glslls', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('glsl_glslls_executable'), -\ 'command_callback': 'ale_linters#glsl#glslls#GetCommand', -\ 'project_root_callback': 'ale_linters#glsl#glslls#GetProjectRoot', +\ 'executable': {b -> ale#Var(b, 'glsl_glslls_executable')}, +\ 'command': function('ale_linters#glsl#glslls#GetCommand'), +\ 'project_root': function('ale_linters#glsl#glslls#GetProjectRoot'), \}) diff --git a/ale_linters/go/bingo.vim b/ale_linters/go/bingo.vim new file mode 100644 index 00000000..e446bdcc --- /dev/null +++ b/ale_linters/go/bingo.vim @@ -0,0 +1,29 @@ +" Author: Jerko Steiner <https://github.com/jeremija> +" Description: https://github.com/saibing/bingo + +call ale#Set('go_bingo_executable', 'bingo') +call ale#Set('go_bingo_options', '--mode stdio') + +function! ale_linters#go#bingo#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'go_bingo_options')) +endfunction + +function! ale_linters#go#bingo#FindProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile(a:buffer, 'go.mod') + let l:mods = ':h' + + if empty(l:project_root) + let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git') + let l:mods = ':h:h' + endif + + return !empty(l:project_root) ? fnamemodify(l:project_root, l:mods) : '' +endfunction + +call ale#linter#Define('go', { +\ 'name': 'bingo', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'go_bingo_executable')}, +\ 'command': function('ale_linters#go#bingo#GetCommand'), +\ 'project_root': function('ale_linters#go#bingo#FindProjectRoot'), +\}) diff --git a/ale_linters/go/gobuild.vim b/ale_linters/go/gobuild.vim index cef1ff88..374ded35 100644 --- a/ale_linters/go/gobuild.vim +++ b/ale_linters/go/gobuild.vim @@ -48,8 +48,8 @@ endfunction call ale#linter#Define('go', { \ 'name': 'gobuild', \ 'aliases': ['go build'], -\ 'executable_callback': ale#VarFunc('go_go_executable'), -\ 'command_callback': 'ale_linters#go#gobuild#GetCommand', +\ 'executable': {b -> ale#Var(b, 'go_go_executable')}, +\ 'command': function('ale_linters#go#gobuild#GetCommand'), \ 'output_stream': 'stderr', \ 'callback': 'ale_linters#go#gobuild#Handler', \ 'lint_file': 1, diff --git a/ale_linters/go/golangci_lint.vim b/ale_linters/go/golangci_lint.vim index dd9a3c64..357f7949 100644 --- a/ale_linters/go/golangci_lint.vim +++ b/ale_linters/go/golangci_lint.vim @@ -49,8 +49,8 @@ endfunction call ale#linter#Define('go', { \ 'name': 'golangci-lint', -\ 'executable_callback': ale#VarFunc('go_golangci_lint_executable'), -\ 'command_callback': 'ale_linters#go#golangci_lint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'go_golangci_lint_executable')}, +\ 'command': function('ale_linters#go#golangci_lint#GetCommand'), \ 'callback': 'ale_linters#go#golangci_lint#Handler', \ 'lint_file': 1, \}) diff --git a/ale_linters/go/golint.vim b/ale_linters/go/golint.vim index 4bf14fcc..765e1477 100644 --- a/ale_linters/go/golint.vim +++ b/ale_linters/go/golint.vim @@ -15,7 +15,7 @@ endfunction call ale#linter#Define('go', { \ 'name': 'golint', \ 'output_stream': 'both', -\ 'executable_callback': ale#VarFunc('go_golint_executable'), -\ 'command_callback': 'ale_linters#go#golint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'go_golint_executable')}, +\ 'command': function('ale_linters#go#golint#GetCommand'), \ 'callback': 'ale#handlers#unix#HandleAsWarning', \}) diff --git a/ale_linters/go/gometalinter.vim b/ale_linters/go/gometalinter.vim index d005e1d2..19d70a81 100644 --- a/ale_linters/go/gometalinter.vim +++ b/ale_linters/go/gometalinter.vim @@ -50,8 +50,8 @@ endfunction call ale#linter#Define('go', { \ 'name': 'gometalinter', -\ 'executable_callback': ale#VarFunc('go_gometalinter_executable'), -\ 'command_callback': 'ale_linters#go#gometalinter#GetCommand', +\ 'executable': {b -> ale#Var(b, 'go_gometalinter_executable')}, +\ 'command': function('ale_linters#go#gometalinter#GetCommand'), \ 'callback': 'ale_linters#go#gometalinter#Handler', \ 'lint_file': 1, \}) diff --git a/ale_linters/go/gopls.vim b/ale_linters/go/gopls.vim new file mode 100644 index 00000000..c411dc2b --- /dev/null +++ b/ale_linters/go/gopls.vim @@ -0,0 +1,30 @@ +" Author: w0rp <devw0rp@gmail.com> +" Author: Jerko Steiner <https://github.com/jeremija> +" Description: https://github.com/saibing/gopls + +call ale#Set('go_gopls_executable', 'gopls') +call ale#Set('go_gopls_options', '--mode stdio') + +function! ale_linters#go#gopls#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'go_gopls_options')) +endfunction + +function! ale_linters#go#gopls#FindProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile(a:buffer, 'go.mod') + let l:mods = ':h' + + if empty(l:project_root) + let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git') + let l:mods = ':h:h' + endif + + return !empty(l:project_root) ? fnamemodify(l:project_root, l:mods) : '' +endfunction + +call ale#linter#Define('go', { +\ 'name': 'gopls', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'go_gopls_executable')}, +\ 'command': function('ale_linters#go#gopls#GetCommand'), +\ 'project_root': function('ale_linters#go#gopls#FindProjectRoot'), +\}) diff --git a/ale_linters/go/gosimple.vim b/ale_linters/go/gosimple.vim index dbdc3fcf..281a0e53 100644 --- a/ale_linters/go/gosimple.vim +++ b/ale_linters/go/gosimple.vim @@ -8,7 +8,7 @@ endfunction call ale#linter#Define('go', { \ 'name': 'gosimple', \ 'executable': 'gosimple', -\ 'command_callback': 'ale_linters#go#gosimple#GetCommand', +\ 'command': function('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 a678e0f3..d5d563aa 100644 --- a/ale_linters/go/gotype.vim +++ b/ale_linters/go/gotype.vim @@ -6,14 +6,14 @@ function! ale_linters#go#gotype#GetCommand(buffer) abort return '' endif - return ale#path#BufferCdString(a:buffer) . ' gotype .' + return ale#path#BufferCdString(a:buffer) . ' gotype -e .' endfunction call ale#linter#Define('go', { \ 'name': 'gotype', \ 'output_stream': 'stderr', \ 'executable': 'gotype', -\ 'command_callback': 'ale_linters#go#gotype#GetCommand', +\ 'command': function('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 3d0d2adf..bb81d5d0 100644 --- a/ale_linters/go/govet.vim +++ b/ale_linters/go/govet.vim @@ -20,8 +20,8 @@ call ale#linter#Define('go', { \ 'name': 'govet', \ 'aliases': ['go vet'], \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('go_go_executable'), -\ 'command_callback': 'ale_linters#go#govet#GetCommand', +\ 'executable': {b -> ale#Var(b, 'go_go_executable')}, +\ 'command': function('ale_linters#go#govet#GetCommand'), \ 'callback': 'ale#handlers#go#Handler', \ 'lint_file': 1, \}) diff --git a/ale_linters/go/langserver.vim b/ale_linters/go/langserver.vim index df956483..776186c7 100644 --- a/ale_linters/go/langserver.vim +++ b/ale_linters/go/langserver.vim @@ -10,8 +10,8 @@ function! ale_linters#go#langserver#GetCommand(buffer) abort let l:options = substitute(l:options, '-gocodecompletion', '', 'g') let l:options = filter(split(l:options, ' '), 'empty(v:val) != 1') - if(ale#Var(a:buffer, 'completion_enabled') == 1) - call add(l:options, '-gocodecompletion') + if ale#Var(a:buffer, 'completion_enabled') + call add(l:options, '-gocodecompletion') endif let l:options = uniq(sort(l:options)) @@ -22,7 +22,7 @@ endfunction call ale#linter#Define('go', { \ 'name': 'golangserver', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('go_langserver_executable'), -\ 'command_callback': 'ale_linters#go#langserver#GetCommand', -\ 'project_root_callback': 'ale#go#FindProjectRoot', +\ 'executable': {b -> ale#Var(b, 'go_langserver_executable')}, +\ 'command': function('ale_linters#go#langserver#GetCommand'), +\ 'project_root': function('ale#go#FindProjectRoot'), \}) diff --git a/ale_linters/go/staticcheck.vim b/ale_linters/go/staticcheck.vim index a3464015..26fe0193 100644 --- a/ale_linters/go/staticcheck.vim +++ b/ale_linters/go/staticcheck.vim @@ -26,7 +26,7 @@ endfunction call ale#linter#Define('go', { \ 'name': 'staticcheck', \ 'executable': 'staticcheck', -\ 'command_callback': 'ale_linters#go#staticcheck#GetCommand', +\ 'command': function('ale_linters#go#staticcheck#GetCommand'), \ 'callback': 'ale#handlers#go#Handler', \ 'output_stream': 'both', \ 'lint_file': 1, diff --git a/ale_linters/graphql/eslint.vim b/ale_linters/graphql/eslint.vim index dfcbf9d9..654b8c17 100644 --- a/ale_linters/graphql/eslint.vim +++ b/ale_linters/graphql/eslint.vim @@ -3,7 +3,7 @@ call ale#linter#Define('graphql', { \ 'name': 'eslint', -\ 'executable_callback': 'ale#handlers#eslint#GetExecutable', -\ 'command_callback': 'ale#handlers#eslint#GetCommand', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'command': function('ale#handlers#eslint#GetCommand'), \ 'callback': 'ale#handlers#eslint#Handle', \}) diff --git a/ale_linters/graphql/gqlint.vim b/ale_linters/graphql/gqlint.vim index 882cc697..d5029de1 100644 --- a/ale_linters/graphql/gqlint.vim +++ b/ale_linters/graphql/gqlint.vim @@ -1,9 +1,15 @@ " Author: Michiel Westerbeek <happylinks@gmail.com> " Description: Linter for GraphQL Schemas +function! ale_linters#graphql#gqlint#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) + \ . 'gqlint' + \ . ' --reporter=simple %t' +endfunction + call ale#linter#Define('graphql', { \ 'name': 'gqlint', \ 'executable': 'gqlint', -\ 'command': 'gqlint --reporter=simple %t', +\ 'command': function('ale_linters#graphql#gqlint#GetCommand'), \ 'callback': 'ale#handlers#unix#HandleAsWarning', \}) diff --git a/ale_linters/hack/hack.vim b/ale_linters/hack/hack.vim index aea428cc..822b5c87 100644 --- a/ale_linters/hack/hack.vim +++ b/ale_linters/hack/hack.vim @@ -16,7 +16,7 @@ endfunction call ale#linter#Define('hack', { \ 'name': 'hack', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#hack#hack#GetExecutable', +\ 'executable': function('ale_linters#hack#hack#GetExecutable'), \ 'command': '%e lsp --from vim-ale', -\ 'project_root_callback': 'ale_linters#hack#hack#GetProjectRoot', +\ 'project_root': function('ale_linters#hack#hack#GetProjectRoot'), \}) diff --git a/ale_linters/hack/hhast.vim b/ale_linters/hack/hhast.vim index 710b7b25..5e6d4dec 100644 --- a/ale_linters/hack/hhast.vim +++ b/ale_linters/hack/hhast.vim @@ -9,7 +9,7 @@ function! ale_linters#hack#hhast#GetProjectRoot(buffer) abort let l:hhconfig = ale#path#FindNearestFile(a:buffer, '.hhconfig') if empty(l:hhconfig) - return '' + return '' endif let l:root = fnamemodify(l:hhconfig, ':h') @@ -33,8 +33,8 @@ endfunction call ale#linter#Define('hack', { \ 'name': 'hhast', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#hack#hhast#GetExecutable', +\ 'executable': function('ale_linters#hack#hhast#GetExecutable'), \ 'command': '%e --mode lsp --from vim-ale', -\ 'project_root_callback': 'ale_linters#hack#hhast#GetProjectRoot', -\ 'initialization_options_callback': 'ale_linters#hack#hhast#GetInitializationOptions', +\ 'project_root': function('ale_linters#hack#hhast#GetProjectRoot'), +\ 'initialization_options': function('ale_linters#hack#hhast#GetInitializationOptions'), \}) diff --git a/ale_linters/haml/hamllint.vim b/ale_linters/haml/hamllint.vim index 6598f81a..9fcd999f 100644 --- a/ale_linters/haml/hamllint.vim +++ b/ale_linters/haml/hamllint.vim @@ -19,7 +19,7 @@ function! ale_linters#haml#hamllint#GetCommand(buffer) abort " See https://github.com/brigade/haml-lint/blob/master/lib/haml_lint/linter/rubocop.rb#L89 " HamlLint::Linter::RuboCop#rubocop_flags if !empty(l:rubocop_config_file_path) - if ale#Has('win32') + if has('win32') let l:prefix = 'set HAML_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config_file_path) . ' &&' else let l:prefix = 'HAML_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config_file_path) @@ -51,7 +51,7 @@ endfunction call ale#linter#Define('haml', { \ 'name': 'hamllint', -\ 'executable_callback': 'ale_linters#haml#hamllint#GetExecutable', -\ 'command_callback': 'ale_linters#haml#hamllint#GetCommand', +\ 'executable': function('ale_linters#haml#hamllint#GetExecutable'), +\ 'command': function('ale_linters#haml#hamllint#GetCommand'), \ 'callback': 'ale_linters#haml#hamllint#Handle' \}) diff --git a/ale_linters/handlebars/embertemplatelint.vim b/ale_linters/handlebars/embertemplatelint.vim index 4fc0f20d..74bd6a99 100644 --- a/ale_linters/handlebars/embertemplatelint.vim +++ b/ale_linters/handlebars/embertemplatelint.vim @@ -31,9 +31,9 @@ endfunction call ale#linter#Define('handlebars', { \ 'name': 'ember-template-lint', -\ 'executable_callback': ale#node#FindExecutableFunc('handlebars_embertemplatelint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'handlebars_embertemplatelint', [ \ 'node_modules/.bin/ember-template-lint', -\ ]), +\ ])}, \ 'command': '%e --json %t', \ 'callback': 'ale_linters#handlebars#embertemplatelint#Handle', \}) diff --git a/ale_linters/haskell/cabal_ghc.vim b/ale_linters/haskell/cabal_ghc.vim index 003adf5d..f3f248f5 100644 --- a/ale_linters/haskell/cabal_ghc.vim +++ b/ale_linters/haskell/cabal_ghc.vim @@ -4,7 +4,8 @@ call ale#Set('haskell_cabal_ghc_options', '-fno-code -v0') function! ale_linters#haskell#cabal_ghc#GetCommand(buffer) abort - return 'cabal exec -- ghc ' + return ale#path#BufferCdString(a:buffer) + \ . 'cabal exec -- ghc ' \ . ale#Var(a:buffer, 'haskell_cabal_ghc_options') \ . ' %t' endfunction @@ -14,6 +15,6 @@ call ale#linter#Define('haskell', { \ 'aliases': ['cabal-ghc'], \ 'output_stream': 'stderr', \ 'executable': 'cabal', -\ 'command_callback': 'ale_linters#haskell#cabal_ghc#GetCommand', +\ 'command': function('ale_linters#haskell#cabal_ghc#GetCommand'), \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) diff --git a/ale_linters/haskell/ghc.vim b/ale_linters/haskell/ghc.vim index daf91c8f..9c3906b2 100644 --- a/ale_linters/haskell/ghc.vim +++ b/ale_linters/haskell/ghc.vim @@ -13,6 +13,6 @@ call ale#linter#Define('haskell', { \ 'name': 'ghc', \ 'output_stream': 'stderr', \ 'executable': 'ghc', -\ 'command_callback': 'ale_linters#haskell#ghc#GetCommand', +\ 'command': function('ale_linters#haskell#ghc#GetCommand'), \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) diff --git a/ale_linters/haskell/ghc_mod.vim b/ale_linters/haskell/ghc_mod.vim index 9762be7a..30e96b40 100644 --- a/ale_linters/haskell/ghc_mod.vim +++ b/ale_linters/haskell/ghc_mod.vim @@ -13,7 +13,7 @@ endfunction call ale#linter#Define('haskell', { \ 'name': 'ghc_mod', \ 'aliases': ['ghc-mod'], -\ 'executable_callback': ale#VarFunc('haskell_ghc_mod_executable'), -\ 'command_callback': 'ale_linters#haskell#ghc_mod#GetCommand', +\ 'executable': {b -> ale#Var(b, 'haskell_ghc_mod_executable')}, +\ 'command': function('ale_linters#haskell#ghc_mod#GetCommand'), \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) diff --git a/ale_linters/haskell/hdevtools.vim b/ale_linters/haskell/hdevtools.vim index cc5ce56f..3e55e4f0 100644 --- a/ale_linters/haskell/hdevtools.vim +++ b/ale_linters/haskell/hdevtools.vim @@ -14,7 +14,7 @@ endfunction call ale#linter#Define('haskell', { \ 'name': 'hdevtools', -\ 'executable_callback': ale#VarFunc('haskell_hdevtools_executable'), -\ 'command_callback': 'ale_linters#haskell#hdevtools#GetCommand', +\ 'executable': {b -> ale#Var(b, 'haskell_hdevtools_executable')}, +\ 'command': function('ale_linters#haskell#hdevtools#GetCommand'), \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) diff --git a/ale_linters/haskell/hie.vim b/ale_linters/haskell/hie.vim index 3ff1180a..c4b5f1df 100644 --- a/ale_linters/haskell/hie.vim +++ b/ale_linters/haskell/hie.vim @@ -9,39 +9,33 @@ function! ale_linters#haskell#hie#GetProjectRoot(buffer) abort " If it's empty, search for the cabal file if empty(l:project_file) - let l:cabal_file = fnamemodify(bufname(a:buffer), ':p:h') - let l:paths = '' - - while empty(matchstr(l:cabal_file, '^\(\/\|\(\w:\\\)\)$')) - let l:cabal_file = fnamemodify(l:cabal_file, ':h') - let l:paths = l:paths . l:cabal_file . ',' - endwhile - + " Search all of the paths except for the root filesystem path. + let l:paths = join( + \ ale#path#Upwards(expand('#' . a:buffer . ':p:h'))[:-2], + \ ',' + \) let l:project_file = globpath(l:paths, '*.cabal') endif - " Either extract the project directory or take the current working - " directory - if !empty(l:project_file) - let l:project_file = fnamemodify(l:project_file, ':h') - else - let l:project_file = expand('#' . a:buffer . ':p:h') + " If we still can't find one, use the current file. + if empty(l:project_file) + let l:project_file = expand('#' . a:buffer . ':p') endif - return l:project_file + return fnamemodify(l:project_file, ':h') endfunction function! ale_linters#haskell#hie#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'haskell_hie_executable') return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hie') -\ . ' --lsp' + \ . ' --lsp' endfunction call ale#linter#Define('haskell', { \ 'name': 'hie', \ 'lsp': 'stdio', -\ 'command_callback': 'ale_linters#haskell#hie#GetCommand', -\ 'executable_callback': ale#VarFunc('haskell_hie_executable'), -\ 'project_root_callback': 'ale_linters#haskell#hie#GetProjectRoot', +\ 'command': function('ale_linters#haskell#hie#GetCommand'), +\ 'executable': {b -> ale#Var(b, 'haskell_hie_executable')}, +\ 'project_root': function('ale_linters#haskell#hie#GetProjectRoot'), \}) diff --git a/ale_linters/haskell/hlint.vim b/ale_linters/haskell/hlint.vim index 0cc7437f..1425251a 100644 --- a/ale_linters/haskell/hlint.vim +++ b/ale_linters/haskell/hlint.vim @@ -40,7 +40,7 @@ endfunction call ale#linter#Define('haskell', { \ 'name': 'hlint', -\ 'executable_callback': ale#VarFunc('haskell_hlint_executable'), -\ 'command_callback': 'ale_linters#haskell#hlint#GetCommand' , +\ 'executable': {b -> ale#Var(b, 'haskell_hlint_executable')}, +\ 'command': function('ale_linters#haskell#hlint#GetCommand') , \ 'callback': 'ale_linters#haskell#hlint#Handle', \}) diff --git a/ale_linters/haskell/stack_build.vim b/ale_linters/haskell/stack_build.vim index 95a54587..8f2d9fd9 100644 --- a/ale_linters/haskell/stack_build.vim +++ b/ale_linters/haskell/stack_build.vim @@ -16,8 +16,8 @@ call ale#linter#Define('haskell', { \ 'name': 'stack_build', \ 'aliases': ['stack-build'], \ 'output_stream': 'stderr', -\ 'executable_callback': 'ale#handlers#haskell#GetStackExecutable', -\ 'command_callback': 'ale_linters#haskell#stack_build#GetCommand', +\ 'executable': function('ale#handlers#haskell#GetStackExecutable'), +\ 'command': function('ale_linters#haskell#stack_build#GetCommand'), \ 'lint_file': 1, \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) diff --git a/ale_linters/haskell/stack_ghc.vim b/ale_linters/haskell/stack_ghc.vim index 8f42b96c..c345fe43 100644 --- a/ale_linters/haskell/stack_ghc.vim +++ b/ale_linters/haskell/stack_ghc.vim @@ -1,11 +1,21 @@ " Author: w0rp <devw0rp@gmail.com> " Description: ghc for Haskell files, using Stack +call ale#Set('haskell_stack_ghc_options', '-fno-code -v0') + +function! ale_linters#haskell#stack_ghc#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) + \ . ale#handlers#haskell#GetStackExecutable(a:buffer) + \ . ' ghc -- ' + \ . ale#Var(a:buffer, 'haskell_stack_ghc_options') + \ . ' %t' +endfunction + call ale#linter#Define('haskell', { \ 'name': 'stack_ghc', \ 'aliases': ['stack-ghc'], \ 'output_stream': 'stderr', -\ 'executable_callback': 'ale#handlers#haskell#GetStackExecutable', -\ 'command': 'stack ghc -- -fno-code -v0 %t', +\ 'executable': function('ale#handlers#haskell#GetStackExecutable'), +\ 'command': function('ale_linters#haskell#stack_ghc#GetCommand'), \ 'callback': 'ale#handlers#haskell#HandleGHCFormat', \}) diff --git a/ale_linters/help/alex.vim b/ale_linters/help/alex.vim index 21b23b4f..9be00a82 100644 --- a/ale_linters/help/alex.vim +++ b/ale_linters/help/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for help files -call ale#linter#Define('help', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('help', '--text') diff --git a/ale_linters/html/alex.vim b/ale_linters/html/alex.vim index 5a1f61e9..97756753 100644 --- a/ale_linters/html/alex.vim +++ b/ale_linters/html/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for HTML files -call ale#linter#Define('html', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('html', '--html') diff --git a/ale_linters/html/fecs.vim b/ale_linters/html/fecs.vim new file mode 100644 index 00000000..15e00e12 --- /dev/null +++ b/ale_linters/html/fecs.vim @@ -0,0 +1,9 @@ +" Author: harttle <yangjvn@126.com> +" Description: fecs for HTMl files + +call ale#linter#Define('html', { +\ 'name': 'fecs', +\ 'executable': function('ale#handlers#fecs#GetExecutable'), +\ 'command': function('ale#handlers#fecs#GetCommand'), +\ 'callback': 'ale#handlers#fecs#Handle', +\}) diff --git a/ale_linters/html/htmlhint.vim b/ale_linters/html/htmlhint.vim index 234c1176..3e01f51a 100644 --- a/ale_linters/html/htmlhint.vim +++ b/ale_linters/html/htmlhint.vim @@ -24,9 +24,9 @@ endfunction call ale#linter#Define('html', { \ 'name': 'htmlhint', -\ 'executable_callback': ale#node#FindExecutableFunc('html_htmlhint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'html_htmlhint', [ \ 'node_modules/.bin/htmlhint', -\ ]), -\ 'command_callback': 'ale_linters#html#htmlhint#GetCommand', +\ ])}, +\ 'command': function('ale_linters#html#htmlhint#GetCommand'), \ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/html/stylelint.vim b/ale_linters/html/stylelint.vim index 908c4b02..ae8955f3 100644 --- a/ale_linters/html/stylelint.vim +++ b/ale_linters/html/stylelint.vim @@ -21,7 +21,7 @@ endfunction call ale#linter#Define('html', { \ 'name': 'stylelint', -\ 'executable_callback': 'ale_linters#html#stylelint#GetExecutable', -\ 'command_callback': 'ale_linters#html#stylelint#GetCommand', +\ 'executable': function('ale_linters#html#stylelint#GetExecutable'), +\ 'command': function('ale_linters#html#stylelint#GetCommand'), \ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/html/tidy.vim b/ale_linters/html/tidy.vim index 4ec29091..1e476d40 100644 --- a/ale_linters/html/tidy.vim +++ b/ale_linters/html/tidy.vim @@ -63,8 +63,8 @@ endfunction call ale#linter#Define('html', { \ 'name': 'tidy', -\ 'executable_callback': ale#VarFunc('html_tidy_executable'), +\ 'executable': {b -> ale#Var(b, 'html_tidy_executable')}, \ 'output_stream': 'stderr', -\ 'command_callback': 'ale_linters#html#tidy#GetCommand', +\ 'command': function('ale_linters#html#tidy#GetCommand'), \ 'callback': 'ale_linters#html#tidy#Handle', \ }) diff --git a/ale_linters/idris/idris.vim b/ale_linters/idris/idris.vim index feac0f10..879e92f2 100644 --- a/ale_linters/idris/idris.vim +++ b/ale_linters/idris/idris.vim @@ -49,11 +49,11 @@ function! ale_linters#idris#idris#Handle(buffer, lines) abort let l:errors = matchlist(l:match[5], '\v([wW]arning|[eE]rror) - ?(.*)') if len(l:errors) > 0 - let l:ghc_type = l:errors[1] - let l:text = l:errors[2] + let l:ghc_type = l:errors[1] + let l:text = l:errors[2] else - let l:ghc_type = '' - let l:text = l:match[5][:0] is# ' ' ? l:match[5][1:] : l:match[5] + let l:ghc_type = '' + let l:text = l:match[5][:0] is# ' ' ? l:match[5][1:] : l:match[5] endif if l:ghc_type is? 'Warning' @@ -75,7 +75,7 @@ endfunction call ale#linter#Define('idris', { \ 'name': 'idris', -\ 'executable_callback': ale#VarFunc('idris_idris_executable'), -\ 'command_callback': 'ale_linters#idris#idris#GetCommand', +\ 'executable': {b -> ale#Var(b, 'idris_idris_executable')}, +\ 'command': function('ale_linters#idris#idris#GetCommand'), \ 'callback': 'ale_linters#idris#idris#Handle', \}) diff --git a/ale_linters/ispc/ispc.vim b/ale_linters/ispc/ispc.vim index de7ceafa..eb365117 100644 --- a/ale_linters/ispc/ispc.vim +++ b/ale_linters/ispc/ispc.vim @@ -38,8 +38,8 @@ endfunction call ale#linter#Define('ispc', { \ 'name': 'ispc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('ispc_ispc_executable'), -\ 'command_callback': 'ale_linters#ispc#ispc#GetCommand', +\ 'executable': {b -> ale#Var(b, 'ispc_ispc_executable')}, +\ 'command': function('ale_linters#ispc#ispc#GetCommand'), \ 'callback': 'ale_linters#ispc#ispc#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/java/checkstyle.vim b/ale_linters/java/checkstyle.vim index c07b65d0..3159cd55 100644 --- a/ale_linters/java/checkstyle.vim +++ b/ale_linters/java/checkstyle.vim @@ -44,7 +44,7 @@ endif call ale#linter#Define('java', { \ 'name': 'checkstyle', \ 'executable': 'checkstyle', -\ 'command_callback': 'ale_linters#java#checkstyle#GetCommand', +\ 'command': function('ale_linters#java#checkstyle#GetCommand'), \ 'callback': 'ale_linters#java#checkstyle#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/java/eclipselsp.vim b/ale_linters/java/eclipselsp.vim new file mode 100644 index 00000000..d0ea9d6c --- /dev/null +++ b/ale_linters/java/eclipselsp.vim @@ -0,0 +1,137 @@ +" Author: Horacio Sanson <https://github.com/hsanson> +" Description: Support for the Eclipse language server https://github.com/eclipse/eclipse.jdt.ls + +let s:version_cache = {} + +call ale#Set('java_eclipselsp_path', ale#path#Simplify($HOME . '/eclipse.jdt.ls')) +call ale#Set('java_eclipselsp_executable', 'java') + +function! ale_linters#java#eclipselsp#Executable(buffer) abort + return ale#Var(a:buffer, 'java_eclipselsp_executable') +endfunction + +function! ale_linters#java#eclipselsp#TargetPath(buffer) abort + return ale#Var(a:buffer, 'java_eclipselsp_path') +endfunction + +function! ale_linters#java#eclipselsp#JarPath(buffer) abort + let l:path = ale_linters#java#eclipselsp#TargetPath(a:buffer) + + " Search jar file within repository path when manually built using mvn + let l:repo_path = l:path . '/org.eclipse.jdt.ls.product/target/repository' + let l:files = globpath(l:repo_path, '**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) + + if len(l:files) == 1 + return l:files[0] + endif + + " Search jar file within VSCode extensions folder. + let l:files = globpath(l:path, '**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) + + if len(l:files) == 1 + return l:files[0] + endif + + return '' +endfunction + +function! ale_linters#java#eclipselsp#ConfigurationPath(buffer) abort + let l:path = fnamemodify(ale_linters#java#eclipselsp#JarPath(a:buffer), ':p:h:h') + + if has('win32') + let l:path = l:path . '/config_win' + elseif has('macunix') + let l:path = l:path . '/config_mac' + else + let l:path = l:path . '/config_linux' + endif + + return ale#path#Simplify(l:path) +endfunction + +function! ale_linters#java#eclipselsp#VersionCheck(version_lines) abort + return s:GetVersion('', a:version_lines) +endfunction + +function! s:GetVersion(executable, version_lines) abort + let l:version = [] + + for l:line in a:version_lines + let l:match = matchlist(l:line, '\(\d\+\)\.\(\d\+\)\.\(\d\+\)') + + if !empty(l:match) + let l:version = [l:match[1] + 0, l:match[2] + 0, l:match[3] + 0] + let s:version_cache[a:executable] = l:version + break + endif + endfor + + return l:version +endfunction + +function! ale_linters#java#eclipselsp#CommandWithVersion(buffer, version_lines, meta) abort + let l:executable = ale_linters#java#eclipselsp#Executable(a:buffer) + let l:version = s:GetVersion(l:executable, a:version_lines) + + return ale_linters#java#eclipselsp#Command(a:buffer, l:version) +endfunction + +function! ale_linters#java#eclipselsp#Command(buffer, version) abort + let l:path = ale#Var(a:buffer, 'java_eclipselsp_path') + + let l:executable = ale_linters#java#eclipselsp#Executable(a:buffer) + + let l:cmd = [ ale#Escape(l:executable), + \ '-Declipse.application=org.eclipse.jdt.ls.core.id1', + \ '-Dosgi.bundles.defaultStartLevel=4', + \ '-Declipse.product=org.eclipse.jdt.ls.core.product', + \ '-Dlog.level=ALL', + \ '-noverify', + \ '-Xmx1G', + \ '-jar', + \ ale_linters#java#eclipselsp#JarPath(a:buffer), + \ '-configuration', + \ ale_linters#java#eclipselsp#ConfigurationPath(a:buffer), + \ '-data', + \ ale#java#FindProjectRoot(a:buffer) + \ ] + + if ale#semver#GTE(a:version, [1, 9]) + call add(l:cmd, '--add-modules=ALL-SYSTEM') + call add(l:cmd, '--add-opens java.base/java.util=ALL-UNNAMED') + call add(l:cmd, '--add-opens java.base/java.lang=ALL-UNNAMED') + endif + + return join(l:cmd, ' ') +endfunction + +function! ale_linters#java#eclipselsp#RunWithVersionCheck(buffer) abort + let l:executable = ale_linters#java#eclipselsp#Executable(a:buffer) + + if empty(l:executable) + return '' + endif + + let l:cache = s:version_cache + + if has_key(s:version_cache, l:executable) + return ale_linters#java#eclipselsp#Command(a:buffer, s:version_cache[l:executable]) + endif + + let l:command = ale#Escape(l:executable) . ' -version' + + return ale#command#Run( + \ a:buffer, + \ l:command, + \ function('ale_linters#java#eclipselsp#CommandWithVersion') + \) +endfunction + +call ale#linter#Define('java', { +\ 'name': 'eclipselsp', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#java#eclipselsp#Executable'), +\ 'command': function('ale_linters#java#eclipselsp#RunWithVersionCheck'), +\ 'language': 'java', +\ 'project_root': function('ale#java#FindProjectRoot'), +\}) diff --git a/ale_linters/java/javac.vim b/ale_linters/java/javac.vim index 659f7d04..3883783b 100644 --- a/ale_linters/java/javac.vim +++ b/ale_linters/java/javac.vim @@ -7,21 +7,29 @@ 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 +function! ale_linters#java#javac#RunWithImportPaths(buffer) abort + let l:command = '' let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') if !empty(l:pom_path) && executable('mvn') - return ale#path#CdString(fnamemodify(l:pom_path, ':h')) + let l:command = ale#path#CdString(fnamemodify(l:pom_path, ':h')) \ . 'mvn dependency:build-classpath' endif - let l:classpath_command = ale#gradle#BuildClasspathCommand(a:buffer) + " Try to use Gradle if Maven isn't available. + if empty(l:command) + let l:command = ale#gradle#BuildClasspathCommand(a:buffer) + endif - if !empty(l:classpath_command) - return l:classpath_command + if empty(l:command) + return ale_linters#java#javac#GetCommand(a:buffer, [], {}) endif - return '' + return ale#command#Run( + \ a:buffer, + \ l:command, + \ function('ale_linters#java#javac#GetCommand') + \) endfunction function! s:BuildClassPathOption(buffer, import_paths) abort @@ -37,7 +45,7 @@ function! s:BuildClassPathOption(buffer, import_paths) abort \ : '' endfunction -function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort +function! ale_linters#java#javac#GetCommand(buffer, import_paths, meta) abort let l:cp_option = s:BuildClassPathOption(a:buffer, a:import_paths) let l:sp_option = '' @@ -55,15 +63,14 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort if isdirectory(l:jaxb_dir) call add(l:sp_dirs, l:jaxb_dir) endif + endif - " Automatically include the test directory, but only for test code. - if expand('#' . a:buffer . ':p') =~? '\vsrc[/\\]test[/\\]java' - let l:test_dir = fnamemodify(l:src_dir, ':h:h:h') - \ . (has('win32') ? '\test\java\' : '/test/java/') + " Automatically include the test directory, but only for test code. + if expand('#' . a:buffer . ':p') =~? '\vsrc[/\\]test[/\\]java' + let l:test_dir = ale#path#FindNearestDirectory(a:buffer, 'src/test/java') - if isdirectory(l:test_dir) - call add(l:sp_dirs, l:test_dir) - endif + if isdirectory(l:test_dir) + call add(l:sp_dirs, l:test_dir) endif endif @@ -73,7 +80,7 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort endif " Create .class files in a temporary directory, which we will delete later. - let l:class_file_directory = ale#engine#CreateDirectory(a:buffer) + let l:class_file_directory = ale#command#CreateDirectory(a:buffer) " Always run javac from the directory the file is in, so we can resolve " relative paths correctly. @@ -120,10 +127,8 @@ endfunction call ale#linter#Define('java', { \ 'name': 'javac', -\ 'executable_callback': ale#VarFunc('java_javac_executable'), -\ 'command_chain': [ -\ {'callback': 'ale_linters#java#javac#GetImportPaths', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#java#javac#GetCommand', 'output_stream': 'stderr'}, -\ ], +\ 'executable': {b -> ale#Var(b, 'java_javac_executable')}, +\ 'command': function('ale_linters#java#javac#RunWithImportPaths'), +\ 'output_stream': 'stderr', \ 'callback': 'ale_linters#java#javac#Handle', \}) diff --git a/ale_linters/java/javalsp.vim b/ale_linters/java/javalsp.vim index 1436a52c..a327363d 100644 --- a/ale_linters/java/javalsp.vim +++ b/ale_linters/java/javalsp.vim @@ -1,7 +1,6 @@ " Author: Horacio Sanson <https://github.com/hsanson> " Description: Support for the Java language server https://github.com/georgewfraser/vscode-javac -call ale#Set('java_javalsp_jar', 'javacs.jar') call ale#Set('java_javalsp_executable', 'java') function! ale_linters#java#javalsp#Executable(buffer) abort @@ -9,17 +8,16 @@ function! ale_linters#java#javalsp#Executable(buffer) abort endfunction function! ale_linters#java#javalsp#Command(buffer) abort - let l:jar = ale#Var(a:buffer, 'java_javalsp_jar') let l:executable = ale_linters#java#javalsp#Executable(a:buffer) - return ale#Escape(l:executable) . ' -cp ' . l:jar . ' -Xverify:none org.javacs.Main' + return ale#Escape(l:executable) . ' -Xverify:none -m javacs/org.javacs.Main' endfunction call ale#linter#Define('java', { \ 'name': 'javalsp', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#java#javalsp#Executable', -\ 'command_callback': 'ale_linters#java#javalsp#Command', +\ 'executable': function('ale_linters#java#javalsp#Executable'), +\ 'command': function('ale_linters#java#javalsp#Command'), \ 'language': 'java', -\ 'project_root_callback': 'ale#java#FindProjectRoot', +\ 'project_root': function('ale#java#FindProjectRoot'), \}) diff --git a/ale_linters/java/pmd.vim b/ale_linters/java/pmd.vim index b530ad09..a1f4c93c 100644 --- a/ale_linters/java/pmd.vim +++ b/ale_linters/java/pmd.vim @@ -31,6 +31,6 @@ endif call ale#linter#Define('java', { \ 'name': 'pmd', \ 'executable': 'pmd', -\ 'command_callback': 'ale_linters#java#pmd#GetCommand', +\ 'command': function('ale_linters#java#pmd#GetCommand'), \ 'callback': 'ale_linters#java#pmd#Handle', \}) diff --git a/ale_linters/javascript/eslint.vim b/ale_linters/javascript/eslint.vim index 23e16949..8aeac2d8 100644 --- a/ale_linters/javascript/eslint.vim +++ b/ale_linters/javascript/eslint.vim @@ -4,7 +4,7 @@ call ale#linter#Define('javascript', { \ 'name': 'eslint', \ 'output_stream': 'both', -\ 'executable_callback': 'ale#handlers#eslint#GetExecutable', -\ 'command_callback': 'ale#handlers#eslint#GetCommand', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'command': function('ale#handlers#eslint#GetCommand'), \ 'callback': 'ale#handlers#eslint#Handle', \}) diff --git a/ale_linters/javascript/fecs.vim b/ale_linters/javascript/fecs.vim new file mode 100644 index 00000000..e47c0a0b --- /dev/null +++ b/ale_linters/javascript/fecs.vim @@ -0,0 +1,10 @@ +" Author: harttle <yangjvn@126.com> +" Description: fecs for JavaScript files + +call ale#linter#Define('javascript', { +\ 'name': 'fecs', +\ 'executable': function('ale#handlers#fecs#GetExecutable'), +\ 'command': function('ale#handlers#fecs#GetCommand'), +\ 'read_buffer': 0, +\ 'callback': 'ale#handlers#fecs#Handle', +\}) diff --git a/ale_linters/javascript/flow.vim b/ale_linters/javascript/flow.vim index cdb289c7..3135e2e9 100755 --- a/ale_linters/javascript/flow.vim +++ b/ale_linters/javascript/flow.vim @@ -27,34 +27,16 @@ function! ale_linters#javascript#flow#GetExecutable(buffer) abort \]) endfunction -function! ale_linters#javascript#flow#VersionCheck(buffer) abort - let l:executable = ale_linters#javascript#flow#GetExecutable(a:buffer) - - if empty(l:executable) - return '' - endif - - return ale#Escape(l:executable) . ' --version' -endfunction - -function! ale_linters#javascript#flow#GetCommand(buffer, version_lines) abort - let l:executable = ale_linters#javascript#flow#GetExecutable(a:buffer) - - if empty(l:executable) - return '' - endif - - let l:version = ale#semver#GetVersion(l:executable, a:version_lines) - +function! ale_linters#javascript#flow#GetCommand(buffer, version) 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 = ale#Var(a:buffer, 'javascript_flow_use_respect_pragma') - \ && (empty(l:version) || ale#semver#GTE(l:version, [0, 36])) + \ && (empty(a:version) || ale#semver#GTE(a:version, [0, 36])) - return ale#Escape(l:executable) - \ . ' check-contents' + return '%e check-contents' \ . (l:use_respect_pragma ? ' --respect-pragma': '') - \ . ' --json --from ale %s' + \ . ' --json --from ale %s < %t' + \ . (!has('win32') ? '; echo' : '') endfunction " Filter lines of flow output until we find the first line where the JSON @@ -86,7 +68,6 @@ function! s:ExtraErrorMsg(current, new) abort return l:newMsg endfunction - function! s:GetDetails(error) abort let l:detail = '' @@ -155,7 +136,8 @@ function! ale_linters#javascript#flow#Handle(buffer, lines) abort \} if has_key(l:error, 'extra') - let l:errorToAdd.detail = s:GetDetails(l:error) + let l:errorToAdd.detail = l:errorToAdd.text + \ . "\n" . s:GetDetails(l:error) endif call add(l:output, l:errorToAdd) @@ -166,11 +148,13 @@ endfunction call ale#linter#Define('javascript', { \ 'name': 'flow', -\ 'executable_callback': 'ale_linters#javascript#flow#GetExecutable', -\ 'command_chain': [ -\ {'callback': 'ale_linters#javascript#flow#VersionCheck'}, -\ {'callback': 'ale_linters#javascript#flow#GetCommand'}, -\ ], +\ 'executable': function('ale_linters#javascript#flow#GetExecutable'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#javascript#flow#GetExecutable(buffer), +\ '%e --version', +\ function('ale_linters#javascript#flow#GetCommand'), +\ )}, \ 'callback': 'ale_linters#javascript#flow#Handle', -\ 'add_newline': !has('win32'), +\ 'read_buffer': 0, \}) diff --git a/ale_linters/javascript/flow_ls.vim b/ale_linters/javascript/flow_ls.vim index 75377183..accaaa73 100644 --- a/ale_linters/javascript/flow_ls.vim +++ b/ale_linters/javascript/flow_ls.vim @@ -19,10 +19,10 @@ endfunction call ale#linter#Define('javascript', { \ 'name': 'flow-language-server', \ 'lsp': 'stdio', -\ 'executable_callback': ale#node#FindExecutableFunc('javascript_flow_ls', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'javascript_flow_ls', [ \ 'node_modules/.bin/flow', -\ ]), +\ ])}, \ 'command': '%e lsp --from ale-lsp', -\ 'project_root_callback': 'ale_linters#javascript#flow_ls#FindProjectRoot', +\ 'project_root': function('ale_linters#javascript#flow_ls#FindProjectRoot'), \ 'language': 'javascript', \}) diff --git a/ale_linters/javascript/jscs.vim b/ale_linters/javascript/jscs.vim index a38766a6..8905b3a1 100644 --- a/ale_linters/javascript/jscs.vim +++ b/ale_linters/javascript/jscs.vim @@ -53,9 +53,9 @@ endfunction call ale#linter#Define('javascript', { \ 'name': 'jscs', -\ 'executable_callback': ale#node#FindExecutableFunc('javascript_jscs', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'javascript_jscs', [ \ 'node_modules/.bin/jscs', -\ ]), -\ 'command_callback': 'ale_linters#javascript#jscs#GetCommand', +\ ])}, +\ 'command': function('ale_linters#javascript#jscs#GetCommand'), \ 'callback': 'ale_linters#javascript#jscs#Handle', \}) diff --git a/ale_linters/javascript/jshint.vim b/ale_linters/javascript/jshint.vim index cb7f66fc..d80a2250 100644 --- a/ale_linters/javascript/jshint.vim +++ b/ale_linters/javascript/jshint.vim @@ -25,9 +25,9 @@ endfunction call ale#linter#Define('javascript', { \ 'name': 'jshint', -\ 'executable_callback': ale#node#FindExecutableFunc('javascript_jshint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'javascript_jshint', [ \ 'node_modules/.bin/jshint', -\ ]), -\ 'command_callback': 'ale_linters#javascript#jshint#GetCommand', +\ ])}, +\ 'command': function('ale_linters#javascript#jshint#GetCommand'), \ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/javascript/standard.vim b/ale_linters/javascript/standard.vim index f16b837a..4cd2c303 100644 --- a/ale_linters/javascript/standard.vim +++ b/ale_linters/javascript/standard.vim @@ -24,7 +24,7 @@ endfunction " standard uses eslint and the output format is the same call ale#linter#Define('javascript', { \ 'name': 'standard', -\ 'executable_callback': 'ale_linters#javascript#standard#GetExecutable', -\ 'command_callback': 'ale_linters#javascript#standard#GetCommand', +\ 'executable': function('ale_linters#javascript#standard#GetExecutable'), +\ 'command': function('ale_linters#javascript#standard#GetCommand'), \ 'callback': 'ale#handlers#eslint#Handle', \}) diff --git a/ale_linters/javascript/tsserver.vim b/ale_linters/javascript/tsserver.vim index 6cf08dd6..68c252c5 100644 --- a/ale_linters/javascript/tsserver.vim +++ b/ale_linters/javascript/tsserver.vim @@ -8,10 +8,10 @@ call ale#Set('javascript_tsserver_use_global', get(g:, 'ale_use_global_executabl call ale#linter#Define('javascript', { \ 'name': 'tsserver', \ 'lsp': 'tsserver', -\ 'executable_callback': ale#node#FindExecutableFunc('javascript_tsserver', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'javascript_tsserver', [ \ 'node_modules/.bin/tsserver', -\ ]), +\ ])}, \ 'command': '%e', -\ 'project_root_callback': {-> ''}, +\ 'project_root': function('ale#handlers#tsserver#GetProjectRoot'), \ 'language': '', \}) diff --git a/ale_linters/javascript/xo.vim b/ale_linters/javascript/xo.vim index bc8657ed..4ba39101 100644 --- a/ale_linters/javascript/xo.vim +++ b/ale_linters/javascript/xo.vim @@ -20,7 +20,7 @@ endfunction " xo uses eslint and the output format is the same call ale#linter#Define('javascript', { \ 'name': 'xo', -\ 'executable_callback': 'ale_linters#javascript#xo#GetExecutable', -\ 'command_callback': 'ale_linters#javascript#xo#GetCommand', +\ 'executable': function('ale_linters#javascript#xo#GetExecutable'), +\ 'command': function('ale_linters#javascript#xo#GetCommand'), \ 'callback': 'ale#handlers#eslint#Handle', \}) diff --git a/ale_linters/json/jsonlint.vim b/ale_linters/json/jsonlint.vim index f01553d6..f677b488 100644 --- a/ale_linters/json/jsonlint.vim +++ b/ale_linters/json/jsonlint.vim @@ -1,4 +1,21 @@ -" Author: KabbAmine <amine.kabb@gmail.com> +" Author: KabbAmine <amine.kabb@gmail.com>, David Sierra <https://github.com/davidsierradz> + +call ale#Set('json_jsonlint_executable', 'jsonlint') +call ale#Set('json_jsonlint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#json#jsonlint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'json_jsonlint', [ + \ 'node_modules/.bin/jsonlint', + \ 'node_modules/jsonlint/lib/cli.js', + \]) +endfunction + +function! ale_linters#json#jsonlint#GetCommand(buffer) abort + let l:executable = ale_linters#json#jsonlint#GetExecutable(a:buffer) + + return ale#node#Executable(a:buffer, l:executable) + \ . ' --compact -' +endfunction function! ale_linters#json#jsonlint#Handle(buffer, lines) abort " Matches patterns like the following: @@ -19,8 +36,8 @@ endfunction call ale#linter#Define('json', { \ 'name': 'jsonlint', -\ 'executable': 'jsonlint', +\ 'executable': function('ale_linters#json#jsonlint#GetExecutable'), \ 'output_stream': 'stderr', -\ 'command': 'jsonlint --compact -', +\ 'command': function('ale_linters#json#jsonlint#GetCommand'), \ 'callback': 'ale_linters#json#jsonlint#Handle', \}) diff --git a/ale_linters/julia/languageserver.vim b/ale_linters/julia/languageserver.vim index cd2000de..564bec39 100644 --- a/ale_linters/julia/languageserver.vim +++ b/ale_linters/julia/languageserver.vim @@ -14,8 +14,8 @@ endfunction call ale#linter#Define('julia', { \ 'name': 'languageserver', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('julia_executable'), -\ 'command_callback': 'ale_linters#julia#languageserver#GetCommand', +\ 'executable': {b -> ale#Var(b, 'julia_executable')}, +\ 'command': function('ale_linters#julia#languageserver#GetCommand'), \ 'language': 'julia', -\ 'project_root_callback': 'ale#julia#FindProjectRoot', +\ 'project_root': function('ale#julia#FindProjectRoot'), \}) diff --git a/ale_linters/kotlin/kotlinc.vim b/ale_linters/kotlin/kotlinc.vim index 4a993986..3c6854fa 100644 --- a/ale_linters/kotlin/kotlinc.vim +++ b/ale_linters/kotlin/kotlinc.vim @@ -11,26 +11,35 @@ let g:ale_kotlin_kotlinc_module_filename = get(g:, 'ale_kotlin_kotlinc_module_fi let s:classpath_sep = has('unix') ? ':' : ';' -function! ale_linters#kotlin#kotlinc#GetImportPaths(buffer) abort +function! ale_linters#kotlin#kotlinc#RunWithImportPaths(buffer) abort + let l:command = '' + " exec maven/gradle only if classpath is not set if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') isnot# '' - return '' - else - let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') + return ale_linters#kotlin#kotlinc#GetCommand(a:buffer, [], {}) + endif - if !empty(l:pom_path) && executable('mvn') - return ale#path#CdString(fnamemodify(l:pom_path, ':h')) - \ . 'mvn dependency:build-classpath' - endif + let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') - let l:classpath_command = ale#gradle#BuildClasspathCommand(a:buffer) + if !empty(l:pom_path) && executable('mvn') + let l:command = ale#path#CdString(fnamemodify(l:pom_path, ':h')) + \ . 'mvn dependency:build-classpath' + endif - if !empty(l:classpath_command) - return l:classpath_command - endif + " Try to use Gradle if Maven isn't available. + if empty(l:command) + let l:command = ale#gradle#BuildClasspathCommand(a:buffer) + endif - return '' + if empty(l:command) + return ale_linters#kotlin#kotlinc#GetCommand(a:buffer, [], {}) endif + + return ale#command#Run( + \ a:buffer, + \ l:command, + \ function('ale_linters#kotlin#kotlinc#GetCommand') + \) endfunction function! s:BuildClassPathOption(buffer, import_paths) abort @@ -46,7 +55,7 @@ function! s:BuildClassPathOption(buffer, import_paths) abort \ : '' endfunction -function! ale_linters#kotlin#kotlinc#GetCommand(buffer, import_paths) abort +function! ale_linters#kotlin#kotlinc#GetCommand(buffer, import_paths, meta) abort let l:kotlinc_opts = ale#Var(a:buffer, 'kotlin_kotlinc_options') let l:command = 'kotlinc ' @@ -165,11 +174,7 @@ endfunction call ale#linter#Define('kotlin', { \ 'name': 'kotlinc', \ 'executable': 'kotlinc', -\ 'command_chain': [ -\ {'callback': 'ale_linters#kotlin#kotlinc#GetImportPaths', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#kotlin#kotlinc#GetCommand', 'output_stream': 'stderr'}, -\ ], +\ 'command': function('ale_linters#kotlin#kotlinc#RunWithImportPaths'), \ 'callback': 'ale_linters#kotlin#kotlinc#Handle', \ 'lint_file': 1, \}) - diff --git a/ale_linters/kotlin/ktlint.vim b/ale_linters/kotlin/ktlint.vim index f474e845..f0384005 100644 --- a/ale_linters/kotlin/ktlint.vim +++ b/ale_linters/kotlin/ktlint.vim @@ -1,54 +1,10 @@ " Author: Francis Agyapong <francisagyapong2@gmail.com> " Description: Lint kotlin files using ktlint -call ale#Set('kotlin_ktlint_executable', 'ktlint') -call ale#Set('kotlin_ktlint_rulesets', []) -call ale#Set('kotlin_ktlint_format', 0) - - -function! ale_linters#kotlin#ktlint#GetCommand(buffer) abort - let l:executable = ale#Var(a:buffer, 'kotlin_ktlint_executable') - let l:file_path = expand('#' . a:buffer . ':p') - let l:options = '' - - " Formmatted content written to original file, not sure how to handle - " if ale#Var(a:buffer, 'kotlin_ktlint_format') - " let l:options = l:options . ' --format' - " endif - - for l:ruleset in ale#Var(a:buffer, 'kotlin_ktlint_rulesets') - let l:options = l:options . ' --ruleset ' . l:ruleset - endfor - - return l:executable . ' ' . l:options . ' ' . l:file_path -endfunction - -function! ale_linters#kotlin#ktlint#Handle(buffer, lines) abort - let l:message_pattern = '^\(.*\):\([0-9]\+\):\([0-9]\+\):\s\+\(.*\)' - let l:output = [] - - for l:match in ale#util#GetMatches(a:lines, l:message_pattern) - let l:line = l:match[2] + 0 - let l:column = l:match[3] + 0 - let l:text = l:match[4] - - let l:type = l:text =~? 'not a valid kotlin file' ? 'E' : 'W' - - call add(l:output, { - \ 'lnum': l:line, - \ 'col': l:column, - \ 'text': l:text, - \ 'type': l:type - \}) - endfor - - return l:output -endfunction - call ale#linter#Define('kotlin', { \ 'name': 'ktlint', \ 'executable': 'ktlint', -\ 'command_callback': 'ale_linters#kotlin#ktlint#GetCommand', -\ 'callback': 'ale_linters#kotlin#ktlint#Handle', +\ 'command': function('ale#handlers#ktlint#GetCommand'), +\ 'callback': 'ale#handlers#ktlint#Handle', \ 'lint_file': 1 \}) diff --git a/ale_linters/kotlin/languageserver.vim b/ale_linters/kotlin/languageserver.vim index aea817ba..af78c0e0 100644 --- a/ale_linters/kotlin/languageserver.vim +++ b/ale_linters/kotlin/languageserver.vim @@ -22,8 +22,8 @@ endfunction call ale#linter#Define('kotlin', { \ 'name': 'languageserver', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('kotlin_languageserver_executable'), +\ 'executable': {b -> ale#Var(b, 'kotlin_languageserver_executable')}, \ 'command': '%e', \ 'language': 'kotlin', -\ 'project_root_callback': 'ale_linters#kotlin#languageserver#GetProjectRoot', +\ 'project_root': function('ale_linters#kotlin#languageserver#GetProjectRoot'), \}) diff --git a/ale_linters/less/lessc.vim b/ale_linters/less/lessc.vim index 37600649..4ec8b00e 100755 --- a/ale_linters/less/lessc.vim +++ b/ale_linters/less/lessc.vim @@ -38,10 +38,10 @@ endfunction call ale#linter#Define('less', { \ 'name': 'lessc', -\ 'executable_callback': ale#node#FindExecutableFunc('less_lessc', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'less_lessc', [ \ 'node_modules/.bin/lessc', -\ ]), -\ 'command_callback': 'ale_linters#less#lessc#GetCommand', +\ ])}, +\ 'command': function('ale_linters#less#lessc#GetCommand'), \ 'callback': 'ale_linters#less#lessc#Handle', \ 'output_stream': 'stderr', \}) diff --git a/ale_linters/less/stylelint.vim b/ale_linters/less/stylelint.vim index 479808c2..efb036c2 100644 --- a/ale_linters/less/stylelint.vim +++ b/ale_linters/less/stylelint.vim @@ -12,9 +12,9 @@ endfunction call ale#linter#Define('less', { \ 'name': 'stylelint', -\ 'executable_callback': ale#node#FindExecutableFunc('less_stylelint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'less_stylelint', [ \ 'node_modules/.bin/stylelint', -\ ]), -\ 'command_callback': 'ale_linters#less#stylelint#GetCommand', +\ ])}, +\ 'command': function('ale_linters#less#stylelint#GetCommand'), \ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/llvm/llc.vim b/ale_linters/llvm/llc.vim index 044f8c44..594be063 100644 --- a/ale_linters/llvm/llc.vim +++ b/ale_linters/llvm/llc.vim @@ -17,8 +17,8 @@ endfunction call ale#linter#Define('llvm', { \ 'name': 'llc', -\ 'executable_callback': ale#VarFunc('llvm_llc_executable'), +\ 'executable': {b -> ale#Var(b, 'llvm_llc_executable')}, \ 'output_stream': 'stderr', -\ 'command_callback': {-> '%e -filetype=null -o=' . g:ale#util#nul_file}, +\ 'command': {-> '%e -filetype=null -o=' . g:ale#util#nul_file}, \ 'callback': 'ale_linters#llvm#llc#HandleErrors', \}) diff --git a/ale_linters/lua/luac.vim b/ale_linters/lua/luac.vim index bca2cd8d..41674a43 100644 --- a/ale_linters/lua/luac.vim +++ b/ale_linters/lua/luac.vim @@ -24,7 +24,7 @@ endfunction call ale#linter#Define('lua', { \ 'name': 'luac', -\ 'executable_callback': ale#VarFunc('lua_luac_executable'), +\ 'executable': {b -> ale#Var(b, 'lua_luac_executable')}, \ 'command': '%e -p -', \ 'output_stream': 'stderr', \ 'callback': 'ale_linters#lua#luac#Handle', diff --git a/ale_linters/lua/luacheck.vim b/ale_linters/lua/luacheck.vim index 669103b8..34be2b5a 100644 --- a/ale_linters/lua/luacheck.vim +++ b/ale_linters/lua/luacheck.vim @@ -38,7 +38,7 @@ endfunction call ale#linter#Define('lua', { \ 'name': 'luacheck', -\ 'executable_callback': ale#VarFunc('lua_luacheck_executable'), -\ 'command_callback': 'ale_linters#lua#luacheck#GetCommand', +\ 'executable': {b -> ale#Var(b, 'lua_luacheck_executable')}, +\ 'command': function('ale_linters#lua#luacheck#GetCommand'), \ 'callback': 'ale_linters#lua#luacheck#Handle', \}) diff --git a/ale_linters/mail/alex.vim b/ale_linters/mail/alex.vim index b0651ccd..0fceea7b 100644 --- a/ale_linters/mail/alex.vim +++ b/ale_linters/mail/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> -" Description: alex for HTML files +" Description: alex for mail files -call ale#linter#Define('mail', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('mail', '--text') diff --git a/ale_linters/mail/languagetool.vim b/ale_linters/mail/languagetool.vim new file mode 100644 index 00000000..330fb8ec --- /dev/null +++ b/ale_linters/mail/languagetool.vim @@ -0,0 +1,5 @@ +" Author: Vincent (wahrwolf [ät] wolfpit.net) +" Description: languagetool for mails + + +call ale#handlers#languagetool#DefineLinter('mail') diff --git a/ale_linters/markdown/alex.vim b/ale_linters/markdown/alex.vim index 29306141..63769b5e 100644 --- a/ale_linters/markdown/alex.vim +++ b/ale_linters/markdown/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for markdown files -call ale#linter#Define('markdown', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('markdown', '') diff --git a/ale_linters/markdown/languagetool.vim b/ale_linters/markdown/languagetool.vim new file mode 100644 index 00000000..d6bca22e --- /dev/null +++ b/ale_linters/markdown/languagetool.vim @@ -0,0 +1,5 @@ +" Author: Vincent (wahrwolf [ät] wolfpit.net) +" Description: languagetool for markdown files + + +call ale#handlers#languagetool#DefineLinter('markdown') diff --git a/ale_linters/markdown/markdownlint.vim b/ale_linters/markdown/markdownlint.vim index 5c8af650..e935cbfe 100644 --- a/ale_linters/markdown/markdownlint.vim +++ b/ale_linters/markdown/markdownlint.vim @@ -2,10 +2,10 @@ " 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' -\ }) +\ '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 0953144b..305f5359 100644 --- a/ale_linters/markdown/mdl.vim +++ b/ale_linters/markdown/mdl.vim @@ -38,7 +38,7 @@ endfunction call ale#linter#Define('markdown', { \ 'name': 'mdl', -\ 'executable_callback': 'ale_linters#markdown#mdl#GetExecutable', -\ 'command_callback': 'ale_linters#markdown#mdl#GetCommand', +\ 'executable': function('ale_linters#markdown#mdl#GetExecutable'), +\ 'command': function('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 4f8d48fa..ed87d1ad 100644 --- a/ale_linters/markdown/remark_lint.vim +++ b/ale_linters/markdown/remark_lint.vim @@ -39,10 +39,10 @@ endfunction call ale#linter#Define('markdown', { \ 'name': 'remark_lint', \ 'aliases': ['remark-lint'], -\ 'executable_callback': ale#node#FindExecutableFunc('markdown_remark_lint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'markdown_remark_lint', [ \ 'node_modules/.bin/remark', -\ ]), -\ 'command_callback': 'ale_linters#markdown#remark_lint#GetCommand', +\ ])}, +\ 'command': function('ale_linters#markdown#remark_lint#GetCommand'), \ 'callback': 'ale_linters#markdown#remark_lint#Handle', \ 'output_stream': 'stderr', \}) diff --git a/ale_linters/markdown/textlint.vim b/ale_linters/markdown/textlint.vim index 26458506..613c8411 100644 --- a/ale_linters/markdown/textlint.vim +++ b/ale_linters/markdown/textlint.vim @@ -3,7 +3,7 @@ call ale#linter#Define('markdown', { \ 'name': 'textlint', -\ 'executable_callback': 'ale#handlers#textlint#GetExecutable', -\ 'command_callback': 'ale#handlers#textlint#GetCommand', +\ 'executable': function('ale#handlers#textlint#GetExecutable'), +\ 'command': function('ale#handlers#textlint#GetCommand'), \ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', \}) diff --git a/ale_linters/matlab/mlint.vim b/ale_linters/matlab/mlint.vim index 3435045e..f58f8b6d 100644 --- a/ale_linters/matlab/mlint.vim +++ b/ale_linters/matlab/mlint.vim @@ -37,7 +37,7 @@ endfunction call ale#linter#Define('matlab', { \ 'name': 'mlint', -\ 'executable_callback': ale#VarFunc('matlab_mlint_executable'), +\ 'executable': {b -> ale#Var(b, 'matlab_mlint_executable')}, \ 'command': '%e -id %t', \ 'output_stream': 'stderr', \ 'callback': 'ale_linters#matlab#mlint#Handle', diff --git a/ale_linters/mercury/mmc.vim b/ale_linters/mercury/mmc.vim index 76d357f0..8a9ccc0e 100644 --- a/ale_linters/mercury/mmc.vim +++ b/ale_linters/mercury/mmc.vim @@ -33,8 +33,8 @@ endfunction call ale#linter#Define('mercury', { \ 'name': 'mmc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('mercury_mmc_executable'), -\ 'command_callback': 'ale_linters#mercury#mmc#GetCommand', +\ 'executable': {b -> ale#Var(b, 'mercury_mmc_executable')}, +\ 'command': function('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 index cb2119a6..347abc1b 100644 --- a/ale_linters/nasm/nasm.vim +++ b/ale_linters/nasm/nasm.vim @@ -36,7 +36,7 @@ call ale#linter#Define('nasm', { \ 'name': 'nasm', \ 'output_stream': 'stderr', \ 'lint_file': 1, -\ 'executable_callback': ale#VarFunc('nasm_nasm_executable'), -\ 'command_callback': 'ale_linters#nasm#nasm#GetCommand', +\ 'executable': {b -> ale#Var(b, 'nasm_nasm_executable')}, +\ 'command': function('ale_linters#nasm#nasm#GetCommand'), \ 'callback': 'ale_linters#nasm#nasm#Handle', \}) diff --git a/ale_linters/nim/nimcheck.vim b/ale_linters/nim/nimcheck.vim index bff45f7d..b5796dcd 100644 --- a/ale_linters/nim/nimcheck.vim +++ b/ale_linters/nim/nimcheck.vim @@ -59,7 +59,7 @@ call ale#linter#Define('nim', { \ 'name': 'nimcheck', \ 'executable': 'nim', \ 'output_stream': 'both', -\ 'command_callback': 'ale_linters#nim#nimcheck#GetCommand', +\ 'command': function('ale_linters#nim#nimcheck#GetCommand'), \ 'callback': 'ale_linters#nim#nimcheck#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/nroff/alex.vim b/ale_linters/nroff/alex.vim index a10db2dd..3f06af26 100644 --- a/ale_linters/nroff/alex.vim +++ b/ale_linters/nroff/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for nroff files -call ale#linter#Define('nroff', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('nroff', '--text') diff --git a/ale_linters/objc/ccls.vim b/ale_linters/objc/ccls.vim index 0aa6a5e5..51ecf056 100644 --- a/ale_linters/objc/ccls.vim +++ b/ale_linters/objc/ccls.vim @@ -7,8 +7,8 @@ call ale#Set('objc_ccls_init_options', {}) call ale#linter#Define('objc', { \ 'name': 'ccls', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('objc_ccls_executable'), +\ 'executable': {b -> ale#Var(b, 'objc_ccls_executable')}, \ 'command': '%e', -\ 'project_root_callback': 'ale#handlers#ccls#GetProjectRoot', -\ 'initialization_options_callback': ale#VarFunc('objc_ccls_init_options'), +\ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), +\ 'initialization_options': {b -> ale#Var(b, 'objc_ccls_init_options')}, \}) diff --git a/ale_linters/objc/clang.vim b/ale_linters/objc/clang.vim index 4e80ac5c..7873dccd 100644 --- a/ale_linters/objc/clang.vim +++ b/ale_linters/objc/clang.vim @@ -18,6 +18,6 @@ call ale#linter#Define('objc', { \ 'name': 'clang', \ 'output_stream': 'stderr', \ 'executable': 'clang', -\ 'command_callback': 'ale_linters#objc#clang#GetCommand', +\ 'command': function('ale_linters#objc#clang#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', \}) diff --git a/ale_linters/objc/clangd.vim b/ale_linters/objc/clangd.vim index f090e6ce..ab52fec3 100644 --- a/ale_linters/objc/clangd.vim +++ b/ale_linters/objc/clangd.vim @@ -17,7 +17,7 @@ endfunction call ale#linter#Define('objc', { \ 'name': 'clangd', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('objc_clangd_executable'), -\ 'command_callback': 'ale_linters#objc#clangd#GetCommand', -\ 'project_root_callback': 'ale_linters#objc#clangd#GetProjectRoot', +\ 'executable': {b -> ale#Var(b, 'objc_clangd_executable')}, +\ 'command': function('ale_linters#objc#clangd#GetCommand'), +\ 'project_root': function('ale_linters#objc#clangd#GetProjectRoot'), \}) diff --git a/ale_linters/objcpp/clang.vim b/ale_linters/objcpp/clang.vim index d1474f17..4dbe55b3 100644 --- a/ale_linters/objcpp/clang.vim +++ b/ale_linters/objcpp/clang.vim @@ -18,6 +18,6 @@ call ale#linter#Define('objcpp', { \ 'name': 'clang', \ 'output_stream': 'stderr', \ 'executable': 'clang++', -\ 'command_callback': 'ale_linters#objcpp#clang#GetCommand', +\ 'command': function('ale_linters#objcpp#clang#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', \}) diff --git a/ale_linters/objcpp/clangd.vim b/ale_linters/objcpp/clangd.vim index a09753be..3991d2ac 100644 --- a/ale_linters/objcpp/clangd.vim +++ b/ale_linters/objcpp/clangd.vim @@ -17,7 +17,7 @@ endfunction call ale#linter#Define('objcpp', { \ 'name': 'clangd', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('objcpp_clangd_executable'), -\ 'command_callback': 'ale_linters#objcpp#clangd#GetCommand', -\ 'project_root_callback': 'ale_linters#objcpp#clangd#GetProjectRoot', +\ 'executable': {b -> ale#Var(b, 'objcpp_clangd_executable')}, +\ 'command': function('ale_linters#objcpp#clangd#GetCommand'), +\ 'project_root': function('ale_linters#objcpp#clangd#GetProjectRoot'), \}) diff --git a/ale_linters/ocaml/ols.vim b/ale_linters/ocaml/ols.vim index 077862fc..d8208c52 100644 --- a/ale_linters/ocaml/ols.vim +++ b/ale_linters/ocaml/ols.vim @@ -7,8 +7,8 @@ call ale#Set('ocaml_ols_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#linter#Define('ocaml', { \ 'name': 'ols', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale#handlers#ols#GetExecutable', -\ 'command_callback': 'ale#handlers#ols#GetCommand', +\ 'executable': function('ale#handlers#ols#GetExecutable'), +\ 'command': function('ale#handlers#ols#GetCommand'), \ 'language_callback': 'ale#handlers#ols#GetLanguage', -\ 'project_root_callback': 'ale#handlers#ols#GetProjectRoot', +\ 'project_root': function('ale#handlers#ols#GetProjectRoot'), \}) diff --git a/ale_linters/perl/perl.vim b/ale_linters/perl/perl.vim index 1cb20fa7..0f06528a 100644 --- a/ale_linters/perl/perl.vim +++ b/ale_linters/perl/perl.vim @@ -18,7 +18,7 @@ function! ale_linters#perl#perl#Handle(buffer, lines) abort return [] endif - let l:pattern = '\(.\+\) at \(.\+\) line \(\d\+\)' + let l:pattern = '\(..\{-}\) at \(..\{-}\) line \(\d\+\)' let l:output = [] let l:basename = expand('#' . a:buffer . ':t') @@ -57,8 +57,8 @@ endfunction call ale#linter#Define('perl', { \ 'name': 'perl', -\ 'executable_callback': ale#VarFunc('perl_perl_executable'), +\ 'executable': {b -> ale#Var(b, 'perl_perl_executable')}, \ 'output_stream': 'both', -\ 'command_callback': 'ale_linters#perl#perl#GetCommand', +\ 'command': function('ale_linters#perl#perl#GetCommand'), \ 'callback': 'ale_linters#perl#perl#Handle', \}) diff --git a/ale_linters/perl/perlcritic.vim b/ale_linters/perl/perlcritic.vim index 8619a404..f3154c09 100644 --- a/ale_linters/perl/perlcritic.vim +++ b/ale_linters/perl/perlcritic.vim @@ -55,7 +55,7 @@ endfunction call ale#linter#Define('perl', { \ 'name': 'perlcritic', \ 'output_stream': 'stdout', -\ 'executable_callback': ale#VarFunc('perl_perlcritic_executable'), -\ 'command_callback': 'ale_linters#perl#perlcritic#GetCommand', +\ 'executable': {b -> ale#Var(b, 'perl_perlcritic_executable')}, +\ 'command': function('ale_linters#perl#perlcritic#GetCommand'), \ 'callback': 'ale_linters#perl#perlcritic#Handle', \}) diff --git a/ale_linters/perl6/perl6.vim b/ale_linters/perl6/perl6.vim index b33a0c51..68ef4769 100644 --- a/ale_linters/perl6/perl6.vim +++ b/ale_linters/perl6/perl6.vim @@ -36,8 +36,8 @@ function! ale_linters#perl6#perl6#ExtractError(dict, item, type, buffer) abort endif if has_key(a:dict[a:item], 'line') && !empty(a:dict[a:item]['line']) - let l:line = a:dict[a:item]['line'] - let l:counter -= 1 + let l:line = a:dict[a:item]['line'] + let l:counter -= 1 endif if has_key(a:dict[a:item], 'column') && !empty(a:dict[a:item]['column']) @@ -61,7 +61,7 @@ function! ale_linters#perl6#perl6#ExtractError(dict, item, type, buffer) abort " Currently, filenames and line numbers are not always given in the error output if l:counter < 2 - \&& ( ale#path#IsBufferPath(a:buffer, l:file) || l:file is# '' ) + \&& ( ale#path#IsBufferPath(a:buffer, l:file) || l:file is# '' ) return { \ 'lnum': '' . l:line, \ 'text': l:text, @@ -83,7 +83,7 @@ function! ale_linters#perl6#perl6#Handle(buffer, lines) abort endif if a:lines[0] is# 'Syntax OK' - return l:output + return l:output endif try @@ -101,8 +101,8 @@ function! ale_linters#perl6#perl6#Handle(buffer, lines) abort if type(l:json) is v:t_dict for l:key in keys(l:json) - if has_key(l:json[l:key], 'sorrows') && - \ has_key(l:json[l:key], 'worries') + if has_key(l:json[l:key], 'sorrows') + \&& has_key(l:json[l:key], 'worries') if !empty(l:json[l:key]['sorrows']) for l:dictionary in get(l:json[l:key], 'sorrows') for l:item in keys(l:dictionary) @@ -115,7 +115,7 @@ function! ale_linters#perl6#perl6#Handle(buffer, lines) abort \ ) if l:result isnot# '' - call add(l:output, l:result) + call add(l:output, l:result) endif endfor endfor @@ -133,7 +133,7 @@ function! ale_linters#perl6#perl6#Handle(buffer, lines) abort \ ) if l:result isnot# '' - call add(l:output, l:result) + call add(l:output, l:result) endif endfor endfor @@ -147,7 +147,7 @@ function! ale_linters#perl6#perl6#Handle(buffer, lines) abort \ ) if l:result isnot# '' - call add(l:output, l:result) + call add(l:output, l:result) endif endif endfor @@ -158,9 +158,9 @@ endfunction call ale#linter#Define('perl6', { \ 'name': 'perl6', -\ 'executable_callback': 'ale_linters#perl6#perl6#GetExecutable', +\ 'executable': function('ale_linters#perl6#perl6#GetExecutable'), \ 'output_stream': 'both', -\ 'command_callback': 'ale_linters#perl6#perl6#GetCommand', +\ 'command': function('ale_linters#perl6#perl6#GetCommand'), \ 'callback': 'ale_linters#perl6#perl6#Handle', \}) diff --git a/ale_linters/php/langserver.vim b/ale_linters/php/langserver.vim index ca91db4c..fdd1bf2b 100644 --- a/ale_linters/php/langserver.vim +++ b/ale_linters/php/langserver.vim @@ -5,6 +5,12 @@ call ale#Set('php_langserver_executable', 'php-language-server.php') call ale#Set('php_langserver_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#php#langserver#GetProjectRoot(buffer) abort + let l:composer_path = ale#path#FindNearestFile(a:buffer, 'composer.json') + + if (!empty(l:composer_path)) + return fnamemodify(l:composer_path, ':h') + endif + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' @@ -13,9 +19,9 @@ endfunction call ale#linter#Define('php', { \ 'name': 'langserver', \ 'lsp': 'stdio', -\ 'executable_callback': ale#node#FindExecutableFunc('php_langserver', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'php_langserver', [ \ 'vendor/bin/php-language-server.php', -\ ]), +\ ])}, \ 'command': 'php %e', -\ 'project_root_callback': 'ale_linters#php#langserver#GetProjectRoot', +\ 'project_root': function('ale_linters#php#langserver#GetProjectRoot'), \}) diff --git a/ale_linters/php/phan.vim b/ale_linters/php/phan.vim index c6f16356..53cb1ea9 100644 --- a/ale_linters/php/phan.vim +++ b/ale_linters/php/phan.vim @@ -67,7 +67,7 @@ endfunction call ale#linter#Define('php', { \ 'name': 'phan', -\ 'executable_callback': 'ale_linters#php#phan#GetExecutable', -\ 'command_callback': 'ale_linters#php#phan#GetCommand', +\ 'executable': function('ale_linters#php#phan#GetExecutable'), +\ 'command': function('ale_linters#php#phan#GetCommand'), \ 'callback': 'ale_linters#php#phan#Handle', \}) diff --git a/ale_linters/php/php.vim b/ale_linters/php/php.vim index 5d87196c..51a109b9 100644 --- a/ale_linters/php/php.vim +++ b/ale_linters/php/php.vim @@ -32,7 +32,7 @@ endfunction call ale#linter#Define('php', { \ 'name': 'php', -\ 'executable_callback': ale#VarFunc('php_php_executable'), +\ 'executable': {b -> ale#Var(b, 'php_php_executable')}, \ 'output_stream': 'stdout', \ 'command': '%e -l -d error_reporting=E_ALL -d display_errors=1 -d log_errors=0 --', \ 'callback': 'ale_linters#php#php#Handle', diff --git a/ale_linters/php/phpcs.vim b/ale_linters/php/phpcs.vim index 408c2652..1c92bbb2 100644 --- a/ale_linters/php/phpcs.vim +++ b/ale_linters/php/phpcs.vim @@ -44,10 +44,10 @@ endfunction call ale#linter#Define('php', { \ 'name': 'phpcs', -\ 'executable_callback': ale#node#FindExecutableFunc('php_phpcs', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'php_phpcs', [ \ 'vendor/bin/phpcs', \ 'phpcs' -\ ]), -\ 'command_callback': 'ale_linters#php#phpcs#GetCommand', +\ ])}, +\ 'command': function('ale_linters#php#phpcs#GetCommand'), \ 'callback': 'ale_linters#php#phpcs#Handle', \}) diff --git a/ale_linters/php/phpmd.vim b/ale_linters/php/phpmd.vim index 65f1cc3c..9b1d1e44 100644 --- a/ale_linters/php/phpmd.vim +++ b/ale_linters/php/phpmd.vim @@ -32,7 +32,7 @@ endfunction call ale#linter#Define('php', { \ 'name': 'phpmd', -\ 'executable_callback': ale#VarFunc('php_phpmd_executable'), -\ 'command_callback': 'ale_linters#php#phpmd#GetCommand', +\ 'executable': {b -> ale#Var(b, 'php_phpmd_executable')}, +\ 'command': function('ale_linters#php#phpmd#GetCommand'), \ 'callback': 'ale_linters#php#phpmd#Handle', \}) diff --git a/ale_linters/php/phpstan.vim b/ale_linters/php/phpstan.vim index 1c831e1b..b621f855 100644 --- a/ale_linters/php/phpstan.vim +++ b/ale_linters/php/phpstan.vim @@ -3,44 +3,35 @@ " Set to change the ruleset let g:ale_php_phpstan_executable = get(g:, 'ale_php_phpstan_executable', 'phpstan') -let g:ale_php_phpstan_level = get(g:, 'ale_php_phpstan_level', '4') +let g:ale_php_phpstan_level = get(g:, 'ale_php_phpstan_level', '') let g:ale_php_phpstan_configuration = get(g:, 'ale_php_phpstan_configuration', '') -function! ale_linters#php#phpstan#GetExecutable(buffer) abort - return ale#Var(a:buffer, 'php_phpstan_executable') -endfunction +function! ale_linters#php#phpstan#GetCommand(buffer, version) abort + let l:configuration = ale#Var(a:buffer, 'php_phpstan_configuration') + let l:configuration_option = !empty(l:configuration) + \ ? ' -c ' . ale#Escape(l:configuration) + \ : '' -function! ale_linters#php#phpstan#VersionCheck(buffer) abort - let l:executable = ale_linters#php#phpstan#GetExecutable(a:buffer) + let l:level = ale#Var(a:buffer, 'php_phpstan_level') + let l:config_file_exists = ale#path#FindNearestFile(a:buffer, 'phpstan.neon') - " If we have previously stored the version number in a cache, then - " don't look it up again. - if ale#semver#HasVersion(l:executable) - " Returning an empty string skips this command. - return '' + if empty(l:level) && empty(l:config_file_exists) + " if no configuration file is found, then use 4 as a default level + let l:level = '4' endif - let l:executable = ale#Escape(l:executable) - - return l:executable . ' --version' -endfunction - -function! ale_linters#php#phpstan#GetCommand(buffer, version_output) abort - let l:configuration = ale#Var(a:buffer, 'php_phpstan_configuration') - let l:configuration_option = !empty(l:configuration) - \ ? ' -c ' . l:configuration + let l:level_option = !empty(l:level) + \ ? ' -l ' . ale#Escape(l:level) \ : '' - let l:executable = ale_linters#php#phpstan#GetExecutable(a:buffer) - let l:version = ale#semver#GetVersion(l:executable, a:version_output) - let l:error_format = ale#semver#GTE(l:version, [0, 10, 3]) + let l:error_format = ale#semver#GTE(a:version, [0, 10, 3]) \ ? ' --error-format raw' \ : ' --errorFormat raw' - return '%e analyze -l' - \ . ale#Var(a:buffer, 'php_phpstan_level') + return '%e analyze --no-progress' \ . l:error_format \ . l:configuration_option + \ . l:level_option \ . ' %s' endfunction @@ -56,7 +47,7 @@ function! ale_linters#php#phpstan#Handle(buffer, lines) abort call add(l:output, { \ 'lnum': l:match[2] + 0, \ 'text': l:match[3], - \ 'type': 'W', + \ 'type': 'E', \}) endfor @@ -65,10 +56,12 @@ endfunction call ale#linter#Define('php', { \ 'name': 'phpstan', -\ 'executable_callback': 'ale_linters#php#phpstan#GetExecutable', -\ 'command_chain': [ -\ {'callback': 'ale_linters#php#phpstan#VersionCheck'}, -\ {'callback': 'ale_linters#php#phpstan#GetCommand'}, -\ ], +\ 'executable': {b -> ale#Var(b, 'php_phpstan_executable')}, +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'php_phpstan_executable'), +\ '%e --version', +\ function('ale_linters#php#phpstan#GetCommand'), +\ )}, \ 'callback': 'ale_linters#php#phpstan#Handle', \}) diff --git a/ale_linters/php/psalm.vim b/ale_linters/php/psalm.vim index dce59178..3cdb026a 100644 --- a/ale_linters/php/psalm.vim +++ b/ale_linters/php/psalm.vim @@ -13,9 +13,9 @@ endfunction call ale#linter#Define('php', { \ 'name': 'psalm', \ 'lsp': 'stdio', -\ 'executable_callback': ale#node#FindExecutableFunc('psalm_langserver', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'psalm_langserver', [ \ 'vendor/bin/psalm-language-server', -\ ]), +\ ])}, \ 'command': '%e', -\ 'project_root_callback': 'ale_linters#php#psalm#GetProjectRoot', +\ 'project_root': function('ale_linters#php#psalm#GetProjectRoot'), \}) diff --git a/ale_linters/po/alex.vim b/ale_linters/po/alex.vim index 411d835b..05c67f15 100644 --- a/ale_linters/po/alex.vim +++ b/ale_linters/po/alex.vim @@ -1,11 +1,4 @@ " Author: Cian Butler https://github.com/butlerx " Description: alex for PO files -call ale#linter#Define('po', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('po', '--text') diff --git a/ale_linters/pod/alex.vim b/ale_linters/pod/alex.vim index 5c09befb..c89f8330 100644 --- a/ale_linters/pod/alex.vim +++ b/ale_linters/pod/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for pod files -call ale#linter#Define('pod', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('pod', '--text') diff --git a/ale_linters/pony/ponyc.vim b/ale_linters/pony/ponyc.vim index 19e7e828..6d4594f9 100644 --- a/ale_linters/pony/ponyc.vim +++ b/ale_linters/pony/ponyc.vim @@ -10,7 +10,7 @@ endfunction call ale#linter#Define('pony', { \ 'name': 'ponyc', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('pony_ponyc_executable'), -\ 'command_callback': 'ale_linters#pony#ponyc#GetCommand', +\ 'executable': {b -> ale#Var(b, 'pony_ponyc_executable')}, +\ 'command': function('ale_linters#pony#ponyc#GetCommand'), \ 'callback': 'ale#handlers#pony#HandlePonycFormat', \}) diff --git a/ale_linters/powershell/powershell.vim b/ale_linters/powershell/powershell.vim new file mode 100755 index 00000000..51ded71d --- /dev/null +++ b/ale_linters/powershell/powershell.vim @@ -0,0 +1,91 @@ +" Author: Jesse Harris - https://github.com/zigford +" Description: This file adds support for powershell scripts synatax errors + +call ale#Set('powershell_powershell_executable', 'pwsh') + +function! ale_linters#powershell#powershell#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'powershell_powershell_executable') +endfunction + +" Some powershell magic to show syntax errors without executing the script +" thanks to keith hill: +" https://rkeithhill.wordpress.com/2007/10/30/powershell-quicktip-preparsing-scripts-to-check-for-syntax-errors/ +function! ale_linters#powershell#powershell#GetCommand(buffer) abort + let l:script = ['Param($Script); + \ trap {$_;continue} & { + \ $Contents = Get-Content -Path $Script; + \ $Contents = [string]::Join([Environment]::NewLine, $Contents); + \ [void]$ExecutionContext.InvokeCommand.NewScriptBlock($Contents); + \ };'] + + return ale#powershell#RunPowerShell( + \ a:buffer, 'powershell_powershell', l:script) +endfunction + +" Parse powershell error output using regex into a list of dicts +function! ale_linters#powershell#powershell#Handle(buffer, lines) abort + let l:output = [] + " Our 3 patterns we need to scrape the data for the dicts + let l:patterns = [ + \ '\v^At line:(\d+) char:(\d+)', + \ '\v^(At|\+| )@!.*', + \ '\vFullyQualifiedErrorId : (\w+)', + \] + + let l:matchcount = 0 + + for l:match in ale#util#GetMatches(a:lines, l:patterns) + " We want to work with 3 matches per syntax error + let l:matchcount = l:matchcount + 1 + + if l:matchcount == 1 || str2nr(l:match[1]) + " First match consists of 2 capture groups, and + " can capture the line and col + if exists('l:item') + " We may be here because the last syntax + " didn't emit a code, and so only had 2 + " matches + call add(l:output, l:item) + let l:matchcount = 1 + endif + + let l:item = { + \ 'lnum': str2nr(l:match[1]), + \ 'col': str2nr(l:match[2]), + \ 'type': 'E', + \} + elseif l:matchcount == 2 + " Second match[0] grabs the full line in order + " to handles the text + let l:item['text'] = l:match[0] + else + " Final match handles the code, however + " powershell only emits 1 code for all errors + " so, we get the final code on the last error + " and loop over the previously added items to + " append the code we now know + call add(l:output, l:item) + unlet l:item + + if len(l:match[1]) > 0 + for l:i in l:output + let l:i['code'] = l:match[1] + endfor + endif + + " Reset the matchcount so we can begin gathering + " matches for the next syntax error + let l:matchcount = 0 + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('powershell', { +\ 'name': 'powershell', +\ 'executable_callback': 'ale_linters#powershell#powershell#GetExecutable', +\ 'command_callback': 'ale_linters#powershell#powershell#GetCommand', +\ 'output_stream': 'stdout', +\ 'callback': 'ale_linters#powershell#powershell#Handle', +\}) diff --git a/ale_linters/powershell/psscriptanalyzer.vim b/ale_linters/powershell/psscriptanalyzer.vim new file mode 100644 index 00000000..4794d9d8 --- /dev/null +++ b/ale_linters/powershell/psscriptanalyzer.vim @@ -0,0 +1,76 @@ +" Author: Jesse Harris - https://github.com/zigford +" Description: This file adds support for lintng powershell scripts +" using the PSScriptAnalyzer module. + +" let g:ale_powershell_psscriptanalyzer_exclusions = +" \ 'PSAvoidUsingWriteHost,PSAvoidGlobalVars' +call ale#Set('powershell_psscriptanalyzer_exclusions', '') +call ale#Set('powershell_psscriptanalyzer_executable', 'pwsh') +call ale#Set('powershell_psscriptanalyzer_module', +\ 'psscriptanalyzer') + +function! ale_linters#powershell#psscriptanalyzer#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'powershell_psscriptanalyzer_executable') +endfunction + +" Run Invoke-ScriptAnalyzer and output each linting message as 4 seperate lines +" for each parsing +function! ale_linters#powershell#psscriptanalyzer#GetCommand(buffer) abort + let l:exclude_option = ale#Var( + \ a:buffer, 'powershell_psscriptanalyzer_exclusions') + let l:module = ale#Var( + \ a:buffer, 'powershell_psscriptanalyzer_module') + let l:script = ['Param($Script); + \ Invoke-ScriptAnalyzer "$Script" ' + \ . (!empty(l:exclude_option) ? '-Exclude ' . l:exclude_option : '') + \ . '| ForEach-Object { + \ $_.Line; + \ $_.Severity; + \ $_.Message; + \ $_.RuleName}'] + + return ale#powershell#RunPowerShell( + \ a:buffer, + \ 'powershell_psscriptanalyzer', + \ l:script) +endfunction + +" add every 4 lines to an item(Dict) and every item to a list +" return the list +function! ale_linters#powershell#psscriptanalyzer#Handle(buffer, lines) abort + let l:output = [] + let l:lcount = 0 + + for l:line in a:lines + if l:lcount is# 0 + " the very first line + let l:item = {'lnum': str2nr(l:line)} + elseif l:lcount is# 1 + if l:line is# 'Error' + let l:item['type'] = 'E' + elseif l:line is# 'Information' + let l:item['type'] = 'I' + else + let l:item['type'] = 'W' + endif + elseif l:lcount is# 2 + let l:item['text'] = l:line + elseif l:lcount is# 3 + let l:item['code'] = l:line + call add(l:output, l:item) + let l:lcount = -1 + endif + + let l:lcount = l:lcount + 1 + endfor + + return l:output +endfunction + +call ale#linter#Define('powershell', { +\ 'name': 'psscriptanalyzer', +\ 'executable': function('ale_linters#powershell#psscriptanalyzer#GetExecutable'), +\ 'command': function('ale_linters#powershell#psscriptanalyzer#GetCommand'), +\ 'output_stream': 'stdout', +\ 'callback': 'ale_linters#powershell#psscriptanalyzer#Handle', +\}) diff --git a/ale_linters/prolog/swipl.vim b/ale_linters/prolog/swipl.vim index 401e52b6..5c601c40 100644 --- a/ale_linters/prolog/swipl.vim +++ b/ale_linters/prolog/swipl.vim @@ -87,14 +87,14 @@ endfunction " Skip sandbox error which is caused by directives " because what we want is syntactic or semantic check. function! s:Ignore(item) abort - return a:item.type is# 'E' && - \ a:item.text =~# '\vNo permission to (call|directive|assert) sandboxed' + return a:item.type is# 'E' + \ && a:item.text =~# '\vNo permission to (call|directive|assert) sandboxed' endfunction call ale#linter#Define('prolog', { \ 'name': 'swipl', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('prolog_swipl_executable'), -\ 'command_callback': 'ale_linters#prolog#swipl#GetCommand', +\ 'executable': {b -> ale#Var(b, 'prolog_swipl_executable')}, +\ 'command': function('ale_linters#prolog#swipl#GetCommand'), \ 'callback': 'ale_linters#prolog#swipl#Handle', \}) diff --git a/ale_linters/proto/protoc_gen_lint.vim b/ale_linters/proto/protoc_gen_lint.vim index c8b5c331..c3d10935 100644 --- a/ale_linters/proto/protoc_gen_lint.vim +++ b/ale_linters/proto/protoc_gen_lint.vim @@ -22,6 +22,6 @@ call ale#linter#Define('proto', { \ 'lint_file': 1, \ 'output_stream': 'stderr', \ 'executable': 'protoc', -\ 'command_callback': 'ale_linters#proto#protoc_gen_lint#GetCommand', +\ 'command': function('ale_linters#proto#protoc_gen_lint#GetCommand'), \ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/pug/puglint.vim b/ale_linters/pug/puglint.vim index 63208986..c4e0e233 100644 --- a/ale_linters/pug/puglint.vim +++ b/ale_linters/pug/puglint.vim @@ -33,10 +33,10 @@ endfunction call ale#linter#Define('pug', { \ 'name': 'puglint', -\ 'executable_callback': ale#node#FindExecutableFunc('pug_puglint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'pug_puglint', [ \ 'node_modules/.bin/pug-lint', -\ ]), +\ ])}, \ 'output_stream': 'stderr', -\ 'command_callback': 'ale_linters#pug#puglint#GetCommand', +\ 'command': function('ale_linters#pug#puglint#GetCommand'), \ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/puppet/languageserver.vim b/ale_linters/puppet/languageserver.vim index a3060e65..2078695f 100644 --- a/ale_linters/puppet/languageserver.vim +++ b/ale_linters/puppet/languageserver.vim @@ -30,8 +30,8 @@ endfunction call ale#linter#Define('puppet', { \ 'name': 'languageserver', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('puppet_languageserver_executable'), +\ 'executable': {b -> ale#Var(b, 'puppet_languageserver_executable')}, \ 'command': '%e --stdio', \ 'language': 'puppet', -\ 'project_root_callback': 'ale_linters#puppet#languageserver#GetProjectRoot', +\ 'project_root': function('ale_linters#puppet#languageserver#GetProjectRoot'), \}) diff --git a/ale_linters/puppet/puppet.vim b/ale_linters/puppet/puppet.vim index 0e37bdbd..ae648615 100644 --- a/ale_linters/puppet/puppet.vim +++ b/ale_linters/puppet/puppet.vim @@ -30,8 +30,8 @@ endfunction call ale#linter#Define('puppet', { \ 'name': 'puppet', -\ 'executable_callback': ale#VarFunc('puppet_puppet_executable'), +\ 'executable': {b -> ale#Var(b, 'puppet_puppet_executable')}, \ 'output_stream': 'stderr', -\ 'command_callback': 'ale_linters#puppet#puppet#GetCommand', +\ 'command': function('ale_linters#puppet#puppet#GetCommand'), \ 'callback': 'ale_linters#puppet#puppet#Handle', \}) diff --git a/ale_linters/puppet/puppetlint.vim b/ale_linters/puppet/puppetlint.vim index c9c16f5e..985d6a4d 100644 --- a/ale_linters/puppet/puppetlint.vim +++ b/ale_linters/puppet/puppetlint.vim @@ -12,7 +12,7 @@ endfunction call ale#linter#Define('puppet', { \ 'name': 'puppetlint', -\ 'executable_callback': ale#VarFunc('puppet_puppetlint_executable'), -\ 'command_callback': 'ale_linters#puppet#puppetlint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'puppet_puppetlint_executable')}, +\ 'command': function('ale_linters#puppet#puppetlint#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/pyrex/cython.vim b/ale_linters/pyrex/cython.vim index d260698c..84382ba1 100644 --- a/ale_linters/pyrex/cython.vim +++ b/ale_linters/pyrex/cython.vim @@ -32,7 +32,7 @@ endfunction call ale#linter#Define('pyrex', { \ 'name': 'cython', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('pyrex_cython_executable'), -\ 'command_callback': 'ale_linters#pyrex#cython#GetCommand', +\ 'executable': {b -> ale#Var(b, 'pyrex_cython_executable')}, +\ 'command': function('ale_linters#pyrex#cython#GetCommand'), \ 'callback': 'ale_linters#pyrex#cython#Handle', \}) diff --git a/ale_linters/python/bandit.vim b/ale_linters/python/bandit.vim new file mode 100644 index 00000000..554f5000 --- /dev/null +++ b/ale_linters/python/bandit.vim @@ -0,0 +1,68 @@ +" Author: Martino Pilia <martino.pilia@gmail.com> +" Description: bandit linting for python files + +call ale#Set('python_bandit_executable', 'bandit') +call ale#Set('python_bandit_options', '') +call ale#Set('python_bandit_use_config', 1) +call ale#Set('python_bandit_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_bandit_auto_pipenv', 0) + +function! ale_linters#python#bandit#GetExecutable(buffer) abort + if ( + \ ale#Var(a:buffer, 'python_auto_pipenv') + \ || ale#Var(a:buffer, 'python_bandit_auto_pipenv') + \) && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_bandit', ['bandit']) +endfunction + +function! ale_linters#python#bandit#GetCommand(buffer) abort + let l:executable = ale_linters#python#bandit#GetExecutable(a:buffer) + let l:flags = ' --format custom' + \ . ' --msg-template "{line}:{test_id}:{severity}:{msg}" ' + + if ale#Var(a:buffer, 'python_bandit_use_config') + let l:config_path = ale#path#FindNearestFile(a:buffer, '.bandit') + + if !empty(l:config_path) + let l:flags = ' --ini ' . ale#Escape(l:config_path) . l:flags + endif + endif + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run bandit' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . l:flags + \ . ale#Pad(ale#Var(a:buffer, 'python_bandit_options')) + \ . ' -' +endfunction + +function! ale_linters#python#bandit#Handle(buffer, lines) abort + " Custom format defined in GetCommand via --msg-template + let l:pattern = '\v^([0-9]+):(B[0-9]+):([A-Z]+):(.*)$' + let l:severity = {'LOW': 'I', 'MEDIUM': 'W', 'HIGH': 'E'} + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': str2nr(l:match[1]), + \ 'code': l:match[2], + \ 'type': l:severity[l:match[3]], + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'bandit', +\ 'executable': function('ale_linters#python#bandit#GetExecutable'), +\ 'command': function('ale_linters#python#bandit#GetCommand'), +\ 'callback': 'ale_linters#python#bandit#Handle', +\}) diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index 14b67d77..e2e7b743 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -24,28 +24,25 @@ function! ale_linters#python#flake8#GetExecutable(buffer) abort return ale#Var(a:buffer, 'python_flake8_executable') endfunction -function! ale_linters#python#flake8#VersionCheck(buffer) abort +function! ale_linters#python#flake8#RunWithVersionCheck(buffer) abort let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) - " If we have previously stored the version number in a cache, then - " don't look it up again. - if ale#semver#HasVersion(l:executable) - " Returning an empty string skips this command. - return '' - endif - - let l:executable = ale#Escape(l:executable) let l:module_string = s:UsingModule(a:buffer) ? ' -m flake8' : '' - - return l:executable . l:module_string . ' --version' + let l:command = ale#Escape(l:executable) . l:module_string . ' --version' + + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ l:command, + \ function('ale_linters#python#flake8#GetCommand'), + \) endfunction -function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort +function! ale_linters#python#flake8#GetCommand(buffer, version) abort 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' @@ -53,7 +50,7 @@ function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort " 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]) + let l:display_name_args = ale#semver#GTE(a:version, [3, 0, 0]) \ ? ' --stdin-display-name %s' \ : '' @@ -74,23 +71,18 @@ let s:end_col_pattern_map = { \} function! ale_linters#python#flake8#Handle(buffer, lines) abort - for l:line in a:lines[:10] - if match(l:line, '^Traceback') >= 0 - return [{ - \ 'lnum': 1, - \ 'text': 'An exception was thrown. See :ALEDetail', - \ 'detail': join(a:lines, "\n"), - \}] - endif - endfor + let l:output = ale#python#HandleTraceback(a:lines, 10) + + if !empty(l:output) + return l:output + endif " Matches patterns line the following: " " Matches patterns line the following: " " stdin:6:6: E111 indentation is not a multiple of four - let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):?(\d+)?: ([[:alnum:]]+) (.*)$' - let l:output = [] + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):?(\d+)?: ([[:alnum:]]+):? (.*)$' for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:code = l:match[3] @@ -148,10 +140,7 @@ endfunction call ale#linter#Define('python', { \ 'name': 'flake8', -\ 'executable_callback': 'ale_linters#python#flake8#GetExecutable', -\ 'command_chain': [ -\ {'callback': 'ale_linters#python#flake8#VersionCheck'}, -\ {'callback': 'ale_linters#python#flake8#GetCommand', 'output_stream': 'both'}, -\ ], +\ 'executable': function('ale_linters#python#flake8#GetExecutable'), +\ 'command': function('ale_linters#python#flake8#RunWithVersionCheck'), \ 'callback': 'ale_linters#python#flake8#Handle', \}) diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim index 0c90a3c7..c4c6507f 100644 --- a/ale_linters/python/mypy.vim +++ b/ale_linters/python/mypy.vim @@ -75,7 +75,7 @@ endfunction call ale#linter#Define('python', { \ 'name': 'mypy', -\ 'executable_callback': 'ale_linters#python#mypy#GetExecutable', -\ 'command_callback': 'ale_linters#python#mypy#GetCommand', +\ 'executable': function('ale_linters#python#mypy#GetExecutable'), +\ 'command': function('ale_linters#python#mypy#GetCommand'), \ 'callback': 'ale_linters#python#mypy#Handle', \}) diff --git a/ale_linters/python/prospector.vim b/ale_linters/python/prospector.vim index b01cec87..ee47012f 100644 --- a/ale_linters/python/prospector.vim +++ b/ale_linters/python/prospector.vim @@ -93,8 +93,8 @@ endfunction call ale#linter#Define('python', { \ 'name': 'prospector', -\ 'executable_callback': 'ale_linters#python#prospector#GetExecutable', -\ 'command_callback': 'ale_linters#python#prospector#GetCommand', +\ 'executable': function('ale_linters#python#prospector#GetExecutable'), +\ 'command': function('ale_linters#python#prospector#GetCommand'), \ 'callback': 'ale_linters#python#prospector#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/python/pycodestyle.vim b/ale_linters/python/pycodestyle.vim index f0269585..fb521bc1 100644 --- a/ale_linters/python/pycodestyle.vim +++ b/ale_linters/python/pycodestyle.vim @@ -69,7 +69,7 @@ endfunction call ale#linter#Define('python', { \ 'name': 'pycodestyle', -\ 'executable_callback': 'ale_linters#python#pycodestyle#GetExecutable', -\ 'command_callback': 'ale_linters#python#pycodestyle#GetCommand', +\ 'executable': function('ale_linters#python#pycodestyle#GetExecutable'), +\ 'command': function('ale_linters#python#pycodestyle#GetCommand'), \ 'callback': 'ale_linters#python#pycodestyle#Handle', \}) diff --git a/ale_linters/python/pydocstyle.vim b/ale_linters/python/pydocstyle.vim index ebf92bf1..3901db4d 100644 --- a/ale_linters/python/pydocstyle.vim +++ b/ale_linters/python/pydocstyle.vim @@ -33,8 +33,7 @@ function! ale_linters#python#pydocstyle#Handle(buffer, lines) abort " Matches patterns like the following: " mydir/myfile.py:33 in public function `myfunction`: " DXXX: Error description - let l:fname = ale#Escape(fnamemodify(bufname(a:buffer), ':p:t')) - let l:line1_pattern = '\v^' . l:fname . ':\s*(\d+)\s+.*$' + let l:line1_pattern = '\v^.*:\s*(\d+)\s+.*$' let l:line2_pattern = '\v^.*([a-zA-Z]\d+):\s*(.*)$' let l:output = [] @@ -68,7 +67,7 @@ endfunction call ale#linter#Define('python', { \ 'name': 'pydocstyle', -\ 'executable_callback': 'ale_linters#python#pydocstyle#GetExecutable', -\ 'command_callback': 'ale_linters#python#pydocstyle#GetCommand', +\ 'executable': function('ale_linters#python#pydocstyle#GetExecutable'), +\ 'command': function('ale_linters#python#pydocstyle#GetCommand'), \ 'callback': 'ale_linters#python#pydocstyle#Handle', \}) diff --git a/ale_linters/python/pyflakes.vim b/ale_linters/python/pyflakes.vim index 091408d5..b5127022 100644 --- a/ale_linters/python/pyflakes.vim +++ b/ale_linters/python/pyflakes.vim @@ -43,8 +43,8 @@ endfunction call ale#linter#Define('python', { \ 'name': 'pyflakes', -\ 'executable_callback': 'ale_linters#python#pyflakes#GetExecutable', -\ 'command_callback': 'ale_linters#python#pyflakes#GetCommand', +\ 'executable': function('ale_linters#python#pyflakes#GetExecutable'), +\ 'command': function('ale_linters#python#pyflakes#GetCommand'), \ 'callback': 'ale_linters#python#pyflakes#Handle', \ 'output_stream': 'both', \}) diff --git a/ale_linters/python/pylama.vim b/ale_linters/python/pylama.vim new file mode 100644 index 00000000..38dd2836 --- /dev/null +++ b/ale_linters/python/pylama.vim @@ -0,0 +1,92 @@ +" Author: Kevin Locke <kevin@kevinlocke.name> +" Description: pylama for python files + +call ale#Set('python_pylama_executable', 'pylama') +call ale#Set('python_pylama_options', '') +call ale#Set('python_pylama_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pylama_auto_pipenv', 0) +call ale#Set('python_pylama_change_directory', 1) + +function! ale_linters#python#pylama#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pylama_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pylama', ['pylama']) +endfunction + +function! ale_linters#python#pylama#GetCommand(buffer) abort + let l:cd_string = '' + + if ale#Var(a:buffer, 'python_pylama_change_directory') + " Pylama loads its configuration from the current directory only, and + " applies file masks using paths relative to the current directory. + " Run from project root, if found, otherwise buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + let l:cd_string = l:project_root isnot# '' + \ ? ale#path#CdString(l:project_root) + \ : ale#path#BufferCdString(a:buffer) + endif + + let l:executable = ale_linters#python#pylama#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pylama' + \ : '' + + " Note: Using %t to lint changes would be preferable, but many pylama + " checks use surrounding paths (e.g. C0103 module name, E0402 relative + " import beyond top, etc.). Neither is ideal. + return l:cd_string + \ . ale#Escape(l:executable) . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_pylama_options')) + \ . ' %s' +endfunction + +function! ale_linters#python#pylama#Handle(buffer, lines) abort + if empty(a:lines) + return [] + endif + + let l:output = ale#python#HandleTraceback(a:lines, 1) + let l:pattern = '\v^.{-}:([0-9]+):([0-9]+): +%(([A-Z][0-9]+):? +)?(.*)$' + + " First letter of error code is a pylint-compatible message type + " http://pylint.pycqa.org/en/latest/user_guide/output.html#source-code-analysis-section + " D is for Documentation (pydocstyle) + let l:pylint_type_to_ale_type = { + \ 'I': 'I', + \ 'R': 'W', + \ 'C': 'W', + \ 'W': 'W', + \ 'E': 'E', + \ 'F': 'E', + \ 'D': 'W', + \} + let l:pylint_type_to_ale_sub_type = { + \ 'R': 'style', + \ 'C': 'style', + \ 'D': 'style', + \} + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[1]), + \ 'col': str2nr(l:match[2]), + \ 'code': l:match[3], + \ 'type': get(l:pylint_type_to_ale_type, l:match[3][0], 'W'), + \ 'sub_type': get(l:pylint_type_to_ale_sub_type, l:match[3][0], ''), + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pylama', +\ 'executable': function('ale_linters#python#pylama#GetExecutable'), +\ 'command': function('ale_linters#python#pylama#GetCommand'), +\ 'callback': 'ale_linters#python#pylama#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim index 01c3cb37..57e82691 100644 --- a/ale_linters/python/pylint.vim +++ b/ale_linters/python/pylint.vim @@ -17,9 +17,17 @@ function! ale_linters#python#pylint#GetExecutable(buffer) abort endfunction function! ale_linters#python#pylint#GetCommand(buffer) abort - let l:cd_string = ale#Var(a:buffer, 'python_pylint_change_directory') - \ ? ale#path#BufferCdString(a:buffer) - \ : '' + let l:cd_string = '' + + if ale#Var(a:buffer, 'python_pylint_change_directory') + " pylint only checks for pylintrc in the packages above its current + " directory before falling back to user and global pylintrc. + " Run from project root, if found, otherwise buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + let l:cd_string = l:project_root isnot# '' + \ ? ale#path#CdString(l:project_root) + \ : ale#path#BufferCdString(a:buffer) + endif let l:executable = ale_linters#python#pylint#GetExecutable(a:buffer) @@ -53,7 +61,7 @@ function! ale_linters#python#pylint#Handle(buffer, lines) abort if l:code is# 'I0011' " Skip 'Locally disabling' message - continue + continue endif call add(l:output, { @@ -70,8 +78,8 @@ endfunction call ale#linter#Define('python', { \ 'name': 'pylint', -\ 'executable_callback': 'ale_linters#python#pylint#GetExecutable', -\ 'command_callback': 'ale_linters#python#pylint#GetCommand', +\ 'executable': function('ale_linters#python#pylint#GetExecutable'), +\ 'command': function('ale_linters#python#pylint#GetCommand'), \ 'callback': 'ale_linters#python#pylint#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/python/pyls.vim b/ale_linters/python/pyls.vim index 83fe8066..c7f91430 100644 --- a/ale_linters/python/pyls.vim +++ b/ale_linters/python/pyls.vim @@ -4,6 +4,7 @@ call ale#Set('python_pyls_executable', 'pyls') call ale#Set('python_pyls_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('python_pyls_auto_pipenv', 0) +call ale#Set('python_pyls_config', {}) function! ale_linters#python#pyls#GetExecutable(buffer) abort if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pyls_auto_pipenv')) @@ -27,8 +28,9 @@ endfunction call ale#linter#Define('python', { \ 'name': 'pyls', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#python#pyls#GetExecutable', -\ 'command_callback': 'ale_linters#python#pyls#GetCommand', -\ 'project_root_callback': 'ale#python#FindProjectRoot', +\ 'executable': function('ale_linters#python#pyls#GetExecutable'), +\ 'command': function('ale_linters#python#pyls#GetCommand'), +\ 'project_root': function('ale#python#FindProjectRoot'), \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', +\ 'lsp_config': {b -> ale#Var(b, 'python_pyls_config')}, \}) diff --git a/ale_linters/python/pyre.vim b/ale_linters/python/pyre.vim index adc185f2..4edd80f7 100644 --- a/ale_linters/python/pyre.vim +++ b/ale_linters/python/pyre.vim @@ -27,8 +27,8 @@ endfunction call ale#linter#Define('python', { \ 'name': 'pyre', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale_linters#python#pyre#GetExecutable', -\ 'command_callback': 'ale_linters#python#pyre#GetCommand', -\ 'project_root_callback': 'ale#python#FindProjectRoot', +\ 'executable': function('ale_linters#python#pyre#GetExecutable'), +\ 'command': function('ale_linters#python#pyre#GetCommand'), +\ 'project_root': function('ale#python#FindProjectRoot'), \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', \}) diff --git a/ale_linters/python/vulture.vim b/ale_linters/python/vulture.vim index 80828013..d328d262 100644 --- a/ale_linters/python/vulture.vim +++ b/ale_linters/python/vulture.vim @@ -46,19 +46,14 @@ endfunction function! ale_linters#python#vulture#Handle(buffer, lines) abort - for l:line in a:lines[:10] - if match(l:line, '^Traceback') >= 0 - return [{ - \ 'lnum': 1, - \ 'text': 'An exception was thrown. See :ALEDetail', - \ 'detail': join(a:lines, "\n"), - \}] - endif - endfor + let l:output = ale#python#HandleTraceback(a:lines, 10) + + if !empty(l:output) + return l:output + endif " Matches patterns line the following: let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+): (.*)$' - let l:output = [] let l:dir = s:GetDir(a:buffer) for l:match in ale#util#GetMatches(a:lines, l:pattern) @@ -78,8 +73,8 @@ endfunction call ale#linter#Define('python', { \ 'name': 'vulture', -\ 'executable_callback': 'ale_linters#python#vulture#GetExecutable', -\ 'command_callback': 'ale_linters#python#vulture#GetCommand', +\ 'executable': function('ale_linters#python#vulture#GetExecutable'), +\ 'command': function('ale_linters#python#vulture#GetCommand'), \ 'callback': 'ale_linters#python#vulture#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/qml/qmlfmt.vim b/ale_linters/qml/qmlfmt.vim index 12f3e97b..11cc9413 100644 --- a/ale_linters/qml/qmlfmt.vim +++ b/ale_linters/qml/qmlfmt.vim @@ -19,7 +19,7 @@ endfunction call ale#linter#Define('qml', { \ 'name': 'qmlfmt', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('qml_qmlfmt_executable'), +\ 'executable': {b -> ale#Var(b, 'qml_qmlfmt_executable')}, \ 'command': '%e -e', \ 'callback': 'ale_linters#qml#qmlfmt#Handle', \}) diff --git a/ale_linters/r/lintr.vim b/ale_linters/r/lintr.vim index 8f74c9b8..3164c06f 100644 --- a/ale_linters/r/lintr.vim +++ b/ale_linters/r/lintr.vim @@ -29,7 +29,7 @@ endfunction call ale#linter#Define('r', { \ 'name': 'lintr', \ 'executable': 'Rscript', -\ 'command_callback': 'ale_linters#r#lintr#GetCommand', +\ 'command': function('ale_linters#r#lintr#GetCommand'), \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \ 'output_stream': 'both', \}) diff --git a/ale_linters/racket/raco.vim b/ale_linters/racket/raco.vim new file mode 100644 index 00000000..e5ee4fb4 --- /dev/null +++ b/ale_linters/racket/raco.vim @@ -0,0 +1,33 @@ +" Author: aqui18 <https://github.com/aqui18> +" Description: This file adds support for checking Racket code with raco. +" This is the same form of syntax-checking used by DrRacket as well. The +" downside is that it will only catch the first error, but none of the +" subsequent ones. This is due to how evaluation in Racket works. + +function! ale_linters#racket#raco#Handle(buffer, lines) abort + " Matches patterns + " <file>:<line>:<column> <message> + " eg: + " info.rkt:4:0: infotab-module: not a well-formed definition + let l:pattern = '^\(\s\)\@!\(.\+\):\(\d\+\):\(\d\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'col': l:match[4] + 0, + \ 'type': 'E', + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('racket', { +\ 'name': 'raco', +\ 'executable': 'raco', +\ 'output_stream': 'stderr', +\ 'command': 'raco expand %s', +\ 'callback': 'ale_linters#racket#raco#Handle', +\}) diff --git a/ale_linters/reason/ols.vim b/ale_linters/reason/ols.vim index 4e5bd395..66137e1b 100644 --- a/ale_linters/reason/ols.vim +++ b/ale_linters/reason/ols.vim @@ -7,8 +7,8 @@ call ale#Set('reason_ols_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#linter#Define('reason', { \ 'name': 'ols', \ 'lsp': 'stdio', -\ 'executable_callback': 'ale#handlers#ols#GetExecutable', -\ 'command_callback': 'ale#handlers#ols#GetCommand', +\ 'executable': function('ale#handlers#ols#GetExecutable'), +\ 'command': function('ale#handlers#ols#GetCommand'), \ 'language_callback': 'ale#handlers#ols#GetLanguage', -\ 'project_root_callback': 'ale#handlers#ols#GetProjectRoot', +\ 'project_root': function('ale#handlers#ols#GetProjectRoot'), \}) diff --git a/ale_linters/rst/alex.vim b/ale_linters/rst/alex.vim index e637eae7..e7ca6fa0 100644 --- a/ale_linters/rst/alex.vim +++ b/ale_linters/rst/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for rst files -call ale#linter#Define('rst', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('rst', '--text') diff --git a/ale_linters/rst/rstcheck.vim b/ale_linters/rst/rstcheck.vim index 8504738b..39e11d6e 100644 --- a/ale_linters/rst/rstcheck.vim +++ b/ale_linters/rst/rstcheck.vim @@ -32,7 +32,7 @@ endfunction call ale#linter#Define('rst', { \ 'name': 'rstcheck', \ 'executable': 'rstcheck', -\ 'command_callback': 'ale_linters#rst#rstcheck#GetCommand', +\ 'command': function('ale_linters#rst#rstcheck#GetCommand'), \ 'callback': 'ale_linters#rst#rstcheck#Handle', \ 'output_stream': 'both', \}) diff --git a/ale_linters/rst/textlint.vim b/ale_linters/rst/textlint.vim new file mode 100644 index 00000000..56dd8db8 --- /dev/null +++ b/ale_linters/rst/textlint.vim @@ -0,0 +1,9 @@ +" Author: hokorobi <hokorobi.hokorobi@gmail.com> +" Description: textlint, a proofreading tool (https://textlint.github.io/) + +call ale#linter#Define('rst', { +\ 'name': 'textlint', +\ 'executable': function('ale#handlers#textlint#GetExecutable'), +\ 'command': function('ale#handlers#textlint#GetCommand'), +\ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', +\}) diff --git a/ale_linters/ruby/brakeman.vim b/ale_linters/ruby/brakeman.vim index 122e0b5b..a8088080 100644 --- a/ale_linters/ruby/brakeman.vim +++ b/ale_linters/ruby/brakeman.vim @@ -44,8 +44,8 @@ endfunction call ale#linter#Define('ruby', { \ 'name': 'brakeman', -\ 'executable_callback': ale#VarFunc('ruby_brakeman_executable'), -\ 'command_callback': 'ale_linters#ruby#brakeman#GetCommand', +\ 'executable': {b -> ale#Var(b, 'ruby_brakeman_executable')}, +\ 'command': function('ale_linters#ruby#brakeman#GetCommand'), \ 'callback': 'ale_linters#ruby#brakeman#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/ruby/rails_best_practices.vim b/ale_linters/ruby/rails_best_practices.vim index 20cadca8..a94fb671 100644 --- a/ale_linters/ruby/rails_best_practices.vim +++ b/ale_linters/ruby/rails_best_practices.vim @@ -30,8 +30,8 @@ function! ale_linters#ruby#rails_best_practices#GetCommand(buffer) abort endif let l:executable = ale#Var(a:buffer, 'ruby_rails_best_practices_executable') - let l:output_file = ale#Has('win32') ? '%t ' : '/dev/stdout ' - let l:cat_file = ale#Has('win32') ? '; type %t' : '' + let l:output_file = has('win32') ? '%t ' : '/dev/stdout ' + let l:cat_file = has('win32') ? '; type %t' : '' return ale#handlers#ruby#EscapeExecutable(l:executable, 'rails_best_practices') \ . ' --silent -f json --output-file ' . l:output_file @@ -42,8 +42,8 @@ endfunction call ale#linter#Define('ruby', { \ 'name': 'rails_best_practices', -\ 'executable_callback': ale#VarFunc('ruby_rails_best_practices_executable'), -\ 'command_callback': 'ale_linters#ruby#rails_best_practices#GetCommand', +\ 'executable': {b -> ale#Var(b, 'ruby_rails_best_practices_executable')}, +\ 'command': function('ale_linters#ruby#rails_best_practices#GetCommand'), \ 'callback': 'ale_linters#ruby#rails_best_practices#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/ruby/reek.vim b/ale_linters/ruby/reek.vim index 53363d31..e39e366f 100644 --- a/ale_linters/ruby/reek.vim +++ b/ale_linters/ruby/reek.vim @@ -6,26 +6,11 @@ call ale#Set('ruby_reek_show_wiki_link', 0) call ale#Set('ruby_reek_options', '') call ale#Set('ruby_reek_executable', 'reek') -function! ale_linters#ruby#reek#VersionCheck(buffer) abort - " If we have previously stored the version number in a cache, then - " don't look it up again. - if ale#semver#HasVersion('reek') - " Returning an empty string skips this command. - return '' - endif - - let l:executable = ale#Var(a:buffer, 'ruby_reek_executable') - - return ale#handlers#ruby#EscapeExecutable(l:executable, 'reek') - \ . ' --version' -endfunction - -function! ale_linters#ruby#reek#GetCommand(buffer, version_output) abort - let l:version = ale#semver#GetVersion('reek', a:version_output) +function! ale_linters#ruby#reek#GetCommand(buffer, version) abort let l:executable = ale#Var(a:buffer, 'ruby_reek_executable') " Tell reek what the filename is if the version of reek is new enough. - let l:display_name_args = ale#semver#GTE(l:version, [5, 0, 0]) + let l:display_name_args = ale#semver#GTE(a:version, [5, 0, 0]) \ ? ' --stdin-filename %s' \ : '' @@ -69,10 +54,12 @@ endfunction call ale#linter#Define('ruby', { \ 'name': 'reek', -\ 'executable_callback': ale#VarFunc('ruby_reek_executable'), -\ 'command_chain': [ -\ {'callback': 'ale_linters#ruby#reek#VersionCheck'}, -\ {'callback': 'ale_linters#ruby#reek#GetCommand'}, -\ ], +\ 'executable': {b -> ale#Var(b, 'ruby_reek_executable')}, +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'ruby_reek_executable'), +\ '%e --version', +\ function('ale_linters#ruby#reek#GetCommand'), +\ )}, \ 'callback': 'ale_linters#ruby#reek#Handle', \}) diff --git a/ale_linters/ruby/rubocop.vim b/ale_linters/ruby/rubocop.vim index 45218394..8b9e9c84 100644 --- a/ale_linters/ruby/rubocop.vim +++ b/ale_linters/ruby/rubocop.vim @@ -13,36 +13,6 @@ function! ale_linters#ruby#rubocop#GetCommand(buffer) abort \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) endfunction -function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort - try - let l:errors = json_decode(a:lines[0]) - catch - return [] - endtry - - if !has_key(l:errors, 'summary') - \|| l:errors['summary']['offense_count'] == 0 - \|| empty(l:errors['files']) - return [] - endif - - let l:output = [] - - for l:error in l:errors['files'][0]['offenses'] - let l:start_col = l:error['location']['column'] + 0 - call add(l:output, { - \ 'lnum': l:error['location']['line'] + 0, - \ 'col': l:start_col, - \ 'end_col': l:start_col + l:error['location']['length'] - 1, - \ 'code': l:error['cop_name'], - \ 'text': l:error['message'], - \ 'type': ale_linters#ruby#rubocop#GetType(l:error['severity']), - \}) - endfor - - return l:output -endfunction - function! ale_linters#ruby#rubocop#GetType(severity) abort if a:severity is? 'convention' \|| a:severity is? 'warning' @@ -55,7 +25,7 @@ endfunction call ale#linter#Define('ruby', { \ 'name': 'rubocop', -\ 'executable_callback': ale#VarFunc('ruby_rubocop_executable'), -\ 'command_callback': 'ale_linters#ruby#rubocop#GetCommand', -\ 'callback': 'ale_linters#ruby#rubocop#Handle', +\ 'executable': {b -> ale#Var(b, 'ruby_rubocop_executable')}, +\ 'command': function('ale_linters#ruby#rubocop#GetCommand'), +\ 'callback': 'ale#ruby#HandleRubocopOutput', \}) diff --git a/ale_linters/ruby/ruby.vim b/ale_linters/ruby/ruby.vim index 2bc4ec4b..2dc55eb0 100644 --- a/ale_linters/ruby/ruby.vim +++ b/ale_linters/ruby/ruby.vim @@ -5,7 +5,7 @@ call ale#Set('ruby_ruby_executable', 'ruby') call ale#linter#Define('ruby', { \ 'name': 'ruby', -\ 'executable_callback': ale#VarFunc('ruby_ruby_executable'), +\ 'executable': {b -> ale#Var(b, 'ruby_ruby_executable')}, \ 'command': '%e -w -c -T1 %t', \ 'output_stream': 'stderr', \ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', diff --git a/ale_linters/ruby/solargraph.vim b/ale_linters/ruby/solargraph.vim index 5ff0a759..bf54a55c 100644 --- a/ale_linters/ruby/solargraph.vim +++ b/ale_linters/ruby/solargraph.vim @@ -15,8 +15,8 @@ call ale#linter#Define('ruby', { \ 'name': 'solargraph', \ 'lsp': 'stdio', \ 'language': 'ruby', -\ 'executable_callback': ale#VarFunc('ruby_solargraph_executable'), -\ 'command_callback': 'ale_linters#ruby#solargraph#GetCommand', -\ 'project_root_callback': 'ale#ruby#FindProjectRoot', -\ 'initialization_options_callback': ale#VarFunc('ruby_solargraph_options'), +\ 'executable': {b -> ale#Var(b, 'ruby_solargraph_executable')}, +\ 'command': function('ale_linters#ruby#solargraph#GetCommand'), +\ 'project_root': function('ale#ruby#FindProjectRoot'), +\ 'initialization_options': {b -> ale#Var(b, 'ruby_solargraph_options')}, \}) diff --git a/ale_linters/ruby/standardrb.vim b/ale_linters/ruby/standardrb.vim new file mode 100644 index 00000000..f075a7d5 --- /dev/null +++ b/ale_linters/ruby/standardrb.vim @@ -0,0 +1,23 @@ +" Author: Justin Searls https://github.com/searls, ynonp - https://github.com/ynonp, Eddie Lebow https://github.com/elebow +" based on the ale rubocop linter +" Description: StandardRB - Ruby Style Guide, with linter & automatic code fixer + +call ale#Set('ruby_standardrb_executable', 'standardrb') +call ale#Set('ruby_standardrb_options', '') + +function! ale_linters#ruby#standardrb#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_standardrb_executable') + + return ale#handlers#ruby#EscapeExecutable(l:executable, 'standardrb') + \ . ' --format json --force-exclusion ' + \ . ale#Var(a:buffer, 'ruby_standardrb_options') + \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) +endfunction + +" standardrb is based on RuboCop so the callback is the same +call ale#linter#Define('ruby', { +\ 'name': 'standardrb', +\ 'executable': {b -> ale#Var(b, 'ruby_standardrb_executable')}, +\ 'command': function('ale_linters#ruby#standardrb#GetCommand'), +\ 'callback': 'ale#ruby#HandleRubocopOutput', +\}) diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim index cf6187f8..f98dee9b 100644 --- a/ale_linters/rust/cargo.vim +++ b/ale_linters/rust/cargo.vim @@ -22,26 +22,18 @@ function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort endif endfunction -function! ale_linters#rust#cargo#VersionCheck(buffer) abort - return !ale#semver#HasVersion('cargo') - \ ? 'cargo --version' - \ : '' -endfunction - -function! ale_linters#rust#cargo#GetCommand(buffer, version_output) abort - let l:version = ale#semver#GetVersion('cargo', a:version_output) - +function! ale_linters#rust#cargo#GetCommand(buffer, version) abort let l:use_check = ale#Var(a:buffer, 'rust_cargo_use_check') - \ && ale#semver#GTE(l:version, [0, 17, 0]) + \ && ale#semver#GTE(a:version, [0, 17, 0]) 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]) + \ && ale#semver#GTE(a: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]) + \ && ale#semver#GTE(a: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]) + \ && ale#semver#GTE(a:version, [0, 22, 0]) let l:include_features = ale#Var(a:buffer, 'rust_cargo_include_features') @@ -93,11 +85,13 @@ endfunction call ale#linter#Define('rust', { \ 'name': 'cargo', -\ 'executable_callback': 'ale_linters#rust#cargo#GetCargoExecutable', -\ 'command_chain': [ -\ {'callback': 'ale_linters#rust#cargo#VersionCheck'}, -\ {'callback': 'ale_linters#rust#cargo#GetCommand'}, -\ ], +\ 'executable': function('ale_linters#rust#cargo#GetCargoExecutable'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#rust#cargo#GetCargoExecutable(buffer), +\ '%e --version', +\ function('ale_linters#rust#cargo#GetCommand'), +\ )}, \ 'callback': 'ale#handlers#rust#HandleRustErrors', \ 'output_stream': 'both', \ 'lint_file': 1, diff --git a/ale_linters/rust/rls.vim b/ale_linters/rust/rls.vim index 60dd3667..111d7558 100644 --- a/ale_linters/rust/rls.vim +++ b/ale_linters/rust/rls.vim @@ -2,7 +2,8 @@ " Description: A language server for Rust call ale#Set('rust_rls_executable', 'rls') -call ale#Set('rust_rls_toolchain', 'nightly') +call ale#Set('rust_rls_toolchain', '') +call ale#Set('rust_rls_config', {}) function! ale_linters#rust#rls#GetCommand(buffer) abort let l:toolchain = ale#Var(a:buffer, 'rust_rls_toolchain') @@ -19,7 +20,8 @@ endfunction call ale#linter#Define('rust', { \ 'name': 'rls', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('rust_rls_executable'), -\ 'command_callback': 'ale_linters#rust#rls#GetCommand', -\ 'project_root_callback': 'ale_linters#rust#rls#GetProjectRoot', +\ 'lsp_config': {b -> ale#Var(b, 'rust_rls_config')}, +\ 'executable': {b -> ale#Var(b, 'rust_rls_executable')}, +\ 'command': function('ale_linters#rust#rls#GetCommand'), +\ 'project_root': function('ale_linters#rust#rls#GetProjectRoot'), \}) diff --git a/ale_linters/rust/rustc.vim b/ale_linters/rust/rustc.vim index 33fb72f4..f140b58b 100644 --- a/ale_linters/rust/rustc.vim +++ b/ale_linters/rust/rustc.vim @@ -27,7 +27,7 @@ endfunction call ale#linter#Define('rust', { \ 'name': 'rustc', \ 'executable': 'rustc', -\ 'command_callback': 'ale_linters#rust#rustc#RustcCommand', +\ 'command': function('ale_linters#rust#rustc#RustcCommand'), \ 'callback': 'ale#handlers#rust#HandleRustErrors', \ 'output_stream': 'stderr', \}) diff --git a/ale_linters/sass/sasslint.vim b/ale_linters/sass/sasslint.vim index 8d24185d..17cd3667 100644 --- a/ale_linters/sass/sasslint.vim +++ b/ale_linters/sass/sasslint.vim @@ -22,7 +22,7 @@ endfunction call ale#linter#Define('sass', { \ 'name': 'sasslint', -\ 'executable_callback': 'ale_linters#sass#sasslint#GetExecutable', -\ 'command_callback': 'ale_linters#sass#sasslint#GetCommand', +\ 'executable': function('ale_linters#sass#sasslint#GetExecutable'), +\ 'command': function('ale_linters#sass#sasslint#GetCommand'), \ 'callback': 'ale#handlers#css#HandleCSSLintFormat', \}) diff --git a/ale_linters/sass/stylelint.vim b/ale_linters/sass/stylelint.vim index b6286f18..7b14c6b4 100644 --- a/ale_linters/sass/stylelint.vim +++ b/ale_linters/sass/stylelint.vim @@ -5,9 +5,9 @@ call ale#Set('sass_stylelint_use_global', get(g:, 'ale_use_global_executables', call ale#linter#Define('sass', { \ 'name': 'stylelint', -\ 'executable_callback': ale#node#FindExecutableFunc('sass_stylelint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'sass_stylelint', [ \ 'node_modules/.bin/stylelint', -\ ]), +\ ])}, \ 'command': '%e --stdin-filename %s', \ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/scala/fsc.vim b/ale_linters/scala/fsc.vim index fbdce20e..94135235 100644 --- a/ale_linters/scala/fsc.vim +++ b/ale_linters/scala/fsc.vim @@ -7,7 +7,7 @@ endfunction call ale#linter#Define('scala', { \ 'name': 'fsc', -\ 'executable_callback': {buf -> s:IsSbt(buf) ? '' : 'fsc'}, +\ 'executable': {buf -> s:IsSbt(buf) ? '' : 'fsc'}, \ 'command': '%e -Ystop-after:parser %t', \ 'callback': 'ale#handlers#scala#HandleScalacLintFormat', \ 'output_stream': 'stderr', diff --git a/ale_linters/scala/sbtserver.vim b/ale_linters/scala/sbtserver.vim index 694241d7..d4f137c2 100644 --- a/ale_linters/scala/sbtserver.vim +++ b/ale_linters/scala/sbtserver.vim @@ -25,7 +25,7 @@ endfunction call ale#linter#Define('scala', { \ 'name': 'sbtserver', \ 'lsp': 'socket', -\ 'address_callback': 'ale_linters#scala#sbtserver#GetAddress', +\ 'address': function('ale_linters#scala#sbtserver#GetAddress'), \ 'language': 'scala', -\ 'project_root_callback': 'ale_linters#scala#sbtserver#GetProjectRoot', +\ 'project_root': function('ale_linters#scala#sbtserver#GetProjectRoot'), \}) diff --git a/ale_linters/scala/scalac.vim b/ale_linters/scala/scalac.vim index 3dbdd925..1dd579b4 100644 --- a/ale_linters/scala/scalac.vim +++ b/ale_linters/scala/scalac.vim @@ -8,7 +8,7 @@ endfunction call ale#linter#Define('scala', { \ 'name': 'scalac', -\ 'executable_callback': {buf -> s:IsSbt(buf) ? '' : 'scalac'}, +\ 'executable': {buf -> s:IsSbt(buf) ? '' : 'scalac'}, \ 'command': '%e -Ystop-after:parser %t', \ 'callback': 'ale#handlers#scala#HandleScalacLintFormat', \ 'output_stream': 'stderr', diff --git a/ale_linters/scala/scalastyle.vim b/ale_linters/scala/scalastyle.vim index 42228cf6..6e9e4c13 100644 --- a/ale_linters/scala/scalastyle.vim +++ b/ale_linters/scala/scalastyle.vim @@ -81,6 +81,6 @@ call ale#linter#Define('scala', { \ 'name': 'scalastyle', \ 'executable': 'scalastyle', \ 'output_stream': 'stdout', -\ 'command_callback': 'ale_linters#scala#scalastyle#GetCommand', +\ 'command': function('ale_linters#scala#scalastyle#GetCommand'), \ 'callback': 'ale_linters#scala#scalastyle#Handle', \}) diff --git a/ale_linters/scss/sasslint.vim b/ale_linters/scss/sasslint.vim index 8b725ba6..cf13546e 100644 --- a/ale_linters/scss/sasslint.vim +++ b/ale_linters/scss/sasslint.vim @@ -22,7 +22,7 @@ endfunction call ale#linter#Define('scss', { \ 'name': 'sasslint', -\ 'executable_callback': 'ale_linters#scss#sasslint#GetExecutable', -\ 'command_callback': 'ale_linters#scss#sasslint#GetCommand', +\ 'executable': function('ale_linters#scss#sasslint#GetExecutable'), +\ 'command': function('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 2bffa8e1..b5b21536 100644 --- a/ale_linters/scss/stylelint.vim +++ b/ale_linters/scss/stylelint.vim @@ -11,9 +11,9 @@ endfunction call ale#linter#Define('scss', { \ 'name': 'stylelint', -\ 'executable_callback': ale#node#FindExecutableFunc('scss_stylelint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'scss_stylelint', [ \ 'node_modules/.bin/stylelint', -\ ]), -\ 'command_callback': 'ale_linters#scss#stylelint#GetCommand', +\ ])}, +\ 'command': function('ale_linters#scss#stylelint#GetCommand'), \ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/sh/language_server.vim b/ale_linters/sh/language_server.vim index 385d1119..5a3b0e9a 100644 --- a/ale_linters/sh/language_server.vim +++ b/ale_linters/sh/language_server.vim @@ -26,7 +26,7 @@ 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', -\ 'project_root_callback': 'ale_linters#sh#language_server#GetProjectRoot', +\ 'executable': function('ale_linters#sh#language_server#GetExecutable'), +\ 'command': function('ale_linters#sh#language_server#GetCommand'), +\ 'project_root': function('ale_linters#sh#language_server#GetProjectRoot'), \}) diff --git a/ale_linters/sh/shell.vim b/ale_linters/sh/shell.vim index cf5e4e6c..189dc21d 100644 --- a/ale_linters/sh/shell.vim +++ b/ale_linters/sh/shell.vim @@ -51,7 +51,7 @@ endfunction call ale#linter#Define('sh', { \ 'name': 'shell', \ 'output_stream': 'stderr', -\ 'executable_callback': 'ale_linters#sh#shell#GetExecutable', -\ 'command_callback': 'ale_linters#sh#shell#GetCommand', +\ 'executable': function('ale_linters#sh#shell#GetExecutable'), +\ 'command': function('ale_linters#sh#shell#GetCommand'), \ 'callback': 'ale_linters#sh#shell#Handle', \}) diff --git a/ale_linters/sh/shellcheck.vim b/ale_linters/sh/shellcheck.vim index 0f68e62c..1d8b6096 100644 --- a/ale_linters/sh/shellcheck.vim +++ b/ale_linters/sh/shellcheck.vim @@ -8,11 +8,9 @@ " let g:ale_sh_shellcheck_exclusions = 'SC2002,SC2004' call ale#Set('sh_shellcheck_exclusions', get(g:, 'ale_linters_sh_shellcheck_exclusions', '')) call ale#Set('sh_shellcheck_executable', 'shellcheck') +call ale#Set('sh_shellcheck_dialect', 'auto') call ale#Set('sh_shellcheck_options', '') - -function! ale_linters#sh#shellcheck#GetExecutable(buffer) abort - return ale#Var(a:buffer, 'sh_shellcheck_executable') -endfunction +call ale#Set('sh_shellcheck_change_directory', 1) function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) @@ -38,26 +36,21 @@ function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort return '' endfunction -function! ale_linters#sh#shellcheck#VersionCheck(buffer) abort - let l:executable = ale_linters#sh#shellcheck#GetExecutable(a:buffer) - - " Don't check the version again if we've already cached it. - return !ale#semver#HasVersion(l:executable) - \ ? ale#Escape(l:executable) . ' --version' - \ : '' -endfunction - -function! ale_linters#sh#shellcheck#GetCommand(buffer, version_output) abort - let l:executable = ale_linters#sh#shellcheck#GetExecutable(a:buffer) - let l:version = ale#semver#GetVersion(l:executable, a:version_output) - +function! ale_linters#sh#shellcheck#GetCommand(buffer, version) abort let l:options = ale#Var(a:buffer, 'sh_shellcheck_options') let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions') - let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer) - let l:external_option = ale#semver#GTE(l:version, [0, 4, 0]) ? ' -x' : '' + let l:dialect = ale#Var(a:buffer, 'sh_shellcheck_dialect') + let l:external_option = ale#semver#GTE(a:version, [0, 4, 0]) ? ' -x' : '' + let l:cd_string = ale#Var(a:buffer, 'sh_shellcheck_change_directory') + \ ? ale#path#BufferCdString(a:buffer) + \ : '' + + if l:dialect is# 'auto' + let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer) + endif - return ale#path#BufferCdString(a:buffer) - \ . ale#Escape(l:executable) + return l:cd_string + \ . '%e' \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '') \ . (!empty(l:options) ? ' ' . l:options : '') \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '') @@ -103,10 +96,12 @@ endfunction call ale#linter#Define('sh', { \ 'name': 'shellcheck', -\ 'executable_callback': 'ale_linters#sh#shellcheck#GetExecutable', -\ 'command_chain': [ -\ {'callback': 'ale_linters#sh#shellcheck#VersionCheck'}, -\ {'callback': 'ale_linters#sh#shellcheck#GetCommand'}, -\ ], +\ 'executable': {buffer -> ale#Var(buffer, 'sh_shellcheck_executable')}, +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'sh_shellcheck_executable'), +\ '%e --version', +\ function('ale_linters#sh#shellcheck#GetCommand'), +\ )}, \ 'callback': 'ale_linters#sh#shellcheck#Handle', \}) diff --git a/ale_linters/slim/slimlint.vim b/ale_linters/slim/slimlint.vim index 00c6b26c..1b365e25 100644 --- a/ale_linters/slim/slimlint.vim +++ b/ale_linters/slim/slimlint.vim @@ -11,11 +11,11 @@ function! ale_linters#slim#slimlint#GetCommand(buffer) abort " " See https://github.com/sds/slim-lint/blob/master/lib/slim_lint/linter/README.md#rubocop if !empty(l:rubocop_config) - if ale#Has('win32') - let l:command = 'set SLIM_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config) . ' && ' . l:command - else - let l:command = 'SLIM_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config) . ' ' . l:command - endif + if has('win32') + let l:command = 'set SLIM_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config) . ' && ' . l:command + else + let l:command = 'SLIM_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config) . ' ' . l:command + endif endif return l:command @@ -50,6 +50,6 @@ endfunction call ale#linter#Define('slim', { \ 'name': 'slimlint', \ 'executable': 'slim-lint', -\ 'command_callback': 'ale_linters#slim#slimlint#GetCommand', +\ 'command': function('ale_linters#slim#slimlint#GetCommand'), \ 'callback': 'ale_linters#slim#slimlint#Handle' \}) diff --git a/ale_linters/sml/smlnj.vim b/ale_linters/sml/smlnj.vim index f15579ea..852ea170 100644 --- a/ale_linters/sml/smlnj.vim +++ b/ale_linters/sml/smlnj.vim @@ -3,7 +3,7 @@ call ale#linter#Define('sml', { \ 'name': 'smlnj', -\ 'executable_callback': 'ale#handlers#sml#GetExecutableSmlnjFile', +\ 'executable': function('ale#handlers#sml#GetExecutableSmlnjFile'), \ 'command': 'sml', \ 'callback': 'ale#handlers#sml#Handle', \}) diff --git a/ale_linters/sml/smlnj_cm.vim b/ale_linters/sml/smlnj_cm.vim index bfa4bc05..9ad24af4 100644 --- a/ale_linters/sml/smlnj_cm.vim +++ b/ale_linters/sml/smlnj_cm.vim @@ -12,9 +12,9 @@ endfunction call ale#linter#Define('sml', { \ 'name': 'smlnj_cm', \ 'aliases': ['smlnj-cm'], -\ 'executable_callback': 'ale#handlers#sml#GetExecutableSmlnjCm', +\ 'executable': function('ale#handlers#sml#GetExecutableSmlnjCm'), \ 'lint_file': 1, -\ 'command_callback': 'ale_linters#sml#smlnj_cm#GetCommand', +\ 'command': function('ale_linters#sml#smlnj_cm#GetCommand'), \ 'callback': 'ale#handlers#sml#Handle', \}) diff --git a/ale_linters/spec/rpmlint.vim b/ale_linters/spec/rpmlint.vim index 486bef1e..92ef4d63 100644 --- a/ale_linters/spec/rpmlint.vim +++ b/ale_linters/spec/rpmlint.vim @@ -72,7 +72,7 @@ endfunction call ale#linter#Define('spec', { \ 'name': 'rpmlint', -\ 'executable_callback': ale#VarFunc('spec_rpmlint_executable'), -\ 'command_callback': 'ale_linters#spec#rpmlint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'spec_rpmlint_executable')}, +\ 'command': function('ale_linters#spec#rpmlint#GetCommand'), \ 'callback': 'ale_linters#spec#rpmlint#Handle', \}) diff --git a/ale_linters/stylus/stylelint.vim b/ale_linters/stylus/stylelint.vim index 2256f3c0..ce6f9426 100644 --- a/ale_linters/stylus/stylelint.vim +++ b/ale_linters/stylus/stylelint.vim @@ -12,9 +12,9 @@ endfunction call ale#linter#Define('stylus', { \ 'name': 'stylelint', -\ 'executable_callback': ale#node#FindExecutableFunc('stylus_stylelint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'stylus_stylelint', [ \ 'node_modules/.bin/stylelint', -\ ]), -\ 'command_callback': 'ale_linters#stylus#stylelint#GetCommand', +\ ])}, +\ 'command': function('ale_linters#stylus#stylelint#GetCommand'), \ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/sugarss/stylelint.vim b/ale_linters/sugarss/stylelint.vim new file mode 100644 index 00000000..6c705e46 --- /dev/null +++ b/ale_linters/sugarss/stylelint.vim @@ -0,0 +1,21 @@ +" Author: toastal <toastal@protonmail.com> +" Description: `stylelint` linter for SugarSS files + +call ale#Set('sugarss_stylelint_executable', 'stylelint') +call ale#Set('sugarss_stylelint_options', '') +call ale#Set('sugarss_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#sugarss#stylelint#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'sugarss_stylelint_options')) + \ . ' --syntax=sugarss' + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('sugarss', { +\ 'name': 'stylelint', +\ 'executable': {b -> ale#node#FindExecutable(b, 'sugarss_stylelint', [ +\ 'node_modules/.bin/stylelint', +\ ])}, +\ 'command': function('ale_linters#sugarss#stylelint#GetCommand'), +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/swift/sourcekitlsp.vim b/ale_linters/swift/sourcekitlsp.vim new file mode 100644 index 00000000..560893bf --- /dev/null +++ b/ale_linters/swift/sourcekitlsp.vim @@ -0,0 +1,13 @@ +" Author: Dan Loman <https://github.com/namolnad> +" Description: Support for sourcekit-lsp https://github.com/apple/sourcekit-lsp + +call ale#Set('sourcekit_lsp_executable', 'sourcekit-lsp') + +call ale#linter#Define('swift', { +\ 'name': 'sourcekitlsp', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'sourcekit_lsp_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale#swift#FindProjectRoot'), +\ 'language': 'swift', +\}) diff --git a/ale_linters/swift/swiftlint.vim b/ale_linters/swift/swiftlint.vim index a1150658..237c45d3 100644 --- a/ale_linters/swift/swiftlint.vim +++ b/ale_linters/swift/swiftlint.vim @@ -6,10 +6,10 @@ call ale#Set('swift_swiftlint_use_global', get(g:, 'ale_use_global_executables', function! ale_linters#swift#swiftlint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'swift_swiftlint', [ - \ 'Pods/SwiftLint/swiftlint', - \ 'ios/Pods/SwiftLint/swiftlint', - \ 'swiftlint', - \]) + \ 'Pods/SwiftLint/swiftlint', + \ 'ios/Pods/SwiftLint/swiftlint', + \ 'swiftlint', + \]) endfunction function! ale_linters#swift#swiftlint#GetCommand(buffer) abort @@ -17,7 +17,7 @@ function! ale_linters#swift#swiftlint#GetCommand(buffer) abort let l:args = 'lint --use-stdin' return ale#Escape(l:executable) - \ . ' ' .l:args + \ . ' ' .l:args endfunction function! ale_linters#swift#swiftlint#Handle(buffer, lines) abort @@ -26,10 +26,10 @@ function! ale_linters#swift#swiftlint#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:item = { - \ 'lnum': str2nr(l:match[2]), - \ 'type': l:match[4] is# 'error' ? 'E' : 'W', - \ 'text': l:match[5], - \} + \ 'lnum': str2nr(l:match[2]), + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'text': l:match[5], + \} if l:match[4] is# 'error' let l:item.type = 'E' @@ -63,7 +63,7 @@ endfunction call ale#linter#Define('swift', { \ 'name': 'swiftlint', -\ 'executable_callback': 'ale_linters#swift#swiftlint#GetExecutable', -\ 'command_callback': 'ale_linters#swift#swiftlint#GetCommand', +\ 'executable': function('ale_linters#swift#swiftlint#GetExecutable'), +\ 'command': function('ale_linters#swift#swiftlint#GetCommand'), \ 'callback': 'ale_linters#swift#swiftlint#Handle', \}) diff --git a/ale_linters/tcl/nagelfar.vim b/ale_linters/tcl/nagelfar.vim index 05fe581b..5a4940e1 100644 --- a/ale_linters/tcl/nagelfar.vim +++ b/ale_linters/tcl/nagelfar.vim @@ -32,8 +32,8 @@ endfunction call ale#linter#Define('tcl', { \ 'name': 'nagelfar', \ 'output_stream': 'stdout', -\ 'executable_callback': ale#VarFunc('tcl_nagelfar_executable'), -\ 'command_callback': 'ale_linters#tcl#nagelfar#GetCommand', +\ 'executable': {b -> ale#Var(b, 'tcl_nagelfar_executable')}, +\ 'command': function('ale_linters#tcl#nagelfar#GetCommand'), \ 'callback': 'ale_linters#tcl#nagelfar#Handle', \ 'lint_file': 1, \}) diff --git a/ale_linters/terraform/tflint.vim b/ale_linters/terraform/tflint.vim index 0d77835a..6d54a8b1 100644 --- a/ale_linters/terraform/tflint.vim +++ b/ale_linters/terraform/tflint.vim @@ -52,7 +52,7 @@ endfunction call ale#linter#Define('terraform', { \ 'name': 'tflint', -\ 'executable_callback': ale#VarFunc('terraform_tflint_executable'), -\ 'command_callback': 'ale_linters#terraform#tflint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'terraform_tflint_executable')}, +\ 'command': function('ale_linters#terraform#tflint#GetCommand'), \ 'callback': 'ale_linters#terraform#tflint#Handle', \}) diff --git a/ale_linters/tex/alex.vim b/ale_linters/tex/alex.vim index 78c530f7..5d9aec66 100644 --- a/ale_linters/tex/alex.vim +++ b/ale_linters/tex/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for TeX files -call ale#linter#Define('tex', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('tex', '--text') diff --git a/ale_linters/tex/chktex.vim b/ale_linters/tex/chktex.vim index 7f1b0c72..160baf0d 100644 --- a/ale_linters/tex/chktex.vim +++ b/ale_linters/tex/chktex.vim @@ -49,6 +49,6 @@ endfunction call ale#linter#Define('tex', { \ 'name': 'chktex', \ 'executable': 'chktex', -\ 'command_callback': 'ale_linters#tex#chktex#GetCommand', +\ 'command': function('ale_linters#tex#chktex#GetCommand'), \ 'callback': 'ale_linters#tex#chktex#Handle' \}) diff --git a/ale_linters/tex/lacheck.vim b/ale_linters/tex/lacheck.vim index 5e5a94f1..19d69403 100644 --- a/ale_linters/tex/lacheck.vim +++ b/ale_linters/tex/lacheck.vim @@ -8,20 +8,26 @@ function! ale_linters#tex#lacheck#Handle(buffer, lines) abort " " "book.tex", line 37: possible unwanted space at "{" " "book.tex", line 38: missing `\ ' after "etc." - let l:pattern = '^".\+", line \(\d\+\): \(.\+\)$' + let l:pattern = '^"\(.\+\)", line \(\d\+\): \(.\+\)$' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) " lacheck follows `\input{}` commands. If the cwd is not the same as the " file in the buffer then it will fail to find the inputed items. We do not " want warnings from those items anyway - if !empty(matchstr(l:match[2], '^Could not open ".\+"$')) + if !empty(matchstr(l:match[3], '^Could not open ".\+"$')) + continue + endif + + " lacheck follows `\input{}` commands. We are only interested in + " reporting errors for the current buffer only. + if empty(matchstr(fnamemodify(l:match[1], ':t'), fnamemodify(bufname(a:buffer), ':t'))) continue endif call add(l:output, { - \ 'lnum': l:match[1] + 0, - \ 'text': l:match[2], + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[3], \ 'type': 'W', \}) endfor @@ -31,7 +37,7 @@ endfunction call ale#linter#Define('tex', { \ 'name': 'lacheck', -\ 'executable_callback': ale#VarFunc('tex_lacheck_executable'), +\ 'executable': {b -> ale#Var(b, 'tex_lacheck_executable')}, \ 'command': '%e %t', \ 'callback': 'ale_linters#tex#lacheck#Handle' \}) diff --git a/ale_linters/tex/textlint.vim b/ale_linters/tex/textlint.vim new file mode 100644 index 00000000..5edac46d --- /dev/null +++ b/ale_linters/tex/textlint.vim @@ -0,0 +1,9 @@ +" Author: TANIGUCHI Masaya <ta2gch@gmail.com> +" Description: textlint for LaTeX files + +call ale#linter#Define('tex', { +\ 'name': 'textlint', +\ 'executable': function('ale#handlers#textlint#GetExecutable'), +\ 'command': function('ale#handlers#textlint#GetCommand'), +\ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', +\}) diff --git a/ale_linters/texinfo/alex.vim b/ale_linters/texinfo/alex.vim index 4a884579..4d245524 100644 --- a/ale_linters/texinfo/alex.vim +++ b/ale_linters/texinfo/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for texinfo files -call ale#linter#Define('texinfo', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('texinfo', '--text') diff --git a/ale_linters/text/alex.vim b/ale_linters/text/alex.vim index c696367b..d87ed915 100644 --- a/ale_linters/text/alex.vim +++ b/ale_linters/text/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for text files -call ale#linter#Define('text', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('text', '--text') diff --git a/ale_linters/text/languagetool.vim b/ale_linters/text/languagetool.vim new file mode 100644 index 00000000..58c99ba2 --- /dev/null +++ b/ale_linters/text/languagetool.vim @@ -0,0 +1,4 @@ +" Author: Vincent (wahrwolf [ät] wolfpit.net) +" Description: languagetool for text files + +call ale#handlers#languagetool#DefineLinter('text') diff --git a/ale_linters/text/textlint.vim b/ale_linters/text/textlint.vim index 8fafdd7d..67c4e378 100644 --- a/ale_linters/text/textlint.vim +++ b/ale_linters/text/textlint.vim @@ -3,7 +3,7 @@ call ale#linter#Define('text', { \ 'name': 'textlint', -\ 'executable_callback': 'ale#handlers#textlint#GetExecutable', -\ 'command_callback': 'ale#handlers#textlint#GetCommand', +\ 'executable': function('ale#handlers#textlint#GetExecutable'), +\ 'command': function('ale#handlers#textlint#GetCommand'), \ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', \}) diff --git a/ale_linters/thrift/thrift.vim b/ale_linters/thrift/thrift.vim index 36a8656e..345c7abe 100644 --- a/ale_linters/thrift/thrift.vim +++ b/ale_linters/thrift/thrift.vim @@ -16,7 +16,7 @@ function! ale_linters#thrift#thrift#GetCommand(buffer) abort let l:generators = ['cpp'] endif - let l:output_dir = ale#engine#CreateDirectory(a:buffer) + let l:output_dir = ale#command#CreateDirectory(a:buffer) return '%e' \ . ale#Pad(join(map(copy(l:generators), "'--gen ' . v:val"))) @@ -80,9 +80,8 @@ endfunction call ale#linter#Define('thrift', { \ 'name': 'thrift', -\ 'executable': 'thrift', \ 'output_stream': 'both', -\ 'executable_callback': ale#VarFunc('thrift_thrift_executable'), -\ 'command_callback': 'ale_linters#thrift#thrift#GetCommand', +\ 'executable': {b -> ale#Var(b, 'thrift_thrift_executable')}, +\ 'command': function('ale_linters#thrift#thrift#GetCommand'), \ 'callback': 'ale_linters#thrift#thrift#Handle', \}) diff --git a/ale_linters/typescript/eslint.vim b/ale_linters/typescript/eslint.vim index f1ae54e7..bf849337 100644 --- a/ale_linters/typescript/eslint.vim +++ b/ale_linters/typescript/eslint.vim @@ -3,7 +3,7 @@ call ale#linter#Define('typescript', { \ 'name': 'eslint', -\ 'executable_callback': 'ale#handlers#eslint#GetExecutable', -\ 'command_callback': 'ale#handlers#eslint#GetCommand', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'command': function('ale#handlers#eslint#GetCommand'), \ 'callback': 'ale#handlers#eslint#Handle', \}) diff --git a/ale_linters/typescript/tslint.vim b/ale_linters/typescript/tslint.vim index ccdca936..f70c2e45 100644 --- a/ale_linters/typescript/tslint.vim +++ b/ale_linters/typescript/tslint.vim @@ -69,7 +69,7 @@ endfunction call ale#linter#Define('typescript', { \ 'name': 'tslint', -\ 'executable_callback': 'ale#handlers#tslint#GetExecutable', -\ 'command_callback': 'ale_linters#typescript#tslint#GetCommand', +\ 'executable': function('ale#handlers#tslint#GetExecutable'), +\ 'command': function('ale_linters#typescript#tslint#GetCommand'), \ 'callback': 'ale_linters#typescript#tslint#Handle', \}) diff --git a/ale_linters/typescript/tsserver.vim b/ale_linters/typescript/tsserver.vim index bac63229..840889f3 100644 --- a/ale_linters/typescript/tsserver.vim +++ b/ale_linters/typescript/tsserver.vim @@ -8,10 +8,10 @@ call ale#Set('typescript_tsserver_use_global', get(g:, 'ale_use_global_executabl call ale#linter#Define('typescript', { \ 'name': 'tsserver', \ 'lsp': 'tsserver', -\ 'executable_callback': ale#node#FindExecutableFunc('typescript_tsserver', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'typescript_tsserver', [ \ 'node_modules/.bin/tsserver', -\ ]), +\ ])}, \ 'command': '%e', -\ 'project_root_callback': {-> ''}, +\ 'project_root': function('ale#handlers#tsserver#GetProjectRoot'), \ 'language': '', \}) diff --git a/ale_linters/typescript/xo.vim b/ale_linters/typescript/xo.vim new file mode 100644 index 00000000..8b015efd --- /dev/null +++ b/ale_linters/typescript/xo.vim @@ -0,0 +1,23 @@ +call ale#Set('typescript_xo_executable', 'xo') +call ale#Set('typescript_xo_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('typescript_xo_options', '') + +function! ale_linters#typescript#xo#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'typescript_xo', [ + \ 'node_modules/.bin/xo', + \]) +endfunction + +function! ale_linters#typescript#xo#GetCommand(buffer) abort + return ale#Escape(ale_linters#typescript#xo#GetExecutable(a:buffer)) + \ . ale#Pad(ale#Var(a:buffer, 'typescript_xo_options')) + \ . ' --reporter unix --stdin --stdin-filename %s' +endfunction + +" xo uses eslint and the output format is the same +call ale#linter#Define('typescript', { +\ 'name': 'xo', +\ 'executable': function('ale_linters#typescript#xo#GetExecutable'), +\ 'command': function('ale_linters#typescript#xo#GetCommand'), +\ 'callback': 'ale#handlers#eslint#Handle', +\}) diff --git a/ale_linters/verilog/iverilog.vim b/ale_linters/verilog/iverilog.vim index c64a3be6..e081f33f 100644 --- a/ale_linters/verilog/iverilog.vim +++ b/ale_linters/verilog/iverilog.vim @@ -38,6 +38,6 @@ call ale#linter#Define('verilog', { \ 'name': 'iverilog', \ 'output_stream': 'stderr', \ 'executable': 'iverilog', -\ 'command_callback': 'ale_linters#verilog#iverilog#GetCommand', +\ 'command': function('ale_linters#verilog#iverilog#GetCommand'), \ 'callback': 'ale_linters#verilog#iverilog#Handle', \}) diff --git a/ale_linters/verilog/verilator.vim b/ale_linters/verilog/verilator.vim index 83d5f59d..64bb6e41 100644 --- a/ale_linters/verilog/verilator.vim +++ b/ale_linters/verilog/verilator.vim @@ -10,7 +10,7 @@ function! ale_linters#verilog#verilator#GetCommand(buffer) abort let l:filename = ale#util#Tempname() . '_verilator_linted.v' " Create a special filename, so we can detect it in the handler. - call ale#engine#ManageFile(a:buffer, l:filename) + call ale#command#ManageFile(a:buffer, l:filename) let l:lines = getbufline(a:buffer, 1, '$') call ale#util#Writefile(a:buffer, l:lines, l:filename) @@ -53,7 +53,7 @@ call ale#linter#Define('verilog', { \ 'name': 'verilator', \ 'output_stream': 'stderr', \ 'executable': 'verilator', -\ 'command_callback': 'ale_linters#verilog#verilator#GetCommand', +\ 'command': function('ale_linters#verilog#verilator#GetCommand'), \ 'callback': 'ale_linters#verilog#verilator#Handle', \ 'read_buffer': 0, \}) diff --git a/ale_linters/verilog/vlog.vim b/ale_linters/verilog/vlog.vim new file mode 100644 index 00000000..37d21c4c --- /dev/null +++ b/ale_linters/verilog/vlog.vim @@ -0,0 +1,36 @@ +" Author: John Gentile <johncgentile17@gmail.com> +" Description: Adds support for Mentor Graphics Questa/ModelSim `vlog` Verilog compiler/checker + +call ale#Set('verilog_vlog_executable', 'vlog') +" See `$ vlog -h` for more options +call ale#Set('verilog_vlog_options', '-quiet -lint') + +function! ale_linters#verilog#vlog#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'verilog_vlog_options')) . ' %t' +endfunction + +function! ale_linters#verilog#vlog#Handle(buffer, lines) abort + "Matches patterns like the following: + "** Warning: add.v(7): (vlog-2623) Undefined variable: C. + "** Error: file.v(1): (vlog-13294) Identifier must be declared with a port mode: C. + let l:pattern = '^**\s\(\w*\):[a-zA-Z0-9\-\.\_\/ ]\+(\(\d\+\)):\s\+\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'type': l:match[1] is? 'Error' ? 'E' : 'W', + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'vlog', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'verilog_vlog_executable')}, +\ 'command': function('ale_linters#verilog#vlog#GetCommand'), +\ 'callback': 'ale_linters#verilog#vlog#Handle', +\}) diff --git a/ale_linters/verilog/xvlog.vim b/ale_linters/verilog/xvlog.vim new file mode 100644 index 00000000..98b5aae7 --- /dev/null +++ b/ale_linters/verilog/xvlog.vim @@ -0,0 +1,35 @@ +" Author: John Gentile <johncgentile17@gmail.com> +" Description: Adds support for Xilinx Vivado `xvlog` Verilog compiler/checker + +call ale#Set('verilog_xvlog_executable', 'xvlog') +call ale#Set('verilog_xvlog_options', '') + +function! ale_linters#verilog#xvlog#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'verilog_xvlog_options')) . ' %t' +endfunction + +function! ale_linters#verilog#xvlog#Handle(buffer, lines) abort + "Matches patterns like the following: + " ERROR: [VRFC 10-1412] syntax error near output [/path/to/file.v:5] + let l:pattern = '^ERROR:\s\+\(\[.*\)\[.*:\([0-9]\+\)\]' + let l:output = [] + + " NOTE: `xvlog` only prints 'INFO' and 'ERROR' messages + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'type': 'E', + \ 'text': l:match[1], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'xvlog', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'verilog_xvlog_executable')}, +\ 'command': function('ale_linters#verilog#xvlog#GetCommand'), +\ 'callback': 'ale_linters#verilog#xvlog#Handle', +\}) diff --git a/ale_linters/vhdl/ghdl.vim b/ale_linters/vhdl/ghdl.vim new file mode 100644 index 00000000..b09e620b --- /dev/null +++ b/ale_linters/vhdl/ghdl.vim @@ -0,0 +1,37 @@ +" Author: John Gentile <johncgentile17@gmail.com> +" Description: Adds support for `ghdl` VHDL compiler/checker + +call ale#Set('vhdl_ghdl_executable', 'ghdl') +" Compile w/VHDL-2008 support +call ale#Set('vhdl_ghdl_options', '--std=08') + +function! ale_linters#vhdl#ghdl#GetCommand(buffer) abort + return '%e -s ' . ale#Pad(ale#Var(a:buffer, 'vhdl_ghdl_options')) . ' %t' +endfunction + +function! ale_linters#vhdl#ghdl#Handle(buffer, lines) abort + " Look for 'error' lines like the following: + " dff_en.vhd:41:5:error: 'begin' is expected instead of 'if' + " /path/to/file.vhdl:12:8: no declaration for "i0" + let l:pattern = '^[a-zA-Z0-9\-\.\_\/ ]\+:\(\d\+\):\(\d\+\):\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col' : l:match[2] + 0, + \ 'text': l:match[3], + \ 'type': 'E', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('vhdl', { +\ 'name': 'ghdl', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'vhdl_ghdl_executable')}, +\ 'command': function('ale_linters#vhdl#ghdl#GetCommand'), +\ 'callback': 'ale_linters#vhdl#ghdl#Handle', +\}) diff --git a/ale_linters/vhdl/vcom.vim b/ale_linters/vhdl/vcom.vim new file mode 100644 index 00000000..1914fd33 --- /dev/null +++ b/ale_linters/vhdl/vcom.vim @@ -0,0 +1,38 @@ +" Author: John Gentile <johncgentile17@gmail.com> +" Description: Adds support for Mentor Graphics Questa/ModelSim `vcom` VHDL compiler/checker + +call ale#Set('vhdl_vcom_executable', 'vcom') +" Use VHDL-2008. See `$ vcom -h` for more options +call ale#Set('vhdl_vcom_options', '-2008 -quiet -lint') + +function! ale_linters#vhdl#vcom#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'vhdl_vcom_options')) . ' %t' +endfunction + +function! ale_linters#vhdl#vcom#Handle(buffer, lines) abort + "Matches patterns like the following: + "** Warning: ../path/to/file.vhd(218): (vcom-1236) Shared variables must be of a protected type. + "** Error: tb_file.vhd(73): (vcom-1136) Unknown identifier "aresetn". + "** Error: tb_file.vhd(73): Bad resolution function (STD_LOGIC) for type (error). + "** Error: tb_file.vhd(73): near ":": (vcom-1576) expecting ';' or ')'. + let l:pattern = '^**\s\(\w*\):[a-zA-Z0-9\-\.\_\/ ]\+(\(\d\+\)):\s\+\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'type': l:match[1] is? 'Error' ? 'E' : 'W', + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('vhdl', { +\ 'name': 'vcom', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'vhdl_vcom_executable')}, +\ 'command': function('ale_linters#vhdl#vcom#GetCommand'), +\ 'callback': 'ale_linters#vhdl#vcom#Handle', +\}) diff --git a/ale_linters/vhdl/xvhdl.vim b/ale_linters/vhdl/xvhdl.vim new file mode 100644 index 00000000..8010ff14 --- /dev/null +++ b/ale_linters/vhdl/xvhdl.vim @@ -0,0 +1,37 @@ +" Author: John Gentile <johncgentile17@gmail.com> +" Description: Adds support for Xilinx Vivado `xvhdl` VHDL compiler/checker + +call ale#Set('vhdl_xvhdl_executable', 'xvhdl') +" Use VHDL-2008. See `$ xvhdl -h` for more options +call ale#Set('vhdl_xvhdl_options', '--2008') + +function! ale_linters#vhdl#xvhdl#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'vhdl_xvhdl_options')) . ' %t' +endfunction + +function! ale_linters#vhdl#xvhdl#Handle(buffer, lines) abort + "Matches patterns like the following: + " ERROR: [VRFC 10-91] aresetn is not declared [/path/to/file.vhd:17] + " ERROR: [VRFC 10-91] m_axis_tx_tdata is not declared [/home/user/tx_data.vhd:128] + let l:pattern = '^ERROR:\s\+\(\[.*\)\[.*:\([0-9]\+\)\]' + let l:output = [] + + " NOTE: `xvhdl` only prints 'INFO' and 'ERROR' messages + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'type': 'E', + \ 'text': l:match[1], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('vhdl', { +\ 'name': 'xvhdl', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'vhdl_xvhdl_executable')}, +\ 'command': function('ale_linters#vhdl#xvhdl#GetCommand'), +\ 'callback': 'ale_linters#vhdl#xvhdl#Handle', +\}) diff --git a/ale_linters/vim/ale_custom_linting_rules.vim b/ale_linters/vim/ale_custom_linting_rules.vim index 3da44206..822eb30a 100644 --- a/ale_linters/vim/ale_custom_linting_rules.vim +++ b/ale_linters/vim/ale_custom_linting_rules.vim @@ -25,7 +25,7 @@ endfunction function! ale_linters#vim#ale_custom_linting_rules#GetCommand(buffer) abort let l:dir = s:GetALEProjectDir(a:buffer) - let l:temp_dir = ale#engine#CreateDirectory(a:buffer) + let l:temp_dir = ale#command#CreateDirectory(a:buffer) let l:temp_file = l:temp_dir . '/example.vim' let l:lines = getbufline(a:buffer, 1, '$') @@ -58,8 +58,8 @@ endfunction call ale#linter#Define('vim', { \ 'name': 'ale_custom_linting_rules', -\ 'executable_callback': 'ale_linters#vim#ale_custom_linting_rules#GetExecutable', -\ 'command_callback': 'ale_linters#vim#ale_custom_linting_rules#GetCommand', +\ 'executable': function('ale_linters#vim#ale_custom_linting_rules#GetExecutable'), +\ 'command': function('ale_linters#vim#ale_custom_linting_rules#GetCommand'), \ 'callback': 'ale_linters#vim#ale_custom_linting_rules#Handle', \ 'read_buffer': 0, \}) diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim index cf2d4afd..65e19126 100644 --- a/ale_linters/vim/vint.vim +++ b/ale_linters/vim/vint.vim @@ -7,29 +7,13 @@ 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(l:executable) - \ ? ale#Escape(l:executable) . ' --version' - \ : '' -endfunction - -function! ale_linters#vim#vint#GetCommand(buffer, version_output) abort - 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]) +function! ale_linters#vim#vint#GetCommand(buffer, version) abort + let l:can_use_no_color_flag = empty(a:version) + \ || ale#semver#GTE(a:version, [0, 3, 7]) let l:warning_flag = ale#Var(a:buffer, 'vim_vint_show_style_issues') ? '-s' : '-w' - return ale#Escape(l:executable) + return '%e' \ . ' ' . l:warning_flag \ . (l:can_use_no_color_flag ? ' --no-color' : '') \ . s:enable_neovim @@ -65,10 +49,12 @@ endfunction call ale#linter#Define('vim', { \ 'name': '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'}, -\ ], +\ 'executable': {buffer -> ale#Var(buffer, 'vim_vint_executable')}, +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'vim_vint_executable'), +\ '%e --version', +\ function('ale_linters#vim#vint#GetCommand'), +\ )}, \ 'callback': 'ale_linters#vim#vint#Handle', \}) diff --git a/ale_linters/vue/vls.vim b/ale_linters/vue/vls.vim index 7116128b..ac451f3c 100644 --- a/ale_linters/vue/vls.vim +++ b/ale_linters/vue/vls.vim @@ -13,10 +13,10 @@ endfunction call ale#linter#Define('vue', { \ 'name': 'vls', \ 'lsp': 'stdio', -\ 'executable_callback': ale#node#FindExecutableFunc('vue_vls', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'vue_vls', [ \ 'node_modules/.bin/vls', -\ ]), +\ ])}, \ 'command': '%e --stdio', \ 'language': 'vue', -\ 'project_root_callback': 'ale_linters#vue#vls#GetProjectRoot', +\ 'project_root': function('ale_linters#vue#vls#GetProjectRoot'), \}) diff --git a/ale_linters/xhtml/alex.vim b/ale_linters/xhtml/alex.vim index 60a9a7c9..97f3b59a 100644 --- a/ale_linters/xhtml/alex.vim +++ b/ale_linters/xhtml/alex.vim @@ -1,11 +1,4 @@ " Author: Johannes Wienke <languitar@semipol.de> " Description: alex for XHTML files -call ale#linter#Define('xhtml', { -\ 'name': 'alex', -\ 'executable': 'alex', -\ 'command': 'alex %s -t', -\ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#alex#Handle', -\ 'lint_file': 1, -\}) +call ale#handlers#alex#DefineLinter('xhtml', '--text') diff --git a/ale_linters/xml/xmllint.vim b/ale_linters/xml/xmllint.vim index 59f43d16..553d0883 100644 --- a/ale_linters/xml/xmllint.vim +++ b/ale_linters/xml/xmllint.vim @@ -29,27 +29,27 @@ function! ale_linters#xml#xmllint#Handle(buffer, lines) abort let l:match_message = matchlist(l:line, l:pattern_message) if !empty(l:match_message) - let l:line = l:match_message[2] + 0 - let l:type = l:match_message[4] =~? 'warning' ? 'W' : 'E' - let l:text = l:match_message[3] + let l:line = l:match_message[2] + 0 + let l:type = l:match_message[4] =~? 'warning' ? 'W' : 'E' + let l:text = l:match_message[3] - call add(l:output, { - \ 'lnum': l:line, - \ 'text': l:text, - \ 'type': l:type, - \}) + call add(l:output, { + \ 'lnum': l:line, + \ 'text': l:text, + \ 'type': l:type, + \}) - continue + continue endif " Parse column position let l:match_column_token = matchlist(l:line, l:pattern_column_token) if !empty(l:output) && !empty(l:match_column_token) - let l:previous = l:output[len(l:output) - 1] - let l:previous['col'] = len(l:match_column_token[0]) + let l:previous = l:output[len(l:output) - 1] + let l:previous['col'] = len(l:match_column_token[0]) - continue + continue endif endfor @@ -59,7 +59,7 @@ endfunction call ale#linter#Define('xml', { \ 'name': 'xmllint', \ 'output_stream': 'stderr', -\ 'executable_callback': ale#VarFunc('xml_xmllint_executable'), -\ 'command_callback': 'ale_linters#xml#xmllint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'xml_xmllint_executable')}, +\ 'command': function('ale_linters#xml#xmllint#GetCommand'), \ 'callback': 'ale_linters#xml#xmllint#Handle', \ }) diff --git a/ale_linters/yaml/swaglint.vim b/ale_linters/yaml/swaglint.vim index 7362536e..1f140e37 100644 --- a/ale_linters/yaml/swaglint.vim +++ b/ale_linters/yaml/swaglint.vim @@ -32,9 +32,9 @@ endfunction call ale#linter#Define('yaml', { \ 'name': 'swaglint', -\ 'executable_callback': ale#node#FindExecutableFunc('yaml_swaglint', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'yaml_swaglint', [ \ 'node_modules/.bin/swaglint', -\ ]), +\ ])}, \ 'command': '%e -r compact --stdin', \ 'callback': 'ale_linters#yaml#swaglint#Handle', \}) diff --git a/ale_linters/yaml/yamllint.vim b/ale_linters/yaml/yamllint.vim index 9d2cc7c2..bedb7bf1 100644 --- a/ale_linters/yaml/yamllint.vim +++ b/ale_linters/yaml/yamllint.vim @@ -44,7 +44,7 @@ endfunction call ale#linter#Define('yaml', { \ 'name': 'yamllint', -\ 'executable_callback': ale#VarFunc('yaml_yamllint_executable'), -\ 'command_callback': 'ale_linters#yaml#yamllint#GetCommand', +\ 'executable': {b -> ale#Var(b, 'yaml_yamllint_executable')}, +\ 'command': function('ale_linters#yaml#yamllint#GetCommand'), \ 'callback': 'ale_linters#yaml#yamllint#Handle', \}) diff --git a/ale_linters/yang/yang_lsp.vim b/ale_linters/yang/yang_lsp.vim index 45776f98..81fcaa0e 100644 --- a/ale_linters/yang/yang_lsp.vim +++ b/ale_linters/yang/yang_lsp.vim @@ -9,7 +9,7 @@ endfunction call ale#linter#Define('yang', { \ 'name': 'yang_lsp', \ 'lsp': 'stdio', -\ 'executable_callback': ale#VarFunc('yang_lsp_executable'), -\ 'project_root_callback': 'ale_linters#yang#yang_lsp#GetProjectRoot', +\ 'executable': {b -> ale#Var(b, 'yang_lsp_executable')}, +\ 'project_root': function('ale_linters#yang#yang_lsp#GetProjectRoot'), \ 'command': '%e', \}) diff --git a/autoload/ale.vim b/autoload/ale.vim index f6c23d72..a887834b 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -8,6 +8,7 @@ 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 g:ale_disable_lsp = get(g:, 'ale_disable_lsp', 0) let s:lint_timer = -1 let s:getcmdwintype_exists = exists('*getcmdwintype') @@ -42,6 +43,11 @@ function! ale#ShouldDoNothing(buffer) abort return 1 endif + " Do nothing for diff buffers. + if getbufvar(a:buffer, '&diff') + return 1 + endif + " Do nothing for blacklisted files. if index(get(g:, 'ale_filetype_blacklist', []), l:filetype) >= 0 return 1 @@ -90,8 +96,9 @@ function! s:Lint(buffer, should_lint_file, timer_id) abort " Apply ignore lists for linters only if needed. let l:ignore_config = ale#Var(a:buffer, 'linters_ignore') + let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') let l:linters = !empty(l:ignore_config) - \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config) + \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config, l:disable_lsp) \ : l:linters " Tell other sources that they can start checking the buffer now. @@ -149,12 +156,19 @@ function! ale#Queue(delay, ...) abort endif endfunction -let g:ale_has_override = get(g:, 'ale_has_override', {}) +let s:current_ale_version = [2, 4, 0] -" Call has(), but check a global Dictionary so we can force flags on or off -" for testing purposes. +" A function used to check for ALE features in files outside of the project. function! ale#Has(feature) abort - return get(g:ale_has_override, a:feature, has(a:feature)) + let l:match = matchlist(a:feature, '\c\v^ale-(\d+)\.(\d+)(\.(\d+))?$') + + if !empty(l:match) + let l:version = [l:match[1] + 0, l:match[2] + 0, l:match[4] + 0] + + return ale#semver#GTE(s:current_ale_version, l:version) + endif + + return 0 endfunction " Given a buffer number and a variable name, look for that variable in the @@ -169,11 +183,6 @@ function! ale#Var(buffer, variable_name) abort return get(l:vars, l:full_name, g:[l:full_name]) endfunction -" As above, but curry the arguments so only the buffer number is required. -function! ale#VarFunc(variable_name) abort - return {buf -> ale#Var(buf, a:variable_name)} -endfunction - " Initialize a variable with a default value, if it isn't already set. " " Every variable name will be prefixed with 'ale_'. diff --git a/autoload/ale/args.vim b/autoload/ale/args.vim new file mode 100644 index 00000000..70afb2e5 --- /dev/null +++ b/autoload/ale/args.vim @@ -0,0 +1,43 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: This module implements a function for parsing arguments for +" commands. + +" Given a list of valid arguments like ['foo', 'bar'] and a string to parse, +" parse the arguments from the string and return [parsed_args, remainder]. +" +" Arguments must be prefixed in the string with a single minus (-), and a +" double minus (--) denotes the end of arguments. +function! ale#args#Parse(arg_list, string) abort + let l:parsed = {} + let l:end_of_args = 0 + let l:word_list = split(a:string, ' ') + let l:index = 0 + + while l:index < len(l:word_list) + let l:word = l:word_list[l:index] + + if l:word[:0] is# '-' + let l:index += 1 + + if l:word is# '--' + break + endif + + let l:arg = l:word[1:] + + if index(a:arg_list, l:arg) >= 0 + let l:parsed[l:arg] = '' + else + throw 'Invalid argument: ' . l:word + endif + elseif l:word is# '' + let l:index += 1 + else + break + endif + endwhile + + let l:new_string = join(l:word_list[l:index :], ' ') + + return [l:parsed, l:new_string] +endfunction diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim index ed08ed09..ed90792d 100644 --- a/autoload/ale/assert.vim +++ b/autoload/ale/assert.vim @@ -1,7 +1,7 @@ -let s:chain_results = [] +let s:command_output = [] -function! ale#assert#WithChainResults(...) abort - let s:chain_results = a:000 +function! ale#assert#GivenCommandOutput(...) abort + let s:command_output = a:000 endfunction function! s:GetLinter() abort @@ -19,6 +19,39 @@ function! s:GetLinter() abort return l:filetype_linters[0] endfunction +function! s:FormatExe(command, executable) abort + return substitute(a:command, '%e', '\=ale#Escape(a:executable)', 'g') +endfunction + +function! s:ProcessDeferredCommands(initial_result) abort + let l:result = a:initial_result + let l:command_index = 0 + let l:command = [] + + while ale#command#IsDeferred(l:result) + call add(l:command, s:FormatExe(l:result.command, l:result.executable)) + + if get(g:, 'ale_run_synchronously_emulate_commands') + " Don't run commands, but simulate the results. + let l:Callback = g:ale_run_synchronously_callbacks[0] + let l:output = get(s:command_output, l:command_index, []) + call l:Callback(0, l:output) + unlet g:ale_run_synchronously_callbacks + + let l:command_index += 1 + else + " Run the commands in the shell, synchronously. + call ale#test#FlushJobs() + endif + + let l:result = l:result.value + endwhile + + call add(l:command, l:result) + + return l:command +endfunction + " Load the currently loaded linter for a test case, and check that the command " matches the given string. function! ale#assert#Linter(expected_executable, expected_command) abort @@ -26,42 +59,25 @@ function! ale#assert#Linter(expected_executable, expected_command) abort let l:linter = s:GetLinter() let l:executable = ale#linter#GetExecutable(l:buffer, l:linter) - if has_key(l:linter, 'command_chain') - let l:callbacks = map(copy(l:linter.command_chain), 'v:val.callback') - - " If the expected command is a string, just check the last one. - if type(a:expected_command) is v:t_string - if len(l:callbacks) is 1 - let l:command = call(l:callbacks[0], [l:buffer]) - else - let l:input = get(s:chain_results, len(l:callbacks) - 2, []) - let l:command = call(l:callbacks[-1], [l:buffer, l:input]) - endif - else - let l:command = [] - let l:chain_index = 0 - - for l:Callback in l:callbacks - if l:chain_index is 0 - call add(l:command, call(l:Callback, [l:buffer])) - else - let l:input = get(s:chain_results, l:chain_index - 1, []) - call add(l:command, call(l:Callback, [l:buffer, l:input])) - endif - - let l:chain_index += 1 - endfor - endif - else - let l:command = ale#linter#GetCommand(l:buffer, l:linter) + while ale#command#IsDeferred(l:executable) + call ale#test#FlushJobs() + let l:executable = l:executable.value + endwhile + + let l:command = s:ProcessDeferredCommands( + \ ale#linter#GetCommand(l:buffer, l:linter), + \) + + if type(a:expected_command) isnot v:t_list + let l:command = l:command[-1] endif if type(l:command) is v:t_string " Replace %e with the escaped executable, so tests keep passing after " linters are changed to use %e. - let l:command = substitute(l:command, '%e', '\=ale#Escape(l:executable)', 'g') - else - call map(l:command, 'substitute(v:val, ''%e'', ''\=ale#Escape(l:executable)'', ''g'')') + let l:command = s:FormatExe(l:command, l:executable) + elseif type(l:command) is v:t_list + call map(l:command, 's:FormatExe(v:val, l:executable)') endif AssertEqual @@ -69,6 +85,17 @@ function! ale#assert#Linter(expected_executable, expected_command) abort \ [l:executable, l:command] endfunction +function! ale#assert#Fixer(expected_result) abort + let l:buffer = bufnr('') + let l:result = s:ProcessDeferredCommands(s:FixerFunction(l:buffer)) + + if type(a:expected_result) isnot v:t_list + let l:result = l:result[-1] + endif + + AssertEqual a:expected_result, l:result +endfunction + function! ale#assert#LinterNotExecuted() abort let l:buffer = bufnr('') let l:linter = s:GetLinter() @@ -104,7 +131,7 @@ endfunction function! ale#assert#LSPProject(expected_root) abort let l:buffer = bufnr('') let l:linter = s:GetLinter() - let l:root = ale#util#GetFunction(l:linter.project_root_callback)(l:buffer) + let l:root = ale#lsp_linter#FindProjectRoot(l:buffer, l:linter) AssertEqual a:expected_root, l:root endfunction @@ -112,11 +139,27 @@ endfunction function! ale#assert#LSPAddress(expected_address) abort let l:buffer = bufnr('') let l:linter = s:GetLinter() - let l:address = ale#util#GetFunction(l:linter.address_callback)(l:buffer) + let l:address = ale#linter#GetAddress(l:buffer, l:linter) AssertEqual a:expected_address, l:address endfunction +function! ale#assert#SetUpLinterTestCommands() abort + command! -nargs=+ GivenCommandOutput :call ale#assert#GivenCommandOutput(<args>) + command! -nargs=+ AssertLinter :call ale#assert#Linter(<args>) + command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted() + command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions(<args>) + command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig(<args>) + command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage(<args>) + command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject(<args>) + command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress(<args>) +endfunction + +function! ale#assert#SetUpFixerTestCommands() abort + command! -nargs=+ GivenCommandOutput :call ale#assert#GivenCommandOutput(<args>) + command! -nargs=+ AssertFixer :call ale#assert#Fixer(<args>) +endfunction + " A dummy function for making sure this module is loaded. function! ale#assert#SetUpLinterTest(filetype, name) abort " Set up a marker so ALE doesn't create real random temporary filenames. @@ -129,6 +172,12 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort let l:prefix = 'ale_' . a:filetype . '_' . a:name let b:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix' + Save g:ale_lsp_root + let g:ale_lsp_root = {} + + Save b:ale_lsp_root + unlet! b:ale_lsp_root + Save g:ale_c_build_dir unlet! g:ale_c_build_dir @@ -151,22 +200,22 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort call ale#test#SetDirectory('/testplugin/test/command_callback') endif - command! -nargs=+ WithChainResults :call ale#assert#WithChainResults(<args>) - command! -nargs=+ AssertLinter :call ale#assert#Linter(<args>) - command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted() - command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions(<args>) - command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig(<args>) - command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage(<args>) - command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject(<args>) - command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress(<args>) + call ale#assert#SetUpLinterTestCommands() + + let g:ale_run_synchronously = 1 + let g:ale_run_synchronously_emulate_commands = 1 endfunction function! ale#assert#TearDownLinterTest() abort unlet! g:ale_create_dummy_temporary_file - let s:chain_results = [] - - if exists(':WithChainResults') - delcommand WithChainResults + unlet! g:ale_run_synchronously + unlet! g:ale_run_synchronously_callbacks + unlet! g:ale_run_synchronously_emulate_commands + unlet! g:ale_run_synchronously_command_results + let s:command_output = [] + + if exists(':GivenCommandOutput') + delcommand GivenCommandOutput endif if exists(':AssertLinter') @@ -209,3 +258,62 @@ function! ale#assert#TearDownLinterTest() abort call ale#semver#ResetVersionCache() endif endfunction + +function! ale#assert#SetUpFixerTest(filetype, name) abort + " Set up a marker so ALE doesn't create real random temporary filenames. + let g:ale_create_dummy_temporary_file = 1 + + let l:function_name = ale#fix#registry#GetFunc(a:name) + let s:FixerFunction = function(l:function_name) + + let l:prefix = 'ale_' . a:filetype . '_' . a:name + let b:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix' + + for l:key in filter(keys(g:), b:filter_expr) + execute 'Save g:' . l:key + unlet g:[l:key] + endfor + + for l:key in filter(keys(b:), b:filter_expr) + unlet b:[l:key] + endfor + + execute 'runtime autoload/ale/fixers/' . a:name . '.vim' + + if !exists('g:dir') + call ale#test#SetDirectory('/testplugin/test/fixers') + endif + + call ale#assert#SetUpFixerTestCommands() + + let g:ale_run_synchronously = 1 + let g:ale_run_synchronously_emulate_commands = 1 +endfunction + +function! ale#assert#TearDownFixerTest() abort + unlet! g:ale_create_dummy_temporary_file + unlet! g:ale_run_synchronously + unlet! g:ale_run_synchronously_callbacks + unlet! g:ale_run_synchronously_emulate_commands + unlet! g:ale_run_synchronously_command_results + let s:command_output = [] + unlet! s:FixerFunction + + if exists('g:dir') + call ale#test#RestoreDirectory() + endif + + Restore + + if exists('*ale#semver#ResetVersionCache') + call ale#semver#ResetVersionCache() + endif + + if exists(':GivenCommandOutput') + delcommand GivenCommandOutput + endif + + if exists(':AssertFixer') + delcommand AssertFixer + endif +endfunction diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim index ce5105b6..a9289e22 100644 --- a/autoload/ale/c.vim +++ b/autoload/ale/c.vim @@ -46,49 +46,76 @@ function! ale#c#FindProjectRoot(buffer) abort return '' endfunction -function! ale#c#ParseCFlags(path_prefix, cflag_line) abort - let l:cflags_list = [] - let l:previous_options = [] +function! ale#c#AreSpecialCharsBalanced(option) abort + " Escape \" + let l:option_escaped = substitute(a:option, '\\"', '', 'g') - let l:split_lines = split(a:cflag_line, '-') - let l:option_index = 0 + " Retain special chars only + let l:special_chars = substitute(l:option_escaped, '[^"''()`]', '', 'g') + let l:special_chars = split(l:special_chars, '\zs') - while l:option_index < len(l:split_lines) - let l:option = l:split_lines[l:option_index] - let l:option_index = l:option_index + 1 - 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') + " Check if they are balanced + let l:stack = [] - if len(l:option_list) > 0 && l:option_list[-1] isnot# ' ' && l:option_index < len(l:split_lines) - continue - endif + for l:char in l:special_chars + if l:char is# ')' + if len(l:stack) == 0 || get(l:stack, -1) isnot# '(' + return 0 + endif - let l:option = join(l:previous_options, '-') - let l:previous_options = [] + call remove(l:stack, -1) + elseif l:char is# '(' + call add(l:stack, l:char) + else + if len(l:stack) > 0 && get(l:stack, -1) is# l:char + call remove(l:stack, -1) + else + call add(l:stack, l:char) + endif + endif + endfor - let l:option = '-' . substitute(l:option, '^\s*\(.\{-}\)\s*$', '\1', '') + return len(l:stack) == 0 +endfunction - " 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 +function! ale#c#ParseCFlags(path_prefix, cflag_line) abort + let l:split_lines = split(a:cflag_line) + let l:option_index = 0 - " 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) + while l:option_index < len(l:split_lines) + let l:next_option_index = l:option_index + 1 + + " Join space-separated option + while l:next_option_index < len(l:split_lines) + \&& stridx(l:split_lines[l:next_option_index], '-') != 0 + let l:next_option_index += 1 + endwhile + + let l:option = join(l:split_lines[l:option_index : l:next_option_index-1], ' ') + call remove(l:split_lines, l:option_index, l:next_option_index-1) + call insert(l:split_lines, l:option, l:option_index) + + " Ignore invalid or conflicting options + if stridx(l:option, '-') != 0 + \|| stridx(l:option, '-o') == 0 + \|| stridx(l:option, '-c') == 0 + call remove(l:split_lines, l:option_index) + let l:option_index = l:option_index - 1 + " Fix relative path + elseif stridx(l:option, '-I') == 0 + if !(stridx(l:option, ':') == 2+1 || stridx(l:option, '/') == 2+0) + let l:option = '-I' . a:path_prefix . s:sep . l:option[2:] + call remove(l:split_lines, l:option_index) + call insert(l:split_lines, l:option, l:option_index) endif endif + + let l:option_index = l:option_index + 1 endwhile - return join(l:cflags_list, ' ') + call uniq(l:split_lines) + + return join(l:split_lines, ' ') endfunction function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort @@ -145,15 +172,17 @@ if !exists('s:compile_commands_cache') let s:compile_commands_cache = {} endif -function! s:GetListFromCompileCommandsFile(compile_commands_file) abort +function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort + let l:empty = [{}, {}] + if empty(a:compile_commands_file) - return [] + return l:empty endif let l:time = getftime(a:compile_commands_file) if l:time < 0 - return [] + return l:empty endif let l:key = a:compile_commands_file . ':' . l:time @@ -162,32 +191,74 @@ function! s:GetListFromCompileCommandsFile(compile_commands_file) abort return s:compile_commands_cache[l:key] endif - let l:data = [] - silent! let l:data = json_decode(join(readfile(a:compile_commands_file), '')) + let l:raw_data = [] + silent! let l:raw_data = json_decode(join(readfile(a:compile_commands_file), '')) + + let l:file_lookup = {} + let l:dir_lookup = {} - if !empty(l:data) - let s:compile_commands_cache[l:key] = l:data + for l:entry in l:raw_data + let l:basename = tolower(fnamemodify(l:entry.file, ':t')) + let l:file_lookup[l:basename] = get(l:file_lookup, l:basename, []) + [l:entry] + + let l:dirbasename = tolower(fnamemodify(l:entry.directory, ':p:h:t')) + let l:dir_lookup[l:dirbasename] = get(l:dir_lookup, l:dirbasename, []) + [l:entry] + endfor - return l:data + if !empty(l:file_lookup) && !empty(l:dir_lookup) + let l:result = [l:file_lookup, l:dir_lookup] + let s:compile_commands_cache[l:key] = l:result + + return l:result endif - return [] + return l:empty endfunction -function! ale#c#ParseCompileCommandsFlags(buffer, dir, json_list) abort +function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort " Search for an exact file match first. - for l:item in a:json_list - if bufnr(l:item.file) is a:buffer - return ale#c#ParseCFlags(a:dir, l:item.command) + let l:basename = tolower(expand('#' . a:buffer . ':t')) + let l:file_list = get(a:file_lookup, l:basename, []) + " A source file matching the header filename. + let l:source_file = '' + + if empty(l:file_list) && l:basename =~? '\.h$\|\.hpp$' + for l:suffix in ['.c', '.cpp'] + let l:key = fnamemodify(l:basename, ':r') . l:suffix + let l:file_list = get(a:file_lookup, l:key, []) + + if !empty(l:file_list) + let l:source_file = l:key + break + endif + endfor + endif + + for l:item in l:file_list + " Load the flags for this file, or for a source file matching the + " header file. + if has_key(l:item, 'command') + \&& ( + \ bufnr(l:item.file) is a:buffer + \ || ( + \ !empty(l:source_file) + \ && l:item.file[-len(l:source_file):] is? l:source_file + \ ) + \) + return ale#c#ParseCFlags(l:item.directory, l:item.command) endif endfor " Look for any file in the same directory if we can't find an exact match. let l:dir = ale#path#Simplify(expand('#' . a:buffer . ':p:h')) - for l:item in a:json_list + let l:dirbasename = tolower(expand('#' . a:buffer . ':p:h:t')) + let l:dir_list = get(a:dir_lookup, l:dirbasename, []) + + for l:item in l:dir_list if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir - return ale#c#ParseCFlags(a:dir, l:item.command) + \&& has_key(l:item, 'command') + return ale#c#ParseCFlags(l:item.directory, l:item.command) endif endfor @@ -195,10 +266,11 @@ function! ale#c#ParseCompileCommandsFlags(buffer, dir, json_list) abort endfunction function! ale#c#FlagsFromCompileCommands(buffer, compile_commands_file) abort - let l:dir = ale#path#Dirname(a:compile_commands_file) - let l:json_list = s:GetListFromCompileCommandsFile(a:compile_commands_file) + let l:lookups = s:GetLookupFromCompileCommandsFile(a:compile_commands_file) + let l:file_lookup = l:lookups[0] + let l:dir_lookup = l:lookups[1] - return ale#c#ParseCompileCommandsFlags(a:buffer, l:dir, l:json_list) + return ale#c#ParseCompileCommandsFlags(a:buffer, l:file_lookup, l:dir_lookup) endfunction function! ale#c#GetCFlags(buffer, output) abort @@ -235,6 +307,20 @@ function! ale#c#GetMakeCommand(buffer) abort return '' endfunction +function! ale#c#RunMakeCommand(buffer, Callback) abort + let l:command = ale#c#GetMakeCommand(a:buffer) + + if empty(l:command) + return a:Callback(a:buffer, []) + endif + + return ale#command#Run( + \ a:buffer, + \ l:command, + \ {b, output -> a:Callback(a:buffer, output)}, + \) +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/command.vim b/autoload/ale/command.vim index 543c9953..1bbc4f4c 100644 --- a/autoload/ale/command.vim +++ b/autoload/ale/command.vim @@ -1,6 +1,117 @@ " Author: w0rp <devw0rp@gmail.com> -" Description: Special command formatting for creating temporary files and -" passing buffer filenames easily. +" Description: Functions for formatting command strings, running commands, and +" managing files during linting and fixing cycles. + +" This dictionary holds lists of files and directories to remove later. +if !exists('s:buffer_data') + let s:buffer_data = {} +endif + +" Used to get the data in tests. +function! ale#command#GetData() abort + return deepcopy(s:buffer_data) +endfunction + +function! ale#command#ClearData() abort + let s:buffer_data = {} +endfunction + +function! ale#command#InitData(buffer) abort + if !has_key(s:buffer_data, a:buffer) + let s:buffer_data[a:buffer] = { + \ 'jobs': {}, + \ 'file_list': [], + \ 'directory_list': [], + \} + endif +endfunction + +function! ale#command#ManageFile(buffer, file) abort + call ale#command#InitData(a:buffer) + call add(s:buffer_data[a:buffer].file_list, a:file) +endfunction + +function! ale#command#ManageDirectory(buffer, directory) abort + call ale#command#InitData(a:buffer) + call add(s:buffer_data[a:buffer].directory_list, a:directory) +endfunction + +function! ale#command#CreateFile(buffer) abort + " This variable can be set to 1 in tests to stub this out. + if get(g:, 'ale_create_dummy_temporary_file') + return 'TEMP' + endif + + let l:temporary_file = ale#util#Tempname() + call ale#command#ManageFile(a:buffer, l:temporary_file) + + return l:temporary_file +endfunction + +" Create a new temporary directory and manage it in one go. +function! ale#command#CreateDirectory(buffer) abort + " This variable can be set to 1 in tests to stub this out. + if get(g:, 'ale_create_dummy_temporary_file') + return 'TEMP_DIR' + endif + + let l:temporary_directory = ale#util#Tempname() + " Create the temporary directory for the file, unreadable by 'other' + " users. + call mkdir(l:temporary_directory, '', 0750) + call ale#command#ManageDirectory(a:buffer, l:temporary_directory) + + return l:temporary_directory +endfunction + +function! ale#command#RemoveManagedFiles(buffer) abort + let l:info = get(s:buffer_data, a:buffer, {}) + + if !empty(l:info) && empty(l:info.jobs) + " We can't delete anything in a sandbox, so wait until we escape from + " it to delete temporary files and directories. + if ale#util#InSandbox() + return + endif + + " Delete files with a call akin to a plan `rm` command. + for l:filename in l:info.file_list + call delete(l:filename) + endfor + + " Delete directories like `rm -rf`. + " Directories are handled differently from files, so paths that are + " intended to be single files can be set up for automatic deletion + " without accidentally deleting entire directories. + for l:directory in l:info.directory_list + call delete(l:directory, 'rf') + endfor + + call remove(s:buffer_data, a:buffer) + endif +endfunction + +function! ale#command#CreateTempFile(buffer, temporary_file, input) abort + if empty(a:temporary_file) + " There is no file, so we didn't create anything. + return 0 + endif + + " Use an existing list of lines of input if we have it, or get the lines + " from the file. + let l:lines = a:input isnot v:null ? a:input : getbufline(a:buffer, 1, '$') + + let l:temporary_directory = fnamemodify(a:temporary_file, ':h') + " Create the temporary directory for the file, unreadable by 'other' + " users. + call mkdir(l:temporary_directory, '', 0750) + " Automatically delete the directory later. + call ale#command#ManageDirectory(a:buffer, l:temporary_directory) + " Write the buffer out to a file. + call ale#util#Writefile(a:buffer, l:lines, a:temporary_file) + + return 1 +endfunction function! s:TemporaryFilename(buffer) abort let l:filename = fnamemodify(bufname(a:buffer), ':t') @@ -16,11 +127,17 @@ function! s:TemporaryFilename(buffer) abort return ale#util#Tempname() . (has('win32') ? '\' : '/') . l:filename endfunction +" Given part of a command, replace any % with %%, so that no characters in +" the string will be replaced with filenames, etc. +function! ale#command#EscapeCommandPart(command_part) abort + return substitute(a:command_part, '%', '%%', 'g') +endfunction + " Given a command string, replace every... " %s -> with the current filename " %t -> with the name of an unused file in a temporary directory " %% -> with a literal % -function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed) abort +function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed, input) abort let l:temporary_file = '' let l:command = a:command @@ -40,7 +157,7 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne let l:command = substitute(l:command, '%s', '\=ale#Escape(l:filename)', 'g') endif - if l:command =~# '%t' + if a:input isnot v:false && l:command =~# '%t' " Create a temporary filename, <temp_dir>/<original_basename> " The file itself will not be created by this function. let l:temporary_file = s:TemporaryFilename(a:buffer) @@ -58,5 +175,205 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne let l:command = l:command . ' < ' . ale#Escape(l:temporary_file) endif - return [l:temporary_file, l:command] + let l:file_created = ale#command#CreateTempFile( + \ a:buffer, + \ l:temporary_file, + \ a:input, + \) + + return [l:temporary_file, l:command, l:file_created] +endfunction + +function! ale#command#StopJobs(buffer, job_type) abort + let l:info = get(s:buffer_data, a:buffer, {}) + + if !empty(l:info) + let l:new_map = {} + + for [l:job_id, l:job_type] in items(l:info.jobs) + let l:job_id = str2nr(l:job_id) + + if a:job_type is# 'all' || a:job_type is# l:job_type + call ale#job#Stop(l:job_id) + else + let l:new_map[l:job_id] = l:job_type + endif + endfor + + let l:info.jobs = l:new_map + endif +endfunction + +function! s:GatherOutput(line_list, job_id, line) abort + call add(a:line_list, a:line) +endfunction + +function! s:ExitCallback(buffer, line_list, Callback, data) abort + if !has_key(s:buffer_data, a:buffer) + return + endif + + let l:jobs = s:buffer_data[a:buffer].jobs + + if !has_key(l:jobs, a:data.job_id) + return + endif + + let l:job_type = remove(l:jobs, a:data.job_id) + + if g:ale_history_enabled + call ale#history#SetExitCode(a:buffer, a:data.job_id, a:data.exit_code) + + " Log the output of the command for ALEInfo if we should. + if g:ale_history_log_output && a:data.log_output is 1 + call ale#history#RememberOutput( + \ a:buffer, + \ a:data.job_id, + \ a:line_list[:] + \) + endif + endif + + " If the callback starts any new jobs, use the same job type for them. + call setbufvar(a:buffer, 'ale_job_type', l:job_type) + let l:value = a:Callback(a:buffer, a:line_list, { + \ 'exit_code': a:data.exit_code, + \ 'temporary_file': a:data.temporary_file, + \}) + + let l:result = a:data.result + let l:result.value = l:value + + if get(l:result, 'result_callback', v:null) isnot v:null + call call(l:result.result_callback, [l:value]) + endif +endfunction + +function! ale#command#Run(buffer, command, Callback, ...) abort + let l:options = get(a:000, 0, {}) + + if len(a:000) > 1 + throw 'Too many arguments!' + endif + + let l:output_stream = get(l:options, 'output_stream', 'stdout') + let l:line_list = [] + + let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand( + \ a:buffer, + \ get(l:options, 'executable', ''), + \ a:command, + \ get(l:options, 'read_buffer', 0), + \ get(l:options, 'input', v:null), + \) + let l:command = ale#job#PrepareCommand(a:buffer, l:command) + let l:job_options = { + \ 'exit_cb': {job_id, exit_code -> s:ExitCallback( + \ a:buffer, + \ l:line_list, + \ a:Callback, + \ { + \ 'job_id': job_id, + \ 'exit_code': exit_code, + \ 'temporary_file': l:temporary_file, + \ 'log_output': get(l:options, 'log_output', 1), + \ 'result': l:result, + \ } + \ )}, + \ 'mode': 'nl', + \} + + if l:output_stream is# 'stdout' + let l:job_options.out_cb = function('s:GatherOutput', [l:line_list]) + elseif l:output_stream is# 'stderr' + let l:job_options.err_cb = function('s:GatherOutput', [l:line_list]) + elseif l:output_stream is# 'both' + let l:job_options.out_cb = function('s:GatherOutput', [l:line_list]) + let l:job_options.err_cb = function('s:GatherOutput', [l:line_list]) + endif + + let l:status = 'failed' + + if get(g:, 'ale_run_synchronously') == 1 + if get(g:, 'ale_emulate_job_failure') == 1 + let l:job_id = 0 + else + " Generate a fake job ID for tests. + let s:fake_job_id = get(s:, 'fake_job_id', 0) + 1 + let l:job_id = s:fake_job_id + endif + elseif has('win32') + let l:job_id = ale#job#StartWithCmd(l:command, l:job_options) + else + let l:job_id = ale#job#Start(l:command, l:job_options) + endif + + if l:job_id + let l:status = 'started' + let l:job_type = getbufvar(a:buffer, 'ale_job_type', 'all') + + call ale#command#InitData(a:buffer) + let s:buffer_data[a:buffer].jobs[l:job_id] = l:job_type + endif + + if g:ale_history_enabled + call ale#history#Add(a:buffer, l:status, l:job_id, l:command) + endif + + if !l:job_id + return 0 + endif + + " We'll return this Dictionary. A `result_callback` can be assigned to it + " later for capturing the result of a:Callback. + " + " The `_deferred_job_id` is used for both checking the type of object, and + " for checking the job ID and status. + " + " The original command here is used in tests. + let l:result = { + \ '_deferred_job_id': l:job_id, + \ 'executable': get(l:options, 'executable', ''), + \ 'command': a:command, + \} + + if get(g:, 'ale_run_synchronously') == 1 && l:job_id + if !exists('g:ale_run_synchronously_callbacks') + let g:ale_run_synchronously_callbacks = [] + endif + + if get(g:, 'ale_run_synchronously_emulate_commands', 0) + call add( + \ g:ale_run_synchronously_callbacks, + \ {exit_code, output -> [ + \ extend(l:line_list, output), + \ l:job_options.exit_cb(l:job_id, exit_code), + \ ]} + \) + else + " Run a command synchronously if this test option is set. + call extend(l:line_list, systemlist( + \ type(l:command) is v:t_list + \ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2]) + \ : l:command + \)) + + " Don't capture output when the callbacks aren't set. + if !has_key(l:job_options, 'out_cb') + \&& !has_key(l:job_options, 'err_cb') + let l:line_list = [] + endif + + call add( + \ g:ale_run_synchronously_callbacks, + \ {-> l:job_options.exit_cb(l:job_id, v:shell_error)} + \) + endif + endif + + return l:result +endfunction + +function! ale#command#IsDeferred(value) abort + return type(a:value) is v:t_dict && has_key(a:value, '_deferred_job_id') endfunction diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index 9dd913f5..1d42c489 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -39,6 +39,9 @@ let s:LSP_COMPLETION_COLOR_KIND = 16 let s:LSP_COMPLETION_FILE_KIND = 17 let s:LSP_COMPLETION_REFERENCE_KIND = 18 +let s:LSP_INSERT_TEXT_FORMAT_PLAIN = 1 +let s:LSP_INSERT_TEXT_FORMAT_SNIPPET = 2 + let s:lisp_regex = '\v[a-zA-Z_\-][a-zA-Z_\-0-9]*$' " Regular expressions for checking the characters in the line before where @@ -89,6 +92,10 @@ function! ale#completion#GetPrefix(filetype, line, column) abort endfunction function! ale#completion#GetTriggerCharacter(filetype, prefix) abort + if empty(a:prefix) + return '' + endif + let l:char_list = s:GetFiletypeValue(s:trigger_character_map, a:filetype) if index(l:char_list, a:prefix) >= 0 @@ -100,33 +107,38 @@ endfunction function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words') - let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype) - " For completing... - " foo. - " ^ - " We need to include all of the given suggestions. - if index(l:triggers, a:prefix) >= 0 + if empty(a:prefix) 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) is v:t_string ? 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 + let l:triggers = s:GetFiletypeValue(s:trigger_character_map, a:filetype) + + " For completing... + " foo. + " ^ + " We need to include all of the given suggestions. + if index(l:triggers, a:prefix) >= 0 || empty(a:prefix) + 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) is v:t_string ? 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 endif if !empty(l:excluded_words) @@ -147,23 +159,29 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort endfunction function! s:ReplaceCompletionOptions() abort - " Remember the old omnifunc value, if there is one. - " If we don't store an old one, we'll just never reset the option. - " This will stop some random exceptions from appearing. - if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc) - let b:ale_old_omnifunc = &l:omnifunc - endif - - let &l:omnifunc = 'ale#completion#OmniFunc' + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if l:source is# 'ale-automatic' || l:source is# 'ale-manual' + " Remember the old omnifunc value, if there is one. + " If we don't store an old one, we'll just never reset the option. + " This will stop some random exceptions from appearing. + if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc) + let b:ale_old_omnifunc = &l:omnifunc + endif - if !exists('b:ale_old_completopt') - let b:ale_old_completopt = &l:completeopt + let &l:omnifunc = 'ale#completion#OmniFunc' endif - if &l:completeopt =~# 'preview' - let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' - else - let &l:completeopt = 'menu,menuone,noselect,noinsert' + if l:source is# 'ale-automatic' + if !exists('b:ale_old_completeopt') + let b:ale_old_completeopt = &l:completeopt + endif + + if &l:completeopt =~# 'preview' + let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' + else + let &l:completeopt = 'menu,menuone,noselect,noinsert' + endif endif endfunction @@ -177,37 +195,55 @@ function! ale#completion#RestoreCompletionOptions() abort unlet b:ale_old_omnifunc endif - if exists('b:ale_old_completopt') - let &l:completeopt = b:ale_old_completopt - unlet b:ale_old_completopt + if exists('b:ale_old_completeopt') + let &l:completeopt = b:ale_old_completeopt + unlet b:ale_old_completeopt endif endfunction -function! ale#completion#OmniFunc(findstart, base) abort - if a:findstart - let l:line = b:ale_completion_info.line - let l:column = b:ale_completion_info.column - let l:regex = s:GetFiletypeValue(s:omni_start_map, &filetype) - let l:up_to_column = getline(l:line)[: l:column - 2] - let l:match = matchstr(l:up_to_column, l:regex) +function! ale#completion#GetCompletionPosition() abort + if !exists('b:ale_completion_info') + return 0 + endif - return l:column - len(l:match) - 1 - else - " Parse a new response if there is one. - if exists('b:ale_completion_response') - \&& exists('b:ale_completion_parser') - let l:response = b:ale_completion_response - let l:parser = b:ale_completion_parser + let l:line = b:ale_completion_info.line + let l:column = b:ale_completion_info.column + let l:regex = s:GetFiletypeValue(s:omni_start_map, &filetype) + let l:up_to_column = getline(l:line)[: l:column - 2] + let l:match = matchstr(l:up_to_column, l:regex) - unlet b:ale_completion_response - unlet b:ale_completion_parser + return l:column - len(l:match) - 1 +endfunction - let b:ale_completion_result = function(l:parser)(l:response) - endif +function! ale#completion#GetCompletionResult() abort + " Parse a new response if there is one. + if exists('b:ale_completion_response') + \&& exists('b:ale_completion_parser') + let l:response = b:ale_completion_response + let l:parser = b:ale_completion_parser + + unlet b:ale_completion_response + unlet b:ale_completion_parser + + let b:ale_completion_result = function(l:parser)(l:response) + endif + + if exists('b:ale_completion_result') + return b:ale_completion_result + endif + + return v:null +endfunction + +function! ale#completion#OmniFunc(findstart, base) abort + if a:findstart + return ale#completion#GetCompletionPosition() + else + let l:result = ale#completion#GetCompletionResult() call s:ReplaceCompletionOptions() - return get(b:, 'ale_completion_result', []) + return l:result isnot v:null ? l:result : [] endif endfunction @@ -223,17 +259,27 @@ function! ale#completion#Show(response, completion_parser) abort " Replace completion options shortly before opening the menu. call s:ReplaceCompletionOptions() - call timer_start(0, {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")}) + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if l:source is# 'ale-automatic' || l:source is# 'ale-manual' + call timer_start( + \ 0, + \ {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")} + \) + endif endfunction function! s:CompletionStillValid(request_id) abort - let [l:line, l:column] = getcurpos()[1:2] + let [l:line, l:column] = getpos('.')[1:2] return ale#util#Mode() is# 'i' \&& has_key(b:, 'ale_completion_info') \&& b:ale_completion_info.request_id == a:request_id \&& b:ale_completion_info.line == l:line - \&& b:ale_completion_info.column == l:column + \&& ( + \ b:ale_completion_info.column == l:column + \ || b:ale_completion_info.source is# 'deoplete' + \) endfunction function! ale#completion#ParseTSServerCompletions(response) abort @@ -307,7 +353,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort endfunction function! ale#completion#NullFilter(buffer, item) abort - return 1 + return 1 endfunction function! ale#completion#ParseLSPCompletions(response) abort @@ -337,7 +383,14 @@ function! ale#completion#ParseLSPCompletions(response) abort continue endif - let l:word = matchstr(l:item.label, '\v^[^(]+') + if get(l:item, 'insertTextFormat') is s:LSP_INSERT_TEXT_FORMAT_PLAIN + \&& type(get(l:item, 'textEdit')) is v:t_dict + let l:text = l:item.textEdit.newText + else + let l:text = l:item.label + endif + + let l:word = matchstr(l:text, '\v^[^(]+') if empty(l:word) continue @@ -376,10 +429,10 @@ function! ale#completion#ParseLSPCompletions(response) abort endfor if has_key(l:info, 'prefix') - return ale#completion#Filter(l:buffer, &filetype, l:results, l:info.prefix) + let l:results = ale#completion#Filter(l:buffer, &filetype, l:results, l:info.prefix) endif - return l:results + return l:results[: g:ale_completion_max_suggestions - 1] endfunction function! ale#completion#HandleTSServerResponse(conn_id, response) abort @@ -437,10 +490,15 @@ function! ale#completion#HandleLSPResponse(conn_id, response) abort \) endfunction -function! s:OnReady(linter, lsp_details, ...) abort - let l:buffer = a:lsp_details.buffer +function! s:OnReady(linter, lsp_details) abort let l:id = a:lsp_details.connection_id + if !ale#lsp#HasCapability(l:id, 'completion') + return + endif + + let l:buffer = a:lsp_details.buffer + " If we have sent a completion request already, don't send another. if b:ale_completion_info.request_id return @@ -472,7 +530,7 @@ function! s:OnReady(linter, lsp_details, ...) abort \ min([ \ b:ale_completion_info.line_length, \ b:ale_completion_info.column, - \ ]), + \ ]) + 1, \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix), \) endif @@ -489,31 +547,14 @@ function! s:OnReady(linter, lsp_details, ...) abort endif endfunction -function! s:GetLSPCompletions(linter) abort - let l:buffer = bufnr('') - let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) - - if empty(l:lsp_details) - return 0 - endif - - let l:id = l:lsp_details.connection_id - - let l:OnReady = function('s:OnReady', [a:linter, l:lsp_details]) - - call ale#lsp#WaitForCapability(l:id, 'completion', l:OnReady) -endfunction - -function! ale#completion#GetCompletions() abort - if !g:ale_completion_enabled - return - endif - - let [l:line, l:column] = getcurpos()[1:2] +" This function can be used to manually trigger autocomplete, even when +" g:ale_completion_enabled is set to false +function! ale#completion#GetCompletions(source) abort + let [l:line, l:column] = getpos('.')[1:2] let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) - if empty(l:prefix) + if a:source is# 'ale-automatic' && empty(l:prefix) return endif @@ -526,24 +567,33 @@ function! ale#completion#GetCompletions() abort \ 'prefix': l:prefix, \ 'conn_id': 0, \ 'request_id': 0, + \ 'source': a:source, \} + unlet! b:ale_completion_result + + let l:buffer = bufnr('') + let l:Callback = function('s:OnReady') for l:linter in ale#linter#Get(&filetype) if !empty(l:linter.lsp) - call s:GetLSPCompletions(l:linter) + call ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback) endif endfor endfunction function! s:TimerHandler(...) abort + if !g:ale_completion_enabled + return + endif + let s:timer_id = -1 - let [l:line, l:column] = getcurpos()[1:2] + let [l:line, l:column] = getpos('.')[1:2] " When running the timer callback, we have to be sure that the cursor " hasn't moved from where it was when we requested completions by typing. if s:timer_pos == [l:line, l:column] && ale#util#Mode() is# 'i' - call ale#completion#GetCompletions() + call ale#completion#GetCompletions('ale-automatic') endif endfunction @@ -561,7 +611,7 @@ function! ale#completion#Queue() abort return endif - let s:timer_pos = getcurpos()[1:2] + let s:timer_pos = getpos('.')[1:2] if s:timer_pos == s:last_done_pos " Do not ask for completions if the cursor rests on the position we @@ -585,7 +635,7 @@ function! ale#completion#Done() abort call ale#completion#RestoreCompletionOptions() - let s:last_done_pos = getcurpos()[1:2] + let s:last_done_pos = getpos('.')[1:2] endfunction function! s:Setup(enabled) abort diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim index 6672c349..8c331c5c 100644 --- a/autoload/ale/cursor.vim +++ b/autoload/ale/cursor.vim @@ -22,10 +22,10 @@ function! ale#cursor#TruncatedEcho(original_message) abort let l:shortmess_options = &l:shortmess try - let l:cursor_position = getcurpos() + let l:cursor_position = getpos('.') " The message is truncated and saved to the history. - setlocal shortmess+=T + silent! setlocal shortmess+=T try exec "norm! :echomsg l:message\n" @@ -44,7 +44,7 @@ function! ale#cursor#TruncatedEcho(original_message) abort " Reset the cursor position if we moved off the end of the line. " Using :norm and :echomsg can move the cursor off the end of the " line. - if l:cursor_position != getcurpos() + if l:cursor_position != getpos('.') call setpos('.', l:cursor_position) endif finally @@ -114,7 +114,7 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort call s:StopCursorTimer() - let l:pos = getcurpos()[0:2] + let l:pos = getpos('.')[0:2] " Check the current buffer, line, and column number against the last " recorded position. If the position has actually changed, *then* diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index 3aed38fe..e4bf5e7e 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -31,6 +31,7 @@ let s:global_variable_list = [ \ 'ale_list_vertical', \ 'ale_list_window_size', \ 'ale_loclist_msg_format', +\ 'ale_lsp_root', \ 'ale_max_buffer_history_size', \ 'ale_max_signs', \ 'ale_maximum_file_size', diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index 984a4f9d..3915cac1 100644 --- a/autoload/ale/definition.vim +++ b/autoload/ale/definition.vim @@ -3,6 +3,9 @@ let s:go_to_definition_map = {} +" Enable automatic updates of the tagstack +let g:ale_update_tagstack = get(g:, 'ale_update_tagstack', 1) + " Used to get the definition map in tests. function! ale#definition#GetMap() abort return deepcopy(s:go_to_definition_map) @@ -17,6 +20,20 @@ function! ale#definition#ClearLSPData() abort let s:go_to_definition_map = {} endfunction +function! ale#definition#UpdateTagStack() abort + let l:should_update_tagstack = exists('*gettagstack') && exists('*settagstack') && g:ale_update_tagstack + + if l:should_update_tagstack + " Grab the old location (to jump back to) and the word under the + " cursor (as a label for the tagstack) + let l:old_location = [bufnr('%'), line('.'), col('.'), 0] + let l:tagname = expand('<cword>') + let l:winid = win_getid() + call settagstack(l:winid, {'items': [{'from': l:old_location, 'tagname': l:tagname}]}, 'a') + call settagstack(l:winid, {'curidx': len(gettagstack(l:winid)['items']) + 1}) + endif +endfunction + function! ale#definition#HandleTSServerResponse(conn_id, response) abort if get(a:response, 'command', '') is# 'definition' \&& has_key(s:go_to_definition_map, a:response.request_seq) @@ -27,6 +44,7 @@ function! ale#definition#HandleTSServerResponse(conn_id, response) abort let l:line = a:response.body[0].start.line let l:column = a:response.body[0].start.offset + call ale#definition#UpdateTagStack() call ale#util#Open(l:filename, l:line, l:column, l:options) endif endif @@ -51,16 +69,22 @@ 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 + 1 + call ale#definition#UpdateTagStack() call ale#util#Open(l:filename, l:line, l:column, l:options) break endfor endif endfunction -function! s:OnReady(linter, lsp_details, line, column, options, ...) abort - let l:buffer = a:lsp_details.buffer +function! s:OnReady(line, column, options, capability, linter, lsp_details) abort let l:id = a:lsp_details.connection_id + if !ale#lsp#HasCapability(l:id, a:capability) + return + endif + + let l:buffer = a:lsp_details.buffer + let l:Callback = a:linter.lsp is# 'tsserver' \ ? function('ale#definition#HandleTSServerResponse') \ : function('ale#definition#HandleLSPResponse') @@ -80,40 +104,53 @@ function! s:OnReady(linter, lsp_details, line, column, options, ...) abort " For LSP completions, we need to clamp the column to the length of " the line. python-language-server and perhaps others do not implement " this correctly. - let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column) + if a:capability is# 'definition' + let l:message = ale#lsp#message#Definition(l:buffer, a:line, a:column) + elseif a:capability is# 'typeDefinition' + let l:message = ale#lsp#message#TypeDefinition(l:buffer, a:line, a:column) + else + " XXX: log here? + return + endif endif let l:request_id = ale#lsp#Send(l:id, l:message) let s:go_to_definition_map[l:request_id] = { - \ 'open_in_tab': get(a:options, 'open_in_tab', 0), + \ 'open_in': get(a:options, 'open_in', 'current-buffer'), \} endfunction -function! s:GoToLSPDefinition(linter, options) abort +function! s:GoToLSPDefinition(linter, options, capability) abort let l:buffer = bufnr('') - let [l:line, l:column] = getcurpos()[1:2] - let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) - - if a:linter.lsp isnot# 'tsserver' - let l:column = min([l:column, len(getline(l:line))]) - endif - - if empty(l:lsp_details) - return 0 - endif - - let l:id = l:lsp_details.connection_id - - call ale#lsp#WaitForCapability(l:id, 'definition', function('s:OnReady', [ - \ a:linter, l:lsp_details, l:line, l:column, a:options - \])) + let [l:line, l:column] = getpos('.')[1:2] + let l:column = min([l:column, len(getline(l:line))]) + + let l:Callback = function( + \ 's:OnReady', + \ [l:line, l:column, a:options, a:capability] + \) + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) endfunction function! ale#definition#GoTo(options) abort for l:linter in ale#linter#Get(&filetype) if !empty(l:linter.lsp) - call s:GoToLSPDefinition(l:linter, a:options) + call s:GoToLSPDefinition(l:linter, a:options, 'definition') + endif + endfor +endfunction + +function! ale#definition#GoToType(options) abort + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + " TODO: handle typeDefinition for tsserver if supported by the + " protocol + if l:linter.lsp is# 'tsserver' + continue + endif + + call s:GoToLSPDefinition(l:linter, a:options, 'typeDefinition') endif endfor endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index b44be73c..7db808d6 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -5,20 +5,10 @@ " 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. -" buffer: The buffer number for the job. -" output: The array of lines for the output of the job. -if !has_key(s:, 'job_info_map') - let s:job_info_map = {} -endif - if !has_key(s:, 'executable_cache_map') let s:executable_cache_map = {} endif - function! ale#engine#CleanupEveryBuffer() abort for l:key in keys(g:ale_buffer_info) " The key could be a filename or a buffer number, so try and @@ -34,6 +24,25 @@ function! ale#engine#CleanupEveryBuffer() abort endfor endfunction +function! ale#engine#MarkLinterActive(info, linter) abort + let l:found = 0 + + for l:other_linter in a:info.active_linter_list + if l:other_linter.name is# a:linter.name + let l:found = 1 + break + endif + endfor + + if !l:found + call add(a:info.active_linter_list, a:linter) + endif +endfunction + +function! ale#engine#MarkLinterInactive(info, linter_name) abort + call filter(a:info.active_linter_list, 'v:val.name isnot# a:linter_name') +endfunction + function! ale#engine#ResetExecutableCache() abort let s:executable_cache_map = {} endfunction @@ -71,18 +80,12 @@ endfunction function! ale#engine#InitBufferInfo(buffer) abort if !has_key(g:ale_buffer_info, a:buffer) - " job_list will hold the list of job IDs " active_linter_list will hold the list of active linter names " loclist holds the loclist items after all jobs have completed. - " temporary_file_list holds temporary files to be cleaned up - " temporary_directory_list holds temporary directories to be cleaned up let g:ale_buffer_info[a:buffer] = { - \ 'job_list': [], \ 'active_linter_list': [], \ 'active_other_sources_list': [], \ 'loclist': [], - \ 'temporary_file_list': [], - \ 'temporary_directory_list': [], \} return 1 @@ -104,79 +107,37 @@ 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) + if !get(g:, 'ale_ignore_2_4_warnings') + execute 'echom ''ale#engine#ManageFile is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' + endif + + call ale#command#ManageFile(a:buffer, 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) + if !get(g:, 'ale_ignore_2_4_warnings') + execute 'echom ''ale#engine#ManageDirectory is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' + endif + + call ale#command#ManageDirectory(a:buffer, a:directory) endfunction function! ale#engine#CreateFile(buffer) abort - " This variable can be set to 1 in tests to stub this out. - if get(g:, 'ale_create_dummy_temporary_file') - return 'TEMP' + if !get(g:, 'ale_ignore_2_4_warnings') + execute 'echom ''ale#engine#CreateFile is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' endif - let l:temporary_file = ale#util#Tempname() - call ale#engine#ManageFile(a:buffer, l:temporary_file) - - return l:temporary_file + return ale#command#CreateFile(a:buffer) endfunction " Create a new temporary directory and manage it in one go. function! ale#engine#CreateDirectory(buffer) abort - " This variable can be set to 1 in tests to stub this out. - if get(g:, 'ale_create_dummy_temporary_file') - return 'TEMP_DIR' - endif - - let l:temporary_directory = ale#util#Tempname() - " Create the temporary directory for the file, unreadable by 'other' - " users. - call mkdir(l:temporary_directory, '', 0750) - call ale#engine#ManageDirectory(a:buffer, l:temporary_directory) - - return l:temporary_directory -endfunction - -function! ale#engine#RemoveManagedFiles(buffer) abort - let l:info = get(g:ale_buffer_info, a:buffer, {}) - - " We can't delete anything in a sandbox, so wait until we escape from - " it to delete temporary files and directories. - if ale#util#InSandbox() - return + if !get(g:, 'ale_ignore_2_4_warnings') + execute 'echom ''ale#engine#CreateDirectory is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' endif - " Delete files with a call akin to a plan `rm` command. - if has_key(l:info, 'temporary_file_list') - for l:filename in l:info.temporary_file_list - call delete(l:filename) - endfor - - let l:info.temporary_file_list = [] - endif - - " Delete directories like `rm -rf`. - " Directories are handled differently from files, so paths that are - " intended to be single files can be set up for automatic deletion without - " accidentally deleting entire directories. - if has_key(l:info, 'temporary_directory_list') - for l:directory in l:info.temporary_directory_list - call delete(l:directory, 'rf') - endfor - - let l:info.temporary_directory_list = [] - endif -endfunction - -function! s:GatherOutput(job_id, line) abort - if has_key(s:job_info_map, a:job_id) - call add(s:job_info_map[a:job_id].output, a:line) - endif + return ale#command#CreateDirectory(a:buffer) endfunction function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort @@ -189,7 +150,7 @@ function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_sour if !a:from_other_source " Remove this linter from the list of active linters. " This may have already been done when the job exits. - call filter(l:info.active_linter_list, 'v:val isnot# a:linter_name') + call filter(l:info.active_linter_list, 'v:val.name isnot# a:linter_name') endif " Make some adjustments to the loclists to fix common problems, and also @@ -222,27 +183,19 @@ function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_sour call ale#engine#SetResults(a:buffer, l:info.loclist) endfunction -function! s:HandleExit(job_id, exit_code) abort - if !has_key(s:job_info_map, a:job_id) +function! s:HandleExit(job_info, buffer, output, data) abort + let l:buffer_info = get(g:ale_buffer_info, a:buffer) + + if empty(l:buffer_info) return endif - let l:job_info = s:job_info_map[a:job_id] - let l:linter = l:job_info.linter - let l:output = l:job_info.output - let l:buffer = l:job_info.buffer - let l:executable = l:job_info.executable - let l:next_chain_index = l:job_info.next_chain_index - - if g:ale_history_enabled - call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code) - endif + let l:linter = a:job_info.linter + let l:executable = a:job_info.executable + let l:next_chain_index = a:job_info.next_chain_index " Remove this job from the list. - call ale#job#Stop(a:job_id) - call remove(s:job_info_map, a:job_id) - call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val isnot# a:job_id') - call filter(g:ale_buffer_info[l:buffer].active_linter_list, 'v:val isnot# l:linter.name') + call ale#engine#MarkLinterInactive(l:buffer_info, l:linter.name) " Stop here if we land in the handle for a job completing if we're in " a sandbox. @@ -250,29 +203,32 @@ function! s:HandleExit(job_id, exit_code) abort return endif - if has('nvim') && !empty(l:output) && empty(l:output[-1]) - call remove(l:output, -1) + if has('nvim') && !empty(a:output) && empty(a:output[-1]) + call remove(a:output, -1) endif if l:next_chain_index < len(get(l:linter, 'command_chain', [])) - call s:InvokeChain(l:buffer, l:executable, l:linter, l:next_chain_index, l:output) + let [l:command, l:options] = ale#engine#ProcessChain( + \ a:buffer, + \ l:executable, + \ l:linter, + \ l:next_chain_index, + \ a:output, + \) - return - endif + call s:RunJob(l:command, l:options) - " Log the output of the command for ALEInfo if we should. - if g:ale_history_enabled && g:ale_history_log_output - call ale#history#RememberOutput(l:buffer, a:job_id, l:output[:]) + return endif try - let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output) + let l:loclist = ale#util#GetFunction(l:linter.callback)(a:buffer, a:output) " Handle the function being unknown, or being deleted. catch /E700/ let l:loclist = [] endtry - call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0) + call ale#engine#HandleLoclist(l:linter.name, a:buffer, l:loclist, 0) endfunction function! ale#engine#SetResults(buffer, loclist) abort @@ -321,7 +277,7 @@ function! ale#engine#SetResults(buffer, loclist) abort " Automatically remove all managed temporary files and directories " now that all jobs have completed. - call ale#engine#RemoveManagedFiles(a:buffer) + call ale#command#RemoveManagedFiles(a:buffer) " Call user autocommands. This allows users to hook into ALE's lint cycle. silent doautocmd <nomodeline> User ALELintPost @@ -472,33 +428,23 @@ endfunction " Given part of a command, replace any % with %%, so that no characters in " the string will be replaced with filenames, etc. function! ale#engine#EscapeCommandPart(command_part) abort - return substitute(a:command_part, '%', '%%', 'g') -endfunction - -function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort - if empty(a:temporary_file) - " There is no file, so we didn't create anything. - return 0 - endif - - let l:temporary_directory = fnamemodify(a:temporary_file, ':h') - " Create the temporary directory for the file, unreadable by 'other' - " users. - call mkdir(l:temporary_directory, '', 0750) - " Automatically delete the directory later. - call ale#engine#ManageDirectory(a:buffer, l:temporary_directory) - " Write the buffer out to a file. - let l:lines = getbufline(a:buffer, 1, '$') - call ale#util#Writefile(a:buffer, l:lines, a:temporary_file) - - return 1 + " TODO: Emit deprecation warning here later. + return ale#command#EscapeCommandPart(a:command_part) endfunction " Run a job. " -" Returns 1 when the job was started successfully. -function! s:RunJob(options) abort - let l:command = a:options.command +" Returns 1 when a job was started successfully. +function! s:RunJob(command, options) abort + if ale#command#IsDeferred(a:command) + let a:command.result_callback = { + \ command -> s:RunJob(command, a:options) + \} + + return 1 + endif + + let l:command = a:command if empty(l:command) return 0 @@ -512,201 +458,106 @@ function! s:RunJob(options) abort let l:read_buffer = a:options.read_buffer let l:info = g:ale_buffer_info[l:buffer] - let [l:temporary_file, l:command] = ale#command#FormatCommand( - \ l:buffer, - \ l:executable, - \ l:command, - \ l:read_buffer, - \) - - if s:CreateTemporaryFileForJob(l:buffer, l:temporary_file) - " If a temporary filename has been formatted in to the command, then - " we do not need to send the Vim buffer to the command. - let l:read_buffer = 0 - endif - - " Add a newline to commands which need it. - " This is only used for Flow for now, and is not documented. - if l:linter.add_newline - if has('win32') - let l:command = l:command . '; echo.' - else - let l:command = l:command . '; echo' - endif - endif - - let l:command = ale#job#PrepareCommand(l:buffer, l:command) - let l:job_options = { - \ 'mode': 'nl', - \ 'exit_cb': function('s:HandleExit'), - \} - - if l:output_stream is# 'stderr' - let l:job_options.err_cb = function('s:GatherOutput') - elseif l:output_stream is# 'both' - let l:job_options.out_cb = function('s:GatherOutput') - let l:job_options.err_cb = function('s:GatherOutput') - else - let l:job_options.out_cb = function('s:GatherOutput') - endif - - if get(g:, 'ale_run_synchronously') == 1 - " Find a unique Job value to use, which will be the same as the ID for - " running commands synchronously. This is only for test code. - let l:job_id = len(s:job_info_map) + 1 - - while has_key(s:job_info_map, l:job_id) - let l:job_id += 1 - endwhile - else - let l:job_id = ale#job#Start(l:command, l:job_options) - endif - - let l:status = 'failed' + let l:Callback = function('s:HandleExit', [{ + \ 'linter': l:linter, + \ 'executable': l:executable, + \ 'next_chain_index': l:next_chain_index, + \}]) + let l:result = ale#command#Run(l:buffer, l:command, l:Callback, { + \ 'output_stream': l:output_stream, + \ 'executable': l:executable, + \ 'read_buffer': l:read_buffer, + \ 'log_output': l:next_chain_index >= len(get(l:linter, 'command_chain', [])), + \}) " Only proceed if the job is being run. - if l:job_id - " Add the job to the list of jobs, so we can track them. - call add(l:info.job_list, l:job_id) - - if index(l:info.active_linter_list, l:linter.name) < 0 - call add(l:info.active_linter_list, l:linter.name) - endif - - let l:status = 'started' - " Store the ID for the job in the map to read back again. - let s:job_info_map[l:job_id] = { - \ 'linter': l:linter, - \ 'buffer': l:buffer, - \ 'executable': l:executable, - \ 'output': [], - \ 'next_chain_index': l:next_chain_index, - \} - - silent doautocmd <nomodeline> User ALEJobStarted - endif - - if g:ale_history_enabled - call ale#history#Add(l:buffer, l:status, l:job_id, l:command) + if empty(l:result) + return 0 endif - if get(g:, 'ale_run_synchronously') == 1 - " Run a command synchronously if this test option is set. - let s:job_info_map[l:job_id].output = systemlist( - \ type(l:command) is v:t_list - \ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2]) - \ : l:command - \) + call ale#engine#MarkLinterActive(l:info, l:linter) - call l:job_options.exit_cb(l:job_id, v:shell_error) - endif + silent doautocmd <nomodeline> User ALEJobStarted - return l:job_id != 0 + return 1 endfunction " Determine which commands to run for a link in a command chain, or " just a regular command. -function! ale#engine#ProcessChain(buffer, linter, chain_index, input) abort +function! ale#engine#ProcessChain(buffer, executable, linter, chain_index, input) abort let l:output_stream = get(a:linter, 'output_stream', 'stdout') let l:read_buffer = a:linter.read_buffer let l:chain_index = a:chain_index let l:input = a:input - if has_key(a:linter, 'command_chain') - while l:chain_index < len(a:linter.command_chain) - " Run a chain of commands, one asynchronous command after the other, - " so that many programs can be run in a sequence. - let l:chain_item = a:linter.command_chain[l:chain_index] - - if l:chain_index == 0 - " The first callback in the chain takes only a buffer number. - let l:command = ale#util#GetFunction(l:chain_item.callback)( - \ a:buffer - \) - else - " The second callback in the chain takes some input too. - let l:command = ale#util#GetFunction(l:chain_item.callback)( - \ a:buffer, - \ l:input - \) + while l:chain_index < len(a:linter.command_chain) + " Run a chain of commands, one asynchronous command after the other, + " so that many programs can be run in a sequence. + let l:chain_item = a:linter.command_chain[l:chain_index] + + if l:chain_index == 0 + " The first callback in the chain takes only a buffer number. + let l:command = ale#util#GetFunction(l:chain_item.callback)( + \ a:buffer + \) + else + " The second callback in the chain takes some input too. + let l:command = ale#util#GetFunction(l:chain_item.callback)( + \ a:buffer, + \ l:input + \) + endif + + " If we have a command to run, execute that. + if !empty(l:command) + " The chain item can override the output_stream option. + if has_key(l:chain_item, 'output_stream') + let l:output_stream = l:chain_item.output_stream endif - " If we have a command to run, execute that. - if !empty(l:command) - " The chain item can override the output_stream option. - if has_key(l:chain_item, 'output_stream') - let l:output_stream = l:chain_item.output_stream - endif - - " The chain item can override the read_buffer option. - if has_key(l:chain_item, 'read_buffer') - let l:read_buffer = l:chain_item.read_buffer - elseif l:chain_index != len(a:linter.command_chain) - 1 - " Don't read the buffer for commands besides the last one - " in the chain by default. - let l:read_buffer = 0 - endif - - break + " The chain item can override the read_buffer option. + if has_key(l:chain_item, 'read_buffer') + let l:read_buffer = l:chain_item.read_buffer + elseif l:chain_index != len(a:linter.command_chain) - 1 + " Don't read the buffer for commands besides the last one + " in the chain by default. + let l:read_buffer = 0 endif - " Command chain items can return an empty string to indicate that - " a command should be skipped, so we should try the next item - " with no input. - let l:input = [] - let l:chain_index += 1 - endwhile - else - let l:command = ale#linter#GetCommand(a:buffer, a:linter) - endif + break + endif + + " Command chain items can return an empty string to indicate that + " a command should be skipped, so we should try the next item + " with no input. + let l:input = [] + let l:chain_index += 1 + endwhile - return { - \ 'command': l:command, + return [l:command, { + \ 'executable': a:executable, \ 'buffer': a:buffer, \ 'linter': a:linter, \ 'output_stream': l:output_stream, \ 'next_chain_index': l:chain_index + 1, \ 'read_buffer': l:read_buffer, - \} + \}] endfunction -function! s:InvokeChain(buffer, executable, linter, chain_index, input) abort - let l:options = ale#engine#ProcessChain(a:buffer, a:linter, a:chain_index, a:input) - let l:options.executable = a:executable - - return s:RunJob(l:options) -endfunction - -function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort +function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) - let l:new_job_list = [] - let l:new_active_linter_list = [] + call ale#command#StopJobs(a:buffer, 'linter') - for l:job_id in get(l:info, 'job_list', []) - let l:job_info = get(s:job_info_map, l:job_id, {}) - - if !empty(l:job_info) - if a:include_lint_file_jobs || !l:job_info.linter.lint_file - call ale#job#Stop(l:job_id) - call remove(s:job_info_map, l:job_id) - else - call add(l:new_job_list, l:job_id) - " Linters with jobs still running are still active. - call add(l:new_active_linter_list, l:job_info.linter.name) - endif - endif - endfor - - " Remove duplicates from the active linter list. - call uniq(sort(l:new_active_linter_list)) - - " Update the List, so it includes only the jobs we still need. - let l:info.job_list = l:new_job_list " Update the active linter list, clearing out anything not running. - let l:info.active_linter_list = l:new_active_linter_list + if a:clear_lint_file_jobs + call ale#command#StopJobs(a:buffer, 'file_linter') + let l:info.active_linter_list = [] + else + " Keep jobs for linting files when we're only linting buffers. + call filter(l:info.active_linter_list, 'get(v:val, ''lint_file'')') + endif endfunction - function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort " Figure out which linters are still enabled, and remove " problems for linters which are no longer enabled. @@ -757,6 +608,48 @@ function! s:AddProblemsFromOtherBuffers(buffer, linters) abort endif endfunction +function! s:RunIfExecutable(buffer, linter, executable) abort + if ale#command#IsDeferred(a:executable) + let a:executable.result_callback = { + \ executable -> s:RunIfExecutable(a:buffer, a:linter, executable) + \} + + return 1 + endif + + if ale#engine#IsExecutable(a:buffer, a:executable) + " Use different job types for file or linter jobs. + let l:job_type = a:linter.lint_file ? 'file_linter' : 'linter' + call setbufvar(a:buffer, 'ale_job_type', l:job_type) + + if has_key(a:linter, 'command_chain') + let [l:command, l:options] = ale#engine#ProcessChain( + \ a:buffer, + \ a:executable, + \ a:linter, + \ 0, + \ [] + \) + + return s:RunJob(l:command, l:options) + endif + + let l:command = ale#linter#GetCommand(a:buffer, a:linter) + let l:options = { + \ 'executable': a:executable, + \ 'buffer': a:buffer, + \ 'linter': a:linter, + \ 'output_stream': get(a:linter, 'output_stream', 'stdout'), + \ 'next_chain_index': 1, + \ 'read_buffer': a:linter.read_buffer, + \} + + return s:RunJob(l:command, l:options) + endif + + return 0 +endfunction + " Run a linter for a buffer. " " Returns 1 if the linter was successfully run. @@ -766,9 +659,7 @@ function! s:RunLinter(buffer, linter) abort else let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) - if ale#engine#IsExecutable(a:buffer, l:executable) - return s:InvokeChain(a:buffer, l:executable, a:linter, 0, []) - endif + return s:RunIfExecutable(a:buffer, a:linter, l:executable) endif return 0 @@ -836,90 +727,3 @@ function! ale#engine#GetLoclist(buffer) abort return g:ale_buffer_info[a:buffer].loclist endfunction - -" This function can be called with a timeout to wait for all jobs to finish. -" If the jobs to not finish in the given number of milliseconds, -" an exception will be thrown. -" -" 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#events#ClockMilliseconds() - - if l:start_time == 0 - throw 'Failed to read milliseconds from the clock!' - endif - - let l:job_list = [] - - " Gather all of the jobs from every buffer. - for l:info in values(g:ale_buffer_info) - call extend(l:job_list, get(l:info, 'job_list', [])) - endfor - - " NeoVim has a built-in API for this, so use that. - if has('nvim') - let l:nvim_code_list = jobwait(l:job_list, a:deadline) - - if index(l:nvim_code_list, -1) >= 0 - throw 'Jobs did not complete on time!' - endif - - return - endif - - let l:should_wait_more = 1 - - while l:should_wait_more - let l:should_wait_more = 0 - - for l:job_id in l:job_list - if ale#job#IsRunning(l:job_id) - 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. - throw 'Jobs did not complete on time!' - endif - - " Wait another 10 milliseconds - let l:should_wait_more = 1 - sleep 10ms - break - endif - endfor - endwhile - - " Sleep for a small amount of time after all jobs finish. - " This seems to be enough to let handlers after jobs end run, and - " prevents the occasional failure where this function exits after jobs - " end, but before handlers are run. - sleep 10ms - - " We must check the buffer data again to see if new jobs started - " for command_chain linters. - let l:has_new_jobs = 0 - - " Check again to see if any jobs are running. - for l:info in values(g:ale_buffer_info) - for l:job_id in get(l:info, 'job_list', []) - if ale#job#IsRunning(l:job_id) - let l:has_new_jobs = 1 - break - endif - endfor - endfor - - if l:has_new_jobs - " We have to wait more. Offset the timeout by the time taken so far. - let l:now = ale#events#ClockMilliseconds() - let l:new_deadline = a:deadline - (l:now - l:start_time) - - if l:new_deadline <= 0 - " Enough time passed already, so stop immediately. - throw 'Jobs did not complete on time!' - endif - - call ale#engine#WaitForJobs(l:new_deadline) - endif -endfunction diff --git a/autoload/ale/engine/ignore.vim b/autoload/ale/engine/ignore.vim index 2db2c6c1..80574656 100644 --- a/autoload/ale/engine/ignore.vim +++ b/autoload/ale/engine/ignore.vim @@ -22,7 +22,7 @@ function! ale#engine#ignore#GetList(filetype, config) abort endfunction " Given a List of linter descriptions, exclude the linters to be ignored. -function! ale#engine#ignore#Exclude(filetype, all_linters, config) abort +function! ale#engine#ignore#Exclude(filetype, all_linters, config, disable_lsp) abort let l:names_to_remove = ale#engine#ignore#GetList(a:filetype, a:config) let l:filtered_linters = [] @@ -37,6 +37,10 @@ function! ale#engine#ignore#Exclude(filetype, all_linters, config) abort endif endfor + if a:disable_lsp && has_key(l:linter, 'lsp') && l:linter.lsp isnot# '' + let l:should_include = 0 + endif + if l:should_include call add(l:filtered_linters, l:linter) endif diff --git a/autoload/ale/filetypes.vim b/autoload/ale/filetypes.vim index 6174aa0e..6cdc9ece 100644 --- a/autoload/ale/filetypes.vim +++ b/autoload/ale/filetypes.vim @@ -5,7 +5,7 @@ function! ale#filetypes#LoadExtensionMap() abort " Output includes: " '*.erl setf erlang' redir => l:output - silent exec 'autocmd' + silent exec 'autocmd' redir end let l:map = {} diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index 03652ecf..92ae3e14 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -1,12 +1,4 @@ -if !has_key(s:, 'job_info_map') - let s:job_info_map = {} -endif - -function! s:GatherOutput(job_id, line) abort - if has_key(s:job_info_map, a:job_id) - call add(s:job_info_map[a:job_id].output, a:line) - endif -endfunction +call ale#Set('fix_on_save_ignore', {}) " Apply fixes queued up for buffers which may be hidden. " Vim doesn't let you modify hidden buffers. @@ -66,11 +58,17 @@ function! ale#fix#ApplyQueuedFixes() abort endfunction function! ale#fix#ApplyFixes(buffer, output) abort - call ale#fix#RemoveManagedFiles(a:buffer) - let l:data = g:ale_fix_buffer_data[a:buffer] let l:data.output = a:output let l:data.changes_made = l:data.lines_before != l:data.output + let l:data.done = 1 + + call ale#command#RemoveManagedFiles(a:buffer) + + if !bufexists(a:buffer) + " Remove the buffer data when it doesn't exist. + call remove(g:ale_fix_buffer_data, a:buffer) + endif if l:data.changes_made && bufexists(a:buffer) let l:lines = getbufline(a:buffer, 1, '$') @@ -83,279 +81,178 @@ function! ale#fix#ApplyFixes(buffer, output) abort endif endif - if !bufexists(a:buffer) - " Remove the buffer data when it doesn't exist. - call remove(g:ale_fix_buffer_data, a:buffer) - endif - - let l:data.done = 1 - " We can only change the lines of a buffer which is currently open, " so try and apply the fixes to the current buffer. call ale#fix#ApplyQueuedFixes() endfunction -function! s:HandleExit(job_id, exit_code) abort - if !has_key(s:job_info_map, a:job_id) - return - endif - - let l:job_info = remove(s:job_info_map, a:job_id) - let l:buffer = l:job_info.buffer +function! s:HandleExit(job_info, buffer, job_output, data) abort + let l:buffer_info = get(g:ale_fix_buffer_data, a:buffer, {}) - if g:ale_history_enabled - call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code) + if empty(l:buffer_info) + return endif - if has_key(l:job_info, 'file_to_read') - let l:job_info.output = readfile(l:job_info.file_to_read) + if a:job_info.read_temporary_file + let l:output = !empty(a:data.temporary_file) + \ ? readfile(a:data.temporary_file) + \ : [] + else + let l:output = a:job_output endif - let l:ChainCallback = get(l:job_info, 'chain_with', v:null) - let l:ProcessWith = get(l:job_info, 'process_with', v:null) + let l:ChainCallback = get(a:job_info, 'chain_with', v:null) + let l:ProcessWith = get(a:job_info, 'process_with', v:null) " Post-process the output with a function if we have one. if l:ProcessWith isnot v:null - let l:job_info.output = call( - \ ale#util#GetFunction(l:ProcessWith), - \ [l:buffer, l:job_info.output] - \) + let l:output = call(l:ProcessWith, [a:buffer, l:output]) endif " Use the output of the job for changing the file if it isn't empty, " otherwise skip this job and use the input from before. " " We'll use the input from before for chained commands. - if l:ChainCallback is v:null && !empty(split(join(l:job_info.output))) - let l:input = l:job_info.output + if l:ChainCallback is v:null && !empty(split(join(l:output))) + let l:input = l:output else - let l:input = l:job_info.input + let l:input = a:job_info.input + endif + + if l:ChainCallback isnot v:null && !get(g:, 'ale_ignore_2_4_warnings') + execute 'echom ''chain_with is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' endif let l:next_index = l:ChainCallback is v:null - \ ? l:job_info.callback_index + 1 - \ : l:job_info.callback_index + \ ? a:job_info.callback_index + 1 + \ : a:job_info.callback_index call s:RunFixer({ - \ 'buffer': l:buffer, + \ 'buffer': a:buffer, \ 'input': l:input, - \ 'output': l:job_info.output, - \ 'callback_list': l:job_info.callback_list, + \ 'output': l:output, + \ 'callback_list': a:job_info.callback_list, \ 'callback_index': l:next_index, \ 'chain_callback': l:ChainCallback, \}) endfunction -function! ale#fix#ManageDirectory(buffer, directory) abort - call add(g:ale_fix_buffer_data[a:buffer].temporary_directory_list, a:directory) -endfunction +function! s:RunJob(result, options) abort + if ale#command#IsDeferred(a:result) + let a:result.result_callback = {x -> s:RunJob(x, a:options)} -function! ale#fix#RemoveManagedFiles(buffer) abort - if !has_key(g:ale_fix_buffer_data, a:buffer) return endif - " We can't delete anything in a sandbox, so wait until we escape from - " it to delete temporary files and directories. - if ale#util#InSandbox() - return - endif + let l:buffer = a:options.buffer + let l:input = a:options.input - " Delete directories like `rm -rf`. - " Directories are handled differently from files, so paths that are - " intended to be single files can be set up for automatic deletion without - " accidentally deleting entire directories. - for l:directory in g:ale_fix_buffer_data[a:buffer].temporary_directory_list - call delete(l:directory, 'rf') - endfor + if a:result is 0 || type(a:result) is v:t_list + if type(a:result) is v:t_list + let l:input = a:result + endif - let g:ale_fix_buffer_data[a:buffer].temporary_directory_list = [] -endfunction + call s:RunFixer({ + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'callback_index': a:options.callback_index + 1, + \ 'callback_list': a:options.callback_list, + \}) -function! s:CreateTemporaryFileForJob(buffer, temporary_file, input) abort - if empty(a:temporary_file) - " There is no file, so we didn't create anything. - return 0 + return endif - let l:temporary_directory = fnamemodify(a:temporary_file, ':h') - " Create the temporary directory for the file, unreadable by 'other' - " users. - call mkdir(l:temporary_directory, '', 0750) - " Automatically delete the directory later. - call ale#fix#ManageDirectory(a:buffer, l:temporary_directory) - " Write the buffer out to a file. - call ale#util#Writefile(a:buffer, a:input, a:temporary_file) - - return 1 -endfunction - -function! s:RunJob(options) abort - let l:buffer = a:options.buffer - let l:command = a:options.command - let l:input = a:options.input - let l:output_stream = a:options.output_stream - let l:read_temporary_file = a:options.read_temporary_file - let l:ChainWith = a:options.chain_with - let l:read_buffer = a:options.read_buffer + let l:command = get(a:result, 'command', '') + let l:ChainWith = get(a:result, 'chain_with', v:null) if empty(l:command) - " If there's nothing further to chain the command with, stop here. - if l:ChainWith is v:null - return 0 - endif - - " If there's another chained callback to run, then run that. + " If the command is empty, skip to the next item, or call the + " chain_with function. call s:RunFixer({ \ 'buffer': l:buffer, \ 'input': l:input, - \ 'callback_index': a:options.callback_index, + \ 'callback_index': a:options.callback_index + (l:ChainWith is v:null), \ 'callback_list': a:options.callback_list, \ 'chain_callback': l:ChainWith, \ 'output': [], \}) - return 1 + return endif - let [l:temporary_file, l:command] = ale#command#FormatCommand( - \ l:buffer, - \ '', - \ l:command, - \ l:read_buffer, - \) - call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input) - - let l:command = ale#job#PrepareCommand(l:buffer, l:command) - let l:job_options = { - \ 'mode': 'nl', - \ 'exit_cb': function('s:HandleExit'), - \} + let l:read_temporary_file = get(a:result, 'read_temporary_file', 0) + " Default to piping the buffer for the last fixer in the chain. + let l:read_buffer = get(a:result, 'read_buffer', l:ChainWith is v:null) + let l:output_stream = get(a:result, 'output_stream', 'stdout') - let l:job_info = { - \ 'buffer': l:buffer, + if l:read_temporary_file + let l:output_stream = 'none' + endif + + let l:Callback = function('s:HandleExit', [{ \ 'input': l:input, - \ 'output': [], \ 'chain_with': l:ChainWith, \ 'callback_index': a:options.callback_index, \ 'callback_list': a:options.callback_list, - \ 'process_with': a:options.process_with, - \} - - if l:read_temporary_file - " TODO: Check that a temporary file is set here. - let l:job_info.file_to_read = l:temporary_file - elseif l:output_stream is# 'stderr' - let l:job_options.err_cb = function('s:GatherOutput') - elseif l:output_stream is# 'both' - let l:job_options.out_cb = function('s:GatherOutput') - let l:job_options.err_cb = function('s:GatherOutput') - else - let l:job_options.out_cb = function('s:GatherOutput') - endif - - if get(g:, 'ale_emulate_job_failure') == 1 - let l:job_id = 0 - elseif get(g:, 'ale_run_synchronously') == 1 - " Find a unique Job value to use, which will be the same as the ID for - " running commands synchronously. This is only for test code. - let l:job_id = len(s:job_info_map) + 1 - - while has_key(s:job_info_map, l:job_id) - let l:job_id += 1 - endwhile - else - let l:job_id = ale#job#Start(l:command, l:job_options) - endif - - let l:status = l:job_id ? 'started' : 'failed' - - if g:ale_history_enabled - call ale#history#Add(l:buffer, l:status, l:job_id, l:command) - endif - - if l:job_id == 0 - return 0 - endif - - let s:job_info_map[l:job_id] = l:job_info - - if get(g:, 'ale_run_synchronously') == 1 - " Run a command synchronously if this test option is set. - let l:output = systemlist( - \ type(l:command) is v:t_list - \ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2]) - \ : l:command - \) - - if !l:read_temporary_file - let s:job_info_map[l:job_id].output = l:output - endif + \ 'process_with': get(a:result, 'process_with', v:null), + \ 'read_temporary_file': l:read_temporary_file, + \}]) + let l:run_result = ale#command#Run(l:buffer, l:command, l:Callback, { + \ 'output_stream': l:output_stream, + \ 'executable': '', + \ 'read_buffer': l:read_buffer, + \ 'input': l:input, + \ 'log_output': 0, + \}) - call l:job_options.exit_cb(l:job_id, v:shell_error) + if empty(l:run_result) + call s:RunFixer({ + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'callback_index': a:options.callback_index + 1, + \ 'callback_list': a:options.callback_list, + \}) endif - - return 1 endfunction function! s:RunFixer(options) abort let l:buffer = a:options.buffer let l:input = a:options.input let l:index = a:options.callback_index + + if len(a:options.callback_list) <= l:index + call ale#fix#ApplyFixes(l:buffer, l:input) + + return + endif + let l:ChainCallback = get(a:options, 'chain_callback', v:null) - while len(a:options.callback_list) > l:index - let l:Function = l:ChainCallback isnot v:null - \ ? ale#util#GetFunction(l:ChainCallback) - \ : a:options.callback_list[l:index] - - if l:ChainCallback isnot v:null - " Chained commands accept (buffer, output, [input]) - let l:result = ale#util#FunctionArgCount(l:Function) == 2 - \ ? call(l:Function, [l:buffer, a:options.output]) - \ : call(l:Function, [l:buffer, a:options.output, copy(l:input)]) - else - " Chained commands accept (buffer, [input]) - let l:result = ale#util#FunctionArgCount(l:Function) == 1 - \ ? call(l:Function, [l:buffer]) - \ : call(l:Function, [l:buffer, copy(l:input)]) - endif + let l:Function = l:ChainCallback isnot v:null + \ ? ale#util#GetFunction(l:ChainCallback) + \ : a:options.callback_list[l:index] - if type(l:result) is v:t_number && l:result == 0 - " When `0` is returned, skip this item. - let l:index += 1 - elseif type(l:result) is v:t_list - let l:input = l:result - let l:index += 1 - else - let l:ChainWith = get(l:result, 'chain_with', v:null) - " Default to piping the buffer for the last fixer in the chain. - let l:read_buffer = get(l:result, 'read_buffer', l:ChainWith is v:null) - - let l:job_ran = s:RunJob({ - \ 'buffer': l:buffer, - \ 'command': l:result.command, - \ 'input': l:input, - \ 'output_stream': get(l:result, 'output_stream', 'stdout'), - \ 'read_temporary_file': get(l:result, 'read_temporary_file', 0), - \ 'read_buffer': l:read_buffer, - \ 'chain_with': l:ChainWith, - \ 'callback_list': a:options.callback_list, - \ 'callback_index': l:index, - \ 'process_with': get(l:result, 'process_with', v:null), - \}) - - if !l:job_ran - " The job failed to run, so skip to the next item. - let l:index += 1 - else - " Stop here, we will handle exit later on. - return - endif - endif - endwhile + " Record new jobs started as fixer jobs. + call setbufvar(l:buffer, 'ale_job_type', 'fixer') - call ale#fix#ApplyFixes(l:buffer, l:input) + if l:ChainCallback isnot v:null + " Chained commands accept (buffer, output, [input]) + let l:result = ale#util#FunctionArgCount(l:Function) == 2 + \ ? call(l:Function, [l:buffer, a:options.output]) + \ : call(l:Function, [l:buffer, a:options.output, copy(l:input)]) + else + " Regular fixer commands accept (buffer, [input]) + let l:result = ale#util#FunctionArgCount(l:Function) == 1 + \ ? call(l:Function, [l:buffer]) + \ : call(l:Function, [l:buffer, copy(l:input)]) + endif + + call s:RunJob(l:result, { + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'callback_list': a:options.callback_list, + \ 'callback_index': l:index, + \}) endfunction function! s:AddSubCallbacks(full_list, callbacks) abort @@ -370,7 +267,21 @@ function! s:AddSubCallbacks(full_list, callbacks) abort return 1 endfunction -function! s:GetCallbacks(buffer, fixers) abort +function! s:IgnoreFixers(callback_list, filetype, config) abort + if type(a:config) is v:t_list + let l:ignore_list = a:config + else + let l:ignore_list = [] + + for l:part in split(a:filetype , '\.') + call extend(l:ignore_list, get(a:config, l:part, [])) + endfor + endif + + call filter(a:callback_list, 'index(l:ignore_list, v:val) < 0') +endfunction + +function! s:GetCallbacks(buffer, fixing_flag, fixers) abort if len(a:fixers) let l:callback_list = a:fixers elseif type(get(b:, 'ale_fixers')) is v:t_list @@ -395,8 +306,12 @@ function! s:GetCallbacks(buffer, fixers) abort endif endif - if empty(l:callback_list) - return [] + if a:fixing_flag is# 'save_file' + let l:config = ale#Var(a:buffer, 'fix_on_save_ignore') + + if !empty(l:config) + call s:IgnoreFixers(l:callback_list, &filetype, l:config) + endif endif let l:corrected_list = [] @@ -444,7 +359,7 @@ function! ale#fix#Fix(buffer, fixing_flag, ...) abort endif try - let l:callback_list = s:GetCallbacks(a:buffer, a:000) + let l:callback_list = s:GetCallbacks(a:buffer, a:fixing_flag, a:000) catch /E700\|BADNAME/ let l:function_name = join(split(split(v:exception, ':')[3])) let l:echo_message = printf( @@ -464,13 +379,9 @@ function! ale#fix#Fix(buffer, fixing_flag, ...) abort return 0 endif - 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 - + call ale#command#StopJobs(a:buffer, 'fixer') " Clean up any files we might have left behind from a previous run. - call ale#fix#RemoveManagedFiles(a:buffer) + call ale#command#RemoveManagedFiles(a:buffer) call ale#fix#InitBufferData(a:buffer, a:fixing_flag) silent doautocmd <nomodeline> User ALEFixPre diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim index 75fd1508..3a36f367 100644 --- a/autoload/ale/fix/registry.vim +++ b/autoload/ale/fix/registry.vim @@ -27,6 +27,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['python'], \ 'description': 'Fix PEP8 issues with black.', \ }, +\ 'fecs': { +\ 'function': 'ale#fixers#fecs#Fix', +\ 'suggested_filetypes': ['javascript', 'css', 'html'], +\ 'description': 'Apply fecs format to a file.', +\ }, \ 'tidy': { \ 'function': 'ale#fixers#tidy#Fix', \ 'suggested_filetypes': ['html'], @@ -115,9 +120,14 @@ let s:default_registry = { \ 'suggested_filetypes': ['javascript'], \ 'description': 'Fix JavaScript files using standard --fix', \ }, +\ 'standardrb': { +\ 'function': 'ale#fixers#standardrb#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with standardrb --fix', +\ }, \ 'stylelint': { \ 'function': 'ale#fixers#stylelint#Fix', -\ 'suggested_filetypes': ['css', 'sass', 'scss', 'stylus'], +\ 'suggested_filetypes': ['css', 'sass', 'scss', 'sugarss', 'stylus'], \ 'description': 'Fix stylesheet files using stylelint --fix.', \ }, \ 'swiftformat': { @@ -137,8 +147,13 @@ let s:default_registry = { \ }, \ 'clang-format': { \ 'function': 'ale#fixers#clangformat#Fix', -\ 'suggested_filetypes': ['c', 'cpp'], -\ 'description': 'Fix C/C++ files with clang-format.', +\ 'suggested_filetypes': ['c', 'cpp', 'cuda'], +\ 'description': 'Fix C/C++ and cuda files with clang-format.', +\ }, +\ 'cmakeformat': { +\ 'function': 'ale#fixers#cmakeformat#Fix', +\ 'suggested_filetypes': ['cmake'], +\ 'description': 'Fix CMake files with cmake-format.', \ }, \ 'gofmt': { \ 'function': 'ale#fixers#gofmt#Fix', @@ -165,11 +180,21 @@ let s:default_registry = { \ 'suggested_filetypes': ['rust'], \ 'description': 'Fix Rust files with Rustfmt.', \ }, +\ 'textlint': { +\ 'function': 'ale#fixers#textlint#Fix', +\ 'suggested_filetypes': ['text','markdown','asciidoc','tex'], +\ 'description': 'Fix text files with textlint --fix', +\ }, \ 'hackfmt': { \ 'function': 'ale#fixers#hackfmt#Fix', \ 'suggested_filetypes': ['hack'], \ 'description': 'Fix Hack files with hackfmt.', \ }, +\ 'floskell': { +\ 'function': 'ale#fixers#floskell#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'Fix Haskell files with floskell.', +\ }, \ 'hfmt': { \ 'function': 'ale#fixers#hfmt#Fix', \ 'suggested_filetypes': ['haskell'], @@ -195,6 +220,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['ocaml'], \ 'description': 'Fix OCaml files with ocamlformat.', \ }, +\ 'ocp-indent': { +\ 'function': 'ale#fixers#ocp_indent#Fix', +\ 'suggested_filetypes': ['ocaml'], +\ 'description': 'Fix OCaml files with ocp-indent.', +\ }, \ 'refmt': { \ 'function': 'ale#fixers#refmt#Fix', \ 'suggested_filetypes': ['reason'], @@ -232,8 +262,8 @@ let s:default_registry = { \ }, \ 'xo': { \ 'function': 'ale#fixers#xo#Fix', -\ 'suggested_filetypes': ['javascript'], -\ 'description': 'Fix JavaScript files using xo --fix.', +\ 'suggested_filetypes': ['javascript', 'typescript'], +\ 'description': 'Fix JavaScript/TypeScript files using xo --fix.', \ }, \ 'qmlfmt': { \ 'function': 'ale#fixers#qmlfmt#Fix', @@ -260,6 +290,21 @@ let s:default_registry = { \ 'suggested_filetypes': ['hcl', 'terraform'], \ 'description': 'Fix tf and hcl files with terraform fmt.', \ }, +\ 'ktlint': { +\ 'function': 'ale#fixers#ktlint#Fix', +\ 'suggested_filetypes': ['kt'], +\ 'description': 'Fix Kotlin files with ktlint.', +\ }, +\ 'styler': { +\ 'function': 'ale#fixers#styler#Fix', +\ 'suggested_filetypes': ['r'], +\ 'description': 'Fix R files with styler.', +\ }, +\ 'latexindent': { +\ 'function': 'ale#fixers#latexindent#Fix', +\ 'suggested_filetypes': ['tex'], +\ 'description' : 'Indent code within environments, commands, after headings and within special code blocks.', +\ }, \} " Reset the function registry to the default entries. diff --git a/autoload/ale/fixers/black.vim b/autoload/ale/fixers/black.vim index 4169322a..367b8d52 100644 --- a/autoload/ale/fixers/black.vim +++ b/autoload/ale/fixers/black.vim @@ -4,22 +4,33 @@ 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', '') +call ale#Set('python_black_auto_pipenv', 0) +call ale#Set('python_black_change_directory', 1) + +function! ale#fixers#black#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_black_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_black', ['black']) +endfunction function! ale#fixers#black#Fix(buffer) abort - let l:executable = ale#python#FindExecutable( - \ a:buffer, - \ 'python_black', - \ ['black'], - \) + let l:cd_string = ale#Var(a:buffer, 'python_black_change_directory') + \ ? ale#path#BufferCdString(a:buffer) + \ : '' - if !executable(l:executable) - return 0 - endif + let l:executable = ale#fixers#black#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run black' + \ : '' let l:options = ale#Var(a:buffer, 'python_black_options') return { - \ 'command': ale#Escape(l:executable) + \ 'command': l:cd_string . ale#Escape(l:executable) . l:exec_args \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' -', \} diff --git a/autoload/ale/fixers/cmakeformat.vim b/autoload/ale/fixers/cmakeformat.vim new file mode 100644 index 00000000..f40ed6ed --- /dev/null +++ b/autoload/ale/fixers/cmakeformat.vim @@ -0,0 +1,18 @@ +" Author: Attila Maczak <attila@maczak.hu> +" Description: Integration of cmakeformat with ALE. + +call ale#Set('cmake_cmakeformat_executable', 'cmake-format') +call ale#Set('cmake_cmakeformat_options', '') + +function! ale#fixers#cmakeformat#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'cmake_cmakeformat_executable') + let l:options = ale#Var(a:buffer, 'cmake_cmakeformat_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -i ' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/eslint.vim b/autoload/ale/fixers/eslint.vim index ea5b2a63..0f57cba6 100644 --- a/autoload/ale/fixers/eslint.vim +++ b/autoload/ale/fixers/eslint.vim @@ -3,15 +3,15 @@ function! ale#fixers#eslint#Fix(buffer) abort let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) + let l:command = ale#node#Executable(a:buffer, l:executable) + \ . ' --version' - let l:command = ale#semver#HasVersion(l:executable) - \ ? '' - \ : ale#node#Executable(a:buffer, l:executable) . ' --version' - - return { - \ 'command': l:command, - \ 'chain_with': 'ale#fixers#eslint#ApplyFixForVersion', - \} + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ l:command, + \ function('ale#fixers#eslint#ApplyFixForVersion'), + \) endfunction function! ale#fixers#eslint#ProcessFixDryRunOutput(buffer, output) abort @@ -33,10 +33,8 @@ function! ale#fixers#eslint#ProcessEslintDOutput(buffer, output) abort return a:output endfunction -function! ale#fixers#eslint#ApplyFixForVersion(buffer, version_output) abort +function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) - let l:version = ale#semver#GetVersion(l:executable, a:version_output) - let l:config = ale#handlers#eslint#FindConfig(a:buffer) if empty(l:config) @@ -44,7 +42,7 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version_output) abort endif " Use --fix-to-stdout with eslint_d - if l:executable =~# 'eslint_d$' && ale#semver#GTE(l:version, [3, 19, 0]) + if l:executable =~# 'eslint_d$' && ale#semver#GTE(a:version, [3, 19, 0]) return { \ 'command': ale#node#Executable(a:buffer, l:executable) \ . ' --stdin-filename %s --stdin --fix-to-stdout', @@ -53,7 +51,7 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version_output) abort endif " 4.9.0 is the first version with --fix-dry-run - if ale#semver#GTE(l:version, [4, 9, 0]) + if ale#semver#GTE(a:version, [4, 9, 0]) return { \ 'command': ale#node#Executable(a:buffer, l:executable) \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', diff --git a/autoload/ale/fixers/fecs.vim b/autoload/ale/fixers/fecs.vim new file mode 100644 index 00000000..d692bc97 --- /dev/null +++ b/autoload/ale/fixers/fecs.vim @@ -0,0 +1,17 @@ +" Author: harttle <yangjvn@126.com> +" Description: Apply fecs format to a file. + +function! ale#fixers#fecs#Fix(buffer) abort + let l:executable = ale#handlers#fecs#GetExecutable(a:buffer) + + if !executable(l:executable) + return 0 + endif + + let l:config_options = ' format --replace=true %t' + + return { + \ 'command': ale#Escape(l:executable) . l:config_options, + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/floskell.vim b/autoload/ale/fixers/floskell.vim new file mode 100644 index 00000000..f0015db7 --- /dev/null +++ b/autoload/ale/fixers/floskell.vim @@ -0,0 +1,20 @@ +" Author: robertjlooby <robertjlooby@gmail.com> +" Description: Integration of floskell with ALE. + +call ale#Set('haskell_floskell_executable', 'floskell') + +function! ale#fixers#floskell#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_floskell_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'floskell') +endfunction + +function! ale#fixers#floskell#Fix(buffer) abort + let l:executable = ale#fixers#floskell#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/jq.vim b/autoload/ale/fixers/jq.vim index 1b743e49..cd9b9138 100644 --- a/autoload/ale/fixers/jq.vim +++ b/autoload/ale/fixers/jq.vim @@ -7,16 +7,16 @@ function! ale#fixers#jq#GetExecutable(buffer) abort endfunction function! ale#fixers#jq#Fix(buffer) abort - let l:options = ale#Var(a:buffer, 'json_jq_options') - let l:filters = ale#Var(a:buffer, 'json_jq_filters') + let l:options = ale#Var(a:buffer, 'json_jq_options') + let l:filters = ale#Var(a:buffer, 'json_jq_filters') - if empty(l:filters) - return 0 - endif + if empty(l:filters) + return 0 + endif - return { - \ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer)) - \ . ' ' . l:filters . ' ' - \ . l:options, - \} + return { + \ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer)) + \ . ' ' . l:filters . ' ' + \ . l:options, + \} endfunction diff --git a/autoload/ale/fixers/ktlint.vim b/autoload/ale/fixers/ktlint.vim new file mode 100644 index 00000000..cb975d6c --- /dev/null +++ b/autoload/ale/fixers/ktlint.vim @@ -0,0 +1,9 @@ +" Author: Michael Phillips <michaeljoelphillips@gmail.com> +" Description: Fix Kotlin files with ktlint. + +function! ale#fixers#ktlint#Fix(buffer) abort + return { + \ 'command': ale#handlers#ktlint#GetCommand(a:buffer) . ' --format', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/latexindent.vim b/autoload/ale/fixers/latexindent.vim new file mode 100644 index 00000000..b0a0884a --- /dev/null +++ b/autoload/ale/fixers/latexindent.vim @@ -0,0 +1,18 @@ +" Author: riley-martine <riley.martine@protonmail.com> +" Description: Integration of latexindent with ALE. + +call ale#Set('tex_latexindent_executable', 'latexindent') +call ale#Set('tex_latexindent_options', '') + +function! ale#fixers#latexindent#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'tex_latexindent_executable') + let l:options = ale#Var(a:buffer, 'tex_latexindent_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -l -w' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/ocp_indent.vim b/autoload/ale/fixers/ocp_indent.vim new file mode 100644 index 00000000..e1b047b3 --- /dev/null +++ b/autoload/ale/fixers/ocp_indent.vim @@ -0,0 +1,18 @@ +" Author: Kanenobu Mitsuru +" Description: Integration of ocp-indent with ALE. + +call ale#Set('ocaml_ocp_indent_executable', 'ocp-indent') +call ale#Set('ocaml_ocp_indent_options', '') +call ale#Set('ocaml_ocp_indent_config', '') + +function! ale#fixers#ocp_indent#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'ocaml_ocp_indent_executable') + let l:config = ale#Var(a:buffer, 'ocaml_ocp_indent_config') + let l:options = ale#Var(a:buffer, 'ocaml_ocp_indent_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:config) ? '' : ' --config=' . ale#Escape(l:config)) + \ . (empty(l:options) ? '': ' ' . l:options) + \} +endfunction diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim index 58dea159..b7f0ecd7 100644 --- a/autoload/ale/fixers/prettier.vim +++ b/autoload/ale/fixers/prettier.vim @@ -15,16 +15,12 @@ function! ale#fixers#prettier#GetExecutable(buffer) abort endfunction function! ale#fixers#prettier#Fix(buffer) abort - let l:executable = ale#fixers#prettier#GetExecutable(a:buffer) - - let l:command = ale#semver#HasVersion(l:executable) - \ ? '' - \ : ale#Escape(l:executable) . ' --version' - - return { - \ 'command': l:command, - \ 'chain_with': 'ale#fixers#prettier#ApplyFixForVersion', - \} + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ ale#fixers#prettier#GetExecutable(a:buffer), + \ '%e --version', + \ function('ale#fixers#prettier#ApplyFixForVersion'), + \) endfunction function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort @@ -38,15 +34,23 @@ function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort return a:output endfunction -function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort +function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) 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 + " Mimic Prettier's defaults. In cases without a file extension or + " filetype (scratch buffer), Prettier needs `parser` set to know how + " to process the buffer. + if ale#semver#GTE(a:version, [1, 16, 0]) + let l:parser = 'babel' + else + let l:parser = 'babylon' + endif + let l:prettier_parsers = { \ 'typescript': 'typescript', \ 'css': 'css', @@ -60,7 +64,6 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort \ 'yaml': 'yaml', \ 'html': 'html', \} - let l:parser = '' for l:filetype in split(getbufvar(a:buffer, '&filetype'), '\.') if has_key(l:prettier_parsers, l:filetype) @@ -86,7 +89,7 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort endif " 1.4.0 is the first version with --stdin-filepath - if ale#semver#GTE(l:version, [1, 4, 0]) + if ale#semver#GTE(a:version, [1, 4, 0]) return { \ 'command': ale#path#BufferCdString(a:buffer) \ . ale#Escape(l:executable) diff --git a/autoload/ale/fixers/prettier_eslint.vim b/autoload/ale/fixers/prettier_eslint.vim index bc0caadd..1e66f49e 100644 --- a/autoload/ale/fixers/prettier_eslint.vim +++ b/autoload/ale/fixers/prettier_eslint.vim @@ -2,13 +2,9 @@ " w0rp <devw0rp@gmail.com>, morhetz (Pavel Pertsev) <morhetz@gmail.com> " Description: Integration between Prettier and ESLint. -function! ale#fixers#prettier_eslint#SetOptionDefaults() abort - call ale#Set('javascript_prettier_eslint_executable', 'prettier-eslint') - call ale#Set('javascript_prettier_eslint_use_global', get(g:, 'ale_use_global_executables', 0)) - call ale#Set('javascript_prettier_eslint_options', '') -endfunction - -call ale#fixers#prettier_eslint#SetOptionDefaults() +call ale#Set('javascript_prettier_eslint_executable', 'prettier-eslint') +call ale#Set('javascript_prettier_eslint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_prettier_eslint_options', '') function! ale#fixers#prettier_eslint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_prettier_eslint', [ @@ -18,26 +14,20 @@ function! ale#fixers#prettier_eslint#GetExecutable(buffer) abort endfunction function! ale#fixers#prettier_eslint#Fix(buffer) abort - let l:executable = ale#fixers#prettier_eslint#GetExecutable(a:buffer) - - let l:command = ale#semver#HasVersion(l:executable) - \ ? '' - \ : ale#Escape(l:executable) . ' --version' - - return { - \ 'command': l:command, - \ 'chain_with': 'ale#fixers#prettier_eslint#ApplyFixForVersion', - \} + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ ale#fixers#prettier_eslint#GetExecutable(a:buffer), + \ '%e --version', + \ function('ale#fixers#prettier_eslint#ApplyFixForVersion'), + \) endfunction -function! ale#fixers#prettier_eslint#ApplyFixForVersion(buffer, version_output) abort +function! ale#fixers#prettier_eslint#ApplyFixForVersion(buffer, version) abort let l:options = ale#Var(a:buffer, 'javascript_prettier_eslint_options') let l:executable = ale#fixers#prettier_eslint#GetExecutable(a:buffer) - let l:version = ale#semver#GetVersion(l:executable, a:version_output) - " 4.2.0 is the first version with --eslint-config-path - let l:config = ale#semver#GTE(l:version, [4, 2, 0]) + let l:config = ale#semver#GTE(a:version, [4, 2, 0]) \ ? ale#handlers#eslint#FindConfig(a:buffer) \ : '' let l:eslint_config_option = !empty(l:config) @@ -45,7 +35,7 @@ function! ale#fixers#prettier_eslint#ApplyFixForVersion(buffer, version_output) \ : '' " 4.4.0 is the first version with --stdin-filepath - if ale#semver#GTE(l:version, [4, 4, 0]) + if ale#semver#GTE(a:version, [4, 4, 0]) return { \ 'command': ale#path#BufferCdString(a:buffer) \ . ale#Escape(l:executable) diff --git a/autoload/ale/fixers/qmlfmt.vim b/autoload/ale/fixers/qmlfmt.vim index d750d1c4..90b25679 100644 --- a/autoload/ale/fixers/qmlfmt.vim +++ b/autoload/ale/fixers/qmlfmt.vim @@ -5,7 +5,7 @@ function! ale#fixers#qmlfmt#GetExecutable(buffer) abort endfunction function! ale#fixers#qmlfmt#Fix(buffer) abort - return { - \ 'command': ale#Escape(ale#fixers#qmlfmt#GetExecutable(a:buffer)), - \} + return { + \ 'command': ale#Escape(ale#fixers#qmlfmt#GetExecutable(a:buffer)), + \} endfunction diff --git a/autoload/ale/fixers/standard.vim b/autoload/ale/fixers/standard.vim index 2b1f6f92..77712d40 100644 --- a/autoload/ale/fixers/standard.vim +++ b/autoload/ale/fixers/standard.vim @@ -14,9 +14,11 @@ endfunction function! ale#fixers#standard#Fix(buffer) abort let l:executable = ale#fixers#standard#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'javascript_standard_options') return { \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --fix %t', \ 'read_temporary_file': 1, \} diff --git a/autoload/ale/fixers/standardrb.vim b/autoload/ale/fixers/standardrb.vim new file mode 100644 index 00000000..fab1e2bc --- /dev/null +++ b/autoload/ale/fixers/standardrb.vim @@ -0,0 +1,23 @@ +" Author: Justin Searls - https://github.com/searls +" Description: Fix Ruby files with StandardRB. + +call ale#Set('ruby_standardrb_options', '') +call ale#Set('ruby_standardrb_executable', 'standardrb') + +function! ale#fixers#standardrb#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_standardrb_executable') + let l:config = ale#path#FindNearestFile(a:buffer, '.standard.yml') + let l:options = ale#Var(a:buffer, 'ruby_standardrb_options') + + return ale#handlers#ruby#EscapeExecutable(l:executable, 'standardrb') + \ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --fix --force-exclusion %t' +endfunction + +function! ale#fixers#standardrb#Fix(buffer) abort + return { + \ 'command': ale#fixers#standardrb#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/stylelint.vim b/autoload/ale/fixers/stylelint.vim index 30309c7e..6bfb2fde 100644 --- a/autoload/ale/fixers/stylelint.vim +++ b/autoload/ale/fixers/stylelint.vim @@ -5,13 +5,12 @@ call ale#Set('stylelint_executable', 'stylelint') 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', [ - \ 'node_modules/stylelint/bin/stylelint.js', - \ 'node_modules/.bin/stylelint', - \]) + return ale#node#FindExecutable(a:buffer, 'stylelint', [ + \ 'node_modules/stylelint/bin/stylelint.js', + \ 'node_modules/.bin/stylelint', + \]) endfunction - function! ale#fixers#stylelint#Fix(buffer) abort let l:executable = ale#fixers#stylelint#GetExecutable(a:buffer) diff --git a/autoload/ale/fixers/styler.vim b/autoload/ale/fixers/styler.vim new file mode 100644 index 00000000..7ff3275c --- /dev/null +++ b/autoload/ale/fixers/styler.vim @@ -0,0 +1,16 @@ +" Author: tvatter <thibault.vatter@gmail.com> +" Description: Fixing R files with styler. + +call ale#Set('r_styler_executable', 'Rscript') +call ale#Set('r_styler_options', 'tidyverse_style') + +function! ale#fixers#styler#Fix(buffer) abort + return { + \ 'command': 'Rscript --vanilla -e ' + \ . '"suppressPackageStartupMessages(library(styler));' + \ . 'style_file(commandArgs(TRUE), style = ' + \ . ale#Var(a:buffer, 'r_styler_options') . ')"' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/textlint.vim b/autoload/ale/fixers/textlint.vim new file mode 100644 index 00000000..38ab2bfd --- /dev/null +++ b/autoload/ale/fixers/textlint.vim @@ -0,0 +1,15 @@ +" Author: TANIGUCHI Masaya <ta2gch@gmail.com> +" Description: Integration of textlint with ALE. + +function! ale#fixers#textlint#Fix(buffer) abort + let l:executable = ale#handlers#textlint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'textlint_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' --fix' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/go.vim b/autoload/ale/go.vim index a166480a..cd7d9503 100644 --- a/autoload/ale/go.vim +++ b/autoload/ale/go.vim @@ -9,18 +9,18 @@ function! ale#go#FindProjectRoot(buffer) abort let l:filename = ale#path#Simplify(expand('#' . a:buffer . ':p')) for l:name in split($GOPATH, l:sep) - let l:path_dir = ale#path#Simplify(l:name) + let l:path_dir = ale#path#Simplify(l:name) - " Use the directory from GOPATH if the current filename starts with it. - if l:filename[: len(l:path_dir) - 1] is? l:path_dir - return l:path_dir - endif + " Use the directory from GOPATH if the current filename starts with it. + if l:filename[: len(l:path_dir) - 1] is? l:path_dir + return l:path_dir + endif endfor let l:default_go_path = ale#path#Simplify(expand('~/go')) if isdirectory(l:default_go_path) - return l:default_go_path + return l:default_go_path endif return '' diff --git a/autoload/ale/handlers/alex.vim b/autoload/ale/handlers/alex.vim index 853d3137..190a7f86 100644 --- a/autoload/ale/handlers/alex.vim +++ b/autoload/ale/handlers/alex.vim @@ -1,10 +1,24 @@ +scriptencoding utf-8 " Author: Johannes Wienke <languitar@semipol.de> " Description: Error handling for errors in alex output format +function! ale#handlers#alex#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'alex', [ + \ 'node_modules/.bin/alex', + \ 'node_modules/alex/cli.js', + \]) +endfunction + +function! ale#handlers#alex#CreateCommandCallback(flags) abort + return {b -> ale#node#Executable(b, ale#handlers#alex#GetExecutable(b)) + \ . ' %s ' + \ . a:flags} +endfunction + function! ale#handlers#alex#Handle(buffer, lines) abort " Example output: " 6:256-6:262 warning Be careful with “killed”, it’s profane in some cases killed retext-profanities - let l:pattern = '^ *\(\d\+\):\(\d\+\)-\(\d\+\):\(\d\+\) \+warning \+\(.\{-\}\) \+\(.\{-\}\) \+\(.\{-\}\)$' + let l:pattern = '\v^ *(\d+):(\d+)-(\d+):(\d+) +warning +(.{-}) +(.{-}) +(.{-})$' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) @@ -20,3 +34,21 @@ function! ale#handlers#alex#Handle(buffer, lines) abort return l:output endfunction + +" Define a linter for a specific filetype. Accept flags to adapt to the filetype. +" no flags treat input as markdown +" --html treat input as HTML +" --text treat input as plaintext +function! ale#handlers#alex#DefineLinter(filetype, flags) abort + call ale#Set('alex_executable', 'alex') + call ale#Set('alex_use_global', get(g:, 'ale_use_global_executables', 0)) + + call ale#linter#Define(a:filetype, { + \ 'name': 'alex', + \ 'executable': function('ale#handlers#alex#GetExecutable'), + \ 'command': ale#handlers#alex#CreateCommandCallback(a:flags), + \ 'output_stream': 'stderr', + \ 'callback': 'ale#handlers#alex#Handle', + \ 'lint_file': 1, + \}) +endfunction diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim index eda033e4..bc7c0321 100644 --- a/autoload/ale/handlers/eslint.vim +++ b/autoload/ale/handlers/eslint.vim @@ -121,7 +121,7 @@ function! ale#handlers#eslint#Handle(buffer, lines) abort let l:text = l:match[3] if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore') - if l:text is# 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.' + if l:text =~# '^File ignored' continue endif endif diff --git a/autoload/ale/handlers/fecs.vim b/autoload/ale/handlers/fecs.vim new file mode 100644 index 00000000..5362edb9 --- /dev/null +++ b/autoload/ale/handlers/fecs.vim @@ -0,0 +1,52 @@ +" Author: harttle <yangjvn@126.com> +" Description: fecs http://fecs.baidu.com/ + +call ale#Set('javascript_fecs_executable', 'fecs') +call ale#Set('javascript_fecs_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#handlers#fecs#GetCommand(buffer) abort + return '%e check --colors=false --rule=true %t' +endfunction + +function! ale#handlers#fecs#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_fecs', [ + \ 'node_modules/.bin/fecs', + \ 'node_modules/fecs/bin/fecs', + \]) +endfunction + +function! ale#handlers#fecs#Handle(buffer, lines) abort + " Matches patterns looking like the following + " + " fecs WARN → line 20, col 25: Unexpected console statement. (no-console) + " fecs ERROR → line 24, col 36: Missing radix parameter. (radix) + " + let l:pattern = '\v^.*(WARN|ERROR)\s+→\s+line (\d+),\s+col\s+(\d+):\s+(.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:obj = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4] + \} + + let l:code_match = matchlist(l:match[4], '\v^(.{-})\s*\((.+)\)$') + + if !empty(l:code_match) + let l:obj.code = l:code_match[2] + let l:obj.text = l:code_match[1] + endif + + if l:match[1] is# 'WARN' + let l:obj.type = 'W' + elseif l:match[1] is# 'ERROR' + let l:obj.type = 'E' + endif + + call add(l:output, l:obj) + endfor + + return l:output +endfunction + diff --git a/autoload/ale/handlers/flawfinder.vim b/autoload/ale/handlers/flawfinder.vim index a650d6dd..b7d2bec3 100644 --- a/autoload/ale/handlers/flawfinder.vim +++ b/autoload/ale/handlers/flawfinder.vim @@ -1,3 +1,4 @@ +scriptencoding utf-8 " Author: Christian Gibbons <cgibbons@gmu.edu> " Description: This file defines a handler function that should work for the " flawfinder format with the -CDQS flags. @@ -30,7 +31,7 @@ function! ale#handlers#flawfinder#HandleFlawfinderFormat(buffer, lines) abort \ 'lnum': str2nr(l:match[2]), \ 'col': str2nr(l:match[3]), \ 'type': (l:severity < ale#Var(a:buffer, 'c_flawfinder_error_severity')) - \ ? 'W' : 'E', + \ ? 'W' : 'E', \ 'text': s:RemoveUnicodeQuotes(join(split(l:match[4])[1:]) . ': ' . l:match[5]), \} diff --git a/autoload/ale/handlers/haskell.vim b/autoload/ale/handlers/haskell.vim index 9e495b36..3613b1bb 100644 --- a/autoload/ale/handlers/haskell.vim +++ b/autoload/ale/handlers/haskell.vim @@ -66,11 +66,11 @@ function! ale#handlers#haskell#HandleGHCFormat(buffer, lines) abort let l:errors = matchlist(l:match[4], '\v([wW]arning|[eE]rror): ?(.*)') if len(l:errors) > 0 - let l:ghc_type = l:errors[1] - let l:text = l:errors[2] + let l:ghc_type = l:errors[1] + let l:text = l:errors[2] else - let l:ghc_type = '' - let l:text = l:match[4][:0] is# ' ' ? l:match[4][1:] : l:match[4] + let l:ghc_type = '' + let l:text = l:match[4][:0] is# ' ' ? l:match[4][1:] : l:match[4] endif if l:ghc_type is? 'Warning' diff --git a/autoload/ale/handlers/ktlint.vim b/autoload/ale/handlers/ktlint.vim new file mode 100644 index 00000000..ad999485 --- /dev/null +++ b/autoload/ale/handlers/ktlint.vim @@ -0,0 +1,45 @@ +" Author: Michael Phillips <michaeljoelphillips@gmail.com> +" Description: Handler functions for ktlint. + +call ale#Set('kotlin_ktlint_executable', 'ktlint') +call ale#Set('kotlin_ktlint_rulesets', []) +call ale#Set('kotlin_ktlint_options', '') + +function! ale#handlers#ktlint#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'kotlin_ktlint_executable') + let l:options = ale#Var(a:buffer, 'kotlin_ktlint_options') + let l:rulesets = ale#handlers#ktlint#GetRulesets(a:buffer) + + return ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . (empty(l:rulesets) ? '' : ' ' . l:rulesets) + \ . ' %t' +endfunction + +function! ale#handlers#ktlint#GetRulesets(buffer) abort + let l:rulesets = map(ale#Var(a:buffer, 'kotlin_ktlint_rulesets'), '''--ruleset '' . v:val') + + return join(l:rulesets, ' ') +endfunction + +function! ale#handlers#ktlint#Handle(buffer, lines) abort + let l:message_pattern = '^\(.*\):\([0-9]\+\):\([0-9]\+\):\s\+\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:message_pattern) + let l:line = l:match[2] + 0 + let l:column = l:match[3] + 0 + let l:text = l:match[4] + + let l:type = l:text =~? 'not a valid kotlin file' ? 'E' : 'W' + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:column, + \ 'text': l:text, + \ 'type': l:type + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/languagetool.vim b/autoload/ale/handlers/languagetool.vim new file mode 100644 index 00000000..10e049df --- /dev/null +++ b/autoload/ale/handlers/languagetool.vim @@ -0,0 +1,74 @@ +" Author: Vincent (wahrwolf [at] wolfpit.net) +" Description: languagetool for markdown files +" +call ale#Set('languagetool_executable', 'languagetool') + +function! ale#handlers#languagetool#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'languagetool_executable') +endfunction + +function! ale#handlers#languagetool#GetCommand(buffer) abort + let l:executable = ale#handlers#languagetool#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' --autoDetect %s' +endfunction + +function! ale#handlers#languagetool#HandleOutput(buffer, lines) abort + " Match lines like: + " 1.) Line 5, column 1, Rule ID: + let l:head_pattern = '^\v.+.\) Line (\d+), column (\d+), Rule ID. (.+)$' + let l:head_matches = ale#util#GetMatches(a:lines, l:head_pattern) + + " Match lines like: + " Message: Did you forget a comma after a conjunctive/linking adverb? + let l:message_pattern = '^\vMessage. (.+)$' + let l:message_matches = ale#util#GetMatches(a:lines, l:message_pattern) + + " Match lines like: + " ^^^^^ " + let l:markers_pattern = '^\v *(\^+) *$' + let l:markers_matches = ale#util#GetMatches(a:lines, l:markers_pattern) + + let l:output = [] + + + " Okay tbh I was to lazy to figure out a smarter solution here + " We just check that the arrays are same sized and merge everything + " together + let l:i = 0 + + while l:i < len(l:head_matches) + \ && ( + \ (len(l:head_matches) == len(l:markers_matches)) + \ && (len(l:head_matches) == len(l:message_matches)) + \ ) + let l:item = { + \ 'lnum' : str2nr(l:head_matches[l:i][1]), + \ 'col' : str2nr(l:head_matches[l:i][2]), + \ 'end_col' : str2nr(l:head_matches[l:i][2]) + len(l:markers_matches[l:i][1])-1, + \ 'type' : 'W', + \ 'code' : l:head_matches[l:i][3], + \ 'text' : l:message_matches[l:i][1] + \} + call add(l:output, l:item) + let l:i+=1 + endwhile + + return l:output +endfunction + +" Define the languagetool linter for a given filetype. +" TODO: +" - Add language detection settings based on user env (for mothertongue) +" - Add fixer +" - Add config options for rules +function! ale#handlers#languagetool#DefineLinter(filetype) abort + call ale#linter#Define(a:filetype, { + \ 'name': 'languagetool', + \ 'executable': function('ale#handlers#languagetool#GetExecutable'), + \ 'command': function('ale#handlers#languagetool#GetCommand'), + \ 'output_stream': 'stdout', + \ 'callback': 'ale#handlers#languagetool#HandleOutput', + \ 'lint_file': 1, + \}) +endfunction diff --git a/autoload/ale/handlers/markdownlint.vim b/autoload/ale/handlers/markdownlint.vim index 12fc501c..daaa1d66 100644 --- a/autoload/ale/handlers/markdownlint.vim +++ b/autoload/ale/handlers/markdownlint.vim @@ -7,10 +7,10 @@ function! ale#handlers#markdownlint#Handle(buffer, lines) abort 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', - \ }) + \ 'lnum': l:match[1] + 0, + \ 'text': '(' . l:match[2] . l:match[3] . l:match[4] . ')' . l:match[5], + \ 'type': 'W', + \}) endfor return l:output diff --git a/autoload/ale/handlers/redpen.vim b/autoload/ale/handlers/redpen.vim index 84e331ed..195057ca 100644 --- a/autoload/ale/handlers/redpen.vim +++ b/autoload/ale/handlers/redpen.vim @@ -4,10 +4,10 @@ function! ale#handlers#redpen#HandleRedpenOutput(buffer, lines) abort " Only one file was passed to redpen. So response array has only one " element. - let l:res = json_decode(join(a:lines))[0] + let l:res = get(ale#util#FuzzyJSONDecode(a:lines, []), 0, {}) let l:output = [] - for l:err in l:res.errors + for l:err in get(l:res, 'errors', []) let l:item = { \ 'text': l:err.message, \ 'type': 'W', diff --git a/autoload/ale/handlers/rust.vim b/autoload/ale/handlers/rust.vim index c6a4b670..dda6466e 100644 --- a/autoload/ale/handlers/rust.vim +++ b/autoload/ale/handlers/rust.vim @@ -60,7 +60,7 @@ function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort \ 'lnum': l:span.line_start, \ 'end_lnum': l:span.line_end, \ 'col': l:span.column_start, - \ 'end_col': l:span.column_end, + \ 'end_col': l:span.column_end-1, \ 'text': empty(l:span.label) ? l:error.message : printf('%s: %s', l:error.message, l:span.label), \ 'type': toupper(l:error.level[0]), \}) diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim index e96dd3ce..75eaf71f 100644 --- a/autoload/ale/handlers/sh.vim +++ b/autoload/ale/handlers/sh.vim @@ -9,7 +9,7 @@ function! ale#handlers#sh#GetShellType(buffer) abort " Remove options like -e, etc. let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g') - for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'sh'] + for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh'] if l:command =~# l:possible_shell . '\s*$' return l:possible_shell endif diff --git a/autoload/ale/handlers/sml.vim b/autoload/ale/handlers/sml.vim index 92c5f83b..594ee4f7 100644 --- a/autoload/ale/handlers/sml.vim +++ b/autoload/ale/handlers/sml.vim @@ -26,7 +26,6 @@ function! ale#handlers#sml#GetCmFile(buffer) abort endfunction " Only one of smlnj or smlnj-cm can be enabled at a time. -" executable_callback is called before *every* lint attempt function! s:GetExecutable(buffer, source) abort if ale#handlers#sml#GetCmFile(a:buffer) is# '' " No CM file found; only allow single-file mode to be enabled @@ -64,26 +63,27 @@ function! ale#handlers#sml#Handle(buffer, lines) abort let l:match2 = matchlist(l:line, l:pattern2) if len(l:match2) != 0 - call add(l:out, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match2[1] + 0, - \ 'col' : l:match2[2] - 1, - \ 'text': l:match2[3], - \ 'type': l:match2[3] =~# '^Warning' ? 'W' : 'E', - \}) - continue + call add(l:out, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match2[1] + 0, + \ 'col' : l:match2[2] - 1, + \ 'text': l:match2[3], + \ 'type': l:match2[3] =~# '^Warning' ? 'W' : 'E', + \}) + + continue endif let l:match = matchlist(l:line, l:pattern) if len(l:match) != 0 - call add(l:out, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'text': l:match[2] . ': ' . l:match[3], - \ 'type': l:match[2] is# 'error' ? 'E' : 'W', - \}) - continue + call add(l:out, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[2] . ': ' . l:match[3], + \ 'type': l:match[2] is# 'error' ? 'E' : 'W', + \}) + continue endif endfor diff --git a/autoload/ale/handlers/tsserver.vim b/autoload/ale/handlers/tsserver.vim new file mode 100644 index 00000000..f78499ac --- /dev/null +++ b/autoload/ale/handlers/tsserver.vim @@ -0,0 +1,8 @@ +" Author: Derek Sifford <dereksifford@gmail.com> +" Description: Handlers for tsserver + +function! ale#handlers#tsserver#GetProjectRoot(buffer) abort + let l:tsconfig_file = ale#path#FindNearestFile(a:buffer, 'tsconfig.json') + + return !empty(l:tsconfig_file) ? fnamemodify(l:tsconfig_file, ':h') : '' +endfunction diff --git a/autoload/ale/handlers/writegood.vim b/autoload/ale/handlers/writegood.vim index aff66d5f..8ae61a38 100644 --- a/autoload/ale/handlers/writegood.vim +++ b/autoload/ale/handlers/writegood.vim @@ -65,8 +65,8 @@ function! ale#handlers#writegood#DefineLinter(filetype) abort call ale#linter#Define(a:filetype, { \ 'name': 'writegood', \ 'aliases': ['write-good'], - \ 'executable_callback': 'ale#handlers#writegood#GetExecutable', - \ 'command_callback': 'ale#handlers#writegood#GetCommand', + \ 'executable': function('ale#handlers#writegood#GetExecutable'), + \ 'command': function('ale#handlers#writegood#GetCommand'), \ 'callback': 'ale#handlers#writegood#Handle', \}) endfunction diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim index ae1f3e7d..172f9d54 100644 --- a/autoload/ale/highlight.vim +++ b/autoload/ale/highlight.vim @@ -26,6 +26,41 @@ endif let s:MAX_POS_VALUES = 8 let s:MAX_COL_SIZE = 1073741824 " pow(2, 30) +" Check if we have neovim's buffer highlight API +" +" Below we define some functions' implementation conditionally if this API +" exists or not. +" +" The API itself is more ergonomic and neovim performs highlights positions +" rebases during edits so we see less stalled highlights. +let s:nvim_api = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') + +function! ale#highlight#HasNeovimApi() abort + return s:nvim_api +endfunction + +function! ale#highlight#nvim_buf_clear_namespace(...) abort + return call('nvim_buf_clear_namespace', a:000) +endfunction + +function! ale#highlight#nvim_buf_add_highlight(...) abort + return call('nvim_buf_add_highlight', a:000) +endfunction + +function! s:ale_nvim_highlight_id(bufnr) abort + let l:id = getbufvar(a:bufnr, 'ale_nvim_highlight_id', -1) + + if l:id is -1 + " NOTE: This will highlight nothing but will allocate new id + let l:id = ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, 0, '', 0, 0, -1 + \) + call setbufvar(a:bufnr, 'ale_nvim_highlight_id', l:id) + endif + + return l:id +endfunction + function! ale#highlight#CreatePositions(line, col, end_line, end_col) abort if a:line >= a:end_line " For single lines, just return the one position. @@ -49,12 +84,90 @@ endfunction " Given a loclist for current items to highlight, remove all highlights " except these which have matching loclist item entries. + function! ale#highlight#RemoveHighlights() abort - for l:match in getmatches() - if l:match.group =~# '^ALE' - call matchdelete(l:match.id) + if ale#highlight#HasNeovimApi() + if get(b:, 'ale_nvim_highlight_id', 0) + let l:bufnr = bufnr('%') + " NOTE: 0, -1 means from 0 line till the end of buffer + call ale#highlight#nvim_buf_clear_namespace( + \ l:bufnr, + \ b:ale_nvim_highlight_id, + \ 0, -1 + \) endif - endfor + else + for l:match in getmatches() + if l:match.group =~# '^ALE' + call matchdelete(l:match.id) + endif + endfor + endif +endfunction + +function! s:highlight_line(bufnr, lnum, group) abort + if ale#highlight#HasNeovimApi() + let l:highlight_id = s:ale_nvim_highlight_id(a:bufnr) + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ a:lnum - 1, 0, -1 + \) + else + call matchaddpos(a:group, [a:lnum]) + endif +endfunction + +function! s:highlight_range(bufnr, range, group) abort + if ale#highlight#HasNeovimApi() + let l:highlight_id = s:ale_nvim_highlight_id(a:bufnr) + " NOTE: lines and columns indicies are 0-based in nvim_buf_* API. + let l:lnum = a:range.lnum - 1 + let l:end_lnum = a:range.end_lnum - 1 + let l:col = a:range.col - 1 + let l:end_col = a:range.end_col + + if l:lnum >= l:end_lnum + " For single lines, just return the one position. + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:lnum, l:col, l:end_col + \) + else + " highlight first line from start till the line end + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:lnum, l:col, -1 + \) + + " highlight all lines between the first and last entirely + let l:cur = l:lnum + 1 + + while l:cur < l:end_lnum + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:cur, 0, -1 + \ ) + let l:cur += 1 + endwhile + + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:end_lnum, 0, l:end_col + \) + endif + else + " Set all of the positions, which are chunked into Lists which + " are as large as will be accepted by matchaddpos. + call map( + \ ale#highlight#CreatePositions( + \ a:range.lnum, + \ a:range.col, + \ a:range.end_lnum, + \ a:range.end_col + \ ), + \ 'matchaddpos(a:group, v:val)' + \) + endif endfunction function! ale#highlight#UpdateHighlights() abort @@ -79,17 +192,14 @@ function! ale#highlight#UpdateHighlights() abort let l:group = 'ALEError' endif - let l:line = l:item.lnum - let l:col = l:item.col - let l:end_line = get(l:item, 'end_lnum', l:line) - let l:end_col = get(l:item, 'end_col', l:col) + let l:range = { + \ 'lnum': l:item.lnum, + \ 'col': l:item.col, + \ 'end_lnum': get(l:item, 'end_lnum', l:item.lnum), + \ 'end_col': get(l:item, 'end_col', l:item.col) + \} - " Set all of the positions, which are chunked into Lists which - " are as large as will be accepted by matchaddpos. - call map( - \ ale#highlight#CreatePositions(l:line, l:col, l:end_line, l:end_col), - \ 'matchaddpos(l:group, v:val)' - \) + call s:highlight_range(l:item.bufnr, l:range, l:group) endfor " If highlights are enabled and signs are not enabled, we should still @@ -111,7 +221,7 @@ function! ale#highlight#UpdateHighlights() abort endif if l:available_groups[l:group] - call matchaddpos(l:group, [l:item.lnum]) + call s:highlight_line(l:item.bufnr, l:item.lnum, l:group) endif endfor endif diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim index 69db276e..2af35aa4 100644 --- a/autoload/ale/hover.vim +++ b/autoload/ale/hover.vim @@ -24,7 +24,21 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort 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) + " If we pass the show_documentation flag, we should show the full + " documentation, and always in the preview window. + if get(l:options, 'show_documentation', 0) + let l:documentation = get(a:response.body, 'documentation', '') + + " displayString is not included here, because it can be very + " noisy and run on for many lines for complex types. A less + " verbose alternative may be nice in future. + if !empty(l:documentation) + call ale#preview#Show(split(l:documentation, "\n"), { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \}) + endif + elseif get(l:options, 'hover_from_balloonexpr', 0) \&& exists('*balloon_show') \&& ale#Var(l:options.buffer, 'set_balloons') call balloon_show(a:response.body.displayString) @@ -43,7 +57,7 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort " 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:line, l:column] = getpos('.')[1:2] let l:end = len(getline(l:line)) if l:buffer isnot l:options.buffer @@ -64,8 +78,8 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort let l:result = l:result.contents if type(l:result) is v:t_string - " The result can be just a string. - let l:result = [l:result] + " The result can be just a string. + let l:result = [l:result] endif if type(l:result) is v:t_dict @@ -92,10 +106,15 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort endif endfunction -function! s:OnReady(linter, lsp_details, line, column, opt, ...) abort - let l:buffer = a:lsp_details.buffer +function! s:OnReady(line, column, opt, linter, lsp_details) abort let l:id = a:lsp_details.connection_id + if !ale#lsp#HasCapability(l:id, 'hover') + return + endif + + let l:buffer = a:lsp_details.buffer + let l:Callback = a:linter.lsp is# 'tsserver' \ ? function('ale#hover#HandleTSServerResponse') \ : function('ale#hover#HandleLSPResponse') @@ -126,23 +145,10 @@ function! s:OnReady(linter, lsp_details, line, column, opt, ...) abort \ 'line': a:line, \ 'column': l:column, \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0), + \ 'show_documentation': get(a:opt, 'show_documentation', 0), \} endfunction -function! s:ShowDetails(linter, buffer, line, column, opt, ...) abort - let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter) - - if empty(l:lsp_details) - return 0 - endif - - let l:id = l:lsp_details.connection_id - - call ale#lsp#WaitForCapability(l:id, 'hover', function('s:OnReady', [ - \ a:linter, l:lsp_details, a:line, a:column, a:opt - \])) -endfunction - " Obtain Hover information for the specified position " Pass optional arguments in the dictionary opt. " Currently, only one key/value is useful: @@ -153,9 +159,31 @@ endfunction " - 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 + let l:show_documentation = get(a:opt, 'show_documentation', 0) + let l:Callback = function('s:OnReady', [a:line, a:col, a:opt]) + for l:linter in ale#linter#Get(getbufvar(a:buffer, '&filetype')) + " Only tsserver supports documentation requests at the moment. if !empty(l:linter.lsp) - call s:ShowDetails(l:linter, a:buffer, a:line, a:col, a:opt) + \&& (!l:show_documentation || l:linter.lsp is# 'tsserver') + call ale#lsp_linter#StartLSP(a:buffer, l:linter, l:Callback) endif endfor endfunction + +" This function implements the :ALEHover command. +function! ale#hover#ShowAtCursor() abort + let l:buffer = bufnr('') + let l:pos = getpos('.') + + call ale#hover#Show(l:buffer, l:pos[1], l:pos[2], {}) +endfunction + +" This function implements the :ALEDocumentation command. +function! ale#hover#ShowDocumentationAtCursor() abort + let l:buffer = bufnr('') + let l:pos = getpos('.') + let l:options = {'show_documentation': 1} + + call ale#hover#Show(l:buffer, l:pos[1], l:pos[2], l:options) +endfunction diff --git a/autoload/ale/job.vim b/autoload/ale/job.vim index 0117c7dd..14b3e484 100644 --- a/autoload/ale/job.vim +++ b/autoload/ale/job.vim @@ -99,7 +99,8 @@ 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, get(l:info, 'exit_code', 1)) + " We have to remove the callback, so we don't call it twice. + call ale#util#GetFunction(remove(l:info, 'exit_cb'))(l:job_id, get(l:info, 'exit_code', 1)) endif finally " Automatically forget about the job after it's done. @@ -124,7 +125,8 @@ function! s:VimExitCallback(job, exit_code) abort if ch_status(job_getchannel(a:job)) is# 'closed' try if !empty(l:info) && has_key(l:info, 'exit_cb') - call ale#util#GetFunction(l:info.exit_cb)(l:job_id, a:exit_code) + " We have to remove the callback, so we don't call it twice. + call ale#util#GetFunction(remove(l:info, 'exit_cb'))(l:job_id, a:exit_code) endif finally " Automatically forget about the job after it's done. @@ -173,10 +175,6 @@ endfunction function! ale#job#PrepareCommand(buffer, command) abort let l:wrapper = ale#Var(a:buffer, 'command_wrapper') - let l:command = !empty(l:wrapper) - \ ? s:PrepareWrappedCommand(l:wrapper, a:command) - \ : a:command - " The command will be executed in a subshell. This fixes a number of " issues, including reading the PATH variables correctly, %PATHEXT% " expansion on Windows, etc. @@ -184,6 +182,17 @@ function! ale#job#PrepareCommand(buffer, command) abort " NeoVim handles this issue automatically if the command is a String, " but we'll do this explicitly, so we use the same exact command for both " versions. + let l:command = !empty(l:wrapper) + \ ? s:PrepareWrappedCommand(l:wrapper, a:command) + \ : a:command + + " If a custom shell is specified, use that. + if exists('g:ale_shell') + let l:shell_arguments = get(g:, 'ale_shell_arguments', &shellcmdflag) + + return split(g:ale_shell) + split(l:shell_arguments) + [l:command] + endif + if has('win32') return 'cmd /s/c "' . l:command . '"' endif @@ -267,12 +276,34 @@ function! ale#job#Start(command, options) abort return l:job_id endfunction +" Force running commands in a Windows CMD command line. +" This means the same command syntax works everywhere. +function! ale#job#StartWithCmd(command, options) abort + let l:shell = &l:shell + let l:shellcmdflag = &l:shellcmdflag + let &l:shell = 'cmd' + let &l:shellcmdflag = '/c' + + try + let l:job_id = ale#job#Start(a:command, a:options) + finally + let &l:shell = l:shell + let &l:shellcmdflag = l:shellcmdflag + endtry + + return l:job_id +endfunction + " Send raw data to the job. function! ale#job#SendRaw(job_id, string) abort if has('nvim') call jobsend(a:job_id, a:string) else - call ch_sendraw(job_getchannel(s:job_map[a:job_id].job), a:string) + let l:job = s:job_map[a:job_id].job + + if ch_status(l:job) is# 'open' + call ch_sendraw(job_getchannel(l:job), a:string) + endif endif endfunction @@ -296,6 +327,20 @@ function! ale#job#IsRunning(job_id) abort return 0 endfunction +function! ale#job#HasOpenChannel(job_id) abort + if ale#job#IsRunning(a:job_id) + if has('nvim') + " TODO: Implement a check for NeoVim. + return 1 + endif + + " Check if the Job's channel can be written to. + return ch_status(s:job_map[a:job_id].job) is# 'open' + endif + + return 0 +endfunction + " Given a Job ID, stop that job. " Invalid job IDs will be ignored. function! ale#job#Stop(job_id) abort diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 1cbc9ffe..8c657675 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -32,7 +32,7 @@ let s:default_ale_linter_aliases = { " NOTE: Update the g:ale_linters documentation when modifying this. let s:default_ale_linters = { \ 'csh': ['shell'], -\ 'elixir': ['credo', 'dialyxir', 'dogma', 'elixir-ls'], +\ 'elixir': ['credo', 'dialyxir', 'dogma'], \ 'go': ['gofmt', 'golint', 'go vet'], \ 'hack': ['hack'], \ 'help': [], @@ -80,7 +80,6 @@ function! ale#linter#PreProcess(filetype, linter) abort endif let l:obj = { - \ 'add_newline': get(a:linter, 'add_newline', 0), \ 'name': get(a:linter, 'name'), \ 'lsp': get(a:linter, 'lsp', ''), \} @@ -121,7 +120,8 @@ function! ale#linter#PreProcess(filetype, linter) abort let l:obj.executable = a:linter.executable if type(l:obj.executable) isnot v:t_string - throw '`executable` must be a string if defined' + \&& type(l:obj.executable) isnot v:t_func + throw '`executable` must be a String or Function if defined' endif else throw 'Either `executable` or `executable_callback` must be defined' @@ -177,7 +177,8 @@ function! ale#linter#PreProcess(filetype, linter) abort let l:obj.command = a:linter.command if type(l:obj.command) isnot v:t_string - throw '`command` must be a string if defined' + \&& type(l:obj.command) isnot v:t_func + throw '`command` must be a String or Function if defined' endif else throw 'Either `command`, `executable_callback`, `command_chain` ' @@ -194,9 +195,16 @@ function! ale#linter#PreProcess(filetype, linter) abort endif if !l:needs_address - if has_key(a:linter, 'address_callback') - throw '`address_callback` cannot be used when lsp != ''socket''' + if has_key(a:linter, 'address') || has_key(a:linter, 'address_callback') + throw '`address` or `address_callback` cannot be used when lsp != ''socket''' endif + elseif has_key(a:linter, 'address') + if type(a:linter.address) isnot v:t_string + \&& type(a:linter.address) isnot v:t_func + throw '`address` must be a String or Function if defined' + endif + + let l:obj.address = a:linter.address elseif has_key(a:linter, 'address_callback') let l:obj.address_callback = a:linter.address_callback @@ -204,7 +212,7 @@ function! ale#linter#PreProcess(filetype, linter) abort throw '`address_callback` must be a callback if defined' endif else - throw '`address_callback` must be defined for getting the LSP address' + throw '`address` or `address_callback` must be defined for getting the LSP address' endif if l:needs_lsp_details @@ -221,20 +229,34 @@ function! ale#linter#PreProcess(filetype, linter) abort endif else " Default to using the filetype as the language. - let l:obj.language = get(a:linter, 'language', a:filetype) - - if type(l:obj.language) isnot v:t_string - throw '`language` must be a string' + let l:Language = get(a:linter, 'language', a:filetype) + + if type(l:Language) is v:t_string + " Make 'language_callback' return the 'language' value. + let l:obj.language = l:Language + let l:obj.language_callback = function('s:LanguageGetter') + elseif type(l:Language) is v:t_func + let l:obj.language_callback = l:Language + else + throw '`language` must be a String or Funcref' endif - - " Make 'language_callback' return the 'language' value. - let l:obj.language_callback = function('s:LanguageGetter') endif - let l:obj.project_root_callback = get(a:linter, 'project_root_callback') + if has_key(a:linter, 'project_root') + let l:obj.project_root = a:linter.project_root - if !s:IsCallback(l:obj.project_root_callback) - throw '`project_root_callback` must be a callback for LSP linters' + if type(l:obj.project_root) isnot v:t_string + \&& type(l:obj.project_root) isnot v:t_func + throw '`project_root` must be a String or Function if defined' + endif + elseif has_key(a:linter, 'project_root_callback') + let l:obj.project_root_callback = a:linter.project_root_callback + + if !s:IsCallback(l:obj.project_root_callback) + throw '`project_root_callback` must be a callback if defined' + endif + else + throw '`project_root` or `project_root_callback` must be defined for LSP linters' endif if has_key(a:linter, 'completion_filter') @@ -258,6 +280,11 @@ function! ale#linter#PreProcess(filetype, linter) abort endif elseif has_key(a:linter, 'initialization_options') let l:obj.initialization_options = a:linter.initialization_options + + if type(l:obj.initialization_options) isnot v:t_dict + \&& type(l:obj.initialization_options) isnot v:t_func + throw '`initialization_options` must be a String or Function if defined' + endif endif if has_key(a:linter, 'lsp_config_callback') @@ -272,7 +299,8 @@ function! ale#linter#PreProcess(filetype, linter) abort endif elseif has_key(a:linter, 'lsp_config') if type(a:linter.lsp_config) isnot v:t_dict - throw '`lsp_config` must be a Dictionary' + \&& type(a:linter.lsp_config) isnot v:t_func + throw '`lsp_config` must be a Dictionary or Function if defined' endif let l:obj.lsp_config = a:linter.lsp_config @@ -312,6 +340,14 @@ function! ale#linter#PreProcess(filetype, linter) abort throw '`aliases` must be a List of String values' endif + for l:key in filter(keys(a:linter), 'v:val[-9:] is# ''_callback'' || v:val is# ''command_chain''') + if !get(g:, 'ale_ignore_2_4_warnings') + execute 'echom l:key . '' is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' + endif + + break + endfor + return l:obj endfunction @@ -477,22 +513,34 @@ endfunction " Given a buffer and linter, get the executable String for the linter. function! ale#linter#GetExecutable(buffer, linter) abort - return has_key(a:linter, 'executable_callback') - \ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer) + let l:Executable = has_key(a:linter, 'executable_callback') + \ ? function(a:linter.executable_callback) \ : a:linter.executable + + return type(l:Executable) is v:t_func + \ ? l:Executable(a:buffer) + \ : l:Executable endfunction " Given a buffer and linter, get the command String for the linter. " The command_chain key is not supported. function! ale#linter#GetCommand(buffer, linter) abort - return has_key(a:linter, 'command_callback') - \ ? ale#util#GetFunction(a:linter.command_callback)(a:buffer) + let l:Command = has_key(a:linter, 'command_callback') + \ ? function(a:linter.command_callback) \ : a:linter.command + + return type(l:Command) is v:t_func + \ ? l:Command(a:buffer) + \ : l:Command endfunction " Given a buffer and linter, get the address for connecting to the server. function! ale#linter#GetAddress(buffer, linter) abort - return has_key(a:linter, 'address_callback') - \ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer) + let l:Address = has_key(a:linter, 'address_callback') + \ ? function(a:linter.address_callback) \ : a:linter.address + + return type(l:Address) is v:t_func + \ ? l:Address(a:buffer) + \ : l:Address endfunction diff --git a/autoload/ale/list.vim b/autoload/ale/list.vim index 3417575c..63d97f35 100644 --- a/autoload/ale/list.vim +++ b/autoload/ale/list.vim @@ -115,7 +115,7 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort let l:open_type = '' if ale#Var(a:buffer, 'list_vertical') == 1 - let l:open_type = 'vert ' + let l:open_type = 'vert rightbelow ' endif if g:ale_set_quickfix diff --git a/autoload/ale/loclist_jumping.vim b/autoload/ale/loclist_jumping.vim index fd5ff922..1d3ef7e5 100644 --- a/autoload/ale/loclist_jumping.vim +++ b/autoload/ale/loclist_jumping.vim @@ -9,14 +9,26 @@ " If there are no items or we have hit the end with wrapping off, an empty " List will be returned, otherwise a pair of [line_number, column_number] will " be returned. -function! ale#loclist_jumping#FindNearest(direction, wrap) abort +function! ale#loclist_jumping#FindNearest(direction, wrap, ...) abort let l:buffer = bufnr('') - let l:pos = getcurpos() + let l:pos = getpos('.') let l:info = get(g:ale_buffer_info, bufnr('%'), {'loclist': []}) " Copy the list and filter to only the items in this buffer. let l:loclist = filter(copy(l:info.loclist), 'v:val.bufnr == l:buffer') let l:search_item = {'bufnr': l:buffer, 'lnum': l:pos[1], 'col': l:pos[2]} + if a:0 > 0 + let l:filter = a:1 + else + let l:filter = 'any' + endif + + if a:0 > 1 + let l:subtype_filter = a:2 + else + let l:subtype_filter = 'any' + endif + " When searching backwards, so we can find the next smallest match. if a:direction is# 'before' call reverse(l:loclist) @@ -41,29 +53,61 @@ function! ale#loclist_jumping#FindNearest(direction, wrap) abort \ l:search_item \) - if a:direction is# 'before' && l:cmp_value < 0 - return [l:item.lnum, l:item.col] - endif + if (l:filter is# 'any' || l:filter is# l:item.type) + \&& ( + \ l:subtype_filter is# 'any' + \ || l:subtype_filter is# get(l:item, 'sub_type', '') + \) - if a:direction is# 'after' && l:cmp_value > 0 - return [l:item.lnum, l:item.col] + if a:direction is# 'before' && l:cmp_value < 0 + return [l:item.lnum, l:item.col] + endif + + if a:direction is# 'after' && l:cmp_value > 0 + return [l:item.lnum, l:item.col] + endif endif endfor " If we found nothing, and the wrap option is set to 1, then we should " wrap around the list of warnings/errors - if a:wrap && !empty(l:loclist) - let l:item = l:loclist[0] - - return [l:item.lnum, l:item.col] + if a:wrap + for l:item in l:loclist + if (l:filter is# 'any' || l:filter is# l:item.type) + \&& ( + \ l:subtype_filter is# 'any' + \ || l:subtype_filter is# get(l:item, 'sub_type', '') + \) + return [l:item.lnum, l:item.col] + endif + endfor endif return [] endfunction " As before, find the nearest match, but position the cursor at it. -function! ale#loclist_jumping#Jump(direction, wrap) abort - let l:nearest = ale#loclist_jumping#FindNearest(a:direction, a:wrap) +function! ale#loclist_jumping#Jump(direction, ...) abort + if a:0 > 0 + let l:wrap = a:1 + else + let l:wrap = 0 + endif + + if a:0 > 1 + let l:filter = a:2 + else + let l:filter = 'any' + endif + + if a:0 > 2 + let l:subtype_filter = a:3 + else + let l:subtype_filter = 'any' + endif + + let l:nearest = ale#loclist_jumping#FindNearest(a:direction, + \ l:wrap, l:filter, l:subtype_filter) if !empty(l:nearest) normal! m` @@ -71,6 +115,36 @@ function! ale#loclist_jumping#Jump(direction, wrap) abort endif endfunction +function! ale#loclist_jumping#WrapJump(direction, sargs) abort + let [l:args, l:rest] = ale#args#Parse(['error', 'warning', 'info', 'wrap', + \ 'style', 'nostyle'], a:sargs) + + let l:wrap = 0 + let l:type_filter = 'any' + let l:subtype_filter = 'any' + + if get(l:args, 'wrap', 'nil') is# '' + let l:wrap = 1 + endif + + if get(l:args, 'error', 'nil') is# '' + let l:type_filter = 'E' + elseif get(l:args, 'warning', 'nil') is# '' + let l:type_filter = 'W' + elseif get(l:args, 'info', 'nil') is# '' + let l:type_filter = 'I' + endif + + if get(l:args, 'nostyle', 'nil') is# '' + let l:subtype_filter = 'style' + elseif get(l:args, 'style', 'nil') is# '' + let l:subtype_filter = '' + endif + + call ale#loclist_jumping#Jump(a:direction, l:wrap, l:type_filter, + \ l:subtype_filter) +endfunction + function! ale#loclist_jumping#JumpToIndex(index) abort let l:buffer = bufnr('') let l:info = get(g:ale_buffer_info, l:buffer, {'loclist': []}) diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index f55096c2..7186d2a9 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -21,7 +21,6 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort " init_options: Options to send to the server. " config: Configuration settings to send to the server. " callback_list: A list of callbacks for handling LSP responses. - " message_queue: Messages queued for sending to callbacks. " capabilities_queue: The list of callbacks to call with capabilities. " capabilities: Features the server supports. let s:connections[l:conn_id] = { @@ -35,14 +34,14 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort \ 'init_options': a:init_options, \ 'config': {}, \ 'callback_list': [], - \ 'message_queue': [], - \ 'capabilities_queue': [], + \ 'init_queue': [], \ 'capabilities': { \ 'hover': 0, \ 'references': 0, \ 'completion': 0, \ 'completion_trigger_characters': [], \ 'definition': 0, + \ 'typeDefinition': 0, \ 'symbol_search': 0, \ }, \} @@ -58,6 +57,15 @@ function! ale#lsp#RemoveConnectionWithID(id) abort endif endfunction +function! ale#lsp#ResetConnections() abort + let s:connections = {} +endfunction + +" Used only in tests. +function! ale#lsp#GetConnections() abort + return s:connections +endfunction + " This is only needed for tests function! ale#lsp#MarkDocumentAsOpen(id, buffer) abort let l:conn = get(s:connections, a:id, {}) @@ -207,6 +215,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort let a:conn.capabilities.definition = 1 endif + if get(a:capabilities, 'typeDefinitionProvider') is v:true + let a:conn.capabilities.typeDefinition = 1 + endif + if get(a:capabilities, 'workspaceSymbolProvider') is v:true let a:conn.capabilities.symbol_search = 1 endif @@ -245,22 +257,15 @@ function! ale#lsp#HandleInitResponse(conn, response) abort return endif - " After the server starts, send messages we had queued previously. - for l:message_data in a:conn.message_queue - call s:SendMessageData(a:conn, l:message_data) - endfor - - " Remove the messages now. - let a:conn.message_queue = [] + " The initialized message must be sent before everything else. + call ale#lsp#Send(a:conn.id, ale#lsp#message#Initialized()) " Call capabilities callbacks queued for the project. - for [l:capability, l:Callback] in a:conn.capabilities_queue - if a:conn.capabilities[l:capability] - call call(l:Callback, [a:conn.id]) - endif + for l:Callback in a:conn.init_queue + call l:Callback() endfor - let a:conn.capabilities_queue = [] + let a:conn.init_queue = [] endfunction function! ale#lsp#HandleMessage(conn_id, message) abort @@ -314,19 +319,35 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort let l:conn.capabilities.symbol_search = 1 endfunction +function! s:SendInitMessage(conn) abort + let [l:init_id, l:init_data] = ale#lsp#CreateMessageData( + \ ale#lsp#message#Initialize(a:conn.root, a:conn.init_options), + \) + let a:conn.init_request_id = l:init_id + call s:SendMessageData(a:conn, l:init_data) +endfunction + " Start a program for LSP servers. " " 1 will be returned if the program is running, or 0 if the program could " not be started. function! ale#lsp#StartProgram(conn_id, executable, command) abort let l:conn = s:connections[a:conn_id] + let l:started = 0 - if !has_key(l:conn, 'job_id') || !ale#job#IsRunning(l:conn.job_id) + if !has_key(l:conn, 'job_id') || !ale#job#HasOpenChannel(l:conn.job_id) let l:options = { \ 'mode': 'raw', \ 'out_cb': {_, message -> ale#lsp#HandleMessage(a:conn_id, message)}, \} - let l:job_id = ale#job#Start(a:command, l:options) + + if has('win32') + let l:job_id = ale#job#StartWithCmd(a:command, l:options) + else + let l:job_id = ale#job#Start(a:command, l:options) + endif + + let l:started = 1 else let l:job_id = l:conn.job_id endif @@ -335,6 +356,10 @@ function! ale#lsp#StartProgram(conn_id, executable, command) abort let l:conn.job_id = l:job_id endif + if l:started && !l:conn.is_tsserver + call s:SendInitMessage(l:conn) + endif + return l:job_id > 0 endfunction @@ -344,11 +369,14 @@ endfunction " not be opened. function! ale#lsp#ConnectToAddress(conn_id, address) abort let l:conn = s:connections[a:conn_id] + let l:started = 0 if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id) let l:channel_id = ale#socket#Open(a:address, { \ 'callback': {_, mess -> ale#lsp#HandleMessage(a:conn_id, mess)}, \}) + + let l:started = 1 else let l:channel_id = l:conn.channel_id endif @@ -357,6 +385,10 @@ function! ale#lsp#ConnectToAddress(conn_id, address) abort let l:conn.channel_id = l:channel_id endif + if l:started + call s:SendInitMessage(l:conn) + endif + return l:channel_id >= 0 endfunction @@ -421,26 +453,12 @@ function! ale#lsp#Send(conn_id, message) abort return 0 endif - " If we haven't initialized the server yet, then send the message for it. - if !l:conn.initialized && !l:conn.init_request_id - let [l:init_id, l:init_data] = ale#lsp#CreateMessageData( - \ ale#lsp#message#Initialize(l:conn.root, l:conn.init_options), - \) - - let l:conn.init_request_id = l:init_id - - call s:SendMessageData(l:conn, l:init_data) + if !l:conn.initialized + throw 'LSP server not initialized yet!' endif let [l:id, l:data] = ale#lsp#CreateMessageData(a:message) - - if l:conn.initialized - " Send the message now. - call s:SendMessageData(l:conn, l:data) - else - " Add the message we wanted to send to a List to send later. - call add(l:conn.message_queue, l:data) - endif + call s:SendMessageData(l:conn, l:data) return l:id == 0 ? -1 : l:id endfunction @@ -491,26 +509,32 @@ function! ale#lsp#NotifyForChanges(conn_id, buffer) abort return l:notified endfunction -" Given some LSP details that must contain at least `connection_id` and -" `project_root` keys, -function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort +" Wait for an LSP server to be initialized. +function! ale#lsp#OnInit(conn_id, Callback) abort let l:conn = get(s:connections, a:conn_id, {}) if empty(l:conn) return endif + if l:conn.initialized + call a:Callback() + else + call add(l:conn.init_queue, a:Callback) + endif +endfunction + +" Check if an LSP has a given capability. +function! ale#lsp#HasCapability(conn_id, capability) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if empty(l:conn) + return 0 + endif + if type(get(l:conn.capabilities, a:capability, v:null)) isnot v:t_number throw 'Invalid capability ' . a:capability endif - if l:conn.initialized - if l:conn.capabilities[a:capability] - " The project has been initialized, so call the callback now. - call call(a:callback, [a:conn_id]) - endif - else - " Call the callback later, once we have the information we need. - call add(l:conn.capabilities_queue, [a:capability, a:callback]) - endif + return l:conn.capabilities[a:capability] endfunction diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim index 9fffb83a..4ad94c4b 100644 --- a/autoload/ale/lsp/message.vim +++ b/autoload/ale/lsp/message.vim @@ -3,6 +3,10 @@ " " Messages in this movie will be returned in the format " [is_notification, method_name, params?] +" +" All functions which accept line and column arguments expect them to be 1-based +" (the same format as being returned by getpos() and friends), those then +" will be converted to 0-based as specified by LSP. let g:ale_lsp_next_version_id = 1 " The LSP protocols demands that we send every change to a document, including @@ -37,7 +41,7 @@ function! ale#lsp#message#Initialize(root_path, initialization_options) abort endfunction function! ale#lsp#message#Initialized() abort - return [1, 'initialized'] + return [1, 'initialized', {}] endfunction function! ale#lsp#message#Shutdown() abort @@ -98,7 +102,7 @@ function! ale#lsp#message#Completion(buffer, line, column, trigger_character) ab \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), \ }, - \ 'position': {'line': a:line - 1, 'character': a:column}, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, \}] if !empty(a:trigger_character) @@ -116,7 +120,16 @@ function! ale#lsp#message#Definition(buffer, line, column) abort \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), \ }, - \ 'position': {'line': a:line - 1, 'character': a:column}, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \}] +endfunction + +function! ale#lsp#message#TypeDefinition(buffer, line, column) abort + return [0, 'textDocument/typeDefinition', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, \}] endfunction @@ -125,7 +138,7 @@ function! ale#lsp#message#References(buffer, line, column) abort \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), \ }, - \ 'position': {'line': a:line - 1, 'character': a:column}, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, \ 'context': {'includeDeclaration': v:false}, \}] endfunction @@ -141,12 +154,12 @@ function! ale#lsp#message#Hover(buffer, line, column) abort \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), \ }, - \ 'position': {'line': a:line - 1, 'character': a:column}, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, \}] endfunction function! ale#lsp#message#DidChangeConfiguration(buffer, config) abort - return [0, 'workspace/didChangeConfiguration', { + return [1, 'workspace/didChangeConfiguration', { \ 'settings': a:config, \}] endfunction diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim index 08b36808..9ce05260 100644 --- a/autoload/ale/lsp/response.vim +++ b/autoload/ale/lsp/response.vim @@ -28,12 +28,12 @@ function! ale#lsp#response#ReadDiagnostics(response) abort for l:diagnostic in a:response.params.diagnostics let l:severity = get(l:diagnostic, 'severity', 0) let l:loclist_item = { - \ 'text': l:diagnostic.message, + \ 'text': substitute(l:diagnostic.message, '\(\r\n\|\n\|\r\)', ' ', 'g'), \ 'type': 'E', \ 'lnum': l:diagnostic.range.start.line + 1, \ 'col': l:diagnostic.range.start.character + 1, \ 'end_lnum': l:diagnostic.range.end.line + 1, - \ 'end_col': l:diagnostic.range.end.character + 1, + \ 'end_col': l:diagnostic.range.end.character, \} if l:severity == s:SEVERITY_WARNING @@ -58,16 +58,20 @@ function! ale#lsp#response#ReadDiagnostics(response) abort if has_key(l:diagnostic, 'relatedInformation') let l:related = deepcopy(l:diagnostic.relatedInformation) call map(l:related, {key, val -> - \ ale#path#FromURI(val.location.uri) . - \ ':' . (val.location.range.start.line + 1) . - \ ':' . (val.location.range.start.character + 1) . - \ ":\n\t" . val.message - \ }) + \ ale#path#FromURI(val.location.uri) . + \ ':' . (val.location.range.start.line + 1) . + \ ':' . (val.location.range.start.character + 1) . + \ ":\n\t" . val.message + \}) let l:loclist_item.detail = l:diagnostic.message . "\n" . join(l:related, "\n") endif if has_key(l:diagnostic, 'source') - let l:loclist_item.detail = printf('[%s] %s', l:diagnostic.source, l:diagnostic.message) + let l:loclist_item.detail = printf( + \ '[%s] %s', + \ l:diagnostic.source, + \ l:diagnostic.message + \) endif call add(l:loclist, l:loclist_item) diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 42d67398..3a596d62 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -27,12 +27,13 @@ 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) + let l:info = get(g:ale_buffer_info, l:buffer, {}) - if s:ShouldIgnore(l:buffer, l:linter_name) + if empty(l:info) return endif - if l:buffer <= 0 + if s:ShouldIgnore(l:buffer, l:linter_name) return endif @@ -50,6 +51,8 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort return endif + call ale#engine#MarkLinterInactive(l:info, l:linter_name) + if s:ShouldIgnore(l:buffer, l:linter_name) return endif @@ -129,113 +132,242 @@ function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort endfunction function! ale#lsp_linter#GetOptions(buffer, linter) abort - 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 + return ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer) + endif + + if has_key(a:linter, 'initialization_options') + let l:Options = a:linter.initialization_options + + if type(l:Options) is v:t_func + let l:Options = l:Options(a:buffer) + endif + + return l:Options endif - return l:initialization_options + return {} endfunction function! ale#lsp_linter#GetConfig(buffer, linter) abort - let l:config = {} - if has_key(a:linter, 'lsp_config_callback') - let l:config = ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer) - elseif has_key(a:linter, 'lsp_config') - let l:config = a:linter.lsp_config + return ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer) + endif + + if has_key(a:linter, 'lsp_config') + let l:Config = a:linter.lsp_config + + if type(l:Config) is v:t_func + let l:Config = l:Config(a:buffer) + endif + + return l:Config endif - return l:config + return {} endfunction -" Given a buffer, an LSP linter, start up an LSP linter and get ready to -" receive messages for the document. -function! ale#lsp_linter#StartLSP(buffer, linter) abort - let l:command = '' - let l:address = '' - let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) +function! ale#lsp_linter#FindProjectRoot(buffer, linter) abort + let l:buffer_ale_root = getbufvar(a:buffer, 'ale_lsp_root', {}) - 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 {} + if type(l:buffer_ale_root) is v:t_string + return l:buffer_ale_root endif - let l:init_options = ale#lsp_linter#GetOptions(a:buffer, a:linter) + " Try to get a buffer-local setting for the root + if has_key(l:buffer_ale_root, a:linter.name) + let l:Root = l:buffer_ale_root[a:linter.name] - if a:linter.lsp is# 'socket' - let l:address = ale#linter#GetAddress(a:buffer, a:linter) - let l:conn_id = ale#lsp#Register(l:address, l:root, l:init_options) - let l:ready = ale#lsp#ConnectToAddress(l:conn_id, l:address) - else - let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) + if type(l:Root) is v:t_func + return l:Root(a:buffer) + else + return l:Root + endif + endif + + " Try to get a global setting for the root + if has_key(g:ale_lsp_root, a:linter.name) + let l:Root = g:ale_lsp_root[a:linter.name] - if empty(l:executable) || !executable(l:executable) - return {} + if type(l:Root) is v:t_func + return l:Root(a:buffer) + else + return l:Root endif + endif - let l:conn_id = ale#lsp#Register(l:executable, l:root, l:init_options) + " Fall back to the linter-specific configuration + if has_key(a:linter, 'project_root') + let l:Root = a:linter.project_root - let l:command = ale#linter#GetCommand(a:buffer, a:linter) - " Format the command, so %e can be formatted into it. - let l:command = ale#command#FormatCommand(a:buffer, l:executable, l:command, 0)[1] - let l:command = ale#job#PrepareCommand(a:buffer, l:command) - let l:ready = ale#lsp#StartProgram(l:conn_id, l:executable, l:command) + return type(l:Root) is v:t_func ? l:Root(a:buffer) : l:Root endif - if !l:ready + return ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) +endfunction + +" This function is accessible so tests can call it. +function! ale#lsp_linter#OnInit(linter, details, Callback) abort + let l:buffer = a:details.buffer + let l:conn_id = a:details.connection_id + let l:command = a:details.command + + let l:config = ale#lsp_linter#GetConfig(l:buffer, a:linter) + let l:language_id = ale#util#GetFunction(a:linter.language_callback)(l:buffer) + + call ale#lsp#UpdateConfig(l:conn_id, l:buffer, l:config) + + if ale#lsp#OpenDocument(l:conn_id, l:buffer, l:language_id) if g:ale_history_enabled && !empty(l:command) - call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command) + call ale#history#Add(l:buffer, 'started', l:conn_id, l:command) endif - - return {} endif - " tsserver behaves differently, so tell the LSP API that it is tsserver. + " The change message needs to be sent for tsserver before doing anything. if a:linter.lsp is# 'tsserver' - call ale#lsp#MarkConnectionAsTsserver(l:conn_id) + call ale#lsp#NotifyForChanges(l:conn_id, l:buffer) + endif + + call a:Callback(a:linter, a:details) +endfunction + +function! s:StartLSP(options, address, executable, command) abort + let l:buffer = a:options.buffer + let l:linter = a:options.linter + let l:root = a:options.root + let l:Callback = a:options.callback + + let l:init_options = ale#lsp_linter#GetOptions(l:buffer, l:linter) + + if l:linter.lsp is# 'socket' + let l:conn_id = ale#lsp#Register(a:address, l:root, l:init_options) + let l:ready = ale#lsp#ConnectToAddress(l:conn_id, a:address) + let l:command = '' + else + let l:conn_id = ale#lsp#Register(a:executable, l:root, l:init_options) + + " tsserver behaves differently, so tell the LSP API that it is tsserver. + if l:linter.lsp is# 'tsserver' + call ale#lsp#MarkConnectionAsTsserver(l:conn_id) + endif + + let l:command = ale#command#FormatCommand(l:buffer, a:executable, a:command, 0, v:false)[1] + let l:command = ale#job#PrepareCommand(l:buffer, l:command) + let l:ready = ale#lsp#StartProgram(l:conn_id, a:executable, l:command) endif - let l:config = ale#lsp_linter#GetConfig(a:buffer, a:linter) - let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) + if !l:ready + if g:ale_history_enabled && !empty(a:command) + call ale#history#Add(l:buffer, 'failed', l:conn_id, a:command) + endif + + return 0 + endif let l:details = { - \ 'buffer': a:buffer, + \ 'buffer': l:buffer, \ 'connection_id': l:conn_id, \ 'command': l:command, \ 'project_root': l:root, - \ 'language_id': l:language_id, \} - call ale#lsp#UpdateConfig(l:conn_id, a:buffer, l:config) + call ale#lsp#OnInit(l:conn_id, {-> + \ ale#lsp_linter#OnInit(l:linter, l:details, l:Callback) + \}) - if ale#lsp#OpenDocument(l:conn_id, a:buffer, l:language_id) - if g:ale_history_enabled && !empty(l:command) - call ale#history#Add(a:buffer, 'started', l:conn_id, l:command) - endif + return 1 +endfunction + +function! s:StartWithAddress(options, address) abort + if ale#command#IsDeferred(a:address) + let a:address.result_callback = { + \ address -> s:StartWithAddress(a:options, address) + \} + + return 1 endif - " The change message needs to be sent for tsserver before doing anything. - if a:linter.lsp is# 'tsserver' - call ale#lsp#NotifyForChanges(l:conn_id, a:buffer) + if empty(a:address) + return 0 endif - return l:details + return s:StartLSP(a:options, a:address, '', '') 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! s:StartWithCommand(options, executable, command) abort + if ale#command#IsDeferred(a:command) + let a:command.result_callback = { + \ command -> s:StartWithCommand(a:options, a:executable, command) + \} + + return 1 + endif + + if empty(a:command) + return 0 + endif + + return s:StartLSP(a:options, '', a:executable, a:command) +endfunction + +function! s:StartIfExecutable(options, executable) abort + if ale#command#IsDeferred(a:executable) + let a:executable.result_callback = { + \ executable -> s:StartIfExecutable(a:options, executable) + \} + + return 1 + endif - if empty(l:lsp_details) + if !ale#engine#IsExecutable(a:options.buffer, a:executable) return 0 endif - let l:id = l:lsp_details.connection_id + let l:command = ale#linter#GetCommand(a:options.buffer, a:options.linter) + + return s:StartWithCommand(a:options, a:executable, l:command) +endfunction + +" Given a buffer, an LSP linter, start up an LSP linter and get ready to +" receive messages for the document. +function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let l:command = '' + let l:address = '' + let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, a:linter) + + 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 0 + endif + + let l:options = { + \ 'buffer': a:buffer, + \ 'linter': a:linter, + \ 'callback': a:Callback, + \ 'root': l:root, + \} + + if a:linter.lsp is# 'socket' + let l:address = ale#linter#GetAddress(a:buffer, a:linter) + + return s:StartWithAddress(l:options, l:address) + endif + + let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) + + return s:StartIfExecutable(l:options, l:executable) +endfunction + +function! s:CheckWithLSP(linter, details) abort + let l:buffer = a:details.buffer + let l:info = get(g:ale_buffer_info, l:buffer) + + if empty(l:info) + return + endif + + let l:id = a:details.connection_id " Register a callback now for handling errors now. let l:Callback = function('ale#lsp_linter#HandleLSPResponse') @@ -245,26 +377,26 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort 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:message = ale#lsp#tsserver_message#Geterr(l:buffer) let l:notified = ale#lsp#Send(l:id, l:message) != 0 + + if l:notified + call ale#engine#MarkLinterActive(l:info, a:linter) + endif else - let l:notified = ale#lsp#NotifyForChanges(l:id, a:buffer) + let l:notified = ale#lsp#NotifyForChanges(l:id, l:buffer) 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) + \&& getbufvar(l:buffer, 'ale_save_event_fired', 0) + let l:save_message = ale#lsp#message#DidSave(l:buffer) let l:notified = ale#lsp#Send(l:id, l:save_message) != 0 endif +endfunction - 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 +function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort + return ale#lsp_linter#StartLSP(a:buffer, a:linter, function('s:CheckWithLSP')) endfunction " Clear LSP linter data for the linting engine. diff --git a/autoload/ale/node.vim b/autoload/ale/node.vim index 5c579c75..69060122 100644 --- a/autoload/ale/node.vim +++ b/autoload/ale/node.vim @@ -23,11 +23,6 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort return ale#Var(a:buffer, a:base_var_name . '_executable') endfunction -" As above, but curry the arguments so only the buffer number is required. -function! ale#node#FindExecutableFunc(base_var_name, path_list) abort - return {buf -> ale#node#FindExecutable(buf, a:base_var_name, a:path_list)} -endfunction - " Create a executable string which executes a Node.js script command with a " Node.js executable if needed. " @@ -37,7 +32,7 @@ endfunction " " The executable is only prefixed for Windows machines function! ale#node#Executable(buffer, executable) abort - if ale#Has('win32') && a:executable =~? '\.js$' + if has('win32') && a:executable =~? '\.js$' let l:node = ale#Var(a:buffer, 'windows_node_executable_path') return ale#Escape(l:node) . ' ' . ale#Escape(a:executable) diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim index 89b119f4..60d42eb5 100644 --- a/autoload/ale/path.vim +++ b/autoload/ale/path.vim @@ -197,14 +197,20 @@ function! ale#path#ToURI(path) abort endfunction function! ale#path#FromURI(uri) abort - let l:i = len('file://') - let l:encoded_path = a:uri[: l:i - 1] is# 'file://' ? a:uri[l:i :] : a:uri + if a:uri[:6] is? 'file://' + let l:encoded_path = a:uri[7:] + elseif a:uri[:4] is? 'file:' + let l:encoded_path = a:uri[5:] + else + let l:encoded_path = a:uri + endif let l:path = ale#uri#Decode(l:encoded_path) " If the path is like /C:/foo/bar, it should be C:\foo\bar instead. - if l:path =~# '^/[a-zA-Z]:' + if has('win32') && l:path =~# '^/[a-zA-Z][:|]' let l:path = substitute(l:path[1:], '/', '\\', 'g') + let l:path = l:path[0] . ':' . l:path[2:] endif return l:path diff --git a/autoload/ale/powershell.vim b/autoload/ale/powershell.vim new file mode 100644 index 00000000..8c163206 --- /dev/null +++ b/autoload/ale/powershell.vim @@ -0,0 +1,32 @@ +" Author: zigford <zigford@gmail.com> +" Description: Functions for integrating with Powershell linters. + +" Write a powershell script to a temp file for execution +" return the command used to execute it +function! s:TemporaryPSScript(buffer, input) abort + let l:filename = 'script.ps1' + " Create a temp dir to house our temp .ps1 script + " a temp dir is needed as powershell needs the .ps1 + " extension + let l:tempdir = ale#util#Tempname() . (has('win32') ? '\' : '/') + let l:tempscript = l:tempdir . l:filename + " Create the temporary directory for the file, unreadable by 'other' + " users. + call mkdir(l:tempdir, '', 0750) + " Automatically delete the directory later. + call ale#command#ManageDirectory(a:buffer, l:tempdir) + " Write the script input out to a file. + call ale#util#Writefile(a:buffer, a:input, l:tempscript) + + return l:tempscript +endfunction + +function! ale#powershell#RunPowerShell(buffer, base_var_name, command) abort + let l:executable = ale#Var(a:buffer, a:base_var_name . '_executable') + let l:tempscript = s:TemporaryPSScript(a:buffer, a:command) + + return ale#Escape(l:executable) + \ . ' -Exe Bypass -NoProfile -File ' + \ . ale#Escape(l:tempscript) + \ . ' %t' +endfunction diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim index 1f50e0ad..6d58aca9 100644 --- a/autoload/ale/preview.vim +++ b/autoload/ale/preview.vim @@ -41,16 +41,24 @@ 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 +function! ale#preview#ShowSelection(item_list, ...) abort + let l:options = get(a:000, 0, {}) + let l:sep = has('win32') ? '\' : '/' let l:lines = [] " Create lines to display to users. for l:item in a:item_list let l:match = get(l:item, 'match', '') + let l:filename = l:item.filename + + if get(l:options, 'use_relative_paths') + let l:cwd = getcwd() " no-custom-checks + let l:filename = substitute(l:filename, '^' . l:cwd . l:sep, '', '') + endif call add( \ l:lines, - \ l:item.filename + \ l:filename \ . ':' . l:item.line \ . ':' . l:item.column \ . (!empty(l:match) ? ' ' . l:match : ''), @@ -63,7 +71,7 @@ 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, {}) + let l:item = get(l:item_list, getpos('.')[1] - 1, {}) if empty(l:item) return diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim index 8d6bf1f0..2f28214b 100644 --- a/autoload/ale/python.vim +++ b/autoload/ale/python.vim @@ -27,6 +27,9 @@ function! ale#python#FindProjectRootIni(buffer) abort \|| filereadable(l:path . '/pycodestyle.cfg') \|| filereadable(l:path . '/flake8.cfg') \|| filereadable(l:path . '/.flake8rc') + \|| filereadable(l:path . '/pylama.ini') + \|| filereadable(l:path . '/pylintrc') + \|| filereadable(l:path . '/.pylintrc') \|| filereadable(l:path . '/Pipfile') \|| filereadable(l:path . '/Pipfile.lock') return l:path @@ -48,7 +51,7 @@ function! ale#python#FindProjectRoot(buffer) abort let l:ini_root = ale#python#FindProjectRootIni(a:buffer) if !empty(l:ini_root) - return l:ini_root + return l:ini_root endif for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) @@ -110,6 +113,44 @@ function! ale#python#FindExecutable(buffer, base_var_name, path_list) abort return ale#Var(a:buffer, a:base_var_name . '_executable') endfunction +" Handle traceback.print_exception() output starting in the first a:limit lines. +function! ale#python#HandleTraceback(lines, limit) abort + let l:nlines = len(a:lines) + let l:limit = a:limit > l:nlines ? l:nlines : a:limit + let l:start = 0 + + while l:start < l:limit + if a:lines[l:start] is# 'Traceback (most recent call last):' + break + endif + + let l:start += 1 + endwhile + + if l:start >= l:limit + return [] + endif + + let l:end = l:start + 1 + + " Traceback entries are always prefixed with 2 spaces. + " SyntaxError marker (if present) is prefixed with at least 4 spaces. + " Final exc line starts with exception class name (never a space). + while l:end < l:nlines && a:lines[l:end][0] is# ' ' + let l:end += 1 + endwhile + + let l:exc_line = l:end < l:nlines + \ ? a:lines[l:end] + \ : 'An exception was thrown.' + + return [{ + \ 'lnum': 1, + \ 'text': l:exc_line . ' (See :ALEDetail)', + \ 'detail': join(a:lines[(l:start):(l:end)], "\n"), + \}] +endfunction + " Detects whether a pipenv environment is present. function! ale#python#PipenvPresent(buffer) abort return findfile('Pipfile.lock', expand('#' . a:buffer . ':p:h') . ';') isnot# '' diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim index d00a1fa9..b9725e1e 100644 --- a/autoload/ale/references.vim +++ b/autoload/ale/references.vim @@ -17,7 +17,7 @@ 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) + let l:options = remove(s:references_map, a:response.request_seq) if get(a:response, 'success', v:false) is v:true let l:item_list = [] @@ -27,13 +27,14 @@ function! ale#references#HandleTSServerResponse(conn_id, response) abort \ 'filename': l:response_item.file, \ 'line': l:response_item.start.line, \ 'column': l:response_item.start.offset, + \ 'match': substitute(l:response_item.lineText, '^\s*\(.\{-}\)\s*$', '\1', ''), \}) endfor if empty(l:item_list) call ale#util#Execute('echom ''No references found.''') else - call ale#preview#ShowSelection(l:item_list) + call ale#preview#ShowSelection(l:item_list, l:options) endif endif endif @@ -42,32 +43,39 @@ 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) + let l:options = 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 type(l:result) is v:t_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 + endif if empty(l:item_list) call ale#util#Execute('echom ''No references found.''') else - call ale#preview#ShowSelection(l:item_list) + call ale#preview#ShowSelection(l:item_list, l:options) endif endif endfunction -function! s:OnReady(linter, lsp_details, line, column, ...) abort - let l:buffer = a:lsp_details.buffer +function! s:OnReady(line, column, options, linter, lsp_details) abort let l:id = a:lsp_details.connection_id + if !ale#lsp#HasCapability(l:id, 'references') + return + endif + + let l:buffer = a:lsp_details.buffer + let l:Callback = a:linter.lsp is# 'tsserver' \ ? function('ale#references#HandleTSServerResponse') \ : function('ale#references#HandleLSPResponse') @@ -90,34 +98,30 @@ function! s:OnReady(linter, lsp_details, line, column, ...) abort let l:request_id = ale#lsp#Send(l:id, l:message) - let s:references_map[l:request_id] = {} + let s:references_map[l:request_id] = { + \ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0 + \} endfunction -function! s:FindReferences(linter) abort - let l:buffer = bufnr('') - let [l:line, l:column] = getcurpos()[1:2] - - if a:linter.lsp isnot# 'tsserver' - let l:column = min([l:column, len(getline(l:line))]) - endif +function! ale#references#Find(...) abort + let l:options = {} - let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter) - - if empty(l:lsp_details) - return 0 + if len(a:000) > 0 + for l:option in a:000 + if l:option is? '-relative' + let l:options.use_relative_paths = 1 + endif + endfor endif - let l:id = l:lsp_details.connection_id - - call ale#lsp#WaitForCapability(l:id, 'references', function('s:OnReady', [ - \ a:linter, l:lsp_details, l:line, l:column - \])) -endfunction + let l:buffer = bufnr('') + let [l:line, l:column] = getpos('.')[1:2] + let l:column = min([l:column, len(getline(l:line))]) + let l:Callback = function('s:OnReady', [l:line, l:column, l:options]) -function! ale#references#Find() abort for l:linter in ale#linter#Get(&filetype) if !empty(l:linter.lsp) - call s:FindReferences(l:linter) + call ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback) endif endfor endfunction diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim index 5f0aa50d..15e835c9 100644 --- a/autoload/ale/ruby.vim +++ b/autoload/ale/ruby.vim @@ -1,7 +1,7 @@ " Author: Eddie Lebow https://github.com/elebow " Description: Functions for integrating with Ruby tools -" Find the nearest dir contining "app", "db", and "config", and assume it is +" Find the nearest dir containing "app", "db", and "config", and assume it is " the root of a Rails app. function! ale#ruby#FindRailsRoot(buffer) abort for l:name in ['app', 'config', 'db'] @@ -26,7 +26,7 @@ function! ale#ruby#FindProjectRoot(buffer) abort let l:dir = ale#ruby#FindRailsRoot(a:buffer) if isdirectory(l:dir) - return l:dir + return l:dir endif for l:name in ['.solargraph.yml', 'Rakefile', 'Gemfile'] @@ -42,3 +42,35 @@ function! ale#ruby#FindProjectRoot(buffer) abort return '' endfunction + +" Handle output from rubocop and linters that depend on it (e.b. standardrb) +function! ale#ruby#HandleRubocopOutput(buffer, lines) abort + try + let l:errors = json_decode(a:lines[0]) + catch + return [] + endtry + + if !has_key(l:errors, 'summary') + \|| l:errors['summary']['offense_count'] == 0 + \|| empty(l:errors['files']) + return [] + endif + + let l:output = [] + + for l:error in l:errors['files'][0]['offenses'] + let l:start_col = l:error['location']['column'] + 0 + call add(l:output, { + \ 'lnum': l:error['location']['line'] + 0, + \ 'col': l:start_col, + \ 'end_col': l:start_col + l:error['location']['length'] - 1, + \ 'code': l:error['cop_name'], + \ 'text': l:error['message'], + \ 'type': ale_linters#ruby#rubocop#GetType(l:error['severity']), + \}) + endfor + + return l:output +endfunction + diff --git a/autoload/ale/semver.vim b/autoload/ale/semver.vim index 6b0fd34a..e3eb49c0 100644 --- a/autoload/ale/semver.vim +++ b/autoload/ale/semver.vim @@ -5,31 +5,52 @@ function! ale#semver#ResetVersionCache() abort let s:version_cache = {} endfunction +function! ale#semver#ParseVersion(version_lines) abort + for l:line in a:version_lines + let l:match = matchlist(l:line, '\v(\d+)\.(\d+)(\.(\d+))?') + + if !empty(l:match) + return [l:match[1] + 0, l:match[2] + 0, l:match[4] + 0] + endif + endfor + + return [] +endfunction + " Given an executable name and some lines of output, which can be empty, " parse the version from the lines of output, or return the cached version " triple [major, minor, patch] " " If the version cannot be found, an empty List will be returned instead. -function! ale#semver#GetVersion(executable, version_lines) abort +function! s:GetVersion(executable, version_lines) abort let l:version = get(s:version_cache, a:executable, []) + let l:parsed_version = ale#semver#ParseVersion(a:version_lines) - for l:line in a:version_lines - let l:match = matchlist(l:line, '\v(\d+)\.(\d+)\.(\d+)') - - if !empty(l:match) - let l:version = [l:match[1] + 0, l:match[2] + 0, l:match[3] + 0] - let s:version_cache[a:executable] = l:version - - break - endif - endfor + if !empty(l:parsed_version) + let l:version = l:parsed_version + let s:version_cache[a:executable] = l:version + endif return l:version endfunction -" Return 1 if the semver version has been cached for a given executable. -function! ale#semver#HasVersion(executable) abort - return has_key(s:version_cache, a:executable) +function! ale#semver#RunWithVersionCheck(buffer, executable, command, Callback) abort + if empty(a:executable) + return '' + endif + + let l:cache = s:version_cache + + if has_key(s:version_cache, a:executable) + return a:Callback(a:buffer, s:version_cache[a:executable]) + endif + + return ale#command#Run( + \ a:buffer, + \ a:command, + \ {_, output -> a:Callback(a:buffer, s:GetVersion(a:executable, output))}, + \ {'output_stream': 'both', 'executable': a:executable} + \) endfunction " Given two triples of integers [major, minor, patch], compare the triples diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim index af863682..7395b0e2 100644 --- a/autoload/ale/sign.vim +++ b/autoload/ale/sign.vim @@ -64,16 +64,21 @@ if !hlexists('ALESignColumnWithoutErrors') call ale#sign#SetUpDefaultColumnWithoutErrorsHighlight() endif +" Spaces and backslashes need to be escaped for signs. +function! s:EscapeSignText(sign_text) abort + return substitute(substitute(a:sign_text, ' *$', '', ''), '\\\| ', '\\\0', 'g') +endfunction + " Signs show up on the left for error markers. -execute 'sign define ALEErrorSign text=' . g:ale_sign_error +execute 'sign define ALEErrorSign text=' . s:EscapeSignText(g:ale_sign_error) \ . ' texthl=ALEErrorSign linehl=ALEErrorLine' -execute 'sign define ALEStyleErrorSign text=' . g:ale_sign_style_error +execute 'sign define ALEStyleErrorSign text=' . s:EscapeSignText(g:ale_sign_style_error) \ . ' texthl=ALEStyleErrorSign linehl=ALEErrorLine' -execute 'sign define ALEWarningSign text=' . g:ale_sign_warning +execute 'sign define ALEWarningSign text=' . s:EscapeSignText(g:ale_sign_warning) \ . ' texthl=ALEWarningSign linehl=ALEWarningLine' -execute 'sign define ALEStyleWarningSign text=' . g:ale_sign_style_warning +execute 'sign define ALEStyleWarningSign text=' . s:EscapeSignText(g:ale_sign_style_warning) \ . ' texthl=ALEStyleWarningSign linehl=ALEWarningLine' -execute 'sign define ALEInfoSign text=' . g:ale_sign_info +execute 'sign define ALEInfoSign text=' . s:EscapeSignText(g:ale_sign_info) \ . ' texthl=ALEInfoSign linehl=ALEInfoLine' sign define ALEDummySign @@ -116,7 +121,7 @@ endfunction " Read sign data for a buffer to a list of lines. function! ale#sign#ReadSigns(buffer) abort redir => l:output - silent execute 'sign place buffer=' . a:buffer + silent execute 'sign place buffer=' . a:buffer redir end return split(l:output, "\n") diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim index 94fbd6c9..6b93ba51 100644 --- a/autoload/ale/statusline.vim +++ b/autoload/ale/statusline.vim @@ -1,4 +1,5 @@ " Author: KabbAmine <amine.kabb@gmail.com> +" Additions by: petpetpetpet <chris@freelanceninjas.com> " Description: Statusline related function(s) function! s:CreateCountDict() abort @@ -26,19 +27,42 @@ function! ale#statusline#Update(buffer, loclist) abort let l:count = s:CreateCountDict() let l:count.total = len(l:loclist) + " Allows easy access to the first instance of each problem type. + let l:first_problems = {} + for l:entry in l:loclist if l:entry.type is# 'W' if get(l:entry, 'sub_type', '') is# 'style' let l:count.style_warning += 1 + + if l:count.style_warning == 1 + let l:first_problems.style_warning = l:entry + endif else let l:count.warning += 1 + + if l:count.warning == 1 + let l:first_problems.warning = l:entry + endif endif elseif l:entry.type is# 'I' let l:count.info += 1 + + if l:count.info == 1 + let l:first_problems.info = l:entry + endif elseif get(l:entry, 'sub_type', '') is# 'style' let l:count.style_error += 1 + + if l:count.style_error == 1 + let l:first_problems.style_error = l:entry + endif else let l:count.error += 1 + + if l:count.error == 1 + let l:first_problems.error = l:entry + endif endif endfor @@ -47,24 +71,65 @@ function! ale#statusline#Update(buffer, loclist) abort let l:count[1] = l:count.total - l:count[0] let g:ale_buffer_info[a:buffer].count = l:count + let g:ale_buffer_info[a:buffer].first_problems = l:first_problems endfunction " Get the counts for the buffer, and update the counts if needed. -function! s:GetCounts(buffer) abort +function! s:UpdateCacheIfNecessary(buffer) abort + " Cache is cold, so manually ask for an update. + if !has_key(g:ale_buffer_info[a:buffer], 'count') + call ale#statusline#Update( + \ a:buffer, + \ g:ale_buffer_info[a:buffer].loclist + \) + endif +endfunction + +function! s:BufferCacheExists(buffer) abort if !exists('g:ale_buffer_info') || !has_key(g:ale_buffer_info, a:buffer) - return s:CreateCountDict() + return 0 endif - " Cache is cold, so manually ask for an update. - if !has_key(g:ale_buffer_info[a:buffer], 'count') - call ale#statusline#Update(a:buffer, g:ale_buffer_info[a:buffer].loclist) + return 1 +endfunction + +" Get the counts for the buffer, and update the counts if needed. +function! s:GetCounts(buffer) abort + if !s:BufferCacheExists(a:buffer) + return s:CreateCountDict() endif + call s:UpdateCacheIfNecessary(a:buffer) + return g:ale_buffer_info[a:buffer].count endfunction +" Get the dict of first_problems, update the buffer info cache if necessary. +function! s:GetFirstProblems(buffer) abort + if !s:BufferCacheExists(a:buffer) + return {} + endif + + call s:UpdateCacheIfNecessary(a:buffer) + + return g:ale_buffer_info[a:buffer].first_problems +endfunction + " Returns a Dictionary with counts for use in third party integrations. function! ale#statusline#Count(buffer) abort " The Dictionary is copied here before exposing it to other plugins. return copy(s:GetCounts(a:buffer)) endfunction + +" Returns a copy of the *first* locline instance of the specified problem +" type. (so this would allow an external integration to know all the info +" about the first style warning in the file, for example.) +function! ale#statusline#FirstProblem(buffer, type) abort + let l:first_problems = s:GetFirstProblems(a:buffer) + + if !empty(l:first_problems) && has_key(l:first_problems, a:type) + return copy(l:first_problems[a:type]) + endif + + return {} +endfunction diff --git a/autoload/ale/swift.vim b/autoload/ale/swift.vim new file mode 100644 index 00000000..b31b8dc5 --- /dev/null +++ b/autoload/ale/swift.vim @@ -0,0 +1,13 @@ +" Author: Dan Loman <https://github.com/namolnad> +" Description: Functions for integrating with Swift tools + +" Find the nearest dir containing a Package.swift file and assume it is the root of the Swift project. +function! ale#swift#FindProjectRoot(buffer) abort + let l:swift_config = ale#path#FindNearestFile(a:buffer, 'Package.swift') + + if !empty(l:swift_config) + return fnamemodify(l:swift_config, ':h') + endif + + return '' +endfunction diff --git a/autoload/ale/symbol.vim b/autoload/ale/symbol.vim index 5180cb86..ae4151ab 100644 --- a/autoload/ale/symbol.vim +++ b/autoload/ale/symbol.vim @@ -52,12 +52,18 @@ function! ale#symbol#HandleLSPResponse(conn_id, response) abort if empty(l:item_list) call ale#util#Execute('echom ''No symbols found.''') else - call ale#preview#ShowSelection(l:item_list) + call ale#preview#ShowSelection(l:item_list, l:options) endif endif endfunction -function! s:OnReady(linter, lsp_details, query, ...) abort +function! s:OnReady(query, options, linter, lsp_details) abort + let l:id = a:lsp_details.connection_id + + if !ale#lsp#HasCapability(l:id, 'symbol_search') + return + endif + let l:buffer = a:lsp_details.buffer " If we already made a request, stop here. @@ -65,8 +71,6 @@ function! s:OnReady(linter, lsp_details, query, ...) abort return endif - let l:id = a:lsp_details.connection_id - let l:Callback = function('ale#symbol#HandleLSPResponse') call ale#lsp#RegisterCallback(l:id, l:Callback) @@ -76,34 +80,31 @@ function! s:OnReady(linter, lsp_details, query, ...) abort call setbufvar(l:buffer, 'ale_symbol_request_made', 1) let s:symbol_map[l:request_id] = { \ 'buffer': l:buffer, + \ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0 \} endfunction -function! s:Search(linter, buffer, query) abort - let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter) - - if !empty(l:lsp_details) - call ale#lsp#WaitForCapability( - \ l:lsp_details.connection_id, - \ 'symbol_search', - \ function('s:OnReady', [a:linter, l:lsp_details, a:query]), - \) - endif -endfunction +function! ale#symbol#Search(args) abort + let [l:opts, l:query] = ale#args#Parse(['relative'], a:args) -function! ale#symbol#Search(query) abort - if type(a:query) isnot v:t_string || empty(a:query) + if empty(l:query) throw 'A non-empty string must be provided!' endif let l:buffer = bufnr('') + let l:options = {} + + if has_key(l:opts, 'relative') + let l:options.use_relative_paths = 1 + endif " Set a flag so we only make one request. call setbufvar(l:buffer, 'ale_symbol_request_made', 0) + let l:Callback = function('s:OnReady', [l:query, l:options]) for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype')) if !empty(l:linter.lsp) && l:linter.lsp isnot# 'tsserver' - call s:Search(l:linter, l:buffer, a:query) + call ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback) endif endfor endfunction diff --git a/autoload/ale/test.vim b/autoload/ale/test.vim index 083b546f..082d91ff 100644 --- a/autoload/ale/test.vim +++ b/autoload/ale/test.vim @@ -55,9 +55,9 @@ endfunction function! s:RemoveModule(results) abort for l:item in a:results - if has_key(l:item, 'module') - call remove(l:item, 'module') - endif + if has_key(l:item, 'module') + call remove(l:item, 'module') + endif endfor endfunction @@ -75,3 +75,113 @@ function! ale#test#GetQflistWithoutModule() abort return l:results endfunction + +function! ale#test#GetPreviewWindowText() abort + for l:window in range(1, winnr('$')) + if getwinvar(l:window, '&previewwindow', 0) + let l:buffer = winbufnr(l:window) + + return getbufline(l:buffer, 1, '$') + endif + endfor +endfunction + +" This function can be called with a timeout to wait for all jobs to finish. +" If the jobs to not finish in the given number of milliseconds, +" an exception will be thrown. +" +" The time taken will be a very rough approximation, and more time may be +" permitted than is specified. +function! ale#test#WaitForJobs(deadline) abort + let l:start_time = ale#events#ClockMilliseconds() + + if l:start_time == 0 + throw 'Failed to read milliseconds from the clock!' + endif + + let l:job_list = [] + + " Gather all of the jobs from every buffer. + for [l:buffer, l:data] in items(ale#command#GetData()) + call extend(l:job_list, map(keys(l:data.jobs), 'str2nr(v:val)')) + endfor + + " NeoVim has a built-in API for this, so use that. + if has('nvim') + let l:nvim_code_list = jobwait(l:job_list, a:deadline) + + if index(l:nvim_code_list, -1) >= 0 + throw 'Jobs did not complete on time!' + endif + + return + endif + + let l:should_wait_more = 1 + + while l:should_wait_more + let l:should_wait_more = 0 + + for l:job_id in l:job_list + if ale#job#IsRunning(l:job_id) + 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. + throw 'Jobs did not complete on time!' + endif + + " Wait another 10 milliseconds + let l:should_wait_more = 1 + sleep 10ms + break + endif + endfor + endwhile + + " Sleep for a small amount of time after all jobs finish. + " This seems to be enough to let handlers after jobs end run, and + " prevents the occasional failure where this function exits after jobs + " end, but before handlers are run. + sleep 10ms + + " We must check the buffer data again to see if new jobs started + " for command_chain linters. + let l:has_new_jobs = 0 + + " Check again to see if any jobs are running. + for l:info in values(g:ale_buffer_info) + for [l:job_id, l:linter] in get(l:info, 'job_list', []) + if ale#job#IsRunning(l:job_id) + let l:has_new_jobs = 1 + break + endif + endfor + endfor + + if l:has_new_jobs + " We have to wait more. Offset the timeout by the time taken so far. + let l:now = ale#events#ClockMilliseconds() + let l:new_deadline = a:deadline - (l:now - l:start_time) + + if l:new_deadline <= 0 + " Enough time passed already, so stop immediately. + throw 'Jobs did not complete on time!' + endif + + call ale#test#WaitForJobs(l:new_deadline) + endif +endfunction + +function! ale#test#FlushJobs() abort + " The variable is checked for in a loop, as calling one series of + " callbacks can trigger a further series of callbacks. + while exists('g:ale_run_synchronously_callbacks') + let l:callbacks = g:ale_run_synchronously_callbacks + unlet g:ale_run_synchronously_callbacks + + for l:Callback in l:callbacks + call l:Callback() + endfor + endwhile +endfunction diff --git a/autoload/ale/toggle.vim b/autoload/ale/toggle.vim index 8e642b3f..1311e527 100644 --- a/autoload/ale/toggle.vim +++ b/autoload/ale/toggle.vim @@ -13,6 +13,10 @@ function! s:DisablePostamble() abort if g:ale_set_highlights call ale#highlight#UpdateHighlights() endif + + if g:ale_virtualtext_cursor + call ale#virtualtext#Clear() + endif endfunction function! ale#toggle#Toggle() abort diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index bb478957..e7563608 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -87,17 +87,31 @@ function! ale#util#GetFunction(string_or_ref) abort return a:string_or_ref endfunction +" Open the file (at the given line). +" options['open_in'] can be: +" current-buffer (default) +" tab +" vertical-split +" horizontal-split function! ale#util#Open(filename, line, column, options) abort - if get(a:options, 'open_in_tab', 0) - call ale#util#Execute('tabedit +' . a:line . ' ' . fnameescape(a:filename)) + let l:open_in = get(a:options, 'open_in', 'current-buffer') + let l:args_to_open = '+' . a:line . ' ' . fnameescape(a:filename) + + if l:open_in is# 'tab' + call ale#util#Execute('tabedit ' . l:args_to_open) + elseif l:open_in is# 'horizontal-split' + call ale#util#Execute('split ' . l:args_to_open) + elseif l:open_in is# 'vertical-split' + call ale#util#Execute('vsplit ' . l:args_to_open) elseif bufnr(a:filename) isnot bufnr('') " Open another file only if we need to. - call ale#util#Execute('edit +' . a:line . ' ' . fnameescape(a:filename)) + call ale#util#Execute('edit ' . l:args_to_open) else normal! m` endif call cursor(a:line, a:column) + normal! zz endfunction let g:ale#util#error_priority = 5 @@ -408,7 +422,7 @@ function! ale#util#Writefile(buffer, lines, filename) abort \ ? map(copy(a:lines), 'substitute(v:val, ''\r*$'', ''\r'', '''')') \ : a:lines - call writefile(l:corrected_lines, a:filename) " no-custom-checks + call writefile(l:corrected_lines, a:filename, 'S') " no-custom-checks endfunction if !exists('s:patial_timers') @@ -456,7 +470,7 @@ endfunction function! ale#util#FindItemAtCursor(buffer) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) let l:loclist = get(l:info, 'loclist', []) - let l:pos = getcurpos() + let l:pos = getpos('.') let l:index = ale#util#BinarySearch(l:loclist, a:buffer, l:pos[1], l:pos[2]) let l:loc = l:index >= 0 ? l:loclist[l:index] : {} diff --git a/autoload/ale/virtualtext.vim b/autoload/ale/virtualtext.vim index c4ce37dd..d7c5360e 100644 --- a/autoload/ale/virtualtext.vim +++ b/autoload/ale/virtualtext.vim @@ -47,7 +47,6 @@ function! ale#virtualtext#ShowMessage(message, hl_group) abort return endif - let l:cursor_position = getcurpos() let l:line = line('.') let l:buffer = bufnr('') let l:prefix = get(g:, 'ale_virtualtext_prefix', '> ') @@ -82,7 +81,7 @@ function! ale#virtualtext#ShowCursorWarning(...) abort call ale#virtualtext#Clear() if !empty(l:loc) - let l:msg = get(l:loc, 'detail', l:loc.text) + let l:msg = l:loc.text let l:hl_group = 'ALEVirtualTextInfo' let l:type = get(l:loc, 'type', 'E') @@ -117,7 +116,7 @@ function! ale#virtualtext#ShowCursorWarningWithDelay() abort call s:StopCursorTimer() - let l:pos = getcurpos()[0:2] + let l:pos = getpos('.')[0:2] " Check the current buffer, line, and column number against the last " recorded position. If the position has actually changed, *then* diff --git a/doc/ale-asciidoc.txt b/doc/ale-asciidoc.txt index b6b64fd3..86629fd4 100644 --- a/doc/ale-asciidoc.txt +++ b/doc/ale-asciidoc.txt @@ -9,4 +9,10 @@ See |ale-write-good-options| =============================================================================== +textlint *ale-asciidoc-textlint* + +See |ale-text-textlint| + + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-chef.txt b/doc/ale-chef.txt index 5024e279..75e144ec 100644 --- a/doc/ale-chef.txt +++ b/doc/ale-chef.txt @@ -3,6 +3,26 @@ ALE Chef Integration *ale-chef-options* =============================================================================== +cookstyle *ale-chef-cookstyle* + +g:ale_chef_cookstyle_options *g:ale_chef_cookstyle_options* + *b:ale_chef_cookstyle_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to cookstyle. + + +g:ale_chef_cookstyle_executable *g:ale_chef_cookstyle_executable* + *b:ale_chef_cookstyle_executable* + Type: |String| + Default: `'cookstyle'` + + This variable can be changed to point to the cookstyle binary in case it's + not on the $PATH or a specific version/path must be used. + + +=============================================================================== foodcritic *ale-chef-foodcritic* g:ale_chef_foodcritic_options *g:ale_chef_foodcritic_options* diff --git a/doc/ale-clojure.txt b/doc/ale-clojure.txt index a83e336f..2bf00c03 100644 --- a/doc/ale-clojure.txt +++ b/doc/ale-clojure.txt @@ -3,6 +3,13 @@ ALE Clojure Integration *ale-clojure-options* =============================================================================== +clj-kondo *ale-clojure-clj-kondo* + +A minimal and opinionated linter for code that sparks joy. + +https://github.com/borkdude/clj-kondo + +=============================================================================== joker *ale-clojure-joker* Joker is a small Clojure interpreter and linter written in Go. diff --git a/doc/ale-cmake.txt b/doc/ale-cmake.txt index fb46336f..602637b1 100644 --- a/doc/ale-cmake.txt +++ b/doc/ale-cmake.txt @@ -22,4 +22,22 @@ g:ale_cmake_cmakelint_options *g:ale_cmake_cmakelint_options* =============================================================================== +cmake-format *ale-cmake-cmakeformat* + +g:ale_cmake_cmakeformat_executable *g:ale_cmake_cmakeformat_executable* + *b:ale_cmake_cmakeformat_executable* + Type: |String| + Default: `'cmakeformat'` + + This variable can be set to change the path the cmake-format. + + +g:ale_cmake_cmakeformat_options *g:ale_cmake_cmakeformat_options* + *b:ale_cmake_cmakeformat_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to cmake-format. + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-css.txt b/doc/ale-css.txt index f3cae385..ff74b263 100644 --- a/doc/ale-css.txt +++ b/doc/ale-css.txt @@ -3,6 +3,14 @@ ALE CSS Integration *ale-css-options* =============================================================================== +fecs *ale-css-fecs* + +`fecs` options for CSS is the same as the options for JavaScript, and both of +them reads `./.fecsrc` as the default configuration file. See: +|ale-javascript-fecs|. + + +=============================================================================== prettier *ale-css-prettier* See |ale-javascript-prettier| for information about the available options. diff --git a/doc/ale-cuda.txt b/doc/ale-cuda.txt index 052b3363..0e53f756 100644 --- a/doc/ale-cuda.txt +++ b/doc/ale-cuda.txt @@ -22,4 +22,11 @@ g:ale_cuda_nvcc_options *g:ale_cuda_nvcc_options* This variable can be changed to modify flags given to nvcc. =============================================================================== +clang-format *ale-cuda-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for cuda. + + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-development.txt b/doc/ale-development.txt index 1e168130..9c9f0394 100644 --- a/doc/ale-development.txt +++ b/doc/ale-development.txt @@ -1,4 +1,5 @@ *ale-development.txt* For Vim version 8.0. +*ale-dev* *ale-development* ALE Development Documentation @@ -11,6 +12,7 @@ CONTENTS *ale-development-contents* 3. Coding Standards.....................|ale-coding-standards| 4. Testing ALE..........................|ale-development-tests| 4.1. Writing Linter Tests.............|ale-development-linter-tests| + 4.2. Writing Fixer Tests..............|ale-development-fixer-tests| =============================================================================== 1. Introduction *ale-development-introduction* @@ -143,7 +145,7 @@ Apply the following rules when writing Bash scripts. * Try to write scripts so they will run on Linux, BSD, or Mac OSX. =============================================================================== -4. Testing ALE *ale-development-tests* +4. Testing ALE *ale-development-tests* *ale-dev-tests* *ale-tests* ALE is tested with a suite of tests executed in Travis CI and AppVeyor. ALE runs tests with the following versions of Vim in the following environments. @@ -287,10 +289,10 @@ and should be written like so. > AssertLinter 'some-command', ale#Escape('some-command') . ' --foo' Execute(Check chained commands): - " WithChainResults can be called with 1 or more list for passing output + " GivenCommandOutput can be called with 1 or more list for passing output " to chained commands. The output for each callback defaults to an empty " list. - WithChainResults ['v2.1.2'] + GivenCommandOutput ['v2.1.2'] " Given a List of commands, check all of them. " Given a String, only the last command in the chain will be checked. AssertLinter 'some-command', [ @@ -301,7 +303,7 @@ and should be written like so. > The full list of commands that will be temporarily defined for linter tests given the above setup are as follows. -`WithChainResults [...]` - Define output for command chain functions. +`GivenCommandOutput [...]` - Define output for ale#command#Run. `AssertLinter executable, command` - Check the executable and command. `AssertLinterNotExecuted` - Check that linters will not be executed. `AssertLSPLanguage language` - Check the language given to an LSP server. @@ -310,5 +312,46 @@ given the above setup are as follows. `AssertLSPProject project_root` - Check the root given to an LSP server. `AssertLSPAddress address` - Check the address to an LSP server. + +=============================================================================== +4.2 Writing Fixer Tests *ale-development-fixer-tests* + +Tests for ALE fixers should go in the `test/fixers` directory, and should +be written like so. > + + Before: + " Load the fixer and set up a series of commands, reset fixer variables, + " clear caches, etc. + " + " Vader's 'Save' command will be called here for fixer variables. + call ale#assert#SetUpFixerTest('filetype', 'fixer_name') + + After: + " Reset fixers, variables, etc. + " + " Vader's 'Restore' command will be called here. + call ale#assert#TearDownFixerTest() + + Execute(The default command should be correct): + " AssertFixer checks the result of the loaded fixer function. + AssertFixer {'command': ale#Escape('some-command') . ' --foo'} + + Execute(Check chained commands): + " Same as above for linter tests. + GivenCommandOutput ['v2.1.2'] + " Given a List of commands, check all of them. + " Given anything else, only the last result will be checked. + AssertFixer [ + \ ale#Escape('some-command') . ' --version', + \ {'command': ale#Escape('some-command') . ' --foo'} + \] +< +The full list of commands that will be temporarily defined for fixer tests +given the above setup are as follows. + +`GivenCommandOutput [...]` - Define output for ale#command#Run. +`AssertFixer results` - Check the fixer results + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-elixir.txt b/doc/ale-elixir.txt index 45c6de1d..5864f728 100644 --- a/doc/ale-elixir.txt +++ b/doc/ale-elixir.txt @@ -72,6 +72,18 @@ g:ale_elixir_elixir_ls_config *g:ale_elixir_elixir_ls_config* \ } < Consult the ElixirLS documentation for more information about settings. +=============================================================================== +credo *ale-elixir-credo* + +Credo (https://github.com/rrrene/credo) + +g:ale_elixir_credo_strict *g:ale_elixir_credo_strict* + + Type: Integer + Default: 0 + + Tells credo to run in strict mode or suggest mode. Set variable to 1 to + enable --strict mode. =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-elm.txt b/doc/ale-elm.txt index de7d8939..bb7a6132 100644 --- a/doc/ale-elm.txt +++ b/doc/ale-elm.txt @@ -29,6 +29,24 @@ g:ale_elm_format_options *g:ale_elm_format_options* This variable can be set to pass additional options to elm-format. =============================================================================== +elm-lsp *ale-elm-elm-lsp* + +g:ale_elm_lsp_executable *g:ale_elm_lsp_executable* + *b:ale_elm_lsp_executable* + Type: |String| + Default: `'elm-lsp'` + + See |ale-integrations-local-executables| + + +g:ale_elm_lsp_use_global *g:ale_elm_lsp_use_global* + *b:ale_elm_lsp_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + +=============================================================================== elm-make *ale-elm-elm-make* g:ale_elm_make_executable *g:ale_elm_make_executable* diff --git a/doc/ale-go.txt b/doc/ale-go.txt index 43289bd5..611e3fdd 100644 --- a/doc/ale-go.txt +++ b/doc/ale-go.txt @@ -7,7 +7,7 @@ Integration Information The `gometalinter` linter is disabled by default. ALE enables `gofmt`, `golint` and `go vet` by default. It also supports `staticcheck`, `go -build`, `gosimple`, and `golangserver`. +build`, `gosimple`, `golangserver`. To enable `gometalinter`, update |g:ale_linters| as appropriate: > @@ -31,6 +31,23 @@ g:ale_go_go_executable *g:ale_go_go_options* =============================================================================== +bingo *ale-go-bingo* + +g:ale_go_bingo_executable *g:ale_go_bingo_executable* + *b:ale_go_bingo_executable* + Type: |String| + Default: `'bingo'` + + Location of the bingo binary file. + + +g:ale_go_bingo_options *g:ale_go_bingo_options* + *b:ale_go_bingo_options* + Type: |String| + Default: `''` + + +=============================================================================== gobuild *ale-go-gobuild* g:ale_go_gobuild_options *g:ale_go_gobuild_options* @@ -54,6 +71,60 @@ g:ale_go_gofmt_options *g:ale_go_gofmt_options* =============================================================================== +golangci-lint *ale-go-golangci-lint* + +`golangci-lint` is a `lint_file` linter, which only lints files that are +written to disk. This differs from the default behavior of linting the buffer. +See: |ale-lint-file| + +g:ale_go_golangci_lint_executable *g:ale_go_golangci_lint_executable* + *b:ale_go_golangci_lint_executable* + Type: |String| + Default: `'golangci-lint'` + + The executable that will be run for golangci-lint. + + +g:ale_go_golangci_lint_options *g:ale_go_golangci_lint_options* + *b:ale_go_golangci_lint_options* + Type: |String| + Default: `'--enable-all'` + + This variable can be changed to alter the command-line arguments to the + golangci-lint invocation. + + +g:ale_go_golangci_lint_package *g:ale_go_golangci_lint_package* + *b:ale_go_golangci_lint_package* + Type: |Number| + Default: `0` + + When set to `1`, the whole Go package will be checked instead of only the + current file. + + +=============================================================================== +golangserver *ale-go-golangserver* + +g:ale_go_langserver_executable *g:ale_go_langserver_executable* + *b:ale_go_langserver_executable* + Type: |String| + Default: `'go-langserver'` + + Location of the go-langserver binary file. + + +g:ale_go_langserver_options *g:ale_go_langserver_options* + *b:ale_go_langserver_options* + Type: |String| + Default: `''` + + Additional options passed to the go-langserver command. Note that the + `-gocodecompletion` option is ignored because it is handled automatically + by the |g:ale_completion_enabled| variable. + + +=============================================================================== golint *ale-go-golint* g:ale_go_golint_executable *g:ale_go_golint_executable* @@ -73,17 +144,6 @@ g:ale_go_golint_options *g:ale_go_golint_options* =============================================================================== -govet *ale-go-govet* - -g:ale_go_govet_options *g:ale_go_govet_options* - *b:ale_go_govet_options* - Type: |String| - Default: `''` - - This variable can be set to pass additional options to the go vet linter. - - -=============================================================================== gometalinter *ale-go-gometalinter* `gometalinter` is a `lint_file` linter, which only lints files that are @@ -122,72 +182,47 @@ g:ale_go_gometalinter_lint_package *g:ale_go_gometalinter_lint_package* =============================================================================== -staticcheck *ale-go-staticcheck* +gopls *ale-go-gopls* -g:ale_go_staticcheck_options *g:ale_go_staticcheck_options* - *b:ale_go_staticcheck_options* +g:ale_go_gopls_executable *g:ale_go_gopls_executable* + *b:ale_go_gopls_executable* Type: |String| - Default: `''` - - This variable can be set to pass additional options to the staticcheck - linter. + Default: `'gopls'` + Location of the gopls binary file. -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. +g:ale_go_gopls_options *g:ale_go_gopls_options* + *b:ale_go_gopls_options* + Type: |String| + Default: `''` =============================================================================== -golangserver *ale-go-golangserver* - -g:ale_go_langserver_executable *g:ale_go_langserver_executable* - *b:ale_go_langserver_executable* - Type: |String| - Default: `'go-langserver'` - - Location of the go-langserver binary file. +govet *ale-go-govet* -g:ale_go_langserver_options *g:ale_go_langserver_options* - *b:ale_go_langserver_options* +g:ale_go_govet_options *g:ale_go_govet_options* + *b:ale_go_govet_options* Type: |String| Default: `''` - Additional options passed to the go-langserver command. Note that the - `-gocodecompletion` option is ignored because it is handled automatically - by the |g:ale_completion_enabled| variable. + This variable can be set to pass additional options to the go vet linter. =============================================================================== -golangci-lint *ale-go-golangci-lint* - -`golangci-lint` is a `lint_file` linter, which only lints files that are -written to disk. This differs from the default behavior of linting the buffer. -See: |ale-lint-file| - -g:ale_go_golangci_lint_executable *g:ale_go_golangci_lint_executable* - *b:ale_go_golangci_lint_executable* - Type: |String| - Default: `'golangci-lint'` - - The executable that will be run for golangci-lint. - +staticcheck *ale-go-staticcheck* -g:ale_go_golangci_lint_options *g:ale_go_golangci_lint_options* - *b:ale_go_golangci_lint_options* +g:ale_go_staticcheck_options *g:ale_go_staticcheck_options* + *b:ale_go_staticcheck_options* Type: |String| - Default: `'--enable-all'` + Default: `''` - This variable can be changed to alter the command-line arguments to the - golangci-lint invocation. + This variable can be set to pass additional options to the staticcheck + linter. -g:ale_go_golangci_lint_package *g:ale_go_golangci_lint_package* - *b:ale_go_golangci_lint_package* +g:ale_go_staticcheck_lint_package *g:ale_go_staticcheck_lint_package* + *b:ale_go_staticcheck_lint_package* Type: |Number| Default: `0` diff --git a/doc/ale-haskell.txt b/doc/ale-haskell.txt index a4db683b..e2073e37 100644 --- a/doc/ale-haskell.txt +++ b/doc/ale-haskell.txt @@ -13,6 +13,16 @@ g:ale_haskell_brittany_executable *g:ale_haskell_brittany_executable* This variable can be changed to use a different executable for brittany. =============================================================================== +floskell *ale-haskell-floskell* + +g:ale_haskell_floskell_executable *g:ale_haskell_floskell_executable* + *b:ale_haskell_floskell_executable* + Type: |String| + Default: `'floskell'` + + This variable can be changed to use a different executable for floskell. + +=============================================================================== ghc *ale-haskell-ghc* g:ale_haskell_ghc_options *g:ale_haskell_ghc_options* @@ -108,6 +118,17 @@ g:ale_haskell_stack_build_options *g:ale_haskell_stack_build_options* programs will be slower unless you separately rebuild them outside of ALE. =============================================================================== +stack-ghc *ale-haskell-stack-ghc* + +g:ale_haskell_stack_ghc_options *g:ale_haskell_stack_ghc_options* + *b:ale_haskell_stack_ghc_options* + Type: |String| + Default: `'-fno-code -v0'` + + This variable can be changed to modify flags given to ghc through `stack + ghc` + +=============================================================================== stylish-haskell *ale-haskell-stylish-haskell* g:ale_haskell_stylish_haskell_executable diff --git a/doc/ale-html.txt b/doc/ale-html.txt index 1d30929f..5d6b20e2 100644 --- a/doc/ale-html.txt +++ b/doc/ale-html.txt @@ -3,6 +3,14 @@ ALE HTML Integration *ale-html-options* =============================================================================== +fecs *ale-html-fecs* + +`fecs` options for HTMl is the same as the options for JavaScript, +and both of them reads `./.fecsrc` as the default configuration file. +See: |ale-javascript-fecs|. + + +=============================================================================== htmlhint *ale-html-htmlhint* g:ale_html_htmlhint_executable *g:ale_html_htmlhint_executable* diff --git a/doc/ale-java.txt b/doc/ale-java.txt index 8e40aea0..2805c9c8 100644 --- a/doc/ale-java.txt +++ b/doc/ale-java.txt @@ -79,14 +79,19 @@ g:ale_java_pmd_options *g:ale_java_pmd_options* javalsp *ale-java-javalsp* To enable Java LSP linter you need to download and build the vscode-javac -language server from https://github.com/georgewfraser/vscode-javac. Simply -download the source code and then build the plugin using maven: +language server from https://github.com/georgewfraser/java-language-server. +Simply download the source code and then build a distribution: - mvn package + scripts/link_mac.sh -This generates a out/fat-jar.jar file that contains the language server. To -let ALE use this language server you need to set the g:ale_java_javalsp_jar -variable to the absolute path of this jar file. +or + + scripts/link_windows.sh + +This generates a dist/mac or dist/windows directory that contains the +language server. To let ALE use this language server you need to set the +g:ale_java_javalsp_executable variable to the absolute path of the java +executable in this directory. g:ale_java_javalsp_executable *g:ale_java_javalsp_executable* *b:ale_java_javalsp_executable* @@ -95,13 +100,45 @@ g:ale_java_javalsp_executable *g:ale_java_javalsp_executable* This variable can be changed to use a different executable for java. -g:ale_java_javalsp_jar *g:ale_java_javalsp_jar* - *b:ale_java_javalsp_jar* + +=============================================================================== +eclipselsp *ale-java-eclipselsp* + +To enable Eclipse LSP linter you need to clone and build the eclipse.jdt.ls +language server from https://github.com/eclipse/eclipse.jdt.ls. Simply +clone the source code repo and then build the plugin: + + ./mvnw clean verify + +Note: currently, the build can only run when launched with JDK 8. JDK 9 or more +recent versions can be used to run the server though. + +After build completes the files required to run the language server will be +located inside the repository folder `eclipse.jdt.ls`. Please ensure to set +|g:ale_java_eclipselsp_path| to the absolute path of that folder. + +You could customize compiler options and code assists of the server. +Under your project folder, modify the file `.settings/org.eclipse.jdt.core.prefs` +with options presented at +https://help.eclipse.org/neon/topic/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/core/JavaCore.html. + +g:ale_java_eclipselsp_path *g:ale_java_eclipselsp_path* + *b:ale_java_eclipselsp_path* Type: |String| - Default: `'fat-jar.jar'` + Default: `'$HOME/eclipse.jdt.ls'` + + Absolute path to the location of the eclipse.jdt.ls repository folder. Or if + you have VSCode extension installed the absolute path to the VSCode extensions + folder (e.g. $HOME/.vscode/extensions in Linux). + + +g:ale_java_eclipselsp_executable *g:ale_java_eclipse_executable* + *b:ale_java_eclipse_executable* + Type: |String| + Default: `'java'` - Path to the location of the vscode-javac language server plugin. + This variable can be set to change the executable path used for java. =============================================================================== diff --git a/doc/ale-javascript.txt b/doc/ale-javascript.txt index 53a70fd7..ea0a7089 100644 --- a/doc/ale-javascript.txt +++ b/doc/ale-javascript.txt @@ -74,6 +74,33 @@ g:ale_javascript_eslint_suppress_missing_config =============================================================================== +fecs *ale-javascript-fecs* + +`fecs` is a lint tool for HTML/CSS/JavaScript, can be installed via: + + `$ npm install --save-dev fecs` + +And the configuration file is located at `./fecsrc`, see http://fecs.baidu.com +for more options. + + +g:ale_javascript_fecs_executable *g:ale_javascript_fecs_executable* + *b:ale_javascript_fecs_executable* + Type: |String| + Default: `'fecs'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_fecs_use_global *g:ale_javascript_fecs_use_global* + *b:ale_javascript_fecs_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== flow *ale-javascript-flow* g:ale_javascript_flow_executable *g:ale_javascript_flow_executable* diff --git a/doc/ale-json.txt b/doc/ale-json.txt index 6a0a9fae..96499a04 100644 --- a/doc/ale-json.txt +++ b/doc/ale-json.txt @@ -52,7 +52,21 @@ g:ale_json_fixjson_use_global *g:ale_json_fixjson_use_global* =============================================================================== jsonlint *ale-json-jsonlint* -There are no options available. +g:ale_json_jsonlint_executable *g:ale_json_jsonlint_executable* + *b:ale_json_jsonlint_executable* + + Type: |String| + Default: `'jsonlint'` + + The executable that will be run for jsonlint. + +g:ale_json_jsonlint_use_global *g:ale_json_jsonlint_use_global* + *b:ale_json_jsonlint_use_global* + + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| =============================================================================== diff --git a/doc/ale-kotlin.txt b/doc/ale-kotlin.txt index 9f9fd16e..4028531f 100644 --- a/doc/ale-kotlin.txt +++ b/doc/ale-kotlin.txt @@ -84,9 +84,17 @@ g:ale_kotlin_ktlint_rulesets *g:ale_kotlin_ktlint_rulesets* This list should contain paths to ruleset jars and/or strings of maven artifact triples. Example: > - let g:ale_kotlin_ktlint_rulesets = ['/path/to/custom-rulset.jar', + let g:ale_kotlin_ktlint_rulesets = ['/path/to/custom-ruleset.jar', 'com.ktlint.rulesets:mycustomrule:1.0.0'] +g:ale_kotlin_ktlint_options *g:ale_kotlin_ktlint_options* + Type: |String| + Default: `''` + + Additional options to pass to ktlint for both linting and fixing. Example: + > + let g:ale_kotlin_ktlint_options = '--android' + =============================================================================== languageserver *ale-kotlin-languageserver* diff --git a/doc/ale-latex.txt b/doc/ale-latex.txt index 87fbd4e8..bedbabcd 100644 --- a/doc/ale-latex.txt +++ b/doc/ale-latex.txt @@ -9,4 +9,10 @@ See |ale-write-good-options| =============================================================================== +textlint *ale-latex-textlint* + +See |ale-text-textlint| + + +=============================================================================== 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 adf17716..8b644c17 100644 --- a/doc/ale-ocaml.txt +++ b/doc/ale-ocaml.txt @@ -51,4 +51,32 @@ g:ale_ocaml_ocamlformat_options *g:ale_ocaml_ocamlformat_options* This variable can be set to pass additional options to the ocamlformat fixer. =============================================================================== +ocp-indent *ale-ocaml-ocp-indent* + +g:ale_ocaml_ocp_indent_executable *g:ale_ocaml_ocp_indent_executable* + *b:ale_ocaml_ocp_indent_executable* + Type: |String| + Default: `ocp-indent` + + This variable can be set to pass the path of the ocp-indent. + +g:ale_ocaml_ocp_indent_options *g:ale_ocaml_ocp_indent_options* + *b:ale_ocaml_ocp_indent_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the ocp-indent. + +g:ale_ocaml_ocp_indent_config *g:ale_ocaml_ocp_indent_config* + *b:ale_ocaml_ocp_indent_config* + Type: |String| + Default: `''` + + This variable can be set to pass additional config to the ocp-indent. + Expand after "--config=". + + "ocp-indent" can also be enabled from ocamlformat config. + + +=============================================================================== 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 83bc0fd5..6f53cead 100644 --- a/doc/ale-php.txt +++ b/doc/ale-php.txt @@ -154,11 +154,13 @@ g:ale_php_phpstan_executable *g:ale_php_phpstan_executable* g:ale_php_phpstan_level *g:ale_php_phpstan_level* *b:ale_php_phpstan_level* - Type: |Number| - Default: `4` + Type: |String| + Default: `''` This variable controls the rule levels. 0 is the loosest and 4 is the - strictest. + strictest. If this option isn't set, the rule level will be controlled by + the configuration file. If no configuration file can be detected, `'4'` will + be used instead. g:ale_php_phpstan_configuration *g:ale_php_phpstan_configuration* diff --git a/doc/ale-powershell.txt b/doc/ale-powershell.txt new file mode 100644 index 00000000..c28ef9ea --- /dev/null +++ b/doc/ale-powershell.txt @@ -0,0 +1,77 @@ +=============================================================================== +ALE PowerShell Integration *ale-powershell-options* + + +=============================================================================== +powershell *ale-powershell-powershell* + +g:ale_powershell_powershell_executable *g:ale_powershell_powershell_executable* + *b:ale_powershell_powershell_executable* + Type: String + Default: `'pwsh'` + + This variable can be changed to use a different executable for powershell. + +> + " Use powershell.exe rather than the default pwsh + let g:ale_powershell_powershell_executable = 'powershell.exe' +> + +=============================================================================== +psscriptanalyzer *ale-powershell-psscriptanalyzer* + +Installation +------------------------------------------------------------------------------- + +Install PSScriptAnalyzer by any means, so long as it can be automatically +imported in PowerShell. +Some PowerShell plugins set the filetype of files to `ps1`. To continue using +these plugins, use the ale_linter_aliases global to alias `ps1` to `powershell` + +> + " Allow ps1 filetype to work with powershell linters + let g:ale_linter_aliases = {'ps1': 'powershell'} +< + +g:ale_powershell_psscriptanalyzer_executable +*g:ale_powershell_psscriptanalyzer_executable* + *b:ale_powershell_psscriptanalyzer_executable* + Type: |String| + Default: `'pwsh'` + + This variable sets executable used for powershell. + + For example, on Windows you could set powershell to be Windows Powershell: +> + let g:ale_powershell_psscriptanalyzer_executable = 'powershell.exe' +< + +g:ale_powershell_psscriptanalyzer_module +*g:ale_powershell_psscriptanalyzer_module* + *b:ale_powershell_psscriptanalyzer_module* + Type: |String + Default: `'psscriptanalyzer'` + + This variable sets the name of the psscriptanalyzer module. + for psscriptanalyzer invocation. + + +g:ale_powershell_psscriptanalyzer_exclusions +*g:ale_powershell_psscriptanalyzer_exclusions* + *b:ale_powershell_psscriptanalyzer_exclusions* + Type: |String| + Default: `''` + + Set this variable to exclude test(s) for psscriptanalyzer + (-ExcludeRule option). To exclude more than one option, separate them with + commas. + +> + " Suppress Write-Host and Global vars warnings + let g:ale_powershell_psscriptanalyzer_exclusions = + \ 'PSAvoidUsingWriteHost,PSAvoidGlobalVars' +< + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-python.txt b/doc/ale-python.txt index f3f2801a..dd946ad4 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -31,6 +31,9 @@ ALE will look for configuration files with the following filenames. > pycodestyle.cfg flake8.cfg .flake8rc + pylama.ini + pylintrc + .pylintrc Pipfile Pipfile.lock < @@ -66,6 +69,56 @@ g:ale_python_autopep8_use_global *g:ale_python_autopep8_use_global* =============================================================================== +bandit *ale-python-bandit* + +g:ale_python_bandit_executable *g:ale_python_bandit_executable* + *b:ale_python_bandit_executable* + Type: |String| + Default: `'bandit'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `bandit'`. + + +g:ale_python_bandit_options *g:ale_python_bandit_options* + *b:ale_python_bandit_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + bandit invocation. + + +g:ale_python_bandit_use_config *g:ale_python_bandit_use_config* + *b:ale_python_bandit_use_config* + Type: |Number| + Default: `1` + + If this variable is true and a `.bandit` file exists in the directory of the + file being checked or a parent directory, an `--ini` option is added to the + `bandit` command for the nearest `.bandit` file. Set this variable false to + disable adding the `--ini` option automatically. + + +g:ale_python_bandit_use_global *g:ale_python_bandit_use_global* + *b:ale_python_bandit_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +g:ale_python_bandit_auto_pipenv *g:ale_python_bandit_auto_pipenv* + *b:ale_python_bandit_auto_pipenv* + Type: |Number| + Default: `0` + + Detect whether the file is inside a pipenv, and set the executable to `pipenv` + if true. This is overridden by a manually-set executable. + + +=============================================================================== black *ale-python-black* g:ale_python_black_executable *g:ale_python_black_executable* @@ -75,7 +128,7 @@ g:ale_python_black_executable *g:ale_python_black_executable* See |ale-integrations-local-executables| -autopep8 + g:ale_python_black_options *g:ale_python_black_options* *b:ale_python_black_options* Type: |String| @@ -92,6 +145,25 @@ g:ale_python_black_use_global *g:ale_python_black_use_global* See |ale-integrations-local-executables| +g:ale_python_black_auto_pipenv *g:ale_python_black_auto_pipenv* + *b:ale_python_black_auto_pipenv* + Type: |Number| + Default: `0` + + Detect whether the file is inside a pipenv, and set the executable to `pipenv` + if true. This is overridden by a manually-set executable. + +g:ale_python_black_change_directory *g:ale_python_black_change_directory* + *b:ale_python_black_change_directory* + Type: |Number| + Default: `1` + + If set to `1`, ALE will switch to the directory the Python file being + checked with `black` is in before checking it. This helps `black` find + configuration files more easily. This option can be turned off if you want + to control the directory Python is executed from yourself. + + =============================================================================== flake8 *ale-python-flake8* @@ -392,6 +464,60 @@ g:ale_python_pyflakes_auto_pipenv *g:ale_python_pyflakes_auto_pipenv* =============================================================================== +pylama *ale-python-pylama* + +g:ale_python_pylama_change_directory *g:ale_python_pylama_change_directory* + *b:ale_python_pylama_change_directory* + Type: |Number| + Default: `1` + + If set to `1`, `pylama` will be run from a detected project root, per + |ale-python-root|. This is useful because `pylama` only searches for + configuration files in its current directory and applies file masks using + paths relative to its current directory. This option can be turned off if + you want to control the directory in which `pylama` is executed. + + +g:ale_python_pylama_executable *g:ale_python_pylama_executable* + *b:ale_python_pylama_executable* + Type: |String| + Default: `'pylama'` + + This variable can be changed to modify the executable used for pylama. Set + this to `'pipenv'` to invoke `'pipenv` `run` `pylama'`. + + +g:ale_python_pylama_options *g:ale_python_pylama_options* + *b:ale_python_pylama_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the pylama + invocation. + + +g:ale_python_pylama_use_global *g:ale_python_pylama_use_global* + *b:ale_python_pylama_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable controls whether or not ALE will search for pylama in a + virtualenv directory first. If this variable is set to `1`, then ALE will + always use |g:ale_python_pylama_executable| for the executable path. + + Both variables can be set with `b:` buffer variables instead. + + +g:ale_python_pylama_auto_pipenv *g:ale_python_pylama_auto_pipenv* + *b:ale_python_pylama_auto_pipenv* + Type: |Number| + Default: `0` + + Detect whether the file is inside a pipenv, and set the executable to `pipenv` + if true. This is overridden by a manually-set executable. + + +=============================================================================== pylint *ale-python-pylint* g:ale_python_pylint_change_directory *g:ale_python_pylint_change_directory* @@ -399,10 +525,12 @@ g:ale_python_pylint_change_directory *g: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. + If set to `1`, `pylint` will be run from a detected project root, per + |ale-python-root|. Since `pylint` only checks for `pylintrc` in the packages + above its current directory before falling back to user and global `pylintrc` + files, this is necessary for `pylint` to use a project `pylintrc` file, if + present. 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* @@ -485,6 +613,24 @@ g:ale_python_pyls_auto_pipenv *g:ale_python_pyls_auto_pipenv* if true. This is overridden by a manually-set executable. +g:ale_python_pyls_config *g:ale_python_pyls_config* + *b:ale_python_pyls_config* + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for pyls. For example, to disable + the pycodestyle linter: > + { + \ 'pyls': { + \ 'plugins': { + \ 'pycodestyle': { + \ 'enabled': v:false + \ } + \ } + \ }, + \ } +< + =============================================================================== pyre *ale-python-pyre* @@ -540,6 +686,15 @@ g:ale_python_vulture_executable *g:ale_python_vulture_executable* See |ale-integrations-local-executables| +g:ale_python_vulture_options *g:ale_python_vulture_options* + *b:ale_python_vulture_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the vulture + invocation. + + g:ale_python_vulture_use_global *g:ale_python_vulture_use_global* *b:ale_python_vulture_use_global* Type: |Number| diff --git a/doc/ale-r.txt b/doc/ale-r.txt index f85f48fd..b5ccebe5 100644 --- a/doc/ale-r.txt +++ b/doc/ale-r.txt @@ -25,5 +25,21 @@ g:ale_r_lintr_lint_package *g:ale_r_lintr_lint_package* of `lintr::lint`. This prevents erroneous namespace warnings when linting package files. + +=============================================================================== +styler *ale-r-styler* + +g:ale_r_styler_options *g:ale_r_styler_options* + *b:ale_r_styler_options* + Type: |String| + Default: `'styler::tidyverse_style'` + + This option can be configured to change the options for styler. + + The value of this option will be used as the `style` argument for the + `styler::style_file` options. Consult the styler documentation + for more information. + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-restructuredtext.txt b/doc/ale-restructuredtext.txt index 02fbc4ad..e308b072 100644 --- a/doc/ale-restructuredtext.txt +++ b/doc/ale-restructuredtext.txt @@ -3,6 +3,20 @@ ALE reStructuredText Integration *ale-restructuredtext-options* =============================================================================== +textlint *ale-restructuredtext-textlint* + +To use textlint at reStructuredText, please install `textlint-plugin-rst`. +https://github.com/jimo1001/textlint-plugin-rst +> + $ npm install textlint-plugin-rst + +To install `textlint-plugin-rst`, `docutils-ast-writer` python package +must be installed. +See: https://github.com/jimo1001/docutils-ast-writer + +See |ale-text-textlint| + +=============================================================================== write-good *ale-restructuredtext-write-good* See |ale-write-good-options| diff --git a/doc/ale-ruby.txt b/doc/ale-ruby.txt index f8a41999..bf971e7c 100644 --- a/doc/ale-ruby.txt +++ b/doc/ale-ruby.txt @@ -130,4 +130,26 @@ g:ale_ruby_solargraph_executable *g:ale_ruby_solargraph_executable* =============================================================================== +standardrb *ale-ruby-standardrb* + +g:ale_ruby_standardrb_executable *g:ale_ruby_standardrb_executable* + *b:ale_ruby_standardrb_executable* + Type: String + Default: `'standardrb'` + + Override the invoked standardrb binary. Set this to `'bundle'` to invoke + `'bundle` `exec` standardrb'. + + +g:ale_ruby_standardrb_options *g:ale_ruby_standardrb_options* + *b:ale_ruby_standardrb_options* + Type: |String| + Default: `''` + + This variable can be change to modify flags given to standardrb. + + +=============================================================================== + +=============================================================================== 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 7510dfbd..44a79b18 100644 --- a/doc/ale-rust.txt +++ b/doc/ale-rust.txt @@ -164,14 +164,32 @@ g:ale_rust_rls_executable *g:ale_rust_rls_executable* g:ale_rust_rls_toolchain *g:ale_rust_rls_toolchain* *b:ale_rust_rls_toolchain* Type: |String| - Default: `'nightly'` + Default: `''` This option can be set to change the toolchain used for `rls`. Possible - values include `'nightly'`, `'beta'`, and `'stable'`. + values include `'nightly'`, `'beta'`, `'stable'`, and `''`. When using + option `''`, rls will automatically find the default toolchain set by + rustup. If you want to use `rls` from a specific toolchain version, you may + also use values like `'channel-yyyy-mm-dd-arch-target'` as long as + `'rls +{toolchain_name} -V'` runs correctly in your command line. The `rls` server will only be started once per executable. +g:ale_rust_rls_config *g:ale_rust_rls_config* + *b:ale_rust_rls_config* + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for rls. For example, to force + using clippy as linter: > + { + \ 'rust': { + \ 'clippy_preference': 'on' + \ } + \ } + + =============================================================================== rustc *ale-rust-rustc* diff --git a/doc/ale-sass.txt b/doc/ale-sass.txt index 735f44b2..22d7c472 100644 --- a/doc/ale-sass.txt +++ b/doc/ale-sass.txt @@ -1,5 +1,5 @@ =============================================================================== -ALE SASS Integration *ale-sass-options* +ALE Sass Integration *ale-sass-options* =============================================================================== diff --git a/doc/ale-sh.txt b/doc/ale-sh.txt index 7557e522..3eac9038 100644 --- a/doc/ale-sh.txt +++ b/doc/ale-sh.txt @@ -61,6 +61,30 @@ g:ale_sh_shellcheck_options *g:ale_sh_shellcheck_options* let g:ale_sh_shellcheck_options = '-x' < + +g:ale_sh_shellcheck_change_directory *g:ale_sh_shellcheck_change_directory* + *b:ale_sh_shellcheck_change_directory* + Type: |Number| + Default: `1` + + If set to `1`, ALE will switch to the directory the shell file being + checked with `shellcheck` is in before checking it. This helps `shellcheck` + determine the path to sourced files more easily. This option can be turned + off if you want to control the directory `shellcheck` is executed from + yourself. + + +g:ale_sh_shellcheck_dialect *g:ale_sh_shellcheck_dialect* + *b:ale_sh_shellcheck_dialect* + Type: |String| + Default: `'auto'` + + This variable specifies the shellcheck dialect (`-s` option). The value + `'auto'` causes ALE to detect the dialect automatically, based on the shebang + line (if present) or the value of `b:is_bash`, `b:is_sh`, or `b:is_kornshell` + (set and used by |sh.vim|). + + g:ale_sh_shellcheck_exclusions *g:ale_sh_shellcheck_exclusions* *b:ale_sh_shellcheck_exclusions* Type: |String| diff --git a/doc/ale-sugarss.txt b/doc/ale-sugarss.txt new file mode 100644 index 00000000..8e991e54 --- /dev/null +++ b/doc/ale-sugarss.txt @@ -0,0 +1,31 @@ +=============================================================================== +ALE SugarSS Integration *ale-sugarss-options* + + +=============================================================================== +stylelint *ale-sugarss-stylelint* + +g:ale_sugarss_stylelint_executable *g:ale_sugarss_stylelint_executable* + *b:ale_sugarss_stylelint_executable* + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + +g:ale_sugarss_stylelint_options *g:ale_sugarss_stylelint_options* + *b:ale_sugarss_stylelint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + +g:ale_sugarss_stylelint_use_global *g:ale_sugarss_stylelint_use_global* + *b:ale_sugarss_stylelint_use_global* + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt new file mode 100644 index 00000000..d6fbafa6 --- /dev/null +++ b/doc/ale-supported-languages-and-tools.txt @@ -0,0 +1,484 @@ +*ale-supported-languages-and-tools.txt* For Vim version 8.0. +*ale-supported-list* + +ALE Supported Languages and Tools + +=============================================================================== + +The following languages and tools are supported by ALE. + +Notes: + +`^` No linters for text or Vim help filetypes are enabled by default. +`!!` These linters check only files on disk. See |ale-lint-file-linters| + +* Ada + * `gcc` +* Ansible + * `ansible-lint` +* API Blueprint + * `drafter` +* AsciiDoc + * `alex`!! + * `proselint` + * `redpen` + * `textlint` + * `vale` + * `write-good` +* ASM + * `gcc` +* Awk + * `gawk` +* Bash + * `language-server` + * `shell` (-n flag) + * `shellcheck` + * `shfmt` +* BibTeX + * `bibclean` +* Bourne Shell + * `shell` (-n flag) + * `shellcheck` + * `shfmt` +* C + * `ccls` + * `clang` + * `clangd` + * `clang-format` + * `clangtidy`!! + * `cppcheck` + * `cpplint`!! + * `cquery` + * `flawfinder` + * `gcc` + * `uncrustify` +* C# + * `mcs` + * `mcsc`!! + * `uncrustify` +* C++ (filetype cpp) + * `ccls` + * `clang` + * `clangcheck`!! + * `clangd` + * `clang-format` + * `clangtidy`!! + * `clazy`!! + * `cppcheck` + * `cpplint`!! + * `cquery` + * `flawfinder` + * `gcc` + * `uncrustify` +* Chef + * `cookstyle` + * `foodcritic` +* Clojure + * `clj-kondo` + * `joker` +* CloudFormation + * `cfn-python-lint` +* CMake + * `cmake-format` + * `cmakelint` +* CoffeeScript + * `coffee` + * `coffeelint` +* Crystal + * `ameba`!! + * `crystal`!! +* CSS + * `csslint` + * `fecs` + * `prettier` + * `stylelint` +* Cucumber + * `cucumber` +* CUDA + * `nvcc`!! +* Cypher + * `cypher-lint` +* Cython (pyrex filetype) + * `cython` +* D + * `dls` + * `dmd` + * `uncrustify` +* Dafny + * `dafny`!! +* Dart + * `dartanalyzer`!! + * `dartfmt`!! + * `language_server` +* Dockerfile + * `dockerfile_lint` + * `hadolint` +* Elixir + * `credo` + * `dialyxir` + * `dogma` + * `elixir-ls` + * `mix`!! +* Elm + * `elm-format` + * `elm-lsp` + * `elm-make` +* Erb + * `erb` + * `erubi` + * `erubis` + * `ruumba` +* Erlang + * `erlc` + * `SyntaxErl` +* Fish + * `fish` (-n flag) +* Fortran + * `gcc` + * `language_server` +* Fountain + * `proselint` +* FusionScript + * `fusion-lint` +* Git Commit Messages + * `gitlint` +* GLSL + * glslang + * `glslls` +* Go + * `bingo` + * `go build`!! + * `gofmt` + * `goimports` + * `golangci-lint`!! + * `golangserver` + * `golint` + * `gometalinter`!! + * `go mod`!! + * `gopls` + * `gosimple`!! + * `gotype`!! + * `go vet`!! + * `staticcheck`!! +* GraphQL + * `eslint` + * `gqlint` + * `prettier` +* Hack + * `hack` + * `hackfmt` + * `hhast` +* Haml + * `haml-lint` +* Handlebars + * `ember-template-lint` +* Haskell + * `brittany` + * `cabal-ghc` + * `floskell` + * `ghc` + * `ghc-mod` + * `hdevtools` + * `hfmt` + * `hie` + * `hlint` + * `stack-build`!! + * `stack-ghc` + * `stylish-haskell` +* HCL + * `terraform-fmt` +* HTML + * `alex`!! + * `fecs` + * `HTMLHint` + * `prettier` + * `proselint` + * `tidy` + * `write-good` +* Idris + * `idris` +* ISPC + * `ispc`!! +* Java + * `checkstyle` + * `eclipselsp` + * `google-java-format` + * `javac` + * `javalsp` + * `PMD` + * `uncrustify` +* JavaScript + * `eslint` + * `fecs` + * `flow` + * `jscs` + * `jshint` + * `prettier` + * `prettier-eslint` + * `prettier-standard` + * `standard` + * `tsserver` + * `xo` +* JSON + * `fixjson` + * `jq` + * `jsonlint` + * `prettier` +* Julia + * `languageserver` +* Kotlin + * `kotlinc`!! + * `ktlint`!! + * `languageserver` +* LaTeX (tex) + * `alex`!! + * `chktex` + * `lacheck` + * `proselint` + * `redpen` + * `textlint` + * `vale` + * `write-good` +* Less + * `lessc` + * `prettier` + * `stylelint` +* LLVM + * `llc` +* Lua + * `luac` + * `luacheck` +* Mail + * `alex`!! + * `languagetool`!! + * `proselint` + * `vale` +* Make + * `checkmake` +* Markdown + * `alex`!! + * `languagetool`!! + * `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 + * `ccls` + * `clang` + * `clangd` + * `uncrustify` +* Objective-C++ + * `clang` + * `clangd` + * `uncrustify` +* OCaml + * `merlin` (see |ale-ocaml-merlin|) + * `ocamlformat` + * `ocp-indent` + * `ols` +* Pawn + * `uncrustify` +* Perl + * `perl -c` + * `perl-critic` + * `perltidy` +* Perl6 + * `perl6 -c` +* PHP + * `langserver` + * `phan` + * `phpcbf` + * `phpcs` + * `php-cs-fixer` + * `php -l` + * `phpmd` + * `phpstan` + * `psalm`!! +* PO + * `alex`!! + * `msgfmt` + * `proselint` + * `write-good` +* Pod + * `alex`!! + * `proselint` + * `write-good` +* Pony + * `ponyc` +* PowerShell + * `powershell` + * `psscriptanalyzer` +* Prolog + * `swipl` +* proto + * `protoc-gen-lint` +* Pug + * `pug-lint` +* Puppet + * `languageserver` + * `puppet` + * `puppet-lint` +* Python + * `autopep8` + * `bandit` + * `black` + * `flake8` + * `isort` + * `mypy` + * `prospector` + * `pycodestyle` + * `pydocstyle` + * `pyflakes` + * `pylama`!! + * `pylint`!! + * `pyls` + * `pyre` + * `vulture`!! + * `yapf` +* QML + * `qmlfmt` + * `qmllint` +* R + * `lintr` + * `styler` +* Racket + * `raco` +* ReasonML + * `merlin` + * `ols` + * `refmt` +* reStructuredText + * `alex`!! + * `proselint` + * `redpen` + * `rstcheck` + * `textlint` + * `vale` + * `write-good` +* Re:VIEW + * `redpen` +* RPM spec + * `rpmlint` +* Ruby + * `brakeman` + * `rails_best_practices`!! + * `reek` + * `rubocop` + * `ruby` + * `rufo` + * `solargraph` + * `standardrb` +* Rust + * `cargo`!! + * `rls` + * `rustc` (see |ale-integration-rust|) + * `rustfmt` +* Sass + * `sass-lint` + * `stylelint` +* Scala + * `fsc` + * `sbtserver` + * `scalac` + * `scalafmt` + * `scalastyle` +* SCSS + * `prettier` + * `sass-lint` + * `scss-lint` + * `stylelint` +* Slim + * `slim-lint` +* SML + * `smlnj` +* Solidity + * `solhint` + * `solium` +* SQL + * `sqlfmt` + * `sqlint` +* Stylus + * `stylelint` +* SugarSS + * `stylelint` +* Swift + * `sourcekit-lsp` + * `swiftformat` + * `swiftlint` +* Tcl + * `nagelfar`!! +* Terraform + * `fmt` + * `tflint` +* Texinfo + * `alex`!! + * `proselint` + * `write-good` +* Text^ + * `alex`!! + * `languagetool`!! + * `proselint` + * `redpen` + * `textlint` + * `vale` + * `write-good` +* Thrift + * `thrift` +* TypeScript + * `eslint` + * `fecs` + * `prettier` + * `tslint` + * `tsserver` + * `typecheck` +* VALA + * `uncrustify` +* Verilog + * `iverilog` + * `verilator` + * `vlog` + * `xvlog` +* VHDL + * `ghdl` + * `vcom` + * `xvhdl` +* Vim + * `vint` +* Vim help^ + * `alex`!! + * `proselint` + * `write-good` +* Vue + * `prettier` + * `vls` +* XHTML + * `alex`!! + * `proselint` + * `write-good` +* XML + * `xmllint` +* YAML + * `prettier` + * `swaglint` + * `yamllint` +* YANG + * `yang-lsp` diff --git a/doc/ale-swift.txt b/doc/ale-swift.txt new file mode 100644 index 00000000..8fa0c06c --- /dev/null +++ b/doc/ale-swift.txt @@ -0,0 +1,21 @@ +=============================================================================== +ALE Swift Integration *ale-swift-options* + + +=============================================================================== +sourcekitlsp *ale-swift-sourcekitlsp* + +To enable the SourceKit-LSP you need to install and build the executable as +described here: https://github.com/apple/sourcekit-lsp#building-sourcekit-lsp + + +g:ale_sourcekit_lsp_executable *g:ale_sourcekit_lsp_executable* + *b:ale_sourcekit_lsp_executable* + Type: |String| + Default: `'sourcekit-lsp'` + + See |ale-integrations-local-executables| + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: + diff --git a/doc/ale-tex.txt b/doc/ale-tex.txt index 24aa3112..b1b09117 100644 --- a/doc/ale-tex.txt +++ b/doc/ale-tex.txt @@ -32,5 +32,26 @@ g:ale_lacheck_executable *g:ale_lacheck_executable* This variable can be changed to change the path to lacheck. + +=============================================================================== +latexindent *ale-tex-latexindent* + +g:ale_tex_latexindent_executable *g:ale_tex_latexindent_executable* + *b:ale_tex_latexindent_executable* + Type: |String| + Default: `'latexindent'` + + This variable can be changed to change the path to latexindent. + + +g:ale_tex_latexindent_options *g:ale_tex_latexindent_options* + *b:ale_tex_latexindent_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to latexindent. + + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-verilog.txt b/doc/ale-verilog.txt index 2b8ce5e2..94b820b8 100644 --- a/doc/ale-verilog.txt +++ b/doc/ale-verilog.txt @@ -3,7 +3,7 @@ ALE Verilog/SystemVerilog Integration *ale-verilog-options* =============================================================================== -ALE can use two different linters for Verilog HDL: +ALE can use four different linters for Verilog HDL: iverilog: Using `iverilog -t null -Wall` @@ -11,6 +11,12 @@ ALE can use two different linters for Verilog HDL: verilator Using `verilator --lint-only -Wall` + ModelSim/Questa + Using `vlog -quiet -lint` + + Vivado + Using `xvlog` + By default, both 'verilog' and 'systemverilog' filetypes are checked. You can limit 'systemverilog' files to be checked using only 'verilator' by @@ -20,6 +26,20 @@ defining 'g:ale_linters' variable: \ let g:ale_linters = {'systemverilog' : ['verilator'],} < +Linters/compilers that utilize a "work" directory for analyzing designs- such +as ModelSim and Vivado- can be passed the location of these directories as +part of their respective option strings listed below. This is useful for +holistic analysis of a file (e.g. a design with components, packages, or other +code defined external to the current file as part of a larger project) or +when wanting to simply pass an alternative location for the auto-generated +work directories (such as '/tmp') so as to not muddle the current directory. +Since these type of linters often use this work directory for holding compiled +design data as part of a single build process, they sometimes cannot handle +the frequent, asynchronous application launches when linting while text is +changing. This can happen in the form of hangs or crashes. To help prevent +this when using these linters, it may help to run linting less frequently; for +example, only when a file is saved. + =============================================================================== iverilog *ale-verilog-iverilog* @@ -39,5 +59,44 @@ g:ale_verilog_verilator_options *g:ale_verilog_verilator_options* For example `'-sv --default-language "1800-2012"'` if you want to enable SystemVerilog parsing and select the 2012 version of the language. + +=============================================================================== +vlog *ale-verilog-vlog* + +g:ale_verilog_vlog_executable *g:ale_verilog_vlog_executable* + *b:ale_verilog_vlog_executable* + Type: |String| + Default: `'vlog'` + + This variable can be changed to the path to the 'vlog' executable. + + +g:ale_verilog_vlog_options *g:ale_verilog_vlog_options* + *b:ale_verilog_vlog_options* + Type: |String| + Default: `'-quiet -lint'` + + This variable can be changed to modify the flags/options passed to 'vlog'. + + +=============================================================================== +xvlog *ale-verilog-xvlog* + +g:ale_verilog_xvlog_executable *g:ale_verilog_xvlog_executable* + *b:ale_verilog_xvlog_executable* + Type: |String| + Default: `'xvlog'` + + This variable can be changed to the path to the 'xvlog' executable. + + +g:ale_verilog_xvlog_options *g:ale_verilog_xvlog_options* + *b:ale_verilog_xvlog_options* + Type: |String| + Default: `''` + + This variable can be changed to modify the flags/options passed to 'xvlog'. + + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-vhdl.txt b/doc/ale-vhdl.txt new file mode 100644 index 00000000..3fea947d --- /dev/null +++ b/doc/ale-vhdl.txt @@ -0,0 +1,92 @@ +=============================================================================== +ALE VHDL Integration *ale-vhdl-options* + + +=============================================================================== +ALE can use three different linters for VHDL: + + iverilog: + Using `iverilog -t null -Wall` + + ModelSim/Questa + Using `vcom -2008 -quiet -lint` + + Vivado + Using `xvhdl --2008` + +Note all linters default to VHDL-2008 support. This, and other options, can be +changed with each linter's respective option variable. + +Linters/compilers that utilize a "work" directory for analyzing designs- such +as ModelSim and Vivado- can be passed the location of these directories as +part of their respective option strings listed below. This is useful for +holistic analysis of a file (e.g. a design with components, packages, or other +code defined external to the current file as part of a larger project) or +when wanting to simply pass an alternative location for the auto-generated +work directories (such as '/tmp') so as to not muddle the current directory. +Since these type of linters often use this work directory for holding compiled +design data as part of a single build process, they sometimes cannot handle +the frequent, asynchronous application launches when linting while text is +changing. This can happen in the form of hangs or crashes. To help prevent +this when using these linters, it may help to run linting less frequently; for +example, only when a file is saved. + +=============================================================================== +ghdl *ale-vhdl-ghdl* + +g:ale_vhdl_ghdl_executable *g:ale_vhdl_ghdl_executable* + *b:ale_vhdl_ghdl_executable* + Type: |String| + Default: `'ghdl'` + + This variable can be changed to the path to the 'ghdl' executable. + + +g:ale_vhdl_ghdl_options *g:ale_vhdl_ghdl_options* + *b:ale_vhdl_ghdl_options* + Type: |String| + Default: `'--std=08'` + + This variable can be changed to modify the flags/options passed to 'ghdl'. + + +=============================================================================== +vcom *ale-vhdl-vcom* + +g:ale_vhdl_vcom_executable *g:ale_vhdl_vcom_executable* + *b:ale_vhdl_vcom_executable* + Type: |String| + Default: `'vcom'` + + This variable can be changed to the path to the 'vcom' executable. + + +g:ale_vhdl_vcom_options *g:ale_vhdl_vcom_options* + *b:ale_vhdl_vcom_options* + Type: |String| + Default: `'-2008 -quiet -lint'` + + This variable can be changed to modify the flags/options passed to 'vcom'. + + +=============================================================================== +xvhdl *ale-vhdl-xvhdl* + +g:ale_vhdl_xvhdl_executable *g:ale_vhdl_xvhdl_executable* + *b:ale_vhdl_xvhdl_executable* + Type: |String| + Default: `'xvhdl'` + + This variable can be changed to the path to the 'xvhdl' executable. + + +g:ale_vhdl_xvhdl_options *g:ale_vhdl_xvhdl_options* + *b:ale_vhdl_xvhdl_options* + Type: |String| + Default: `'--2008'` + + This variable can be changed to modify the flags/options passed to 'xvhdl'. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale.txt b/doc/ale.txt index 68d685d9..b777e575 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -1,4 +1,4 @@ -*ale.txt* For Vim version 8.0. +*ale.txt* Plugin to lint and fix files asynchronously *ale* ALE - Asynchronous Lint Engine @@ -14,351 +14,17 @@ 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| - 5.4 Hovering..........................|ale-hover| - 5.5 Symbol Search.....................|ale-symbol-search| + 5.3 Go To Type Definition.............|ale-go-to-type-definition| + 5.4 Find References...................|ale-find-references| + 5.5 Hovering..........................|ale-hover| + 5.6 Symbol Search.....................|ale-symbol-search| 6. Global Options.......................|ale-options| 6.1 Highlights........................|ale-highlights| - 6.2 Options for write-good Linter.....|ale-write-good-options| - 7. Integration Documentation............|ale-integrations| - ada...................................|ale-ada-options| - gcc.................................|ale-ada-gcc| - ansible...............................|ale-ansible-options| - ansible-lint........................|ale-ansible-ansible-lint| - asciidoc..............................|ale-asciidoc-options| - write-good..........................|ale-asciidoc-write-good| - asm...................................|ale-asm-options| - gcc.................................|ale-asm-gcc| - awk...................................|ale-awk-options| - gawk................................|ale-awk-gawk| - bib...................................|ale-bib-options| - bibclean............................|ale-bib-bibclean| - c.....................................|ale-c-options| - clang...............................|ale-c-clang| - clangd..............................|ale-c-clangd| - clang-format........................|ale-c-clangformat| - clangtidy...........................|ale-c-clangtidy| - cppcheck............................|ale-c-cppcheck| - cquery..............................|ale-c-cquery| - flawfinder..........................|ale-c-flawfinder| - gcc.................................|ale-c-gcc| - uncrustify..........................|ale-c-uncrustify| - ccls................................|ale-c-ccls| - 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| - clang...............................|ale-cpp-clang| - clangd..............................|ale-cpp-clangd| - clangcheck..........................|ale-cpp-clangcheck| - clang-format........................|ale-cpp-clangformat| - clangtidy...........................|ale-cpp-clangtidy| - clazy...............................|ale-cpp-clazy| - cppcheck............................|ale-cpp-cppcheck| - cpplint.............................|ale-cpp-cpplint| - cquery..............................|ale-cpp-cquery| - flawfinder..........................|ale-cpp-flawfinder| - gcc.................................|ale-cpp-gcc| - uncrustify..........................|ale-cpp-uncrustify| - ccls................................|ale-cpp-ccls| - c#....................................|ale-cs-options| - mcs.................................|ale-cs-mcs| - mcsc................................|ale-cs-mcsc| - uncrustify..........................|ale-cs-uncrustify| - css...................................|ale-css-options| - prettier............................|ale-css-prettier| - stylelint...........................|ale-css-stylelint| - cuda..................................|ale-cuda-options| - nvcc................................|ale-cuda-nvcc| - d.....................................|ale-d-options| - dls.................................|ale-d-dls| - uncrustify..........................|ale-d-uncrustify| - dart..................................|ale-dart-options| - dartanalyzer........................|ale-dart-dartanalyzer| - dartfmt.............................|ale-dart-dartfmt| - dockerfile............................|ale-dockerfile-options| - dockerfile_lint.....................|ale-dockerfile-dockerfile_lint| - hadolint............................|ale-dockerfile-hadolint| - elixir................................|ale-elixir-options| - mix.................................|ale-elixir-mix| - mix_format..........................|ale-elixir-mix-format| - dialyxir............................|ale-elixir-dialyxir| - elixir-ls...........................|ale-elixir-elixir-ls| - elm...................................|ale-elm-options| - elm-format..........................|ale-elm-elm-format| - elm-make............................|ale-elm-elm-make| - erlang................................|ale-erlang-options| - erlc................................|ale-erlang-erlc| - syntaxerl...........................|ale-erlang-syntaxerl| - eruby.................................|ale-eruby-options| - ruumba..............................|ale-eruby-ruumba| - fish..................................|ale-fish-options| - fortran...............................|ale-fortran-options| - gcc.................................|ale-fortran-gcc| - language_server.....................|ale-fortran-language-server| - fountain..............................|ale-fountain-options| - fusionscript..........................|ale-fuse-options| - fusion-lint.........................|ale-fuse-fusionlint| - git commit............................|ale-gitcommit-options| - gitlint.............................|ale-gitcommit-gitlint| - glsl..................................|ale-glsl-options| - glslang.............................|ale-glsl-glslang| - glslls..............................|ale-glsl-glslls| - go....................................|ale-go-options| - gobuild.............................|ale-go-gobuild| - gofmt...............................|ale-go-gofmt| - golint..............................|ale-go-golint| - govet...............................|ale-go-govet| - gometalinter........................|ale-go-gometalinter| - staticcheck.........................|ale-go-staticcheck| - golangserver........................|ale-go-golangserver| - golangci-lint.......................|ale-go-golangci-lint| - graphql...............................|ale-graphql-options| - eslint..............................|ale-graphql-eslint| - gqlint..............................|ale-graphql-gqlint| - prettier............................|ale-graphql-prettier| - hack..................................|ale-hack-options| - hack................................|ale-hack-hack| - hackfmt.............................|ale-hack-hackfmt| - hhast...............................|ale-hack-hhast| - handlebars............................|ale-handlebars-options| - ember-template-lint.................|ale-handlebars-embertemplatelint| - haskell...............................|ale-haskell-options| - brittany............................|ale-haskell-brittany| - ghc.................................|ale-haskell-ghc| - ghc-mod.............................|ale-haskell-ghc-mod| - cabal-ghc...........................|ale-haskell-cabal-ghc| - hdevtools...........................|ale-haskell-hdevtools| - hfmt................................|ale-haskell-hfmt| - hlint...............................|ale-haskell-hlint| - stack-build.........................|ale-haskell-stack-build| - stylish-haskell.....................|ale-haskell-stylish-haskell| - hie.................................|ale-haskell-hie| - hcl...................................|ale-hcl-options| - terraform-fmt.......................|ale-hcl-terraform-fmt| - html..................................|ale-html-options| - htmlhint............................|ale-html-htmlhint| - tidy................................|ale-html-tidy| - prettier............................|ale-html-prettier| - stylelint...........................|ale-html-stylelint| - write-good..........................|ale-html-write-good| - idris.................................|ale-idris-options| - idris...............................|ale-idris-idris| - ispc..................................|ale-ispc-options| - ispc................................|ale-ispc-ispc| - java..................................|ale-java-options| - checkstyle..........................|ale-java-checkstyle| - javac...............................|ale-java-javac| - google-java-format..................|ale-java-google-java-format| - pmd.................................|ale-java-pmd| - javalsp.............................|ale-java-javalsp| - uncrustify..........................|ale-java-uncrustify| - javascript............................|ale-javascript-options| - eslint..............................|ale-javascript-eslint| - flow................................|ale-javascript-flow| - importjs............................|ale-javascript-importjs| - jscs................................|ale-javascript-jscs| - jshint..............................|ale-javascript-jshint| - prettier............................|ale-javascript-prettier| - prettier-eslint.....................|ale-javascript-prettier-eslint| - prettier-standard...................|ale-javascript-prettier-standard| - standard............................|ale-javascript-standard| - xo..................................|ale-javascript-xo| - json..................................|ale-json-options| - fixjson.............................|ale-json-fixjson| - jsonlint............................|ale-json-jsonlint| - jq..................................|ale-json-jq| - prettier............................|ale-json-prettier| - julia.................................|ale-julia-options| - languageserver......................|ale-julia-languageserver| - kotlin................................|ale-kotlin-options| - kotlinc.............................|ale-kotlin-kotlinc| - ktlint..............................|ale-kotlin-ktlint| - languageserver......................|ale-kotlin-languageserver| - latex.................................|ale-latex-options| - write-good..........................|ale-latex-write-good| - less..................................|ale-less-options| - lessc...............................|ale-less-lessc| - prettier............................|ale-less-prettier| - stylelint...........................|ale-less-stylelint| - llvm..................................|ale-llvm-options| - llc.................................|ale-llvm-llc| - lua...................................|ale-lua-options| - luac................................|ale-lua-luac| - luacheck............................|ale-lua-luacheck| - markdown..............................|ale-markdown-options| - mdl.................................|ale-markdown-mdl| - prettier............................|ale-markdown-prettier| - remark-lint.........................|ale-markdown-remark-lint| - 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| - clang...............................|ale-objc-clang| - clangd..............................|ale-objc-clangd| - uncrustify..........................|ale-objc-uncrustify| - ccls................................|ale-objc-ccls| - objcpp................................|ale-objcpp-options| - clang...............................|ale-objcpp-clang| - clangd..............................|ale-objcpp-clangd| - uncrustify..........................|ale-objcpp-uncrustify| - ocaml.................................|ale-ocaml-options| - merlin..............................|ale-ocaml-merlin| - ols.................................|ale-ocaml-ols| - ocamlformat.........................|ale-ocaml-ocamlformat| - pawn..................................|ale-pawn-options| - uncrustify..........................|ale-pawn-uncrustify| - perl..................................|ale-perl-options| - perl................................|ale-perl-perl| - perlcritic..........................|ale-perl-perlcritic| - perltidy............................|ale-perl-perltidy| - perl6.................................|ale-perl6-options| - perl6...............................|ale-perl6-perl6| - php...................................|ale-php-options| - langserver..........................|ale-php-langserver| - phan................................|ale-php-phan| - phpcbf..............................|ale-php-phpcbf| - phpcs...............................|ale-php-phpcs| - phpmd...............................|ale-php-phpmd| - phpstan.............................|ale-php-phpstan| - psalm...............................|ale-php-psalm| - php-cs-fixer........................|ale-php-php-cs-fixer| - php.................................|ale-php-php| - 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| - prolog................................|ale-prolog-options| - swipl...............................|ale-prolog-swipl| - proto.................................|ale-proto-options| - protoc-gen-lint.....................|ale-proto-protoc-gen-lint| - pug...................................|ale-pug-options| - puglint.............................|ale-pug-puglint| - puppet................................|ale-puppet-options| - puppet..............................|ale-puppet-puppet| - puppetlint..........................|ale-puppet-puppetlint| - puppet-languageserver...............|ale-puppet-languageserver| - pyrex (cython)........................|ale-pyrex-options| - cython..............................|ale-pyrex-cython| - 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| - pydocstyle..........................|ale-python-pydocstyle| - pyflakes............................|ale-python-pyflakes| - pylint..............................|ale-python-pylint| - pyls................................|ale-python-pyls| - pyre................................|ale-python-pyre| - vulture.............................|ale-python-vulture| - yapf................................|ale-python-yapf| - qml...................................|ale-qml-options| - qmlfmt..............................|ale-qml-qmlfmt| - r.....................................|ale-r-options| - lintr...............................|ale-r-lintr| - reasonml..............................|ale-reasonml-options| - merlin..............................|ale-reasonml-merlin| - ols.................................|ale-reasonml-ols| - refmt...............................|ale-reasonml-refmt| - restructuredtext......................|ale-restructuredtext-options| - write-good..........................|ale-restructuredtext-write-good| - ruby..................................|ale-ruby-options| - brakeman............................|ale-ruby-brakeman| - rails_best_practices................|ale-ruby-rails_best_practices| - reek................................|ale-ruby-reek| - rubocop.............................|ale-ruby-rubocop| - ruby................................|ale-ruby-ruby| - rufo................................|ale-ruby-rufo| - solargraph..........................|ale-ruby-solargraph| - rust..................................|ale-rust-options| - cargo...............................|ale-rust-cargo| - rls.................................|ale-rust-rls| - rustc...............................|ale-rust-rustc| - rustfmt.............................|ale-rust-rustfmt| - sass..................................|ale-sass-options| - sasslint............................|ale-sass-sasslint| - stylelint...........................|ale-sass-stylelint| - scala.................................|ale-scala-options| - sbtserver...........................|ale-scala-sbtserver| - scalafmt............................|ale-scala-scalafmt| - scalastyle..........................|ale-scala-scalastyle| - scss..................................|ale-scss-options| - prettier............................|ale-scss-prettier| - sasslint............................|ale-scss-sasslint| - 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| - sml...................................|ale-sml-options| - smlnj...............................|ale-sml-smlnj| - solidity..............................|ale-solidity-options| - solhint.............................|ale-solidity-solhint| - solium..............................|ale-solidity-solium| - spec..................................|ale-spec-options| - rpmlint.............................|ale-spec-rpmlint| - sql...................................|ale-sql-options| - sqlfmt..............................|ale-sql-sqlfmt| - stylus................................|ale-stylus-options| - stylelint...........................|ale-stylus-stylelint| - tcl...................................|ale-tcl-options| - nagelfar............................|ale-tcl-nagelfar| - terraform.............................|ale-terraform-options| - fmt.................................|ale-terraform-fmt| - tflint..............................|ale-terraform-tflint| - tex...................................|ale-tex-options| - chktex..............................|ale-tex-chktex| - lacheck.............................|ale-tex-lacheck| - 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| - typescript............................|ale-typescript-options| - eslint..............................|ale-typescript-eslint| - prettier............................|ale-typescript-prettier| - tslint..............................|ale-typescript-tslint| - tsserver............................|ale-typescript-tsserver| - vala..................................|ale-vala-options| - uncrustify..........................|ale-vala-uncrustify| - verilog/systemverilog.................|ale-verilog-options| - iverilog............................|ale-verilog-iverilog| - verilator...........................|ale-verilog-verilator| - vim...................................|ale-vim-options| - vint................................|ale-vim-vint| - vim help..............................|ale-vim-help-options| - write-good..........................|ale-vim-help-write-good| - vue...................................|ale-vue-options| - prettier............................|ale-vue-prettier| - vls.................................|ale-vue-vls| - xhtml.................................|ale-xhtml-options| - write-good..........................|ale-xhtml-write-good| - xml...................................|ale-xml-options| - xmllint.............................|ale-xml-xmllint| - yaml..................................|ale-yaml-options| - prettier............................|ale-yaml-prettier| - swaglint............................|ale-yaml-swaglint| - yamllint............................|ale-yaml-yamllint| - yang..................................|ale-yang-options| - yang-lsp............................|ale-yang-lsp| + 7. Linter/Fixer Options.................|ale-integration-options| + 7.1 Options for alex..................|ale-alex-options| + 7.2 Options for languagetool..........|ale-languagetool-options| + 7.3 Options for write-good............|ale-write-good-options| + 7.4 Other Linter/Fixer Options........|ale-other-integration-options| 8. Commands/Keybinds....................|ale-commands| 9. API..................................|ale-api| 10. Special Thanks......................|ale-special-thanks| @@ -394,124 +60,8 @@ developer documentation. See |ale-development| =============================================================================== 2. Supported Languages & Tools *ale-support* -The following languages and tools are supported. - -Notes: - -`^` No linters for text or Vim help filetypes are enabled by default. -`!!` These linters check only files on disk. See |ale-lint-file-linters| - -* Ada: `gcc` -* ASM: `gcc` -* Ansible: `ansible-lint` -* API Blueprint: `drafter` -* AsciiDoc: `alex`!!, `proselint`, `redpen`, `write-good`, `vale` -* Awk: `gawk` -* Bash: `language-server`, `shell` (-n flag), `shellcheck`, `shfmt` -* BibTeX: `bibclean` -* Bourne Shell: `shell` (-n flag), `shellcheck`, `shfmt` -* C: `cppcheck`, `cpplint`!!, `clang`, `clangd`, `clangtidy`!!, `clang-format`, `cquery`, `flawfinder`, `gcc`, `uncrustify`, `ccls` -* C++ (filetype cpp): `clang`, `clangd`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `clazy`!!, `cppcheck`, `cpplint`!!, `cquery`, `flawfinder`, `gcc`, `uncrustify`, `ccls` -* CUDA: `nvcc`!! -* C#: `mcs`, `mcsc`!!, `uncrustify` -* 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: `dls`, `dmd`, `uncrustify` -* Dafny: `dafny`!! -* Dart: `dartanalyzer`!!, `language_server`, dartfmt!! -* Dockerfile: `dockerfile_lint`, `hadolint` -* Elixir: `credo`, `dialyxir`, `dogma`, `mix`!!, `elixir-ls` -* Elm: `elm-format, elm-make` -* Erb: `erb`, `erubi`, `erubis`, `ruumba` -* Erlang: `erlc`, `SyntaxErl` -* Fish: `fish` (-n flag) -* Fortran: `gcc`, `language_server` -* Fountain: `proselint` -* FusionScript: `fusion-lint` -* Git Commit Messages: `gitlint` -* GLSL: glslang, `glslls` -* Go: `gofmt`, `goimports`, `go mod`!!, `go vet`!!, `golint`, `gotype`!!, `gometalinter`!!, `go build`!!, `gosimple`!!, `staticcheck`!!, `golangserver`, `golangci-lint`!! -* GraphQL: `eslint`, `gqlint`, `prettier` -* Hack: `hack`, `hackfmt`, `hhast` -* Haml: `haml-lint` -* Handlebars: `ember-template-lint` -* Haskell: `brittany`, `ghc`, `cabal-ghc`, `stylish-haskell`, `stack-ghc`, `stack-build`!!, `ghc-mod`, `hlint`, `hdevtools`, `hfmt`, `hie` -* HCL: `terraform-fmt` -* HTML: `alex`!!, `HTMLHint`, `proselint`, `tidy`, `prettier`, `write-good` -* Idris: `idris` -* ISPC: `ispc`!! -* Java: `checkstyle`, `javac`, `google-java-format`, `PMD`, `javalsp`, `uncrustify` -* JavaScript: `eslint`, `flow`, `jscs`, `jshint`, `prettier`, `prettier-eslint`, `prettier-standard`, `standard`, `xo` -* JSON: `fixjson`, `jsonlint`, `jq`, `prettier` -* Julia: `languageserver` -* Kotlin: `kotlinc`!!, `ktlint`!!, `languageserver` -* LaTeX (tex): `alex`!!, `chktex`, `lacheck`, `proselint`, `redpen`, `vale`, `write-good` -* Less: `lessc`, `prettier`, `stylelint` -* LLVM: `llc` -* Lua: `luac`, `luacheck` -* Mail: `alex`!!, `proselint`, `vale` -* Make: `checkmake` -* 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`, `clangd`, `uncrustify`, `ccls` -* Objective-C++: `clang`, `clangd`, `uncrustify` -* OCaml: `merlin` (see |ale-ocaml-merlin|), `ols`, `ocamlformat` -* Pawn: `uncrustify` -* Perl: `perl -c`, `perl-critic`, `perltidy` -* Perl6: `perl6 -c` -* PHP: `langserver`, `phan`, `php -l`, `phpcs`, `phpmd`, `phpstan`, `phpcbf`, `php-cs-fixer`, `psalm`!! -* PO: `alex`!!, `msgfmt`, `proselint`, `write-good` -* Pod: `alex`!!, `proselint`, `write-good` -* Pony: `ponyc` -* Prolog: `swipl` -* proto: `protoc-gen-lint` -* Pug: `pug-lint` -* Puppet: `languageserver`, `puppet`, `puppet-lint` -* Python: `autopep8`, `black`, `flake8`, `isort`, `mypy`, `prospector`, `pycodestyle`, `pydocstyle`, `pyls`, `pyre`, `pylint`!!, `vulture`!!, `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`, `rufo`, `solargraph` -* Rust: `cargo`!!, `rls`, `rustc` (see |ale-integration-rust|), `rustfmt` -* SASS: `sass-lint`, `stylelint` -* SCSS: `prettier`, `sass-lint`, `scss-lint`, `stylelint` -* Scala: `fsc`, `sbtserver`, `scalac`, `scalafmt`, `scalastyle` -* Slim: `slim-lint` -* SML: `smlnj` -* Solidity: `solhint`, `solium` -* Stylus: `stylelint` -* SQL: `sqlint`, `sqlfmt` -* Swift: `swiftlint`, `swiftformat` -* Tcl: `nagelfar`!! -* Terraform: `fmt`, `tflint` -* Texinfo: `alex`!!, `proselint`, `write-good` -* Text^: `alex`!!, `proselint`, `redpen`, `textlint`, `vale`, `write-good` -* Thrift: `thrift` -* TypeScript: `eslint`, `prettier`, `tslint`, `tsserver`, `typecheck` -* VALA: `uncrustify` -* Verilog: `iverilog`, `verilator` -* Vim: `vint` -* Vim help^: `alex`!!, `proselint`, `write-good` -* Vue: `prettier`, `vls` -* XHTML: `alex`!!, `proselint`, `write-good` -* XML: `xmllint` -* YAML: `prettier`, `swaglint`, `yamllint` -* YANG: `yang-lsp` +ALE supports a wide variety of languages and tools. See |ale-supported-list| +for the full list. =============================================================================== 3. Linting *ale-lint* @@ -676,8 +226,8 @@ for a function set in the ALE fixer registry. Each function for fixing errors must accept either one argument `(buffer)` or two arguments `(buffer, lines)`, representing the buffer being fixed and the lines to fix. The functions must return either `0`, for changing nothing, a -|List| for new lines to set, or a |Dictionary| for describing a command to be -run in the background. +|List| for new lines to set, a |Dictionary| for describing a command to be +run in the background, or the result of |ale#command#Run()|. Functions receiving a variable number of arguments will not receive the second argument `lines`. Functions should name two arguments if the `lines` argument @@ -707,26 +257,6 @@ are supported for running the commands. A |List| of |String|s must be returned. - `chain_with` An optional key for defining a callback to call next. - - The callback must accept two or three arguments, - `(buffer, output)` or `(buffer, output, input)` . - Functions receiving a variable number of arguments will - only receive the first two values. The `output` argument - will contain the lines of output from the command run. - The `input` argument is the List of lines for the - buffer, after applying any previous fixers. - - The callback must return the same values returned for - any fixer function. This allows fixer functions to be - chained recursively. - - When the command string returned for a fixer is an empty - string, the next command in the chain will still be run. - This allows commands to be skipped, like version checks - that are cached. An empty List will be passed to the - next callback in the chain for the `output`. - `read_buffer` An optional key for disabling reading the buffer. When set to `0`, ALE will not pipe the buffer's data @@ -734,10 +264,7 @@ are supported for running the commands. the buffer is not read when `read_temporary_file` is `1`. - This option defaults to `0` when `chain_with` is defined - as anything other than `v:null`, and defaults to `1` - otherwise. This is so earlier commands in a chain - do not receive the buffer's data by default. + This option defaults to `1`. *ale-fix-configuration* @@ -782,6 +309,9 @@ by default. |g:ale_fix_on_save| - Fix files when they are saved. +Fixers can be disabled on save with |g:ale_fix_on_save_ignore|. They will +still be run when you manually run |ALEFix|. + =============================================================================== 5. Language Server Protocol Support *ale-lsp* @@ -791,37 +321,44 @@ servers. LSP linters can be used in combination with any other linter, and will automatically connect to LSP servers when needed. ALE also supports `tsserver` for TypeScript, which uses a different but very similar protocol. -ALE supports the following LSP/tsserver features: - -1. Diagnostics/linting - Enabled via selecting linters as usual. -2. Completion -3. Go to definition - ------------------------------------------------------------------------------- 5.1 Completion *ale-completion* -ALE offers limited support for automatic completion of code while you type. +ALE offers support for automatic completion of code while you type. Completion is only supported while at least one LSP linter is enabled. ALE will only suggest symbols provided by the LSP servers. -Suggestions will be made while you type after completion is enabled. -Completion can be enabled by setting |g:ale_completion_enabled| to `1`. This -setting must be set to `1` before ALE is loaded. The delay for completion can -be configured with |g:ale_completion_delay|. ALE will only suggest so many -possible matches for completion. The maximum number of items can be controlled -with |g:ale_completion_max_suggestions|. + *ale-deoplete-integration* + +ALE integrates with Deoplete for offering automatic completion data. ALE's +completion source for Deoplete is named `'ale'`, and should enabled +automatically if Deoplete is enabled and configured correctly. Deoplete +integration should not be combined with ALE's own implementation. + +ALE also offers its own completion implementation, which does not require any +other plugins. Suggestions will be made while you type after completion is +enabled. Completion can be enabled by setting |g:ale_completion_enabled| to +`1`. This setting must be set to `1` before ALE is loaded. The delay for +completion can be configured with |g:ale_completion_delay|. + +ALE will only suggest so many possible matches for completion. The maximum +number of items can be controlled with |g:ale_completion_max_suggestions|. 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|. - *ale-completion-completopt-bug* +The |ALEComplete| command can be used to show completion suggestions manually, +even when |g:ale_completion_enabled| is set to `0`. For manually requesting +completion information with Deoplete, consult Deoplete's documentation. -ALE implements completion as you type by temporarily adjusting |completeopt| -before opening the omnicomplete menu with <C-x><C-o>. In some versions of Vim, -the value set for the option will not be respected. If you experience issues -with Vim automatically inserting text while you type, set the following option -in vimrc, and your issues should go away. > + *ale-completion-completeopt-bug* + +ALE Automatic completion implementation replaces |completeopt| before opening +the omnicomplete menu with <C-x><C-o>. In some versions of Vim, the value set +for the option will not be respected. If you experience issues with Vim +automatically inserting text while you type, set the following option in +vimrc, and your issues should go away. > set completeopt=menu,menuone,preview,noselect,noinsert < @@ -835,10 +372,29 @@ information returned by LSP servers. The following commands are supported: |ALEGoToDefinition| - Open the definition of the symbol under the cursor. |ALEGoToDefinitionInTab| - The same, but for opening the file in a new tab. +|ALEGoToDefinitionInSplit| - The same, but in a new split. +|ALEGoToDefinitionInVSplit| - The same, but in a new vertical split. +ALE will update Vim's |tagstack| automatically unless |g:ale_update_tagstack| is +set to `0`. ------------------------------------------------------------------------------- -5.3 Find References *ale-find-references* +5.3 Go To Type Definition *ale-go-to-type-definition* + +ALE supports jumping to the files and locations where symbols' types are +defined through any enabled LSP linters. The locations ALE will jump to depend +on the information returned by LSP servers. The following commands are +supported: + +|ALEGoToTypeDefinition| - Open the definition of the symbol's type under + the cursor. +|ALEGoToTypeDefinitionInTab| - The same, but for opening the file in a new tab. +|ALEGoToTypeDefinitionInSplit| - The same, but in a new split. +|ALEGoToTypeDefinitionInVSplit| - The same, but in a new vertical split. + + +------------------------------------------------------------------------------- +5.4 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 @@ -847,9 +403,11 @@ supported: |ALEFindReferences| - Find references for the word under the cursor. +Options: + `-relative` Show file paths in the results relative to the working dir ------------------------------------------------------------------------------- -5.4 Hovering *ale-hover* +5.5 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: @@ -876,14 +434,19 @@ settings. For example: > set ttymouse=xterm < +Documentation for symbols at the cursor can be retrieved using the +|ALEDocumentation| command. This command is only available for `tsserver`. + ------------------------------------------------------------------------------- -5.5 Symbol Search *ale-symbol-search* +5.6 Symbol Search *ale-symbol-search* ALE supports searching for workspace symbols via LSP linters. The following commands are supported: |ALESymbolSearch| - Search for symbols in the workspace. +Options: + `-relative` Show file paths in the results relative to the working dir =============================================================================== 6. Global Options *ale-options* @@ -1058,6 +621,16 @@ g:ale_cursor_detail *g:ale_cursor_detail* loaded for messages to be displayed. See |ale-lint-settings-on-startup|. +g:ale_disable_lsp *g:ale_disable_lsp* + *b:ale_disable_lsp* + + Type: |Number| + Default: `0` + + When this option is set to `1`, ALE ignores all linters powered by LSP. + Please see also |ale-lsp|. + + g:ale_echo_cursor *g:ale_echo_cursor* Type: |Number| @@ -1207,6 +780,43 @@ b:ale_fix_on_save *b:ale_fix_on_save* Fixing files can be disabled or enabled for individual buffers by setting `b:ale_fix_on_save` to `0` or `1`. + Some fixers can be excluded from being run automatically when you save files + with the |g:ale_fix_on_save_ignore| setting. + + +g:ale_fix_on_save_ignore *g:ale_fix_on_save_ignore* +b:ale_fix_on_save_ignore *b:ale_fix_on_save_ignore* + + Type: |Dictionary| or |List| + Default: `{}` + + Given a |Dictionary| mapping filetypes to |Lists| of fixers to ignore, or + just a |List| of fixers to ignore, exclude those fixers from being run + automatically when files are saved. + + You can disable some fixers in your ftplugin file: > + + " Disable fixers 'b' and 'c' when fixing on safe for this buffer. + let b:ale_fix_on_save_ignore = ['b', 'c'] + " Alternatively, define ignore lists for different filetypes. + let b:ale_fix_on_save_ignore = {'foo': ['b'], 'bar': ['c']} +< + You can disable some fixers globally per filetype like so: > + + let g:ale_fixers = {'foo': ['a', 'b'], 'bar': ['c', 'd']} + let g:ale_fix_on_save = 1 + " For filetype `foo.bar`, only fixers 'b' and 'd' will be run on save. + let g:ale_fix_on_save_ignore = {'foo': ['a'], 'bar': ['c']} + " Alternatively, disable these fixers on save for all filetypes. + let g:ale_fix_on_save_ignore = ['a', 'c'] +< + You can ignore fixers based on matching |Funcref| values too: > + + let g:AddBar = {buffer, lines -> lines + ['bar']} + let g:ale_fixers = {'foo': g:AddBar} + " The lambda fixer will be ignored, as it will be found in the ignore list. + let g:ale_fix_on_save_ignore = [g:AddBar] +< g:ale_history_enabled *g:ale_history_enabled* @@ -1431,7 +1041,7 @@ g:ale_linters *g:ale_linters* { \ 'csh': ['shell'], - \ 'elixir': ['credo', 'dialyxir', 'dogma', 'elixir-ls'], + \ 'elixir': ['credo', 'dialyxir', 'dogma'], \ 'go': ['gofmt', 'golint', 'go vet'], \ 'hack': ['hack'], \ 'help': [], @@ -1540,6 +1150,22 @@ b:ale_loclist_msg_format *b:ale_loclist_msg_format* The strings for configuring `%severity%` are also used for this option. +g:ale_lsp_root *g:ale_lsp_root* +b:ale_lsp_root *b:ale_lsp_root* + + Type: |Dictionary| or |String| + Default: {} + + This option is used to determine the project root for the LSP linter. If the + value is a |Dictionary|, it maps a linter to either a string containing the + project root or a |Funcref| to call to look up the root. The funcref is + provided the buffer number as its argument. + + The buffer-specific variable may additionally be a string containing the + project root itself. + + If neither variable yields a result, a linter-specific function is invoked to + detect a project root. If this, too, yields no result, the linter is disabled. g:ale_max_buffer_history_size *g:ale_max_buffer_history_size* @@ -1783,6 +1409,33 @@ g:ale_set_signs *g:ale_set_signs* To limit the number of signs ALE will set, see |g:ale_max_signs|. +g:ale_shell *g:ale_shell* + + Type: |String| + Default: not set + + Override the shell used by ALE for executing commands. ALE uses 'shell' by + default, but falls back in `/bin/sh` if the default shell looks like `fish` + or `pwsh`, which are not compatible with all of the commands run by ALE. The + shell specified with this option will be used even if it might not work in + all cases. + + For Windows, ALE uses `cmd` when this option isn't set. Setting this option + will apply shell escaping to the command string, even on Windows. + + NOTE: Consider setting |g:ale_shell_arguments| if this option is defined. + + +g:ale_shell_arguments *g:ale_shell_arguments* + + Type: |String| + Default: not set + + This option specifies the arguments to use for executing a command with a + custom shell, per |g:ale_shell|. If this option is not set, 'shellcmdflag' + will be used instead. + + g:ale_sign_column_always *g:ale_sign_column_always* Type: |Number| @@ -1848,6 +1501,14 @@ g:ale_sign_warning *g:ale_sign_warning* The sign for warnings in the sign gutter. +g:ale_update_tagstack *g:ale_update_tagstack* + *b:ale_update_tagstack* + Type: |Number| + Default: `1` + + This option can be set to disable updating Vim's |tagstack| automatically. + + g:ale_type_map *g:ale_type_map* *b:ale_type_map* Type: |Dictionary| @@ -2129,11 +1790,79 @@ ALEWarningSign *ALEWarningSign* The highlight for warning signs. See |g:ale_set_signs|. +=============================================================================== +7. Linter/Fixer Options *ale-integration-options* + +Linter and fixer options are documented below and in individual help files. + +Every option for programs can be set globally, or individually for each +buffer. For example, `b:ale_python_flake8_executable` will override any +values set for `g:ale_python_flake8_executable`. + + *ale-integrations-local-executables* + +Some tools will prefer to search for locally-installed executables, unless +configured otherwise. For example, the `eslint` linter will search for +various executable paths in `node_modules`. The `flake8` linter will search +for virtualenv directories. + +If you prefer to use global executables for those tools, set the relevant +`_use_global` and `_executable` options for those linters. > + + " Use the global executable with a special name for eslint. + let g:ale_javascript_eslint_executable = 'special-eslint' + let g:ale_javascript_eslint_use_global = 1 + + " Use the global executable with a special name for flake8. + let g:ale_python_flake8_executable = '/foo/bar/flake8' + 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. + + ------------------------------------------------------------------------------- -6.2. Options for write-good *ale-write-good-options* +7.1. Options for alex *ale-alex-options* + +The options for `alex` are shared between all filetypes, so options can be +configured once. -The options for the write-good linter are global because it does not make -sense to have them specified on a per-language basis. +g:ale_alex_executable *g:ale_alex_executable* + *b:ale_alex_executable* + Type: |String| + Default: `'alex'` + + See |ale-integrations-local-executables| + + +g:ale_alex_use_global *g:ale_alex_use_global* + *b:ale_alex_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +------------------------------------------------------------------------------- +7.2. Options for languagetool *ale-languagetool-options* + +g:ale_languagetool_executable *g:ale_languagetool_executable* + *b:ale_languagetool_executable* + + Type: |String| + Default: `'languagetool'` + + The executable to run for languagetool. + + +------------------------------------------------------------------------------- +7.3. Options for write-good *ale-write-good-options* + +The options for `write-good` are shared between all filetypes, so options can +be configured once. g:ale_writegood_executable *g:ale_writegood_executable* *b:ale_writegood_executable* @@ -2159,44 +1888,412 @@ g:ale_writegood_use_global *g:ale_writegood_use_global* See |ale-integrations-local-executables| -=============================================================================== -7. Integration Documentation *ale-integrations* - -Linter and fixer options are documented in individual help files. See the -table of contents at |ale-contents|. +------------------------------------------------------------------------------- +7.4. Other Linter/Fixer Options *ale-other-integration-options* + +ALE supports a very wide variety of tools. Other linter or fixer options are +documented in additional help files. + + ada.....................................|ale-ada-options| + gcc...................................|ale-ada-gcc| + ansible.................................|ale-ansible-options| + ansible-lint..........................|ale-ansible-ansible-lint| + asciidoc................................|ale-asciidoc-options| + write-good............................|ale-asciidoc-write-good| + textlint..............................|ale-asciidoc-textlint| + asm.....................................|ale-asm-options| + gcc...................................|ale-asm-gcc| + awk.....................................|ale-awk-options| + gawk..................................|ale-awk-gawk| + bib.....................................|ale-bib-options| + bibclean..............................|ale-bib-bibclean| + c.......................................|ale-c-options| + clang.................................|ale-c-clang| + clangd................................|ale-c-clangd| + clang-format..........................|ale-c-clangformat| + clangtidy.............................|ale-c-clangtidy| + cppcheck..............................|ale-c-cppcheck| + cquery................................|ale-c-cquery| + flawfinder............................|ale-c-flawfinder| + gcc...................................|ale-c-gcc| + uncrustify............................|ale-c-uncrustify| + ccls..................................|ale-c-ccls| + chef....................................|ale-chef-options| + cookstyle.............................|ale-chef-cookstyle| + foodcritic............................|ale-chef-foodcritic| + clojure.................................|ale-clojure-options| + clj-kondo.............................|ale-clojure-clj-kondo| + joker.................................|ale-clojure-joker| + cloudformation..........................|ale-cloudformation-options| + cfn-python-lint.......................|ale-cloudformation-cfn-python-lint| + cmake...................................|ale-cmake-options| + cmakelint.............................|ale-cmake-cmakelint| + cmake-format..........................|ale-cmake-cmakeformat| + cpp.....................................|ale-cpp-options| + clang.................................|ale-cpp-clang| + clangd................................|ale-cpp-clangd| + clangcheck............................|ale-cpp-clangcheck| + clang-format..........................|ale-cpp-clangformat| + clangtidy.............................|ale-cpp-clangtidy| + clazy.................................|ale-cpp-clazy| + cppcheck..............................|ale-cpp-cppcheck| + cpplint...............................|ale-cpp-cpplint| + cquery................................|ale-cpp-cquery| + flawfinder............................|ale-cpp-flawfinder| + gcc...................................|ale-cpp-gcc| + uncrustify............................|ale-cpp-uncrustify| + ccls..................................|ale-cpp-ccls| + c#......................................|ale-cs-options| + mcs...................................|ale-cs-mcs| + mcsc..................................|ale-cs-mcsc| + uncrustify............................|ale-cs-uncrustify| + css.....................................|ale-css-options| + fecs..................................|ale-css-fecs| + prettier..............................|ale-css-prettier| + stylelint.............................|ale-css-stylelint| + cuda....................................|ale-cuda-options| + nvcc..................................|ale-cuda-nvcc| + clang-format..........................|ale-cuda-clangformat| + d.......................................|ale-d-options| + dls...................................|ale-d-dls| + uncrustify............................|ale-d-uncrustify| + dart....................................|ale-dart-options| + dartanalyzer..........................|ale-dart-dartanalyzer| + dartfmt...............................|ale-dart-dartfmt| + dockerfile..............................|ale-dockerfile-options| + dockerfile_lint.......................|ale-dockerfile-dockerfile_lint| + hadolint..............................|ale-dockerfile-hadolint| + elixir..................................|ale-elixir-options| + mix...................................|ale-elixir-mix| + mix_format............................|ale-elixir-mix-format| + dialyxir..............................|ale-elixir-dialyxir| + elixir-ls.............................|ale-elixir-elixir-ls| + credo.................................|ale-elixir-credo| + elm.....................................|ale-elm-options| + elm-format............................|ale-elm-elm-format| + elm-lsp...............................|ale-elm-elm-lsp| + elm-make..............................|ale-elm-elm-make| + erlang..................................|ale-erlang-options| + erlc..................................|ale-erlang-erlc| + syntaxerl.............................|ale-erlang-syntaxerl| + eruby...................................|ale-eruby-options| + ruumba................................|ale-eruby-ruumba| + fish....................................|ale-fish-options| + fortran.................................|ale-fortran-options| + gcc...................................|ale-fortran-gcc| + language_server.......................|ale-fortran-language-server| + fountain................................|ale-fountain-options| + fusionscript............................|ale-fuse-options| + fusion-lint...........................|ale-fuse-fusionlint| + git commit..............................|ale-gitcommit-options| + gitlint...............................|ale-gitcommit-gitlint| + glsl....................................|ale-glsl-options| + glslang...............................|ale-glsl-glslang| + glslls................................|ale-glsl-glslls| + go......................................|ale-go-options| + bingo.................................|ale-go-bingo| + gobuild...............................|ale-go-gobuild| + gofmt.................................|ale-go-gofmt| + golangci-lint.........................|ale-go-golangci-lint| + golangserver..........................|ale-go-golangserver| + golint................................|ale-go-golint| + gometalinter..........................|ale-go-gometalinter| + gopls.................................|ale-go-gopls| + govet.................................|ale-go-govet| + staticcheck...........................|ale-go-staticcheck| + graphql.................................|ale-graphql-options| + eslint................................|ale-graphql-eslint| + gqlint................................|ale-graphql-gqlint| + prettier..............................|ale-graphql-prettier| + hack....................................|ale-hack-options| + hack..................................|ale-hack-hack| + hackfmt...............................|ale-hack-hackfmt| + hhast.................................|ale-hack-hhast| + handlebars..............................|ale-handlebars-options| + ember-template-lint...................|ale-handlebars-embertemplatelint| + haskell.................................|ale-haskell-options| + brittany..............................|ale-haskell-brittany| + floskell..............................|ale-haskell-floskell| + ghc...................................|ale-haskell-ghc| + ghc-mod...............................|ale-haskell-ghc-mod| + cabal-ghc.............................|ale-haskell-cabal-ghc| + hdevtools.............................|ale-haskell-hdevtools| + hfmt..................................|ale-haskell-hfmt| + hlint.................................|ale-haskell-hlint| + stack-build...........................|ale-haskell-stack-build| + stack-ghc.............................|ale-haskell-stack-ghc| + stylish-haskell.......................|ale-haskell-stylish-haskell| + hie...................................|ale-haskell-hie| + hcl.....................................|ale-hcl-options| + terraform-fmt.........................|ale-hcl-terraform-fmt| + html....................................|ale-html-options| + fecs..................................|ale-html-fecs| + htmlhint..............................|ale-html-htmlhint| + tidy..................................|ale-html-tidy| + prettier..............................|ale-html-prettier| + stylelint.............................|ale-html-stylelint| + write-good............................|ale-html-write-good| + idris...................................|ale-idris-options| + idris.................................|ale-idris-idris| + ispc....................................|ale-ispc-options| + ispc..................................|ale-ispc-ispc| + java....................................|ale-java-options| + checkstyle............................|ale-java-checkstyle| + javac.................................|ale-java-javac| + google-java-format....................|ale-java-google-java-format| + pmd...................................|ale-java-pmd| + javalsp...............................|ale-java-javalsp| + eclipselsp............................|ale-java-eclipselsp| + uncrustify............................|ale-java-uncrustify| + javascript..............................|ale-javascript-options| + eslint................................|ale-javascript-eslint| + fecs..................................|ale-javascript-fecs| + flow..................................|ale-javascript-flow| + importjs..............................|ale-javascript-importjs| + jscs..................................|ale-javascript-jscs| + jshint................................|ale-javascript-jshint| + prettier..............................|ale-javascript-prettier| + prettier-eslint.......................|ale-javascript-prettier-eslint| + prettier-standard.....................|ale-javascript-prettier-standard| + standard..............................|ale-javascript-standard| + xo....................................|ale-javascript-xo| + json....................................|ale-json-options| + fixjson...............................|ale-json-fixjson| + jsonlint..............................|ale-json-jsonlint| + jq....................................|ale-json-jq| + prettier..............................|ale-json-prettier| + julia...................................|ale-julia-options| + languageserver........................|ale-julia-languageserver| + kotlin..................................|ale-kotlin-options| + kotlinc...............................|ale-kotlin-kotlinc| + ktlint................................|ale-kotlin-ktlint| + languageserver........................|ale-kotlin-languageserver| + latex...................................|ale-latex-options| + write-good............................|ale-latex-write-good| + textlint..............................|ale-latex-textlint| + less....................................|ale-less-options| + lessc.................................|ale-less-lessc| + prettier..............................|ale-less-prettier| + stylelint.............................|ale-less-stylelint| + llvm....................................|ale-llvm-options| + llc...................................|ale-llvm-llc| + lua.....................................|ale-lua-options| + luac..................................|ale-lua-luac| + luacheck..............................|ale-lua-luacheck| + markdown................................|ale-markdown-options| + mdl...................................|ale-markdown-mdl| + prettier..............................|ale-markdown-prettier| + remark-lint...........................|ale-markdown-remark-lint| + 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| + clang.................................|ale-objc-clang| + clangd................................|ale-objc-clangd| + uncrustify............................|ale-objc-uncrustify| + ccls..................................|ale-objc-ccls| + objcpp..................................|ale-objcpp-options| + clang.................................|ale-objcpp-clang| + clangd................................|ale-objcpp-clangd| + uncrustify............................|ale-objcpp-uncrustify| + ocaml...................................|ale-ocaml-options| + merlin................................|ale-ocaml-merlin| + ols...................................|ale-ocaml-ols| + ocamlformat...........................|ale-ocaml-ocamlformat| + ocp-indent............................|ale-ocaml-ocp-indent| + pawn....................................|ale-pawn-options| + uncrustify............................|ale-pawn-uncrustify| + perl....................................|ale-perl-options| + perl..................................|ale-perl-perl| + perlcritic............................|ale-perl-perlcritic| + perltidy..............................|ale-perl-perltidy| + perl6...................................|ale-perl6-options| + perl6.................................|ale-perl6-perl6| + php.....................................|ale-php-options| + langserver............................|ale-php-langserver| + phan..................................|ale-php-phan| + phpcbf................................|ale-php-phpcbf| + phpcs.................................|ale-php-phpcs| + phpmd.................................|ale-php-phpmd| + phpstan...............................|ale-php-phpstan| + psalm.................................|ale-php-psalm| + php-cs-fixer..........................|ale-php-php-cs-fixer| + php...................................|ale-php-php| + 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| + powershell............................|ale-powershell-options| + powershell..........................|ale-powershell-powershell| + psscriptanalyzer....................|ale-powershell-psscriptanalyzer| + prolog..................................|ale-prolog-options| + swipl.................................|ale-prolog-swipl| + proto...................................|ale-proto-options| + protoc-gen-lint.......................|ale-proto-protoc-gen-lint| + pug.....................................|ale-pug-options| + puglint...............................|ale-pug-puglint| + puppet..................................|ale-puppet-options| + puppet................................|ale-puppet-puppet| + puppetlint............................|ale-puppet-puppetlint| + puppet-languageserver.................|ale-puppet-languageserver| + pyrex (cython)..........................|ale-pyrex-options| + cython................................|ale-pyrex-cython| + python..................................|ale-python-options| + autopep8..............................|ale-python-autopep8| + bandit................................|ale-python-bandit| + black.................................|ale-python-black| + flake8................................|ale-python-flake8| + isort.................................|ale-python-isort| + mypy..................................|ale-python-mypy| + prospector............................|ale-python-prospector| + pycodestyle...........................|ale-python-pycodestyle| + pydocstyle............................|ale-python-pydocstyle| + pyflakes..............................|ale-python-pyflakes| + pylama................................|ale-python-pylama| + pylint................................|ale-python-pylint| + pyls..................................|ale-python-pyls| + pyre..................................|ale-python-pyre| + vulture...............................|ale-python-vulture| + yapf..................................|ale-python-yapf| + qml.....................................|ale-qml-options| + qmlfmt................................|ale-qml-qmlfmt| + r.......................................|ale-r-options| + lintr.................................|ale-r-lintr| + styler................................|ale-r-styler| + reasonml................................|ale-reasonml-options| + merlin................................|ale-reasonml-merlin| + ols...................................|ale-reasonml-ols| + refmt.................................|ale-reasonml-refmt| + restructuredtext........................|ale-restructuredtext-options| + textlint..............................|ale-restructuredtext-textlint| + write-good............................|ale-restructuredtext-write-good| + ruby....................................|ale-ruby-options| + brakeman..............................|ale-ruby-brakeman| + rails_best_practices..................|ale-ruby-rails_best_practices| + reek..................................|ale-ruby-reek| + rubocop...............................|ale-ruby-rubocop| + ruby..................................|ale-ruby-ruby| + rufo..................................|ale-ruby-rufo| + solargraph............................|ale-ruby-solargraph| + standardrb............................|ale-ruby-standardrb| + rust....................................|ale-rust-options| + cargo.................................|ale-rust-cargo| + rls...................................|ale-rust-rls| + rustc.................................|ale-rust-rustc| + rustfmt...............................|ale-rust-rustfmt| + sass....................................|ale-sass-options| + sasslint..............................|ale-sass-sasslint| + stylelint.............................|ale-sass-stylelint| + scala...................................|ale-scala-options| + sbtserver.............................|ale-scala-sbtserver| + scalafmt..............................|ale-scala-scalafmt| + scalastyle............................|ale-scala-scalastyle| + scss....................................|ale-scss-options| + prettier..............................|ale-scss-prettier| + sasslint..............................|ale-scss-sasslint| + 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| + sml.....................................|ale-sml-options| + smlnj.................................|ale-sml-smlnj| + solidity................................|ale-solidity-options| + solhint...............................|ale-solidity-solhint| + solium................................|ale-solidity-solium| + spec....................................|ale-spec-options| + rpmlint...............................|ale-spec-rpmlint| + sql.....................................|ale-sql-options| + sqlfmt................................|ale-sql-sqlfmt| + stylus..................................|ale-stylus-options| + stylelint.............................|ale-stylus-stylelint| + sugarss.................................|ale-sugarss-options| + stylelint.............................|ale-sugarss-stylelint| + swift...................................|ale-swift-options| + sourcekitlsp..........................|ale-swift-sourcekitlsp| + tcl.....................................|ale-tcl-options| + nagelfar..............................|ale-tcl-nagelfar| + terraform...............................|ale-terraform-options| + fmt...................................|ale-terraform-fmt| + tflint................................|ale-terraform-tflint| + tex.....................................|ale-tex-options| + chktex................................|ale-tex-chktex| + lacheck...............................|ale-tex-lacheck| + latexindent...........................|ale-tex-latexindent| + 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| + typescript..............................|ale-typescript-options| + eslint................................|ale-typescript-eslint| + prettier..............................|ale-typescript-prettier| + tslint................................|ale-typescript-tslint| + tsserver..............................|ale-typescript-tsserver| + vala....................................|ale-vala-options| + uncrustify............................|ale-vala-uncrustify| + verilog/systemverilog...................|ale-verilog-options| + iverilog..............................|ale-verilog-iverilog| + verilator.............................|ale-verilog-verilator| + vlog..................................|ale-verilog-vlog| + xvlog.................................|ale-verilog-xvlog| + vhdl....................................|ale-vhdl-options| + ghdl..................................|ale-vhdl-ghdl| + vcom..................................|ale-vhdl-vcom| + xvhdl.................................|ale-vhdl-xvhdl| + vim.....................................|ale-vim-options| + vint..................................|ale-vim-vint| + vim help................................|ale-vim-help-options| + write-good............................|ale-vim-help-write-good| + vue.....................................|ale-vue-options| + prettier..............................|ale-vue-prettier| + vls...................................|ale-vue-vls| + xhtml...................................|ale-xhtml-options| + write-good............................|ale-xhtml-write-good| + xml.....................................|ale-xml-options| + xmllint...............................|ale-xml-xmllint| + yaml....................................|ale-yaml-options| + prettier..............................|ale-yaml-prettier| + swaglint..............................|ale-yaml-swaglint| + yamllint..............................|ale-yaml-yamllint| + yang....................................|ale-yang-options| + yang-lsp..............................|ale-yang-lsp| -Every option for programs can be set globally, or individually for each -buffer. For example, `b:ale_python_flake8_executable` will override any -values set for `g:ale_python_flake8_executable`. - *ale-integrations-local-executables* +=============================================================================== +8. Commands/Keybinds *ale-commands* -Some tools will prefer to search for locally-installed executables, unless -configured otherwise. For example, the `eslint` linter will search for -various executable paths in `node_modules`. The `flake8` linter will search -for virtualenv directories. +ALEComplete *ALEComplete* -If you prefer to use global executables for those tools, set the relevant -`_use_global` and `_executable` options for those linters. > + Manually trigger LSP autocomplete and show the menu. Works only when called + from insert mode. > - " Use the global executable with a special name for eslint. - let g:ale_javascript_eslint_executable = 'special-eslint' - let g:ale_javascript_eslint_use_global = 1 + inoremap <silent> <C-Space> <C-\><C-O>:AleComplete<CR> +< + A plug mapping `<Plug>(ale_complete)` is defined for this command. > - " Use the global executable with a special name for flake8. - let g:ale_python_flake8_executable = '/foo/bar/flake8' - let g:ale_python_flake8_use_global = 1 + imap <C-Space> <Plug>(ale_complete) < +ALEDocumentation *ALEDocumentation* -|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. + Similar to the |ALEHover| command, retrieve documentation information for + the symbol at the cursor. Documentation data will always be shown in a + preview window, no matter how small the documentation content is. -The option |g:ale_virtualenv_dir_names| controls the local virtualenv paths -ALE will use to search for Python executables. + NOTE: This command is only available for `tsserver`. + A plug mapping `<Plug>(ale_documentation)` is defined for this command. -=============================================================================== -8. Commands/Keybinds *ale-commands* ALEFindReferences *ALEFindReferences* @@ -2247,6 +2344,61 @@ ALEGoToDefinitionInTab *ALEGoToDefinitionInTab* command. +ALEGoToDefinitionInSplit *ALEGoToDefinitionInSplit* + + The same as |ALEGoToDefinition|, but opens results in a new split. + + A plug mapping `<Plug>(ale_go_to_definition_in_split)` is defined for this + command. + + +ALEGoToDefinitionInVSplit *ALEGoToDefinitionInVSplit* + + The same as |ALEGoToDefinition|, but opens results in a new vertical split. + + A plug mapping `<Plug>(ale_go_to_definition_in_vsplit)` is defined for this + command. + + +ALEGoToTypeDefinition *ALEGoToTypeDefinition* + + This works similar to |ALEGoToDefinition| but instead jumps to the + definition of a type of a symbol under the cursor. 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_type_definition)` is defined for this + command. + + +ALEGoToTypeDefinitionInTab *ALEGoToTypeDefinitionInTab* + + The same as |ALEGoToTypeDefinition|, but opens results in a new tab. + + A plug mapping `<Plug>(ale_go_to_type_definition_in_tab)` is defined for + this command. + + +ALEGoToTypeDefinitionInSplit *ALEGoToTypeDefinitionInSplit* + + The same as |ALEGoToTypeDefinition|, but opens results in a new split. + + A plug mapping `<Plug>(ale_go_to_type_definition_in_split)` is defined for + this command. + + +ALEGoToTypeDefinitionInVSplit *ALEGoToTypeDefinitionInVSplit* + + The same as |ALEGoToTypeDefinition|, but opens results in a new vertical + split. + + A plug mapping `<Plug>(ale_go_to_type_definition_in_vsplit)` is defined for + this command. + + ALEHover *ALEHover* Print brief information about the symbol under the cursor, taken from any @@ -2295,14 +2447,36 @@ ALELast *ALELast* `ALEPreviousWrap` and `ALENextWrap` will wrap around the file to find the last or first warning or error in the file, respectively. + `ALEPrevious` and `ALENext` take optional flags arguments to custom their + behaviour : + `-wrap` enable wrapping around the file + `-error`, `-warning` and `-info` enable jumping to errors, warnings or infos + respectively, ignoring anything else. They are mutually exclusive and if + several are provided the priority is the following: error > warning > info. + `-style` and `-nostyle` allow you to jump respectively to style error or + warning and to not style error or warning. They also are mutually + exclusive and nostyle has priority over style. + + Flags can be combined to create create custom jumping. Thus you can use + ":ALENext -wrap -error -nosyle" to jump to the next error which is not a + style error while going back to the begining of the file if needed. + `ALEFirst` goes to the first error or warning in the buffer, while `ALELast` goes to the last one. The following |<Plug>| mappings are defined for the commands: > <Plug>(ale_previous) - ALEPrevious <Plug>(ale_previous_wrap) - ALEPreviousWrap + <Plug>(ale_previous_error) - ALEPrevious -error + <Plug>(ale_previous_wrap_error) - ALEPrevious -wrap -error + <Plug>(ale_previous_warning) - ALEPrevious -warning + <Plug>(ale_previous_wrap_warning) - ALEPrevious -wrap -warning <Plug>(ale_next) - ALENext <Plug>(ale_next_wrap) - ALENextWrap + <Plug>(ale_next_error) - ALENext -error + <Plug>(ale_next_wrap_error) - ALENext -wrap -error + <Plug>(ale_next_warning) - ALENext -warning + <Plug>(ale_next_wrap_warning) - ALENext -wrap -warning <Plug>(ale_first) - ALEFirst <Plug>(ale_last) - ALELast < @@ -2434,6 +2608,14 @@ ale#Env(variable_name, value) *ale#Env()* 'set VAR="some value" && command' # On Windows +ale#Has(feature) *ale#Has()* + + Return `1` if ALE supports a given feature, like |has()| for Vim features. + + ALE versions can be checked with version strings in the format + `ale#Has('ale-x.y.z')`, such as `ale#Has('ale-2.4.0')`. + + ale#Pad(string) *ale#Pad()* Given a string or any |empty()| value, return either the string prefixed @@ -2462,26 +2644,105 @@ ale#Queue(delay, [linting_flag, buffer_number]) *ale#Queue()* is broken, or when developing ALE itself. -ale#engine#CreateDirectory(buffer) *ale#engine#CreateDirectory()* +ale#command#CreateDirectory(buffer) *ale#command#CreateDirectory()* Create a new temporary directory with a unique name, and manage that - directory with |ale#engine#ManageDirectory()|, so it will be removed as soon + directory with |ale#command#ManageDirectory()|, so it will be removed as soon as possible. It is advised to only call this function from a callback function for returning a linter command to run. -ale#engine#CreateFile(buffer) *ale#engine#CreateFile()* +ale#command#CreateFile(buffer) *ale#command#CreateFile()* Create a new temporary file with a unique name, and manage that file with - |ale#engine#ManageFile()|, so it will be removed as soon as possible. + |ale#command#ManageFile()|, so it will be removed as soon as possible. It is advised to only call this function from a callback function for returning a linter command to run. -ale#engine#EscapeCommandPart(command_part) *ale#engine#EscapeCommandPart()* +ale#command#Run(buffer, command, callback, [options]) *ale#command#Run()* + + Start running a job in the background, and pass the results to the given + callback later. + + This function can be used for computing the results of ALE linter or fixer + functions asynchronously with jobs. `buffer` must match the buffer being + linted or fixed, `command` must be a |String| for a shell command to + execute, `callback` must be defined as a |Funcref| to call later with the + results, and an optional |Dictionary| of `options` can be provided. + + The `callback` will receive the arguments `(buffer, output, metadata)`, + where the `buffer` will match the buffer given to the function, the `output` + will be a `List` of lines of output from the job that was run, and the + `metadata` will be a |Dictionary| with additional information about the job + that was run, including: + + `exit_code` - A |Number| with the exit code for the program that was run. + + The result of this function is either a special |Dictionary| ALE will use + for waiting for the command to finish, or `0` if the job is not started. The + The return value of the `callback` will be used as the eventual result for + whatever value is being given to ALE. For example: > + + function! s:GetCommand(buffer, output, meta) abort + " Do something with a:output here, from the foo command. + + " This is used as the command to run for linting. + return 'final command' + endfunction + + " ... + + 'command': {b -> ale#command#Run(b, 'foo', function('s:GetCommand'))} +< + The result of a callback can also be the result of another call to this + function, so that several commands can be arbitrarily chained together. For + example: > + + function! s:GetAnotherCommand(buffer, output, meta) abort + " We can finally return this command. + return 'last command' + endfunction + + function! s:GetCommand(buffer, output, meta) abort + " We can return another deferred result. + return ale#command#Run( + \ a:buffer, + \ 'second command', + \ function('s:GetAnotherCommand') + \) + endfunction + + " ... + + 'command': {b -> ale#command#Run(b, 'foo', function('s:GetCommand'))} +< + + The following `options` can be provided. + + `output_stream` - Either `'stdout'`, `'stderr'`, `'both'`, or `'none`' for + selecting which output streams to read lines from. + + The default is `'stdout'` + + `executable` - An executable for formatting into `%e` in the command. + If this option is not provided, formatting commands with + `%e` will not work. + + `read_buffer` - If set to `1`, the buffer will be piped into the + command. + + The default is `0`. + + `input` - When creating temporary files with `%t` or piping text + into a command `input` can be set to a |List| of text to + use instead of the buffer's text. + + +ale#command#EscapeCommandPart(command_part) *ale#command#EscapeCommandPart()* Given a |String|, return a |String| with all `%` characters replaced with `%%` instead. This function can be used to escape strings which are @@ -2490,27 +2751,22 @@ ale#engine#EscapeCommandPart(command_part) *ale#engine#EscapeCommandPart()* specially. -ale#engine#GetLoclist(buffer) *ale#engine#GetLoclist()* +ale#command#ManageDirectory(buffer, directory) *ale#command#ManageDirectory()* - Given a buffer number, this function will return the list of problems - reported by ALE for a given buffer in the format accepted by |setqflist()|. - - A reference to the buffer's list of problems will be returned. The list must - 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. + Like |ale#command#ManageFile()|, but directories and all of their contents + will be deleted, akin to `rm -rf directory`, which could lead to loss of + data if mistakes are made. This command will also delete any temporary + filenames given to it. - This function can be used for status lines, tab names, etc. + It is advised to use |ale#command#ManageFile()| instead for deleting single + files. -ale#engine#ManageFile(buffer, filename) *ale#engine#ManageFile()* +ale#command#ManageFile(buffer, filename) *ale#command#ManageFile()* - Given a buffer number for a buffer currently running some linting tasks - and a filename, register a filename with ALE for automatic deletion after - linting is complete, or when Vim exits. + Given a buffer number for a buffer currently running some linting or fixing + tasks and a filename, register a filename with ALE for automatic deletion + after linting or fixing is complete, or when Vim exits. If Vim exits suddenly, ALE will try its best to remove temporary files, but ALE cannot guarantee with absolute certainty that the files will be removed. @@ -2521,18 +2777,23 @@ ale#engine#ManageFile(buffer, filename) *ale#engine#ManageFile()* files and symlinks given to this function. This is to prevent entire directories from being accidentally deleted, say in cases of writing `dir . '/' . filename` where `filename` is actually `''`, etc. ALE instead - manages directories separetly with the |ale#engine#ManageDirectory| function. + manages directories separately with the |ale#command#ManageDirectory| function. -ale#engine#ManageDirectory(buffer, directory) *ale#engine#ManageDirectory()* +ale#engine#GetLoclist(buffer) *ale#engine#GetLoclist()* - Like |ale#engine#ManageFile()|, but directories and all of their contents - will be deleted, akin to `rm -rf directory`, which could lead to loss of - data if mistakes are made. This command will also delete any temporary - filenames given to it. + Given a buffer number, this function will return the list of problems + reported by ALE for a given buffer in the format accepted by |setqflist()|. - It is advised to use |ale#engine#ManageFile()| instead for deleting single - files. + A reference to the buffer's list of problems will be returned. The list must + 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#fix#registry#Add(name, func, filetypes, desc, [aliases]) @@ -2624,7 +2885,7 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* and have been checked at least once. Temporary files in directories used for Vim - temporary files with `tempname()` will be asssumed + temporary files with `tempname()` will be assumed to be the buffer being checked, unless the `bufnr` key is also set with a valid number for some other buffer. @@ -2647,74 +2908,30 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* Human-readable |String| error code. `executable` A |String| naming the executable itself which - will be run. This value will be used to check if the - program requested is installed or not. + will be run, or a |Funcref| for a function to call + for computing the executable, accepting a buffer + number. + + The result can be computed with |ale#command#Run()|. - Either this or the `executable_callback` argument - must be provided. + This value will be used to check if the program + requested is installed or not. - `executable_callback ` A |String| or |Funcref| for a callback function - accepting a buffer number. A |String| should be - returned for the executable to check. This can be - used in place of `executable` when more complicated - processing is needed. + If an `executable` is not defined, the command will + be run without checking if a program is executable + first. Defining an executable path is recommended to + avoid starting too many processes. + + `command` A |String| for a command to run asynchronously, or a + |Funcref| for a function to call for computing the + command, accepting a buffer number. + + The result can be computed with |ale#command#Run()|. - `command` A |String| for an executable to run asynchronously. This command will be fed the lines from the buffer to check, and will produce the lines of output given to the `callback`. - `command_callback` A |String| or |Funcref| for a callback function - accepting a buffer number. A |String| should be - returned for a command to run. This can be used in - place of `command` when more complicated processing - is needed. - - If an empty string is returned from the callback, - no jobs for linting will be run for that linter. - This can be used for skipping a linter call, - say if no configuration file was found. - - *ale-command-chain* - `command_chain` A |List| of |Dictionary| items defining a series - of commands to be run. At least one |Dictionary| - should be provided. Each Dictionary must contain the - key `callback`, defining a |String| or |Funcref| for - a function returning a |String| for a command to run. - - The callback functions for each command after the - first command in in the chain should accept two - arguments `(buffer, output)`, a buffer number and a - |List| of lines of output from the previous command - in the chain. - - The first callback function in a chain accepts only - a `(buffer)` argument, as there are no previous - commands to run which return `output`. - - If an empty string is returned for a command in a - chain, that command in the chain will be skipped, - and the next function in the chain will be called - immediately instead. If the last command in a chain - returns an empty string, then no linting will be - performed. - - Commands in the chain will all use the - `output_stream` value provided in the root - |Dictionary|. Each command in the chain can also - provide an `output_stream` key to override this value. - See the `output_stream` description for more - information. - - Commands in the chain all behave as if `read_buffer` - is set to `0` by default, except for the last command - in the chain, which uses the value set for - `read_buffer` in the root |Dictionary|. Each command - in the chain can also provide a `read_buffer` key - to override these values. - See the `read_buffer` description for more - information. - `output_stream` A |String| for the output stream the lines of output should be read from for the command which is run. The accepted values are `'stdout'`, `'stderr'`, and @@ -2760,21 +2977,20 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* linter will be defined as an LSP linter which keeps a process for a language server running, and communicates with it directly via a |channel|. - `executable` or `executable_callback` must be set, - and `command` or `command_callback` must be set. + `executable` and `command` must be set. When this argument is set to `'socket'`, then the linter will be defined as an LSP linter via a TCP - socket connection. `address_callback` must be set - with a callback returning an address to connect to. + socket connection. `address` must be set. + ALE will not start a server automatically. - When this argument is not empty - `project_root_callback` must be defined. + When this argument is not empty `project_root` must + be defined. - `language` or `language_callback` can be defined to - describe the language for a file. The filetype will - be used as the language by default. + `language` can be defined to describe the language + for a file. The filetype will be used as the language + by default. LSP linters handle diagnostics automatically, so the `callback` argument must not be defined. @@ -2782,45 +2998,41 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* 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. + `initialization_options` may be defined to pass + initialization options to the LSP. - An optional `lsp_config` or `lsp_config_callback` may - be defined to pass configuration settings to the LSP. + `lsp_config` may be defined to pass configuration + settings to the LSP. - `address_callback` A |String| or |Funcref| for a callback function - accepting a buffer number. A |String| should be - returned with an address to connect to. + `address` A |String| representing an address to connect to, + or a |Funcref| accepting a buffer number and + returning the |String|. + + The result can be computed with |ale#command#Run()|. This argument must only be set if the `lsp` argument is set to `'socket'`. - `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 - empty string is returned, the file will not be + `project_root` A |String| representing a path to the project for + the file being checked with the language server, or + a |Funcref| accepting a buffer number and returning + the |String|. + + If an empty string is returned, the file will not be checked at all. This argument must only be set if the `lsp` argument is also set to a non-empty string. `language` 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. - - If this or `language_callback` isn't set, the - language will default to the value of the filetype - given to |ale#linter#Define|. - - `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. + being checked, or a |Funcref| accepting a buffer + number and returning the |String|. This string will + be sent to the LSP to tell it what type of language + is being checked. - This option can be used instead of `language` if a - linter can check multiple languages. + If a language isn't provided, the language will + default to the value of the filetype given to + |ale#linter#Define|. `completion_filter` A |String| or |Funcref| for a callback function accepting a buffer number and a completion item. @@ -2838,38 +3050,24 @@ 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. + `initialization_options` A |Dictionary| of initialization options for LSPs, + or a |Funcref| for a callback function accepting + a buffer number and returning the |Dictionary|. + 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. + `lsp_config` A |Dictionary| for configuring a language server, + or a |Funcref| for a callback function accepting + a buffer number and returning the |Dictionary|. - `lsp_config` A |Dictionary| of configuration settings for LSPs. This will be fed (as JSON) to the LSP in the workspace/didChangeConfiguration command. - `lsp_config_callback` A |String| or |Funcref| for a callback function - accepting a buffer number. A |Dictionary| should be - returned for configuration settings to pass the LSP. - This can be used in place of `lsp_config` 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. - `command_chain` is recommended where any system calls need to be made to - retrieve some kind of information before running the final command. - If temporary files or directories are created for commands run with - `command_callback` or `command_chain`, then these tempoary files or - directories can be managed by ALE, for automatic deletion. - See |ale#engine#ManageFile()| and |ale#engine#ManageDirectory| for more - information. + `command`, then these temporary files or directories can be managed by ALE, + for automatic deletion. See |ale#command#ManageFile()| and + |ale#command#ManageDirectory| for more information. *ale-command-format-strings* @@ -2887,16 +3085,16 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* strings will reference the one temporary file. The temporary file will be created inside a temporary directory, and the entire temporary directory will be automatically deleted, following the behaviour of - |ale#engine#ManageDirectory|. This option can be used for some linters which + |ale#command#ManageDirectory|. This option can be used for some linters which do not support reading from stdin. For example: > 'command': 'ghc -fno-code -v0 %t', < Any substring `%e` will be replaced with the escaped executable supplied - with `executable` or `executable_callback`. This provides a convenient way - to define a command string which needs to include a dynamic executable name, - but which is otherwise static. + with `executable`. This provides a convenient way to define a command string + which needs to include a dynamic executable name, but which is otherwise + static. For example: > 'command': '%e --some-argument', @@ -2907,7 +3105,7 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* If a callback for a command generates part of a command string which might possibly contain `%%`, `%s`, `%t`, or `%e`, where the special formatting - behavior is not desired, the |ale#engine#EscapeCommandPart()| function can + behavior is not desired, the |ale#command#EscapeCommandPart()| function can be used to replace those characters to avoid formatting issues. *ale-linter-loading-behavior* @@ -2993,6 +3191,21 @@ ale#statusline#Count(buffer) *ale#statusline#Count()* `total` -> The total number of problems. +ale#statusline#FirstProblem(buffer, type) *ale#statusline#FirstProblem()* + + Returns a copy of the first entry in the `loclist` that matches the supplied + buffer number and problem type. If there is no such enty, an empty dictionary + is returned. + Problem type should be one of the strings listed below: + + `error` -> Returns the first `loclist` item with type `E` and + `sub_type != 'style'` + `warning` -> First item with type `W` and `sub_type != 'style'` + `info` -> First item with type `I` + `style_error` -> First item with type `E` and `sub_type == 'style'` + `style_warning` -> First item with type `W` and `sub_type == 'style'` + + b:ale_linted *b:ale_linted* `b:ale_linted` is set to the number of times a buffer has been checked by diff --git a/plugin/ale.vim b/plugin/ale.vim index 7231d1bd..cf39d632 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -87,6 +87,9 @@ 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) +" This Dictionary configures the default LSP roots for various linters. +let g:ale_lsp_root = get(g:, 'ale_lsp_root', {}) + " 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) @@ -148,9 +151,12 @@ if g:ale_completion_enabled endif " Define commands for moving through warnings and errors. -command! -bar ALEPrevious :call ale#loclist_jumping#Jump('before', 0) +command! -bar -nargs=* ALEPrevious +\ :call ale#loclist_jumping#WrapJump('before', <q-args>) +command! -bar -nargs=* ALENext +\ :call ale#loclist_jumping#WrapJump('after', <q-args>) + command! -bar ALEPreviousWrap :call ale#loclist_jumping#Jump('before', 1) -command! -bar ALENext :call ale#loclist_jumping#Jump('after', 0) command! -bar ALENextWrap :call ale#loclist_jumping#Jump('after', 1) command! -bar ALEFirst :call ale#loclist_jumping#JumpToIndex(0) command! -bar ALELast :call ale#loclist_jumping#JumpToIndex(-1) @@ -188,23 +194,43 @@ command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype) " Go to definition for tsserver and LSP command! -bar ALEGoToDefinition :call ale#definition#GoTo({}) -command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in_tab': 1}) +command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in': 'tab'}) +command! -bar ALEGoToDefinitionInSplit :call ale#definition#GoTo({'open_in': 'horizontal-split'}) +command! -bar ALEGoToDefinitionInVSplit :call ale#definition#GoTo({'open_in': 'vertical-split'}) + +" Go to type definition for tsserver and LSP +command! -bar ALEGoToTypeDefinition :call ale#definition#GoToType({}) +command! -bar ALEGoToTypeDefinitionInTab :call ale#definition#GoToType({'open_in': 'tab'}) +command! -bar ALEGoToTypeDefinitionInSplit :call ale#definition#GoToType({'open_in': 'horizontal-split'}) +command! -bar ALEGoToTypeDefinitionInVSplit :call ale#definition#GoToType({'open_in': 'vertical-split'}) " Find references for tsserver and LSP -command! -bar ALEFindReferences :call ale#references#Find() +command! -bar -nargs=* ALEFindReferences :call ale#references#Find(<f-args>) -" Get information for the cursor. -command! -bar ALEHover :call ale#hover#Show(bufnr(''), getcurpos()[1], - \ getcurpos()[2], {}) +" Show summary information for the cursor. +command! -bar ALEHover :call ale#hover#ShowAtCursor() + +" Show documentation for the cursor. +command! -bar ALEDocumentation :call ale#hover#ShowDocumentationAtCursor() " Search for appearances of a symbol, such as a type name or function name. command! -nargs=1 ALESymbolSearch :call ale#symbol#Search(<q-args>) +command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual') + " <Plug> mappings for commands nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return> nnoremap <silent> <Plug>(ale_previous_wrap) :ALEPreviousWrap<Return> +nnoremap <silent> <Plug>(ale_previous_error) :ALEPrevious -error<Return> +nnoremap <silent> <Plug>(ale_previous_wrap_error) :ALEPrevious -wrap -error<Return> +nnoremap <silent> <Plug>(ale_previous_warning) :ALEPrevious -warning<Return> +nnoremap <silent> <Plug>(ale_previous_wrap_warning) :ALEPrevious -wrap -warning<Return> nnoremap <silent> <Plug>(ale_next) :ALENext<Return> nnoremap <silent> <Plug>(ale_next_wrap) :ALENextWrap<Return> +nnoremap <silent> <Plug>(ale_next_error) :ALENext -error<Return> +nnoremap <silent> <Plug>(ale_next_wrap_error) :ALENext -wrap -error<Return> +nnoremap <silent> <Plug>(ale_next_warning) :ALENext -warning<Return> +nnoremap <silent> <Plug>(ale_next_wrap_warning) :ALENext -wrap -warning<Return> nnoremap <silent> <Plug>(ale_first) :ALEFirst<Return> nnoremap <silent> <Plug>(ale_last) :ALELast<Return> nnoremap <silent> <Plug>(ale_toggle) :ALEToggle<Return> @@ -220,8 +246,16 @@ 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_go_to_definition_in_split) :ALEGoToDefinitionInSplit<Return> +nnoremap <silent> <Plug>(ale_go_to_definition_in_vsplit) :ALEGoToDefinitionInVSplit<Return> +nnoremap <silent> <Plug>(ale_go_to_type_definition) :ALEGoToTypeDefinition<Return> +nnoremap <silent> <Plug>(ale_go_to_type_definition_in_tab) :ALEGoToTypeDefinitionInTab<Return> +nnoremap <silent> <Plug>(ale_go_to_type_definition_in_split) :ALEGoToTypeDefinitionInSplit<Return> +nnoremap <silent> <Plug>(ale_go_to_type_definition_in_vsplit) :ALEGoToTypeDefinitionInVSplit<Return> nnoremap <silent> <Plug>(ale_find_references) :ALEFindReferences<Return> nnoremap <silent> <Plug>(ale_hover) :ALEHover<Return> +nnoremap <silent> <Plug>(ale_documentation) :ALEDocumentation<Return> +inoremap <silent> <Plug>(ale_complete) <C-\><C-O>:ALEComplete<Return> " Set up autocmd groups now. call ale#events#Init() @@ -235,6 +269,6 @@ augroup ALECleanupGroup autocmd QuitPre * call ale#events#QuitEvent(str2nr(expand('<abuf>'))) if exists('##VimSuspend') - autocmd VimSuspend * if exists('*ale#engine#CleanupEveryBuffer') | call ale#engine#CleanupEveryBuffer() | endif + autocmd VimSuspend * if exists('*ale#engine#CleanupEveryBuffer') | call ale#engine#CleanupEveryBuffer() | endif endif augroup END diff --git a/rplugin/python3/deoplete/sources/ale.py b/rplugin/python3/deoplete/sources/ale.py new file mode 100644 index 00000000..1addfae3 --- /dev/null +++ b/rplugin/python3/deoplete/sources/ale.py @@ -0,0 +1,50 @@ +""" +A Deoplete source for ALE completion via tsserver and LSP. +""" +__author__ = 'Joao Paulo, w0rp' + +try: + from deoplete.source.base import Base +except ImportError: + # Mock the Base class if deoplete isn't available, as mock isn't available + # in the Docker image. + class Base(object): + def __init__(self, vim): + pass + + +# Make sure this code is valid in Python 2, used for running unit tests. +class Source(Base): + + def __init__(self, vim): + super(Source, self).__init__(vim) + + self.name = 'ale' + self.mark = '[L]' + self.rank = 100 + self.is_bytepos = True + self.min_pattern_length = 1 + + # Returns an integer for the start position, as with omnifunc. + def get_completion_position(self): + return self.vim.call('ale#completion#GetCompletionPosition') + + def gather_candidates(self, context): + if context.get('is_refresh'): + context['is_async'] = False + + if context['is_async']: + # Result is the same as for omnifunc, or None. + result = self.vim.call('ale#completion#GetCompletionResult') + + if result is not None: + context['is_async'] = False + + return result + else: + context['is_async'] = True + + # Request some completion results. + self.vim.call('ale#completion#GetCompletions', 'deoplete') + + return [] diff --git a/supported-tools.md b/supported-tools.md new file mode 100644 index 00000000..18d69388 --- /dev/null +++ b/supported-tools.md @@ -0,0 +1,493 @@ +# ALE Supported Languages and Tools + +This plugin supports the following languages and tools. All available +tools will be run in combination, so they can be complementary. + +<!-- +Keep the table rows sorted alphabetically by the language name, +and the tools in the tools column sorted alphabetically by the tool +name. That seems to be the fairest way to arrange this table. + +Remember to also update doc/ale.txt, which has a similar list with different +formatting. +--> + +**Legend** + +| Key | Definition | +| ------------- | -------------------------------- | +| :floppy_disk: | Only checked when saved to disk | +| :warning: | Disabled by default | + +--- + +* Ada + * [gcc](https://gcc.gnu.org) +* Ansible + * [ansible-lint](https://github.com/willthames/ansible-lint) +* API Blueprint + * [drafter](https://github.com/apiaryio/drafter) +* AsciiDoc + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [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) +* ASM + * [gcc](https://gcc.gnu.org) +* Awk + * [gawk](https://www.gnu.org/software/gawk/) +* 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) +* BibTeX + * [bibclean](http://ftp.math.utah.edu/pub/bibclean/) +* Bourne Shell + * shell [-n flag](http://linux.die.net/man/1/sh) + * [shellcheck](https://www.shellcheck.net/) + * [shfmt](https://github.com/mvdan/sh) +* C + * [ccls](https://github.com/MaskRay/ccls) + * [clang](http://clang.llvm.org/) + * [clangd](https://clang.llvm.org/extra/clangd.html) + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [clangtidy](http://clang.llvm.org/extra/clang-tidy/) :floppy_disk: + * [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/) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* 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/) :floppy_disk: see:`help ale-cs-mcsc` for details and configuration + * [uncrustify](https://github.com/uncrustify/uncrustify) +* C++ (filetype cpp) + * [ccls](https://github.com/MaskRay/ccls) + * [clang](http://clang.llvm.org/) + * [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) :floppy_disk: + * [clangd](https://clang.llvm.org/extra/clangd.html) + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [clangtidy](http://clang.llvm.org/extra/clang-tidy/) :floppy_disk: + * [clazy](https://github.com/KDE/clazy) :floppy_disk: + * [cppcheck](http://cppcheck.sourceforge.net) + * [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) :floppy_disk: + * [cquery](https://github.com/cquery-project/cquery) + * [flawfinder](https://www.dwheeler.com/flawfinder/) + * [gcc](https://gcc.gnu.org/) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* Chef + * [cookstyle](https://docs.chef.io/cookstyle.html) + * [foodcritic](http://www.foodcritic.io/) +* Clojure + * [clj-kondo](https://github.com/borkdude/clj-kondo) + * [joker](https://github.com/candid82/joker) +* CloudFormation + * [cfn-python-lint](https://github.com/awslabs/cfn-python-lint) +* CMake + * [cmake-format](https://github.com/cheshirekow/cmake_format) + * [cmakelint](https://github.com/richq/cmake-lint) +* CoffeeScript + * [coffee](http://coffeescript.org/) + * [coffeelint](https://www.npmjs.com/package/coffeelint) +* Crystal + * [ameba](https://github.com/veelenga/ameba) :floppy_disk: + * [crystal](https://crystal-lang.org/) :floppy_disk: +* CSS + * [csslint](http://csslint.net/) + * [fecs](http://fecs.baidu.com/) + * [prettier](https://github.com/prettier/prettier) + * [stylelint](https://github.com/stylelint/stylelint) +* Cucumber + * [cucumber](https://cucumber.io/) +* CUDA + * [nvcc](http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html) +* Cypher + * [cypher-lint](https://github.com/cleishm/libcypher-parser) +* Cython (pyrex filetype) + * [cython](http://cython.org/) +* D + * [dls](https://github.com/d-language-server/dls) + * [dmd](https://dlang.org/dmd-linux.html) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* Dafny + * [dafny](https://rise4fun.com/Dafny) :floppy_disk: +* Dart + * [dartanalyzer](https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli) :floppy_disk: + * [dartfmt](https://github.com/dart-lang/sdk/tree/master/utils/dartfmt) + * [language_server](https://github.com/natebosch/dart_language_server) +* Dockerfile + * [dockerfile_lint](https://github.com/projectatomic/dockerfile_lint) + * [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-ls](https://github.com/JakeBecker/elixir-ls) :warning: + * [mix](https://hexdocs.pm/mix/Mix.html) :warning: :floppy_disk: +* Elm + * [elm-format](https://github.com/avh4/elm-format) + * [elm-lsp](https://github.com/antew/elm-lsp) + * [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) + * [ruumba](https://github.com/ericqweinstein/ruumba) +* Erlang + * [erlc](http://erlang.org/doc/man/erlc.html) + * [SyntaxErl](https://github.com/ten0s/syntaxerl) +* Fish + * fish [-n flag](https://linux.die.net/man/1/fish) +* Fortran + * [gcc](https://gcc.gnu.org/) + * [language_server](https://github.com/hansec/fortran-language-server) +* Fountain + * [proselint](http://proselint.com/) +* 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 + * [bingo](https://github.com/saibing/bingo) :warning: + * [go build](https://golang.org/cmd/go/) :warning: :floppy_disk: + * [gofmt](https://golang.org/cmd/gofmt/) + * [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) :warning: + * [golangci-lint](https://github.com/golangci/golangci-lint) :warning: :floppy_disk: + * [golangserver](https://github.com/sourcegraph/go-langserver) :warning: + * [golint](https://godoc.org/github.com/golang/lint) + * [gometalinter](https://github.com/alecthomas/gometalinter) :warning: :floppy_disk: + * [go mod](https://golang.org/cmd/go/) :warning: :floppy_disk: + * [gopls](https://github.com/golang/go/wiki/gopls) :warning: + * [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple) :warning: :floppy_disk: + * [gotype](https://godoc.org/golang.org/x/tools/cmd/gotype) :warning: :floppy_disk: + * [go vet](https://golang.org/cmd/vet/) :floppy_disk: + * [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) :warning: :floppy_disk: +* GraphQL + * [eslint](http://eslint.org/) + * [gqlint](https://github.com/happylinks/gqlint) + * [prettier](https://github.com/prettier/prettier) +* Hack + * [hack](http://hacklang.org/) + * [hackfmt](https://github.com/facebook/hhvm/tree/master/hphp/hack/hackfmt) + * [hhast](https://github.com/hhvm/hhast) :warning: (see `:help ale-integration-hack`) +* 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) + * [cabal-ghc](https://www.haskell.org/cabal/) + * [floskell](https://github.com/ennocramer/floskell) + * [ghc](https://www.haskell.org/ghc/) + * [ghc-mod](https://github.com/DanielG/ghc-mod) + * [hdevtools](https://hackage.haskell.org/package/hdevtools) + * [hfmt](https://github.com/danstiner/hfmt) + * [hie](https://github.com/haskell/haskell-ide-engine) + * [hlint](https://hackage.haskell.org/package/hlint) + * [stack-build](https://haskellstack.org/) :floppy_disk: + * [stack-ghc](https://haskellstack.org/) + * [stylish-haskell](https://github.com/jaspervdj/stylish-haskell) +* HCL + * [terraform-fmt](https://github.com/hashicorp/terraform) +* HTML + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [fecs](http://fecs.baidu.com/) + * [HTMLHint](http://htmlhint.com/) + * [prettier](https://github.com/prettier/prettier) + * [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/) +* ISPC + * [ispc](https://ispc.github.io/) :floppy_disk: +* Java + * [checkstyle](http://checkstyle.sourceforge.net) + * [eclipselsp](https://github.com/eclipse/eclipse.jdt.ls) + * [google-java-format](https://github.com/google/google-java-format) + * [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html) + * [javalsp](https://github.com/georgewfraser/vscode-javac) + * [PMD](https://pmd.github.io/) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* JavaScript + * [eslint](http://eslint.org/) + * [fecs](http://fecs.baidu.com/) + * [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/) + * [tsserver](https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29) + * [xo](https://github.com/sindresorhus/xo) +* JSON + * [fixjson](https://github.com/rhysd/fixjson) + * [jq](https://stedolan.github.io/jq/) + * [jsonlint](http://zaa.ch/jsonlint/) + * [prettier](https://github.com/prettier/prettier) +* Julia + * [languageserver](https://github.com/JuliaEditorSupport/LanguageServer.jl) +* Kotlin + * [kotlinc](https://kotlinlang.org) :floppy_disk: + * [ktlint](https://ktlint.github.io) :floppy_disk: + * [languageserver](https://github.com/fwcd/KotlinLanguageServer) see `:help ale-integration-kotlin` for configuration instructions +* LaTeX + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [chktex](http://www.nongnu.org/chktex/) + * [lacheck](https://www.ctan.org/pkg/lacheck) + * [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) +* Less + * [lessc](https://www.npmjs.com/package/less) + * [prettier](https://github.com/prettier/prettier) + * [stylelint](https://github.com/stylelint/stylelint) +* LLVM + * [llc](https://llvm.org/docs/CommandGuide/llc.html) +* Lua + * [luac](https://www.lua.org/manual/5.1/luac.html) + * [luacheck](https://github.com/mpeterv/luacheck) +* Mail + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [languagetool](https://languagetool.org/) :floppy_disk: + * [proselint](http://proselint.com/) + * [vale](https://github.com/ValeLint/vale) +* Make + * [checkmake](https://github.com/mrtazz/checkmake) +* Markdown + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [languagetool](https://languagetool.org/) :floppy_disk: + * [markdownlint](https://github.com/DavidAnson/markdownlint) :floppy_disk: + * [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) :floppy_disk: +* NASM + * [nasm](https://www.nasm.us/) :floppy_disk: +* Nim + * [nim check](https://nim-lang.org/docs/nimc.html) :floppy_disk: +* nix + * [nix-instantiate](http://nixos.org/nix/manual/#sec-nix-instantiate) +* nroff + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* Objective-C + * [ccls](https://github.com/MaskRay/ccls) + * [clang](http://clang.llvm.org/) + * [clangd](https://clang.llvm.org/extra/clangd.html) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* Objective-C++ + * [clang](http://clang.llvm.org/) + * [clangd](https://clang.llvm.org/extra/clangd.html) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* OCaml + * [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-ocaml-merlin` for configuration instructions + * [ocamlformat](https://github.com/ocaml-ppx/ocamlformat) + * [ocp-indent](https://github.com/OCamlPro/ocp-indent) + * [ols](https://github.com/freebroccolo/ocaml-language-server) +* Pawn + * [uncrustify](https://github.com/uncrustify/uncrustify) +* Perl + * [perl -c](https://perl.org/) :warning: + * [perl-critic](https://metacpan.org/pod/Perl::Critic) + * [perltidy](https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy) +* Perl6 + * [perl6 -c](https://perl6.org) :warning: +* PHP + * [langserver](https://github.com/felixfbecker/php-language-server) + * [phan](https://github.com/phan/phan) see `:help ale-php-phan` to instructions + * [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer) + * [phpcs](https://github.com/squizlabs/PHP_CodeSniffer) + * [php-cs-fixer](http://cs.sensiolabs.org/) + * [php -l](https://secure.php.net/) + * [phpmd](https://phpmd.org) + * [phpstan](https://github.com/phpstan/phpstan) + * [psalm](https://getpsalm.org) :floppy_disk: +* PO + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [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) :floppy_disk: + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* Pony + * [ponyc](https://github.com/ponylang/ponyc) +* PowerShell + * [powershell](https://github.com/PowerShell/PowerShell) :floppy_disk + * [psscriptanalyzer](https://github.com/PowerShell/PSScriptAnalyzer) :floppy_disk +* Prolog + * [swipl](https://github.com/SWI-Prolog/swipl-devel) +* proto + * [protoc-gen-lint](https://github.com/ckaznocha/protoc-gen-lint) +* Pug + * [pug-lint](https://github.com/pugjs/pug-lint) +* Puppet + * [languageserver](https://github.com/lingua-pupuli/puppet-editor-services) + * [puppet](https://puppet.com) + * [puppet-lint](https://puppet-lint.com) +* Python + * [autopep8](https://github.com/hhatto/autopep8) + * [bandit](https://github.com/PyCQA/bandit) :warning: + * [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](https://github.com/PyCQA/prospector) :warning: + * [pycodestyle](https://github.com/PyCQA/pycodestyle) :warning: + * [pydocstyle](https://www.pydocstyle.org/) :warning: + * [pyflakes](https://github.com/PyCQA/pyflakes) + * [pylama](https://github.com/klen/pylama) :floppy_disk: + * [pylint](https://www.pylint.org/) :floppy_disk: + * [pyls](https://github.com/palantir/python-language-server) :warning: + * [pyre](https://github.com/facebook/pyre-check) :warning: + * [vulture](https://github.com/jendrikseipp/vulture) :warning: :floppy_disk: + * [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) + * [styler](https://github.com/r-lib/styler) +* Racket + * [raco](https://docs.racket-lang.org/raco/) +* 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) :floppy_disk: + * [proselint](http://proselint.com/) + * [redpen](http://redpen.cc/) + * [rstcheck](https://github.com/myint/rstcheck) + * [textlint](https://textlint.github.io/) + * [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) :warning: (see `:help ale-integration-spec`) +* Ruby + * [brakeman](http://brakemanscanner.org/) :floppy_disk: + * [rails_best_practices](https://github.com/flyerhzm/rails_best_practices) :floppy_disk: + * [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) + * [solargraph](https://solargraph.org) + * [standardrb](https://github.com/testdouble/standard) +* Rust + * [cargo](https://github.com/rust-lang/cargo) :floppy_disk: (see `:help ale-integration-rust` for configuration instructions) + * [rls](https://github.com/rust-lang-nursery/rls) :warning: + * [rustc](https://www.rust-lang.org/) :warning: + * [rustfmt](https://github.com/rust-lang-nursery/rustfmt) +* Sass + * [sass-lint](https://www.npmjs.com/package/sass-lint) + * [stylelint](https://github.com/stylelint/stylelint) +* Scala + * [fsc](https://www.scala-lang.org/old/sites/default/files/linuxsoft_archives/docu/files/tools/fsc.html) + * [sbtserver](https://www.scala-sbt.org/1.x/docs/sbt-server.html) + * [scalac](http://scala-lang.org) + * [scalafmt](https://scalameta.org/scalafmt/) + * [scalastyle](http://www.scalastyle.org) +* 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) +* 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) +* SQL + * [sqlfmt](https://github.com/jackc/sqlfmt) + * [sqlint](https://github.com/purcell/sqlint) +* Stylus + * [stylelint](https://github.com/stylelint/stylelint) +* SugarSS + * [stylelint](https://github.com/stylelint/stylelint) +* Swift + * [sourcekit-lsp](https://github.com/apple/sourcekit-lsp) + * [swiftformat](https://github.com/nicklockwood/SwiftFormat) + * [swiftlint](https://github.com/realm/SwiftLint) +* Tcl + * [nagelfar](http://nagelfar.sourceforge.net) :floppy_disk: +* Terraform + * [fmt](https://github.com/hashicorp/terraform) + * [tflint](https://github.com/wata727/tflint) +* Texinfo + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* Text + * [alex](https://github.com/wooorm/alex) :warning: :floppy_disk: + * [languagetool](https://languagetool.org/) :floppy_disk: + * [proselint](http://proselint.com/) :warning: + * [redpen](http://redpen.cc/) :warning: + * [textlint](https://textlint.github.io/) :warning: + * [vale](https://github.com/ValeLint/vale) :warning: + * [write-good](https://github.com/btford/write-good) :warning: +* Thrift + * [thrift](http://thrift.apache.org/) +* TypeScript + * [eslint](http://eslint.org/) + * [fecs](http://fecs.baidu.com/) + * [prettier](https://github.com/prettier/prettier) + * [tslint](https://github.com/palantir/tslint) + * [tsserver](https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29) + * typecheck +* VALA + * [uncrustify](https://github.com/uncrustify/uncrustify) +* Verilog + * [iverilog](https://github.com/steveicarus/iverilog) + * [verilator](http://www.veripool.org/projects/verilator/wiki/Intro) + * [vlog](https://www.mentor.com/products/fv/questa/) + * [xvlog](https://www.xilinx.com/products/design-tools/vivado.html) +* VHDL + * [ghdl](https://github.com/ghdl/ghdl) + * [vcom](https://www.mentor.com/products/fv/questa/) + * [xvhdl](https://www.xilinx.com/products/design-tools/vivado.html) +* Vim + * [vint](https://github.com/Kuniwak/vint) +* Vim help + * [alex](https://github.com/wooorm/alex) :warning: :floppy_disk: + * [proselint](http://proselint.com/) :warning: + * [write-good](https://github.com/btford/write-good) :warning: +* Vue + * [prettier](https://github.com/prettier/prettier) + * [vls](https://github.com/vuejs/vetur/tree/master/server) +* XHTML + * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* XML + * [xmllint](http://xmlsoft.org/xmllint.html) +* YAML + * [prettier](https://github.com/prettier/prettier) + * [swaglint](https://github.com/byCedric/swaglint) + * [yamllint](https://yamllint.readthedocs.io/) +* YANG + * [yang-lsp](https://github.com/theia-ide/yang-lsp) diff --git a/test/elm-test-files/app/filetest.elm b/test/command_callback/alex-node-modules-2/node_modules/alex/cli.js index e69de29b..e69de29b 100644 --- a/test/elm-test-files/app/filetest.elm +++ b/test/command_callback/alex-node-modules-2/node_modules/alex/cli.js diff --git a/test/elm-test-files/app/node_modules/.bin/elm b/test/command_callback/alex-node-modules/node_modules/.bin/alex index e69de29b..e69de29b 100644 --- a/test/elm-test-files/app/node_modules/.bin/elm +++ b/test/command_callback/alex-node-modules/node_modules/.bin/alex diff --git a/test/command_callback/fecs_paths/fecs b/test/command_callback/fecs_paths/fecs new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/fecs_paths/fecs diff --git a/test/command_callback/fecs_paths/fecs.exe b/test/command_callback/fecs_paths/fecs.exe new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/fecs_paths/fecs.exe diff --git a/test/command_callback/java_paths_no_main/src/test/java/com/something/dummy b/test/command_callback/java_paths_no_main/src/test/java/com/something/dummy new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/java_paths_no_main/src/test/java/com/something/dummy diff --git a/test/command_callback/php-langserver-project/with-composer/composer.json b/test/command_callback/php-langserver-project/with-composer/composer.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php-langserver-project/with-composer/composer.json diff --git a/test/command_callback/php-langserver-project/with-composer/vendor/bin/php-language-server.php b/test/command_callback/php-langserver-project/with-composer/vendor/bin/php-language-server.php new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php-langserver-project/with-composer/vendor/bin/php-language-server.php diff --git a/test/command_callback/php-langserver-project/with-git/vendor/bin/php-language-server.php b/test/command_callback/php-langserver-project/with-git/vendor/bin/php-language-server.php new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/php-langserver-project/with-git/vendor/bin/php-language-server.php diff --git a/test/command_callback/python_paths/with_bandit/.bandit b/test/command_callback/python_paths/with_bandit/.bandit new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_bandit/.bandit diff --git a/test/command_callback/python_paths/with_bandit/namespace/foo/__init__.py b/test/command_callback/python_paths/with_bandit/namespace/foo/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_bandit/namespace/foo/__init__.py diff --git a/test/command_callback/python_paths/with_bandit/namespace/foo/bar.py b/test/command_callback/python_paths/with_bandit/namespace/foo/bar.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_bandit/namespace/foo/bar.py diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/pylama.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/pylama.exe new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_virtualenv/env/Scripts/pylama.exe diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/vulture.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/vulture.exe new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_virtualenv/env/Scripts/vulture.exe diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/pylama b/test/command_callback/python_paths/with_virtualenv/env/bin/pylama new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_virtualenv/env/bin/pylama diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/vulture b/test/command_callback/python_paths/with_virtualenv/env/bin/vulture new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_virtualenv/env/bin/vulture diff --git a/test/command_callback/ruby_paths/with_config/.standard.yml b/test/command_callback/ruby_paths/with_config/.standard.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/ruby_paths/with_config/.standard.yml diff --git a/test/command_callback/test_alex_command_callback.vader b/test/command_callback/test_alex_command_callback.vader new file mode 100644 index 00000000..98769e0b --- /dev/null +++ b/test/command_callback/test_alex_command_callback.vader @@ -0,0 +1,34 @@ +Before: + call ale#assert#SetUpLinterTest('tex', 'alex') + call ale#test#SetFilename('test_file.tex') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The global executable should be used when the local one cannot be found): + AssertLinter 'alex', + \ ale#Escape('alex') . ' %s --text', + +Execute(Should use the node_modules/.bin executable, if available): + call ale#test#SetFilename('alex-node-modules/test_file.tex') + + AssertLinter ale#path#Simplify(g:dir . '/alex-node-modules/node_modules/.bin/alex'), + \ ale#Escape(ale#path#Simplify(g:dir . '/alex-node-modules/node_modules/.bin/alex')) + \ . ' %s --text', + +Execute(Should use the node_modules/alex executable, if available): + call ale#test#SetFilename('alex-node-modules-2/test_file.tex') + + AssertLinter ale#path#Simplify(g:dir . '/alex-node-modules-2/node_modules/alex/cli.js'), + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/alex-node-modules-2/node_modules/alex/cli.js')) + \ . ' %s --text', + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('write-good-node-modules-2/test_file.tex') + + let g:ale_alex_executable = '/path/to/custom/alex' + let g:ale_alex_use_global = 1 + + AssertLinter '/path/to/custom/alex', + \ ale#Escape('/path/to/custom/alex') . ' %s --text' diff --git a/test/command_callback/test_ameba_command_callback.vader b/test/command_callback/test_ameba_command_callback.vader new file mode 100644 index 00000000..7746b44f --- /dev/null +++ b/test/command_callback/test_ameba_command_callback.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('crystal', 'ameba') + call ale#test#SetFilename('dummy.cr') + + let g:ale_crystal_ameba_executable = 'bin/ameba' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to bin/ameba): + AssertLinter 'bin/ameba', ale#Escape('bin/ameba') + \ . ' --format json ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.cr')) + +Execute(Should be able to set a custom executable): + let g:ale_crystal_ameba_executable = 'ameba' + + AssertLinter 'ameba' , ale#Escape('ameba') + \ . ' --format json ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.cr')) diff --git a/test/command_callback/test_asciidoc_textlint_command_callbacks.vader b/test/command_callback/test_asciidoc_textlint_command_callbacks.vader new file mode 100644 index 00000000..623833b2 --- /dev/null +++ b/test/command_callback/test_asciidoc_textlint_command_callbacks.vader @@ -0,0 +1,65 @@ +" Author: januswel, w0rp + +Before: + " This is just one language for the linter. + call ale#assert#SetUpLinterTest('asciidoc', 'textlint') + + " The configuration is shared between many languages. + Save g:ale_textlint_executable + Save g:ale_textlint_use_global + Save g:ale_textlint_options + + let g:ale_textlint_executable = 'textlint' + let g:ale_textlint_use_global = 0 + let g:ale_textlint_options = '' + + unlet! b:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + +After: + unlet! b:command_tail + unlet! b:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' -f json --stdin --stdin-filename %s' + +Execute(The executable should be configurable): + let b:ale_textlint_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -f json --stdin --stdin-filename %s' + +Execute(The options should be configurable): + let b:ale_textlint_options = '--something' + + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' --something -f json --stdin --stdin-filename %s' + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_bin_path/foo.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint')) + \ . ' -f json --stdin --stdin-filename %s' + +Execute(The local executable from textlint/bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_textlint_bin_path/foo.txt') + + if has('win32') + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ 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' + else + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ 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' + endif diff --git a/test/command_callback/test_bandit_command_callback.vader b/test/command_callback/test_bandit_command_callback.vader new file mode 100644 index 00000000..274ce901 --- /dev/null +++ b/test/command_callback/test_bandit_command_callback.vader @@ -0,0 +1,71 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'bandit') + let b:bandit_flags = ' --format custom ' + \ . '--msg-template "{line}:{test_id}:{severity}:{msg}" ' + +After: + call ale#assert#TearDownLinterTest() + unlet! b:bandit_flags + +Execute(The bandit command callback should return default string): + AssertLinter 'bandit', + \ ale#Escape('bandit') + \ . b:bandit_flags + \ . ' -' + +Execute(The bandit command callback should allow options): + let g:ale_python_bandit_options = '--configfile bandit.yaml' + + AssertLinter 'bandit', + \ ale#Escape('bandit') + \ . b:bandit_flags + \ . ' --configfile bandit.yaml -' + +Execute(The bandit executable should be configurable): + let g:ale_python_bandit_executable = '~/.local/bin/bandit' + + AssertLinter '~/.local/bin/bandit', + \ ale#Escape('~/.local/bin/bandit') + \ . b:bandit_flags + \ . ' -' + +Execute(Setting executable to 'pipenv' appends 'run bandit'): + let g:ale_python_bandit_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' + +Execute(Pipenv is detected when python_bandit_auto_pipenv is set): + let g:ale_python_bandit_auto_pipenv = 1 + call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' + +Execute(The bandit command callback should add .bandit by default): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_bandit/namespace/foo/bar.py') + + let b:config_path = ale#path#Simplify( + \ g:dir . '/python_paths/with_bandit/.bandit' + \) + + AssertLinter 'bandit', + \ ale#Escape('bandit') + \ . ' --ini ' . ale#Escape(b:config_path) + \ . b:bandit_flags + \ . ' -' + +Execute(The bandit command callback should support not using .bandit): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_bandit/subdir/foo/bar.py') + let g:ale_python_bandit_use_config = 0 + + AssertLinter 'bandit', + \ ale#Escape('bandit') + \ . b:bandit_flags + \ . ' -' diff --git a/test/command_callback/test_bingo_command_callback.vader b/test/command_callback/test_bingo_command_callback.vader new file mode 100644 index 00000000..f4cb3231 --- /dev/null +++ b/test/command_callback/test_bingo_command_callback.vader @@ -0,0 +1,46 @@ +Before: + call ale#assert#SetUpLinterTest('go', 'bingo') + +After: + Restore + + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + unlet! b:ale_completion_enabled + + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'bingo', ale#Escape('bingo') . ' --mode stdio' + +Execute(should configure bingo callback executable): + let b:ale_go_bingo_executable = 'boo' + let b:ale_go_bingo_options = '' + + AssertLinter 'boo', ale#Escape('boo') + +Execute(should set bingo options): + call ale#test#SetFilename('go_paths/go1/prj1/file.go') + " let b:ale_completion_enabled = 1 + let b:ale_go_bingo_options = '' + + AssertLinter 'bingo', + \ ale#Escape('bingo') . '' + + let b:ale_go_bingo_options = '--mode stdio --trace' + + AssertLinter 'bingo', + \ ale#Escape('bingo') . ' --mode stdio --trace' + +Execute(Should return directory for 'go.mod' if found in parent directory): + call ale#test#SetFilename('../go_files/test.go') + + AssertLSPProject ale#path#Simplify(g:dir . '/../go_files') + +Execute(Should return nearest directory with '.git' if found in parent directory): + call ale#test#SetFilename('test.go') + call mkdir(g:dir . '/.git') + + AssertLSPProject g:dir diff --git a/test/command_callback/test_c_clang_command_callbacks.vader b/test/command_callback/test_c_clang_command_callbacks.vader index 7d2ff0bf..b8c02e4d 100644 --- a/test/command_callback/test_c_clang_command_callbacks.vader +++ b/test/command_callback/test_c_clang_command_callbacks.vader @@ -13,8 +13,8 @@ After: call ale#assert#TearDownLinterTest() Execute(The executable should be configurable): - AssertLinter 'clang', ['', ale#Escape('clang') . b:command_tail] + AssertLinter 'clang', [ale#Escape('clang') . b:command_tail] let b:ale_c_clang_executable = 'foobar' - AssertLinter 'foobar', ['', ale#Escape('foobar') . b:command_tail] + AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] diff --git a/test/command_callback/test_c_gcc_command_callbacks.vader b/test/command_callback/test_c_gcc_command_callbacks.vader index 1f51c3bc..c5b316c8 100644 --- a/test/command_callback/test_c_gcc_command_callbacks.vader +++ b/test/command_callback/test_c_gcc_command_callbacks.vader @@ -14,8 +14,8 @@ After: unlet! b:command_tail Execute(The executable should be configurable): - AssertLinter 'gcc', ['', ale#Escape('gcc') . b:command_tail] + AssertLinter 'gcc', [ale#Escape('gcc') . b:command_tail] let b:ale_c_gcc_executable = 'foobar' - AssertLinter 'foobar', ['', ale#Escape('foobar') . b:command_tail] + AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] diff --git a/test/command_callback/test_cargo_command_callbacks.vader b/test/command_callback/test_cargo_command_callbacks.vader index f0afbc91..438c97db 100644 --- a/test/command_callback/test_cargo_command_callbacks.vader +++ b/test/command_callback/test_cargo_command_callbacks.vader @@ -1,106 +1,113 @@ Before: call ale#assert#SetUpLinterTest('rust', 'cargo') + call ale#test#SetFilename('cargo_paths/test.rs') + let g:cd = 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/cargo_paths')) . ' && ' let g:suffix = ' --frozen --message-format=json -q' + let g:ale_rust_cargo_avoid_whole_workspace = 0 " Test with version 0.22.0 by default. - WithChainResults ['cargo 0.22.0 (3423351a5 2017-10-06)'] + GivenCommandOutput ['cargo 0.22.0 (3423351a5 2017-10-06)'] After: call ale#assert#TearDownLinterTest() + unlet! g:cd unlet! g:suffix Execute(The linter should not be executed when there's no Cargo.toml file): + call ale#test#SetFilename('../foo.rs') AssertLinterNotExecuted Execute(The linter should be executed when there is a Cargo.toml file): - call ale#test#SetFilename('cargo_paths/test.rs') - - WithChainResults [] - AssertLinter 'cargo', - \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/cargo_paths')) . ' && ' - \ . 'cargo build --frozen --message-format=json -q' - -Execute(The default command should be correct): - WithChainResults [] - AssertLinter '', ['cargo --version', 'cargo build' . g:suffix] + GivenCommandOutput [] + AssertLinter 'cargo', 'cargo build --frozen --message-format=json -q' Execute(`cargo check` should be used when the version is new enough): - WithChainResults ['cargo 0.17.0 (3423351a5 2017-10-06)'] - AssertLinter '', ['cargo --version', 'cargo check' . g:suffix] + GivenCommandOutput ['cargo 0.17.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo check' . g:suffix, + \] " We should cache the version check - WithChainResults [] - AssertLinter '', ['', 'cargo check' . g:suffix] + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo check' . g:suffix] Execute(`cargo build` should be used when cargo is too old): - WithChainResults ['cargo 0.16.0 (3423351a5 2017-10-06)'] - AssertLinter '', ['cargo --version', 'cargo build' . g:suffix] + GivenCommandOutput ['cargo 0.16.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo build' . g:suffix, + \] - WithChainResults [] - AssertLinter '', ['', 'cargo build' . g:suffix] + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo build' . g:suffix] Execute(`cargo build` should be used when g:ale_rust_cargo_use_check is set to 0): let g:ale_rust_cargo_use_check = 0 - WithChainResults ['cargo 0.24.0 (3423351a5 2017-10-06)'] - AssertLinter '', ['cargo --version', 'cargo build' . g:suffix] + GivenCommandOutput ['cargo 0.24.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo build' . g:suffix, + \] " We should cache the version check - WithChainResults [] - AssertLinter '', ['', 'cargo build' . g:suffix] + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo build' . g:suffix] Execute(`cargo check` should be used when the version is new enough): - AssertLinter '', ['cargo --version', 'cargo check' . g:suffix] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo check' . g:suffix, + \] " We should cache the version check - WithChainResults [] - AssertLinter '', ['', 'cargo check' . g:suffix] + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo check' . g:suffix] Execute(--all-targets should be used when g:ale_rust_cargo_check_all_targets is set to 1): let g:ale_rust_cargo_check_all_targets = 1 - AssertLinter '', ['cargo --version', 'cargo check --all-targets' . g:suffix] - + AssertLinter 'cargo', [ale#Escape('cargo') . ' --version', 'cargo check --all-targets' . g:suffix] " We should cache the version check - WithChainResults [] - AssertLinter '', ['', 'cargo check --all-targets' . g:suffix] + AssertLinter 'cargo', ['cargo check --all-targets' . g:suffix] Execute(--tests should be used when g:ale_rust_cargo_check_tests is set to 1): let g:ale_rust_cargo_check_tests = 1 - AssertLinter '', ['cargo --version', 'cargo check --tests' . g:suffix] + AssertLinter 'cargo', [ale#Escape('cargo') . ' --version', 'cargo check --tests' . g:suffix] " We should cache the version check - WithChainResults [] - AssertLinter '', ['', 'cargo check --tests' . g:suffix] + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo check --tests' . g:suffix] Execute(--examples should be used when g:ale_rust_cargo_check_examples is set to 1): let g:ale_rust_cargo_check_examples = 1 - AssertLinter '', ['cargo --version', 'cargo check --examples' . g:suffix] + AssertLinter 'cargo', [ale#Escape('cargo') . ' --version', 'cargo check --examples' . g:suffix] " We should cache the version check - WithChainResults [] - AssertLinter '', ['', 'cargo check --examples' . g:suffix] + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo check --examples' . g:suffix] Execute(--no-default-features should be used when g:ale_rust_cargo_default_feature_behavior is none): let b:ale_rust_cargo_default_feature_behavior = 'none' - AssertLinter '', ['cargo --version', 'cargo check --frozen --message-format=json -q --no-default-features'] + AssertLinter 'cargo', [ale#Escape('cargo') . ' --version', 'cargo check --frozen --message-format=json -q --no-default-features'] Execute(g:ale_rust_cargo_include_features added when g:ale_rust_cargo_default_feature_behavior is none): let b:ale_rust_cargo_default_feature_behavior = 'none' let b:ale_rust_cargo_include_features = 'foo bar' - AssertLinter '', ['cargo --version', 'cargo check --frozen --message-format=json -q --no-default-features --features ' . ale#Escape('foo bar')] + AssertLinter 'cargo', [ale#Escape('cargo') . ' --version', 'cargo check --frozen --message-format=json -q --no-default-features --features ' . ale#Escape('foo bar')] Execute(g:ale_rust_cargo_include_features added and escaped): let b:ale_rust_cargo_default_feature_behavior = 'default' let b:ale_rust_cargo_include_features = "foo bar baz" - AssertLinter '', ['cargo --version', 'cargo check --frozen --message-format=json -q --features ' . ale#Escape('foo bar baz')] + AssertLinter 'cargo', [ale#Escape('cargo') . ' --version', 'cargo check --frozen --message-format=json -q --features ' . ale#Escape('foo bar baz')] Execute(--all-features should be used when g:ale_rust_cargo_default_feature_behavior is all): let b:ale_rust_cargo_default_feature_behavior = 'all' @@ -108,14 +115,15 @@ Execute(--all-features should be used when g:ale_rust_cargo_default_feature_beha " since it won't do anything let b:ale_rust_cargo_include_features = 'foo bar' - WithChainResults ['cargo 0.22.0 (3423351a5 2017-10-06)'] - AssertLinter '', ['cargo --version', 'cargo check --frozen --message-format=json -q --all-features'] + GivenCommandOutput ['cargo 0.22.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ale#Escape('cargo') . ' --version', 'cargo check --frozen --message-format=json -q --all-features'] Execute(When a crate belongs to a workspace we should cd into the crate): + let g:ale_rust_cargo_avoid_whole_workspace = 1 call ale#test#SetFilename('cargo_workspace_paths/subpath/test.rs') AssertLinter 'cargo', [ - \ 'cargo --version', + \ ale#Escape('cargo') . ' --version', \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/cargo_workspace_paths/subpath')) . ' && ' \ . 'cargo check --frozen --message-format=json -q', \] @@ -125,22 +133,22 @@ Execute(When a crate belongs to a workspace we chdir into the crate, unless we d call ale#test#SetFilename('cargo_workspace_paths/subpath/test.rs') AssertLinter 'cargo', [ - \ 'cargo --version', + \ ale#Escape('cargo') . ' --version', \ 'cargo check --frozen --message-format=json -q', \] Execute(When ale_rust_cargo_use_clippy is set, cargo-clippy is used as linter): let b:ale_rust_cargo_use_clippy = 1 - AssertLinter '', [ - \ 'cargo --version', + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', \ 'cargo clippy --frozen --message-format=json -q ', \] Execute(When ale_rust_cargo_clippy_options is set, cargo-clippy appends it to commandline): let b:ale_rust_cargo_use_clippy = 1 let b:ale_rust_cargo_clippy_options = '-- -D warnings' - AssertLinter '', [ - \ 'cargo --version', + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', \ 'cargo clippy --frozen --message-format=json -q -- -D warnings', \] @@ -148,7 +156,7 @@ Execute(cargo-check does not refer ale_rust_cargo_clippy_options): let b:ale_rust_cargo_use_clippy = 0 let b:ale_rust_cargo_use_check = 1 let b:ale_rust_cargo_clippy_options = '-- -D warnings' - AssertLinter '', [ - \ 'cargo --version', + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', \ 'cargo check --frozen --message-format=json -q', \] diff --git a/test/command_callback/test_cookstyle_command_callback.vader b/test/command_callback/test_cookstyle_command_callback.vader new file mode 100644 index 00000000..ad7391cc --- /dev/null +++ b/test/command_callback/test_cookstyle_command_callback.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('chef', 'cookstyle') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'cookstyle', ale#Escape('cookstyle') . ' --force-exclusion --format json --stdin %s' + +Execute(The executable path should be configurable): + let b:ale_chef_cookstyle_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --force-exclusion --format json --stdin %s' + +Execute(The linter options should be configurable): + let b:ale_chef_cookstyle_options = '--parallel' + + AssertLinter 'cookstyle', ale#Escape('cookstyle') . ' --parallel --force-exclusion --format json --stdin %s' + diff --git a/test/command_callback/test_cypher_cypher_lint_command_callback.vader b/test/command_callback/test_cypher_cypher_lint_command_callback.vader new file mode 100644 index 00000000..6b64dc1f --- /dev/null +++ b/test/command_callback/test_cypher_cypher_lint_command_callback.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('cypher', 'cypher_lint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command and executable should be correct): + AssertLinter 'cypher-lint', 'cypher-lint' diff --git a/test/command_callback/test_eclipselsp_command_callback.vader b/test/command_callback/test_eclipselsp_command_callback.vader new file mode 100644 index 00000000..c0ad89a5 --- /dev/null +++ b/test/command_callback/test_eclipselsp_command_callback.vader @@ -0,0 +1,87 @@ +Before: + call ale#assert#SetUpLinterTest('java', 'eclipselsp') + let b:ale_java_eclipselsp_path = '/home/user/eclipse.dst.ls' + call ale#test#SetFilename('dummy.java') + + let b:cfg = '/testplugin/test/config_linux' + + if has('win32') + let b:cfg = 'C:\testplugin\test\config_win' + elseif has('macunix') + let b:cfg = '/testplugin/test/config_mac' + endif + + +After: + unlet b:ale_java_eclipselsp_path + call ale#assert#TearDownLinterTest() + +Execute(VersionCheck should return correct version): + + " OpenJDK Java 1.8 + AssertEqual [1, 8, 0], ale_linters#java#eclipselsp#VersionCheck([ + \ 'openjdk version "1.8.0_191"', + \ 'OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-0ubuntu0.18.04.1-b12)', + \ 'OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)' + \]) + + " OpenJDK Java 10 + AssertEqual [10, 0, 2], ale_linters#java#eclipselsp#VersionCheck([ + \ 'openjdk version "10.0.2" 2018-07-17', + \ 'OpenJDK Runtime Environment (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4)', + \ 'OpenJDK 64-Bit Server VM (build 10.0.2+13-Ubuntu-1ubuntu0.18.04.4, mixed mode)' + \]) + + " Oracle Java 1.8 + AssertEqual [1, 8, 0], ale_linters#java#eclipselsp#VersionCheck([ + \ 'java version "1.8.0_161"', + \ 'Java(TM) SE Runtime Environment (build 1.8.0_161-b12)', + \ 'Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)' + \]) + + " Oracle Java 10 + AssertEqual [10, 0, 1], ale_linters#java#eclipselsp#VersionCheck([ + \ 'java version "10.0.1" 2018-04-17', + \ 'Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)', + \ 'Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)' + \]) + + AssertEqual [], ale_linters#java#eclipselsp#VersionCheck(['x']) + + AssertEqual [], ale_linters#java#eclipselsp#VersionCheck([]) + +Execute(The eclipselsp callback should return the correct default value): + let cmd = [ ale#Escape('java'), + \ '-Declipse.application=org.eclipse.jdt.ls.core.id1', + \ '-Dosgi.bundles.defaultStartLevel=4', + \ '-Declipse.product=org.eclipse.jdt.ls.core.product', + \ '-Dlog.level=ALL', + \ '-noverify', + \ '-Xmx1G', + \ '-jar', + \ '', + \ '-configuration', + \ b:cfg, + \ '-data', + \ '' + \] + AssertLinter 'java', join(cmd, ' ') + +Execute(The eclipselsp callback should allow custom executable): + let b:ale_java_eclipselsp_executable='/bin/foobar' + let cmd = [ ale#Escape('/bin/foobar'), + \ '-Declipse.application=org.eclipse.jdt.ls.core.id1', + \ '-Dosgi.bundles.defaultStartLevel=4', + \ '-Declipse.product=org.eclipse.jdt.ls.core.product', + \ '-Dlog.level=ALL', + \ '-noverify', + \ '-Xmx1G', + \ '-jar', + \ '', + \ '-configuration', + \ b:cfg, + \ '-data', + \ '' + \] + AssertLinter '/bin/foobar', join(cmd, ' ') + diff --git a/test/command_callback/test_elixir_credo.vader b/test/command_callback/test_elixir_credo.vader new file mode 100644 index 00000000..1a146db8 --- /dev/null +++ b/test/command_callback/test_elixir_credo.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpLinterTest('elixir', 'credo') + call ale#test#SetFilename('elixir_paths/mix_project/lib/app.ex') + + +After: + unlet! g:ale_elixir_credo_strict + + call ale#assert#TearDownLinterTest() + +Execute(Builds credo command with --strict mode when set to 1): + let g:ale_elixir_credo_strict = 1 + + AssertLinter 'mix', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/elixir_paths/mix_project')) + \ . 'mix help credo && mix credo --strict --format=flycheck --read-from-stdin %s' + +Execute(Builds credo command with suggest mode by default): + AssertLinter 'mix', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/elixir_paths/mix_project')) + \ . 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' + +Execute(Builds credo command with suggest mode when set to 0): + let g:ale_elixir_credo_strict = 0 + + AssertLinter 'mix', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/elixir_paths/mix_project')) + \ . 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' diff --git a/test/command_callback/test_elixir_ls_command_callbacks.vader b/test/command_callback/test_elixir_ls_command_callbacks.vader index ca785054..41a5bc34 100644 --- a/test/command_callback/test_elixir_ls_command_callbacks.vader +++ b/test/command_callback/test_elixir_ls_command_callbacks.vader @@ -1,25 +1,24 @@ Before: call ale#assert#SetUpLinterTest('elixir', 'elixir_ls') - let g:ale_has_override['win32'] = 0 - After: - let g:ale_has_override = {} - call ale#assert#TearDownLinterTest() -Execute(should set correct defaults (unix)): - AssertLinter 'elixir-ls/language_server.sh', 'elixir-ls/language_server.sh' - -Execute(should set correct defaults (win32)): - let g:ale_has_override['win32'] = 1 - - AssertLinter 'elixir-ls\language_server.bat', 'elixir-ls\language_server.bat' +Execute(should set correct defaults): + if has('win32') + AssertLinter 'elixir-ls\language_server.bat', 'elixir-ls\language_server.bat' + else + AssertLinter 'elixir-ls/language_server.sh', 'elixir-ls/language_server.sh' + endif Execute(should configure elixir-ls release location): let b:ale_elixir_elixir_ls_release = 'boo' - AssertLinter 'boo/language_server.sh', 'boo/language_server.sh' + if has('win32') + AssertLinter 'boo\language_server.bat', 'boo\language_server.bat' + else + AssertLinter 'boo/language_server.sh', 'boo/language_server.sh' + endif Execute(should set correct LSP values): call ale#test#SetFilename('elixir_paths/umbrella_project/apps/app1/lib/app.ex') diff --git a/test/command_callback/test_elm_lsp_command_callbacks.vader b/test/command_callback/test_elm_lsp_command_callbacks.vader new file mode 100644 index 00000000..d06ef134 --- /dev/null +++ b/test/command_callback/test_elm_lsp_command_callbacks.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('elm', 'elm_lsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + call ale#test#SetFilename('../elm-test-files/newapp/src/Main.elm') + + AssertLinter 'elm-lsp', ale#Escape('elm-lsp') . ' --stdio' + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../elm-test-files/newapp/src/Main.elm') + + AssertLSPProject ale#path#Simplify(g:dir . '/../elm-test-files/newapp') + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('../elm-test-files/newapp/src/Main.elm') + + let g:ale_elm_lsp_executable = '/path/to/custom/elm-lsp' + let g:ale_elm_lsp_use_global = 1 + + AssertLinter '/path/to/custom/elm-lsp', + \ ale#Escape('/path/to/custom/elm-lsp') . ' --stdio' + +Execute(The language should be correct): + AssertLSPLanguage 'elm' diff --git a/test/command_callback/test_elm_make_command_callback.vader b/test/command_callback/test_elm_make_command_callback.vader index 6d95676f..7ad439f9 100644 --- a/test/command_callback/test_elm_make_command_callback.vader +++ b/test/command_callback/test_elm_make_command_callback.vader @@ -7,26 +7,57 @@ After: call ale#assert#TearDownLinterTest() Execute(should get valid executable with default params): - call ale#test#SetFilename('../elm-test-files/app/testfile.elm') + call ale#test#SetFilename('../elm-test-files/newapp/src/Main.elm') - let g:executable = ale#path#Simplify(g:dir . '/../elm-test-files/app/node_modules/.bin/elm') + let g:executable = ale#path#Simplify(g:dir . '/../elm-test-files/newapp/node_modules/.bin/elm') AssertLinter g:executable, - \ ale#Escape(g:executable) . ' make --report=json --output=/dev/null %t' + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/newapp')) . ' && ' + \ . ale#Escape(g:executable) . ' make --report=json --output=/dev/null %t' + +Execute(should get elm-test executable for test code with elm >= 0.19): + call ale#test#SetFilename('../elm-test-files/newapp/tests/TestSuite.elm') + + let g:executable = ale#path#Simplify(g:dir . '/../elm-test-files/newapp/node_modules/.bin/elm-test') + + AssertLinter g:executable, + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/newapp')) . ' && ' + \ . ale#Escape(g:executable) . ' make --report=json --output=/dev/null --compiler ' + \ . ale#path#Simplify(g:dir . '/../elm-test-files/newapp/node_modules/.bin/elm') . ' %t' + +Execute(should fallback to elm executable with elm >= 0.19): + call ale#test#SetFilename('../elm-test-files/newapp-notests/tests/TestMain.elm') + + let g:executable = ale#path#Simplify(g:dir . '/../elm-test-files/newapp-notests/node_modules/.bin/elm') + + AssertLinter g:executable, + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/newapp-notests')) . ' && ' + \ . ale#Escape(g:executable) . ' make --report=json --output=/dev/null %t' + +Execute(should get plain elm executable for test code with elm < 0.19): + call ale#test#SetFilename('../elm-test-files/oldapp/tests/TestSuite.elm') + + let g:executable = ale#path#Simplify(g:dir . '/../elm-test-files/oldapp/node_modules/.bin/elm') + + AssertLinter g:executable, + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/oldapp')) . ' && ' + \ . ale#Escape(g:executable) . ' make --report=json --output=/dev/null %t' Execute(should get valid executable with 'use_global' params): let g:ale_elm_make_use_global = 1 - call ale#test#SetFilename('../elm-test-files/app/testfile.elm') + call ale#test#SetFilename('../elm-test-files/newapp/src/Main.elm') AssertLinter 'elm', - \ ale#Escape('elm') . ' make --report=json --output=/dev/null %t' + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/newapp')) . ' && ' + \ . ale#Escape('elm') . ' make --report=json --output=/dev/null %t' Execute(should get valid executable with 'use_global' and 'executable' params): 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') + call ale#test#SetFilename('../elm-test-files/newapp/src/Main.elm') AssertLinter 'other-elm', - \ ale#Escape('other-elm') . ' make --report=json --output=/dev/null %t' + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/newapp')) . ' && ' + \ . ale#Escape('other-elm') . ' make --report=json --output=/dev/null %t' diff --git a/test/command_callback/test_erlang_syntaxerl_command_callback.vader b/test/command_callback/test_erlang_syntaxerl_command_callback.vader index 28cff9b9..e7cc26ea 100644 --- a/test/command_callback/test_erlang_syntaxerl_command_callback.vader +++ b/test/command_callback/test_erlang_syntaxerl_command_callback.vader @@ -19,7 +19,7 @@ Execute (The executable should be configurable): \] Execute (The -b option should be used when available): - WithChainResults [ + GivenCommandOutput [ \ 'Syntax checker for Erlang (0.14.0)', \ 'Usage: syntaxerl [-d | --debug] <FILENAME>', \ ' syntaxerl <-h | --help>', @@ -31,7 +31,7 @@ Execute (The -b option should be used when available): \ ale#Escape('syntaxerl') . ' %t', \] - WithChainResults [ + GivenCommandOutput [ \ 'Syntax checker for Erlang (0.14.0)', \ 'Usage: syntaxerl [-b | --base <FILENAME>] [-d | --debug] <FILENAME>', \ ' syntaxerl <-h | --help>', diff --git a/test/command_callback/test_erubi_command_callback.vader b/test/command_callback/test_erubi_command_callback.vader index 0643efc2..0d88ff93 100644 --- a/test/command_callback/test_erubi_command_callback.vader +++ b/test/command_callback/test_erubi_command_callback.vader @@ -21,7 +21,7 @@ Execute(Executable should filter invalid eRuby when inside a Rails project): \] Execute(Command should be blank if the first command in the chain returns output): - WithChainResults [ + GivenCommandOutput [ \ "/usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- erubi/capture_end (LoadError)", \ " from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'", \] diff --git a/test/command_callback/test_fecs_command_callback.vader b/test/command_callback/test_fecs_command_callback.vader new file mode 100644 index 00000000..f70ad084 --- /dev/null +++ b/test/command_callback/test_fecs_command_callback.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'fecs') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'fecs', ale#Escape('fecs') . ' check --colors=false --rule=true %t' diff --git a/test/command_callback/test_flake8_command_callback.vader b/test/command_callback/test_flake8_command_callback.vader index ede511e0..f082a63a 100644 --- a/test/command_callback/test_flake8_command_callback.vader +++ b/test/command_callback/test_flake8_command_callback.vader @@ -3,7 +3,7 @@ Before: let b:bin_dir = has('win32') ? 'Scripts' : 'bin' - WithChainResults ['3.0.0'] + GivenCommandOutput ['3.0.0'] After: unlet! b:executable @@ -18,16 +18,15 @@ Execute(The flake8 callbacks should return the correct default values): \] " The version check should be cached. - WithChainResults [] + GivenCommandOutput [] AssertLinter 'flake8', [ - \ '', \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', \] " Try with older versions. call ale#semver#ResetVersionCache() - WithChainResults ['2.9.9'] + GivenCommandOutput ['2.9.9'] AssertLinter 'flake8', [ \ ale#Escape('flake8') . ' --version', \ ale#path#BufferCdString(bufnr('')) @@ -45,7 +44,7 @@ Execute(The option for disabling changing directories should work): Execute(The flake8 command callback should let you set options): let g:ale_python_flake8_options = '--some-option' - WithChainResults ['3.0.4'] + GivenCommandOutput ['3.0.4'] AssertLinter 'flake8', [ \ ale#Escape('flake8') . ' --version', \ ale#path#BufferCdString(bufnr('')) @@ -54,7 +53,7 @@ Execute(The flake8 command callback should let you set options): \] call ale#semver#ResetVersionCache() - WithChainResults ['2.9.9'] + GivenCommandOutput ['2.9.9'] AssertLinter 'flake8', [ \ ale#Escape('flake8') . ' --version', \ ale#path#BufferCdString(bufnr('')) @@ -129,7 +128,7 @@ Execute(Using `python -m flake8` should be supported for running flake8): let g:ale_python_flake8_executable = 'python' let g:ale_python_flake8_options = '-m flake8 --some-option' - WithChainResults ['2.9.9'] + GivenCommandOutput ['2.9.9'] AssertLinter 'python', [ \ ale#Escape('python') . ' -m flake8 --version', \ ale#path#BufferCdString(bufnr('')) @@ -142,7 +141,7 @@ Execute(Using `python -m flake8` should be supported for running flake8): " Leading spaces shouldn't matter let g:ale_python_flake8_options = ' -m flake8 --some-option' - WithChainResults ['2.9.9'] + GivenCommandOutput ['2.9.9'] AssertLinter 'python', [ \ ale#Escape('python') . ' -m flake8 --version', \ ale#path#BufferCdString(bufnr('')) @@ -154,14 +153,14 @@ Execute(Setting executable to 'pipenv' should append 'run flake8'): let g:ale_python_flake8_executable = 'path/to/pipenv' " FIXME: pipenv should check the version with flake8. - WithChainResults [] + GivenCommandOutput [] AssertLinter 'path/to/pipenv', \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('path/to/pipenv') . ' run flake8 --format=default -' Execute(Pipenv is detected when python_flake8_auto_pipenv is set): let g:ale_python_flake8_auto_pipenv = 1 - call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + call ale#test#SetFilename('../python_fixtures/pipenv/whatever.py') AssertLinter 'pipenv', \ ale#path#BufferCdString(bufnr('')) diff --git a/test/command_callback/test_ghdl_command_callbacks.vader b/test/command_callback/test_ghdl_command_callbacks.vader new file mode 100644 index 00000000..f254e11f --- /dev/null +++ b/test/command_callback/test_ghdl_command_callbacks.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('vhdl', 'ghdl') + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'ghdl', ale#Escape('ghdl') . ' -s --std=08 %t' + + let b:ale_vhdl_ghdl_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -s --std=08 %t' + +Execute(The options should be configurable): + let b:ale_vhdl_ghdl_options = '--something' + + AssertLinter 'ghdl', ale#Escape('ghdl') . ' -s --something %t' diff --git a/test/command_callback/test_gobuild_command_callback.vader b/test/command_callback/test_gobuild_command_callback.vader index c6e324f2..79015b75 100644 --- a/test/command_callback/test_gobuild_command_callback.vader +++ b/test/command_callback/test_gobuild_command_callback.vader @@ -3,7 +3,7 @@ Before: call ale#assert#SetUpLinterTest('go', 'gobuild') - WithChainResults ['/foo/bar', '/foo/baz'] + GivenCommandOutput ['/foo/bar', '/foo/baz'] After: Restore diff --git a/test/command_callback/test_gopls_command_callback.vader b/test/command_callback/test_gopls_command_callback.vader new file mode 100644 index 00000000..0e079a73 --- /dev/null +++ b/test/command_callback/test_gopls_command_callback.vader @@ -0,0 +1,46 @@ +Before: + call ale#assert#SetUpLinterTest('go', 'gopls') + +After: + Restore + + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + unlet! b:ale_completion_enabled + + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'gopls', ale#Escape('gopls') . ' --mode stdio' + +Execute(should configure gopls callback executable): + let b:ale_go_gopls_executable = 'boo' + let b:ale_go_gopls_options = '' + + AssertLinter 'boo', ale#Escape('boo') + +Execute(should set gopls options): + call ale#test#SetFilename('go_paths/go1/prj1/file.go') + " let b:ale_completion_enabled = 1 + let b:ale_go_gopls_options = '' + + AssertLinter 'gopls', + \ ale#Escape('gopls') . '' + + let b:ale_go_gopls_options = '--mode stdio --trace' + + AssertLinter 'gopls', + \ ale#Escape('gopls') . ' --mode stdio --trace' + +Execute(Should return directory for 'go.mod' if found in parent directory): + call ale#test#SetFilename('../go_files/test.go') + + AssertLSPProject ale#path#Simplify(g:dir . '/../go_files') + +Execute(Should return nearest directory with '.git' if found in parent directory): + call ale#test#SetFilename('test.go') + call mkdir(g:dir . '/.git') + + AssertLSPProject g:dir diff --git a/test/command_callback/test_gotype_command_callback.vader b/test/command_callback/test_gotype_command_callback.vader index 1898a0cb..113f4823 100644 --- a/test/command_callback/test_gotype_command_callback.vader +++ b/test/command_callback/test_gotype_command_callback.vader @@ -7,7 +7,7 @@ After: Execute(The default gotype command should be correct): AssertLinter 'gotype', - \ ale#path#CdString(expand('%:p:h')) . ' gotype .' + \ ale#path#CdString(expand('%:p:h')) . ' gotype -e .' Execute(The gotype callback should ignore test files): call ale#test#SetFilename('bla_test.go') diff --git a/test/command_callback/test_graphql_gqlint_command_callbacks.vader b/test/command_callback/test_graphql_gqlint_command_callbacks.vader new file mode 100644 index 00000000..0f4e9770 --- /dev/null +++ b/test/command_callback/test_graphql_gqlint_command_callbacks.vader @@ -0,0 +1,11 @@ +Before: + call ale#assert#SetUpLinterTest('graphql', 'gqlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The linter should run from the directory of the file in the buffer): + AssertLinter 'gqlint', + \ ale#path#CdString(expand('%:p:h')) + \ . 'gqlint --reporter=simple' + \ . ' %t' diff --git a/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader b/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader index 650aefa3..9e3712d6 100644 --- a/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader +++ b/test/command_callback/test_haskell_cabal_ghc_command_callbacks.vader @@ -13,11 +13,11 @@ After: Execute(The options should be used in the command): AssertEqual - \ 'cabal exec -- ghc -fno-code -v0 %t', + \ ale#path#BufferCdString(bufnr('')) . 'cabal exec -- ghc -fno-code -v0 %t', \ ale_linters#haskell#cabal_ghc#GetCommand(bufnr('')) let b:ale_haskell_cabal_ghc_options = 'foobar' AssertEqual - \ 'cabal exec -- ghc foobar %t', + \ ale#path#BufferCdString(bufnr('')) . 'cabal exec -- ghc foobar %t', \ ale_linters#haskell#cabal_ghc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_haskell_stack_ghc_command_callback.vader b/test/command_callback/test_haskell_stack_ghc_command_callback.vader index 4adab583..aa13fb2b 100644 --- a/test/command_callback/test_haskell_stack_ghc_command_callback.vader +++ b/test/command_callback/test_haskell_stack_ghc_command_callback.vader @@ -10,5 +10,8 @@ Execute(The linter should not be executed when there's no stack.yaml file): Execute(The linter should be executed when there is a stack.yaml file): call ale#test#SetFilename('stack_ghc_paths/test.hs') - AssertLinter 'stack', 'stack ghc -- -fno-code -v0 %t' + AssertLinter 'stack', ale#path#BufferCdString(bufnr('')) . 'stack ghc -- -fno-code -v0 %t' + let b:ale_haskell_stack_ghc_options = 'foobar' + + AssertLinter 'stack', ale#path#BufferCdString(bufnr('')) . 'stack ghc -- foobar %t' diff --git a/test/command_callback/test_javac_command_callback.vader b/test/command_callback/test_javac_command_callback.vader index 2dcb6a1b..42c64e54 100644 --- a/test/command_callback/test_javac_command_callback.vader +++ b/test/command_callback/test_javac_command_callback.vader @@ -48,30 +48,30 @@ Execute(The executable should be configurable): \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' Execute(The javac callback should include discovered classpaths): - WithChainResults [ + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ \ '[DEBUG] Ignore this.', \ '[INFO] Something we should ignore.', \ '/foo/bar.jar', \ '/xyz/abc.jar', - \] + \], {}) - AssertLinter 'javac', + AssertEqual \ g:prefix \ . ' -cp ' \ . ale#Escape(join(['/foo/bar.jar', '/xyz/abc.jar'], g:cp_sep)) - \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') Execute(The javac callback should combine discovered classpaths and manual ones): let g:ale_java_javac_classpath = 'configured.jar' - - WithChainResults [ + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ \ '[DEBUG] Ignore this.', \ '[INFO] Something we should ignore.', \ '/foo/bar.jar', \ '/xyz/abc.jar', - \] + \], {}) - AssertLinter 'javac', + AssertEqual \ g:prefix \ . ' -cp ' \ . ale#Escape(join( @@ -82,11 +82,18 @@ Execute(The javac callback should combine discovered classpaths and manual ones) \ ], \ g:cp_sep \ )) - \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') let g:ale_java_javac_classpath = 'configured.jar' . g:cp_sep . 'configured2.jar' + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \], {}) - AssertLinter 'javac', + AssertEqual \ g:prefix \ . ' -cp ' \ . ale#Escape(join( @@ -98,7 +105,8 @@ Execute(The javac callback should combine discovered classpaths and manual ones) \ ], \ g:cp_sep \ )) - \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') Execute(The javac callback should detect source directories): call ale#engine#Cleanup(bufnr('')) @@ -117,25 +125,25 @@ Execute(The javac callback should combine detected source directories and classp call ale#test#SetFilename('java_paths/src/main/java/com/something/dummy.java') call ale#engine#InitBufferInfo(bufnr('')) - WithChainResults [ + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ \ '[DEBUG] Ignore this.', \ '[INFO] Something we should ignore.', \ '/foo/bar.jar', \ '/xyz/abc.jar', - \] - AssertLinter 'javac', + \], {}) + + AssertEqual \ ale#path#CdString(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/') \ ) - \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') Execute(The javac callback should use g:ale_java_javac_options correctly): let g:ale_java_javac_options = '--anything --else' - let b:command = ale_linters#java#javac#GetCommand(bufnr(''), []) - AssertLinter 'javac', \ g:prefix . ' -d ' . ale#Escape('TEMP_DIR') . ' --anything --else %t' @@ -166,3 +174,15 @@ Execute(The javac callback should include src/main/jaxb when available): \ ale#path#Simplify(g:dir . '/java_paths_with_jaxb/src/main/jaxb/'), \ ], g:cp_sep)) \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should add -sourcepath even if src/java/main doesn't exist): + call ale#engine#Cleanup(bufnr('')) + call ale#test#SetFilename('java_paths_no_main/src/test/java/com/something/dummy.java') + call ale#engine#InitBufferInfo(bufnr('')) + + AssertLinter 'javac', + \ ale#path#CdString(expand('%:p:h')) . ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/java_paths_no_main/src/test/java/'), + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' diff --git a/test/command_callback/test_javalsp_command_callback.vader b/test/command_callback/test_javalsp_command_callback.vader index aedb4a4b..8bfaa8ee 100644 --- a/test/command_callback/test_javalsp_command_callback.vader +++ b/test/command_callback/test_javalsp_command_callback.vader @@ -6,9 +6,9 @@ After: call ale#assert#TearDownLinterTest() Execute(The javalsp callback should return the correct default value): - AssertLinter 'java', ale#Escape('java') . ' -cp javacs.jar -Xverify:none org.javacs.Main' + AssertLinter 'java', ale#Escape('java') . ' -Xverify:none -m javacs/org.javacs.Main' Execute(The javalsp java executable should be configurable): let b:ale_java_javalsp_executable = '/bin/foobar' - AssertLinter '/bin/foobar', ale#Escape('/bin/foobar') . ' -cp javacs.jar -Xverify:none org.javacs.Main' + AssertLinter '/bin/foobar', ale#Escape('/bin/foobar') . ' -Xverify:none -m javacs/org.javacs.Main' diff --git a/test/command_callback/test_javascript_tsserver_command_callback.vader b/test/command_callback/test_javascript_tsserver_command_callback.vader index 638dd873..04230238 100644 --- a/test/command_callback/test_javascript_tsserver_command_callback.vader +++ b/test/command_callback/test_javascript_tsserver_command_callback.vader @@ -6,3 +6,11 @@ After: Execute(The default command should be correct): AssertLinter 'tsserver', ale#Escape('tsserver') + +Execute(should resolve correct path when nested 1): + call ale#test#SetFilename('tsserver_paths/src/level-1/level-2/file3.ts') + AssertLSPProject ale#path#Simplify(g:dir . '/tsserver_paths/src/level-1') + +Execute(should resolve correct path when nested 2): + call ale#test#SetFilename('tsserver_paths/src/file1.ts') + AssertLSPProject ale#path#Simplify(g:dir . '/tsserver_paths') diff --git a/test/command_callback/test_kotlinc_command_callback.vader b/test/command_callback/test_kotlinc_command_callback.vader new file mode 100644 index 00000000..fe94bffa --- /dev/null +++ b/test/command_callback/test_kotlinc_command_callback.vader @@ -0,0 +1,9 @@ +Before: + call ale#assert#SetUpLinterTest('kotlin', 'kotlinc') + call ale#test#SetFilename('test.kt') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'kotlinc', 'kotlinc ' . ale#Escape(expand('%:p')) diff --git a/test/command_callback/test_languagetool_command_callback.vader b/test/command_callback/test_languagetool_command_callback.vader new file mode 100644 index 00000000..a79662b9 --- /dev/null +++ b/test/command_callback/test_languagetool_command_callback.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('text', 'languagetool') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'languagetool', ale#Escape('languagetool') + \ . ' --autoDetect %s' + +Execute(Should be able to set a custom executable): + let g:ale_languagetool_executable = 'foobar' + + AssertLinter 'foobar' , ale#Escape('foobar') + \ . ' --autoDetect %s' diff --git a/test/command_callback/test_php_langserver_callbacks.vader b/test/command_callback/test_php_langserver_callbacks.vader index 3b0a427e..59c3fe6c 100644 --- a/test/command_callback/test_php_langserver_callbacks.vader +++ b/test/command_callback/test_php_langserver_callbacks.vader @@ -2,10 +2,6 @@ Before: call ale#assert#SetUpLinterTest('php', 'langserver') After: - if isdirectory(g:dir . '/.git') - call delete(g:dir . '/.git', 'd') - endif - call ale#assert#TearDownLinterTest() Execute(The default executable path should be correct): @@ -23,7 +19,12 @@ Execute(Vendor executables should be detected): \ )) Execute(The project path should be correct for .git directories): - call ale#test#SetFilename('php-langserver-project/test.php') - call mkdir(g:dir . '/.git') + call ale#test#SetFilename('php-langserver-project/with-git/test.php') + silent! call mkdir('php-langserver-project/with-git/.git') + + AssertLSPProject ale#path#Simplify(g:dir . '/php-langserver-project/with-git') + +Execute(The project path should be correct for composer.json file): + call ale#test#SetFilename('php-langserver-project/with-composer/test.php') - AssertLSPProject g:dir + AssertLSPProject ale#path#Simplify(g:dir . '/php-langserver-project/with-composer') diff --git a/test/command_callback/test_phpstan_command_callbacks.vader b/test/command_callback/test_phpstan_command_callbacks.vader index 665661a3..3714fc64 100644 --- a/test/command_callback/test_phpstan_command_callbacks.vader +++ b/test/command_callback/test_phpstan_command_callbacks.vader @@ -1,34 +1,66 @@ Before: + call delete('./phpstan.neon') call ale#assert#SetUpLinterTest('php', 'phpstan') - WithChainResults ['0.10.2'] + GivenCommandOutput ['0.10.2'] After: + call delete('./phpstan.neon') call ale#assert#TearDownLinterTest() Execute(Custom executables should be used for the executable and command): let g:ale_php_phpstan_executable = 'phpstan_test' AssertLinter 'phpstan_test', - \ ale#Escape('phpstan_test') . ' analyze -l4 --errorFormat raw %s' + \ ale#Escape('phpstan_test') . ' analyze --no-progress --errorFormat raw -l ' . ale#Escape('4') . ' %s' Execute(project with level set to 3): call ale#test#SetFilename('phpstan-test-files/foo/test.php') let g:ale_php_phpstan_level = 3 AssertLinter 'phpstan', - \ ale#Escape('phpstan') . ' analyze -l3 --errorFormat raw %s' + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat raw -l ' . ale#Escape('3') . ' %s' Execute(Custom phpstan configuration file): let g:ale_php_phpstan_configuration = 'phpstan_config' AssertLinter 'phpstan', - \ ale#Escape('phpstan') . ' analyze -l4 --errorFormat raw -c phpstan_config %s' + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat raw -c ' . ale#Escape('phpstan_config') . ' -l ' . ale#Escape('4') . ' %s' Execute(Choose the right format for error format param): - WithChainResults ['0.10.3'] + GivenCommandOutput ['0.10.3'] AssertLinter 'phpstan', [ \ ale#Escape('phpstan') . ' --version', - \ ale#Escape('phpstan') . ' analyze -l4 --error-format raw %s' + \ ale#Escape('phpstan') . ' analyze --no-progress --error-format raw -l ' . ale#Escape('4') . ' %s' + \ ] + +Execute(Configuration file exists in current directory): + call writefile(['parameters:', ' level: 7'], './phpstan.neon') + let g:ale_php_phpstan_level = '' + let g:ale_php_phpstan_configuration = '' + + AssertLinter 'phpstan', [ + \ ale#Escape('phpstan') . ' --version', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat raw %s' + \ ] + +Execute(Configuration file exists in current directory, but force phpstan level): + call writefile(['parameters:', ' level: 7'], './phpstan.neon') + let g:ale_php_phpstan_configuration = '' + let g:ale_php_phpstan_level = '7' + + AssertLinter 'phpstan', [ + \ ale#Escape('phpstan') . ' --version', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat raw -l ' . ale#Escape('7') . ' %s' + \ ] + +Execute(Configuration file exists in current directory, but force phpstan configuration): + call writefile(['parameters:', ' level: 7'], './phpstan.neon') + let g:ale_php_phpstan_level = '' + let g:ale_php_phpstan_configuration = 'phpstan.custom.neon' + + AssertLinter 'phpstan', [ + \ ale#Escape('phpstan') . ' --version', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat raw -c ' . ale#Escape('phpstan.custom.neon') . ' %s' \ ] diff --git a/test/command_callback/test_psalm_command_callbacks.vader b/test/command_callback/test_psalm_command_callbacks.vader index d731054f..33d770c2 100644 --- a/test/command_callback/test_psalm_command_callbacks.vader +++ b/test/command_callback/test_psalm_command_callbacks.vader @@ -24,6 +24,9 @@ Execute(Vendor executables should be detected): Execute(The project path should be correct for .git directories): call ale#test#SetFilename('psalm-project/test.php') - call mkdir(g:dir . '/.git') - AssertLSPProject g:dir
\ No newline at end of file + if !isdirectory(g:dir . '/.git') + call mkdir(g:dir . '/.git') + endif + + AssertLSPProject g:dir diff --git a/test/command_callback/test_pylama_command_callback.vader b/test/command_callback/test_pylama_command_callback.vader new file mode 100644 index 00000000..417cb5c9 --- /dev/null +++ b/test/command_callback/test_pylama_command_callback.vader @@ -0,0 +1,85 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pylama') + call ale#test#SetFilename('test.py') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + let b:command_tail = ' %s' + +After: + unlet! b:bin_dir + unlet! b:executable + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The pylama command callback should return a default): + AssertLinter 'pylama', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('pylama') . b:command_tail + +Execute(The option for disabling changing directories should work): + let g:ale_python_pylama_change_directory = 0 + + AssertLinter 'pylama', ale#Escape('pylama') . b:command_tail + +Execute(The pylama executable should be configurable, and escaped properly): + let g:ale_python_pylama_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('executable with spaces') . b:command_tail + +Execute(The pylama command callback should let you set options): + let g:ale_python_pylama_options = '--some-option' + + AssertLinter 'pylama', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('pylama') . ' --some-option' . b:command_tail + +Execute(The pylama command callback should switch directories to the detected project root): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/bar.py') + + AssertLinter 'pylama', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/no_virtualenv/subdir')) + \ . ale#Escape('pylama') . b:command_tail + +Execute(The pylama command callback shouldn't detect virtualenv directories where they don't exist): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/bar.py') + + AssertLinter 'pylama', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/no_virtualenv/subdir')) + \ . ale#Escape('pylama') . b:command_tail + +Execute(The pylama command callback should detect virtualenv directories and switch to the project root): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/pylama' + \) + + AssertLinter b:executable, + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) + \ . ale#Escape(b:executable) . b:command_tail + +Execute(You should able able to use the global pylama instead): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_pylama_use_global = 1 + + AssertLinter 'pylama', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) + \ . ale#Escape('pylama') . b:command_tail + +Execute(Setting executable to 'pipenv' appends 'run pylama'): + let g:ale_python_pylama_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run pylama' . b:command_tail + +Execute(Pipenv is detected when python_pylama_auto_pipenv is set): + let g:ale_python_pylama_auto_pipenv = 1 + call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('pipenv') . ' run pylama' . b:command_tail diff --git a/test/command_callback/test_pylint_command_callback.vader b/test/command_callback/test_pylint_command_callback.vader index 6b21b127..c41c8398 100644 --- a/test/command_callback/test_pylint_command_callback.vader +++ b/test/command_callback/test_pylint_command_callback.vader @@ -39,7 +39,7 @@ Execute(The pylint callbacks shouldn't detect virtualenv directories where they silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/bar.py') AssertLinter 'pylint', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/no_virtualenv/subdir')) \ . ale#Escape('pylint') . ' ' . b:command_tail Execute(The pylint callbacks should detect virtualenv directories): @@ -50,7 +50,7 @@ Execute(The pylint callbacks should detect virtualenv directories): \) AssertLinter b:executable, - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) \ . ale#Escape(b:executable) . ' ' . b:command_tail Execute(You should able able to use the global pylint instead): @@ -58,7 +58,7 @@ Execute(You should able able to use the global pylint instead): let g:ale_python_pylint_use_global = 1 AssertLinter 'pylint', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) \ . ale#Escape('pylint') . ' ' . b:command_tail Execute(Setting executable to 'pipenv' appends 'run pylint'): diff --git a/test/command_callback/test_pyls_command_callback.vader b/test/command_callback/test_pyls_command_callback.vader index 531b5b3b..aff2fcd7 100644 --- a/test/command_callback/test_pyls_command_callback.vader +++ b/test/command_callback/test_pyls_command_callback.vader @@ -45,3 +45,8 @@ Execute(Pipenv is detected when python_pyls_auto_pipenv is set): AssertLinter 'pipenv', \ ale#Escape('pipenv') . ' run pyls' + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_python_pyls_config = {'pyls': {'plugins': {'preload': {'enabled': v:false}}}} + AssertLSPConfig {'pyls': {'plugins': {'preload': {'enabled': v:false}}}} diff --git a/test/command_callback/test_racket_raco_command_callback.vader b/test/command_callback/test_racket_raco_command_callback.vader new file mode 100644 index 00000000..fb83ffa1 --- /dev/null +++ b/test/command_callback/test_racket_raco_command_callback.vader @@ -0,0 +1,10 @@ +Before: + call ale#assert#SetUpLinterTest('racket', 'raco') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command and executable should be correct): + AssertLinter 'raco', 'raco expand %s' + + diff --git a/test/command_callback/test_reek_command_callback.vader b/test/command_callback/test_reek_command_callback.vader index a7cb7fb9..798c3314 100644 --- a/test/command_callback/test_reek_command_callback.vader +++ b/test/command_callback/test_reek_command_callback.vader @@ -5,7 +5,7 @@ After: call ale#assert#TearDownLinterTest() Execute(The reek callbacks should return the correct default values): - WithChainResults ['reek 5.0.0'] + GivenCommandOutput ['reek 5.0.0'] AssertLinter 'reek', [ \ ale#Escape('reek') . ' --version', \ ale#Escape('reek') . ' -f json --no-progress --no-color --force-exclusion --stdin-filename %s', @@ -14,7 +14,7 @@ Execute(The reek callbacks should return the correct default values): " Try with older versions. call ale#semver#ResetVersionCache() - WithChainResults ['reek 4.8.2'] + GivenCommandOutput ['reek 4.8.2'] AssertLinter 'reek', [ \ ale#Escape('reek') . ' --version', \ ale#Escape('reek') . ' -f json --no-progress --no-color --force-exclusion', @@ -23,7 +23,7 @@ Execute(The reek callbacks should return the correct default values): Execute(Setting bundle appends 'exec reek'): let g:ale_ruby_reek_executable = 'bundle' - WithChainResults ['reek 5.0.0'] + GivenCommandOutput ['reek 5.0.0'] AssertLinter 'bundle', ale#Escape('bundle') \ . ' exec reek' \ . ' -f json --no-progress --no-color --force-exclusion --stdin-filename %s', @@ -31,20 +31,19 @@ Execute(Setting bundle appends 'exec reek'): " Try with older versions. call ale#semver#ResetVersionCache() - WithChainResults ['reek 4.8.2'] + GivenCommandOutput ['reek 4.8.2'] AssertLinter 'bundle', ale#Escape('bundle') \ . ' exec reek' \ . ' -f json --no-progress --no-color --force-exclusion' Execute(The reek version check should be cached): - WithChainResults ['reek 5.0.0'] + GivenCommandOutput ['reek 5.0.0'] AssertLinter 'reek', [ \ ale#Escape('reek') . ' --version', \ ale#Escape('reek') . ' -f json --no-progress --no-color --force-exclusion --stdin-filename %s', \] - WithChainResults [] + GivenCommandOutput [] AssertLinter 'reek', [ - \ '', \ ale#Escape('reek') . ' -f json --no-progress --no-color --force-exclusion --stdin-filename %s', \] diff --git a/test/command_callback/test_rst_textlint_command_callbacks.vader b/test/command_callback/test_rst_textlint_command_callbacks.vader new file mode 100644 index 00000000..9bc4c5c2 --- /dev/null +++ b/test/command_callback/test_rst_textlint_command_callbacks.vader @@ -0,0 +1,65 @@ +" Author: januswel, w0rp + +Before: + " This is just one language for the linter. + call ale#assert#SetUpLinterTest('rst', 'textlint') + + " The configuration is shared between many languages. + Save g:ale_textlint_executable + Save g:ale_textlint_use_global + Save g:ale_textlint_options + + let g:ale_textlint_executable = 'textlint' + let g:ale_textlint_use_global = 0 + let g:ale_textlint_options = '' + + unlet! b:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + +After: + unlet! b:command_tail + unlet! b:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' -f json --stdin --stdin-filename %s' + +Execute(The executable should be configurable): + let b:ale_textlint_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -f json --stdin --stdin-filename %s' + +Execute(The options should be configurable): + let b:ale_textlint_options = '--something' + + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' --something -f json --stdin --stdin-filename %s' + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_bin_path/foo.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint')) + \ . ' -f json --stdin --stdin-filename %s' + +Execute(The local executable from textlint/bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_textlint_bin_path/foo.txt') + + if has('win32') + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ 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' + else + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ 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' + endif diff --git a/test/command_callback/test_rust_rls_callbacks.vader b/test/command_callback/test_rust_rls_callbacks.vader index a710161d..ec8c8811 100644 --- a/test/command_callback/test_rust_rls_callbacks.vader +++ b/test/command_callback/test_rust_rls_callbacks.vader @@ -5,7 +5,7 @@ After: call ale#assert#TearDownLinterTest() Execute(The default executable path should be correct): - AssertLinter 'rls', ale#Escape('rls') . ' +' . ale#Escape('nightly') + AssertLinter 'rls', ale#Escape('rls') Execute(The toolchain should be configurable): let g:ale_rust_rls_toolchain = 'stable' @@ -23,3 +23,8 @@ Execute(The project root should be detected correctly): call ale#test#SetFilename('rust-rls-project/test.rs') AssertLSPProject ale#path#Simplify(g:dir . '/rust-rls-project') + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_rust_rls_config = {'rust': {'clippy_preference': 'on'}} + AssertLSPConfig {'rust': {'clippy_preference': 'on'}} diff --git a/test/command_callback/test_shellcheck_command_callback.vader b/test/command_callback/test_shellcheck_command_callback.vader index 22a9ccb5..1d5b056b 100644 --- a/test/command_callback/test_shellcheck_command_callback.vader +++ b/test/command_callback/test_shellcheck_command_callback.vader @@ -14,6 +14,11 @@ After: Execute(The default shellcheck command should be correct): AssertLinter 'shellcheck', b:prefix . ale#Escape('shellcheck') . b:suffix +Execute(The option disabling changing directories should work): + let g:ale_sh_shellcheck_change_directory = 0 + + AssertLinter 'shellcheck', ale#Escape('shellcheck') . b:suffix + Execute(The shellcheck command should accept options): let b:ale_sh_shellcheck_options = '--foobar' @@ -33,6 +38,18 @@ Execute(The shellcheck command should include the dialect): AssertLinter 'shellcheck', \ b:prefix . ale#Escape('shellcheck') . ' -s bash' . b:suffix +Execute(The shellcheck command should use ale_sh_shellcheck_dialect): + let b:ale_sh_shellcheck_dialect = 'ksh93' + + AssertLinter 'shellcheck', + \ b:prefix . ale#Escape('shellcheck') . ' -s ksh93' . b:suffix + +Execute(The shellcheck command should allow unspecified dialect): + let b:ale_sh_shellcheck_dialect = '' + + AssertLinter 'shellcheck', + \ b:prefix . ale#Escape('shellcheck') . b:suffix + Execute(The shellcheck command should include the dialect before options and exclusions): let b:is_bash = 1 let b:ale_sh_shellcheck_options = '--foobar' @@ -48,7 +65,7 @@ Execute(The -x option should be added when the version is new enough): \ b:prefix . ale#Escape('shellcheck') . b:suffix, \] - WithChainResults [ + GivenCommandOutput [ \ 'ShellCheck - shell script analysis tool', \ 'version: 0.4.4', \ 'license: GNU General Public License, version 3', @@ -60,14 +77,13 @@ Execute(The -x option should be added when the version is new enough): \] " We should cache the version check - WithChainResults [] + GivenCommandOutput [] AssertLinter 'shellcheck', [ - \ '', \ b:prefix . ale#Escape('shellcheck') . ' -x' . b:suffix, \] Execute(The -x option should not be added when the version is too old): - WithChainResults [ + GivenCommandOutput [ \ 'ShellCheck - shell script analysis tool', \ 'version: 0.3.9', \ 'license: GNU General Public License, version 3', @@ -79,7 +95,7 @@ Execute(The -x option should not be added when the version is too old): \] Execute(The version check shouldn't be run again for old versions): - WithChainResults [ + GivenCommandOutput [ \ 'ShellCheck - shell script analysis tool', \ 'version: 0.3.9', \ 'license: GNU General Public License, version 3', @@ -90,6 +106,5 @@ Execute(The version check shouldn't be run again for old versions): \ b:prefix . ale#Escape('shellcheck') . b:suffix, \] AssertLinter 'shellcheck', [ - \ '', \ b:prefix . ale#Escape('shellcheck') . b:suffix, \] diff --git a/test/command_callback/test_standardrb_command_callback.vader b/test/command_callback/test_standardrb_command_callback.vader new file mode 100644 index 00000000..7bc1c976 --- /dev/null +++ b/test/command_callback/test_standardrb_command_callback.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'standardrb') + call ale#test#SetFilename('dummy.rb') + + let g:ale_ruby_standardrb_executable = 'standardrb' + let g:ale_ruby_standardrb_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to standardrb): + AssertLinter 'standardrb', ale#Escape('standardrb') + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + +Execute(Should be able to set a custom executable): + let g:ale_ruby_standardrb_executable = 'bin/standardrb' + + AssertLinter 'bin/standardrb' , ale#Escape('bin/standardrb') + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + +Execute(Setting bundle appends 'exec standardrb'): + let g:ale_ruby_standardrb_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec standardrb' + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) diff --git a/test/command_callback/test_sugarss_stylelint_command_callback.vader b/test/command_callback/test_sugarss_stylelint_command_callback.vader new file mode 100644 index 00000000..448436fb --- /dev/null +++ b/test/command_callback/test_sugarss_stylelint_command_callback.vader @@ -0,0 +1,31 @@ +Before: + call ale#assert#SetUpLinterTest('sugarss', 'stylelint') + unlet! b:executable + +After: + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(node_modules directories should be discovered): + call ale#test#SetFilename('stylelint_paths/nested/testfile.sss') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/stylelint_paths/node_modules/.bin/stylelint' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --syntax=sugarss --stdin-filename %s' + +Execute(The global override should work): + let b:ale_sugarss_stylelint_executable = 'foobar' + let b:ale_sugarss_stylelint_use_global = 1 + + call ale#test#SetFilename('stylelint_paths/nested/testfile.sss') + + AssertLinter 'foobar', ale#Escape('foobar') . ' --syntax=sugarss --stdin-filename %s' + +Execute(Extra options should be configurable): + let b:ale_sugarss_stylelint_options = '--configFile ''/absolute/path/to/file''' + + AssertLinter 'stylelint', + \ ale#Escape('stylelint') . ' --configFile ''/absolute/path/to/file'' --syntax=sugarss --stdin-filename %s' diff --git a/test/command_callback/test_swift_sourcekitlsp_command_callbacks.vader b/test/command_callback/test_swift_sourcekitlsp_command_callbacks.vader new file mode 100644 index 00000000..4611c6de --- /dev/null +++ b/test/command_callback/test_swift_sourcekitlsp_command_callbacks.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('swift', 'sourcekitlsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + call ale#test#SetFilename('../swift-test-files/swift-package-project/src/folder/dummy.swift') + + AssertLinter 'sourcekit-lsp', ale#Escape('sourcekit-lsp') + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('../swift-test-files/swift-package-project/src/folder/dummy.swift') + + let g:ale_sourcekit_lsp_executable = '/path/to/custom/sourcekitlsp' + + AssertLinter '/path/to/custom/sourcekitlsp', + \ ale#Escape('/path/to/custom/sourcekitlsp') + +Execute(The language should be correct): + AssertLSPLanguage 'swift' diff --git a/test/command_callback/test_tex_lacheck_command_callback.vader b/test/command_callback/test_tex_lacheck_command_callback.vader new file mode 100644 index 00000000..b404cc78 --- /dev/null +++ b/test/command_callback/test_tex_lacheck_command_callback.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('tex', 'lacheck') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to lacheck): + AssertLinter 'lacheck', ale#Escape('lacheck') . ' %t' + +Execute(Should be able to set a custom executable): + let g:ale_tex_lacheck_executable = 'bin/foo' + + AssertLinter 'bin/foo' , ale#Escape('bin/foo') . ' %t' diff --git a/test/command_callback/test_tex_textlint_command_callbacks.vader b/test/command_callback/test_tex_textlint_command_callbacks.vader new file mode 100644 index 00000000..d1e2ab91 --- /dev/null +++ b/test/command_callback/test_tex_textlint_command_callbacks.vader @@ -0,0 +1,65 @@ +" Author: januswel, w0rp + +Before: + " This is just one language for the linter. + call ale#assert#SetUpLinterTest('tex', 'textlint') + + " The configuration is shared between many languages. + Save g:ale_textlint_executable + Save g:ale_textlint_use_global + Save g:ale_textlint_options + + let g:ale_textlint_executable = 'textlint' + let g:ale_textlint_use_global = 0 + let g:ale_textlint_options = '' + + unlet! b:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + +After: + unlet! b:command_tail + unlet! b:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' -f json --stdin --stdin-filename %s' + +Execute(The executable should be configurable): + let b:ale_textlint_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -f json --stdin --stdin-filename %s' + +Execute(The options should be configurable): + let b:ale_textlint_options = '--something' + + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' --something -f json --stdin --stdin-filename %s' + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_bin_path/foo.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/textlint_paths/with_bin_path/node_modules/.bin/textlint')) + \ . ' -f json --stdin --stdin-filename %s' + +Execute(The local executable from textlint/bin should be used if available): + call ale#test#SetFilename('textlint_paths/with_textlint_bin_path/foo.txt') + + if has('win32') + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ 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' + else + AssertLinter + \ ale#path#Simplify(g:dir . '/textlint_paths/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ 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' + endif diff --git a/test/command_callback/test_vcom_command_callbacks.vader b/test/command_callback/test_vcom_command_callbacks.vader new file mode 100644 index 00000000..77218f74 --- /dev/null +++ b/test/command_callback/test_vcom_command_callbacks.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('vhdl', 'vcom') + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'vcom', ale#Escape('vcom') . ' -2008 -quiet -lint %t' + + let b:ale_vhdl_vcom_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -2008 -quiet -lint %t' + +Execute(The options should be configurable): + let b:ale_vhdl_vcom_options = '--something' + + AssertLinter 'vcom', ale#Escape('vcom') . ' --something %t' diff --git a/test/command_callback/test_vlog_command_callbacks.vader b/test/command_callback/test_vlog_command_callbacks.vader new file mode 100644 index 00000000..a07944f7 --- /dev/null +++ b/test/command_callback/test_vlog_command_callbacks.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('verilog', 'vlog') + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'vlog', ale#Escape('vlog') . ' -quiet -lint %t' + + let b:ale_verilog_vlog_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -quiet -lint %t' + +Execute(The options should be configurable): + let b:ale_verilog_vlog_options = '--something' + + AssertLinter 'vlog', ale#Escape('vlog') . ' --something %t' diff --git a/test/command_callback/test_vulture_command_callback.vader b/test/command_callback/test_vulture_command_callback.vader new file mode 100644 index 00000000..d6c866b9 --- /dev/null +++ b/test/command_callback/test_vulture_command_callback.vader @@ -0,0 +1,68 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'vulture') + call ale#test#SetFilename('test.py') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + + call ale#assert#TearDownLinterTest() + +Execute(The vulture command callback should lint file directory by default): + AssertLinter 'vulture', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('vulture') . ' .' + +Execute(The vulture command callback should lint project root, when present): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/bar.py') + + AssertLinter 'vulture', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/no_virtualenv/subdir')) + \ . ale#Escape('vulture') . ' .' + +Execute(The option for disabling change directory works and only lints file): + let g:ale_python_vulture_change_directory = 0 + + AssertLinter 'vulture', ale#Escape('vulture') . ' %s' + +Execute(The vulture executable should be configurable, and escaped properly): + let g:ale_python_vulture_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('executable with spaces') . ' .' + +Execute(The vulture command callback should let you set options): + let g:ale_python_vulture_options = '--some-option' + + AssertLinter 'vulture', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('vulture') . ' --some-option .' + +Execute(The vulture command callback should detect virtualenv directories and switch to the project root): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/vulture' + \) + + AssertLinter b:executable, + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) + \ . ale#Escape(b:executable) . ' .' + +Execute(You should able able to use the global vulture instead): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_vulture_use_global = 1 + + AssertLinter 'vulture', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) + \ . ale#Escape('vulture') . ' .' + +Execute(Setting executable to 'pipenv' appends 'run vulture'): + let g:ale_python_vulture_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run vulture' . ' .' diff --git a/test/command_callback/test_xo_command_callback.vader b/test/command_callback/test_xo_command_callback.vader new file mode 100644 index 00000000..12de595d --- /dev/null +++ b/test/command_callback/test_xo_command_callback.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('typescript', 'xo') + call ale#test#SetFilename('testfile.ts') + unlet! b:executable + +After: + call ale#assert#TearDownLinterTest() + +Execute(The XO executable should be called): + AssertLinter 'xo', ale#Escape('xo') . ' --reporter unix --stdin --stdin-filename %s' + +Execute(The XO executable should be configurable): + let b:ale_typescript_xo_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --reporter unix --stdin --stdin-filename %s' + +Execute(The XO options should be configurable): + let b:ale_typescript_xo_options = '--wat' + + AssertLinter 'xo', ale#Escape('xo') . ' --wat --reporter unix --stdin --stdin-filename %s' diff --git a/test/command_callback/test_xvhdl_command_callbacks.vader b/test/command_callback/test_xvhdl_command_callbacks.vader new file mode 100644 index 00000000..86f9a32d --- /dev/null +++ b/test/command_callback/test_xvhdl_command_callbacks.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('vhdl', 'xvhdl') + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'xvhdl', ale#Escape('xvhdl') . ' --2008 %t' + + let b:ale_vhdl_xvhdl_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --2008 %t' + +Execute(The options should be configurable): + let b:ale_vhdl_xvhdl_options = '--something' + + AssertLinter 'xvhdl', ale#Escape('xvhdl') . ' --something %t' diff --git a/test/command_callback/test_xvlog_command_callbacks.vader b/test/command_callback/test_xvlog_command_callbacks.vader new file mode 100644 index 00000000..564ac979 --- /dev/null +++ b/test/command_callback/test_xvlog_command_callbacks.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('verilog', 'xvlog') + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'xvlog', ale#Escape('xvlog') . ' %t' + + let b:ale_verilog_xvlog_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' %t' + +Execute(The options should be configurable): + let b:ale_verilog_xvlog_options = '--something' + + AssertLinter 'xvlog', ale#Escape('xvlog') . ' --something %t' diff --git a/test/command_callback/tex_paths/sample1.tex b/test/command_callback/tex_paths/sample1.tex new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tex_paths/sample1.tex diff --git a/test/command_callback/tex_paths/sample2.tex b/test/command_callback/tex_paths/sample2.tex new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tex_paths/sample2.tex diff --git a/test/command_callback/tsserver_paths/src/file1.ts b/test/command_callback/tsserver_paths/src/file1.ts new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tsserver_paths/src/file1.ts diff --git a/test/command_callback/tsserver_paths/src/level-1/file2.ts b/test/command_callback/tsserver_paths/src/level-1/file2.ts new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tsserver_paths/src/level-1/file2.ts diff --git a/test/command_callback/tsserver_paths/src/level-1/level-2/file3.ts b/test/command_callback/tsserver_paths/src/level-1/level-2/file3.ts new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tsserver_paths/src/level-1/level-2/file3.ts diff --git a/test/command_callback/tsserver_paths/src/level-1/tsconfig.json b/test/command_callback/tsserver_paths/src/level-1/tsconfig.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tsserver_paths/src/level-1/tsconfig.json diff --git a/test/command_callback/tsserver_paths/tsconfig.json b/test/command_callback/tsserver_paths/tsconfig.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/tsserver_paths/tsconfig.json diff --git a/test/compile_database_perf/test.sh b/test/compile_database_perf/test.sh new file mode 100755 index 00000000..15a2b442 --- /dev/null +++ b/test/compile_database_perf/test.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Generate source files for ALE to read. They don't have to be very long, the delay is in reading compile_commands, not actually running tests +mkdir -p gen_src +for i in {1..400}; do echo "const char *GeneratedFunc${i}() { return \"Word ${i}\"; }" > gen_src/source${i}.cpp; done + +# Create the compile_commands database +echo "[ {" > compile_commands.json + +for i in {1..399}; do + { + echo "\"command\": \"clang++ -c $(pwd)/gen_src/source${i}.cpp -o $(pwd)/build/obj/Debug/source${i}.o -MF $(pwd)/build/obj/Debug/source${i}.d -MMD -MP\"," + echo "\"directory\": \"$(pwd)/build\"," + echo "\"file\": \"$(pwd)/gen_src/source${i}.cpp\"" + echo "}, {" + } >> compile_commands.json +done + +{ + echo "\"command\": \"clang++ -c $(pwd)/gen_src/source400.cpp -o $(pwd)/build/obj/Debug/source400.o -MF $(pwd)/build/obj/Debug/source400.d -MMD -MP\"," + echo "\"directory\": \"$(pwd)/build\"," + echo "\"file\": \"$(pwd)/gen_src/source400.cpp\"" + echo "} ]" +} >> compile_commands.json + +# Start up vim and switch back and forth between files -- at least one of the files must be near the bottom of compile_commands.json +time vim -c "for i in range(0,20) | edit gen_src/source10.cpp | edit gen_src/source400.cpp | endfor" \ + -c "noautocmd qa!" \ + `find . | grep "source..\.cpp"` diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader index 3f0bfa70..f79773cc 100644 --- a/test/completion/test_completion_events.vader +++ b/test/completion/test_completion_events.vader @@ -27,7 +27,7 @@ Before: let g:get_completions_called = 0 " We just want to check if the function is called. - function! ale#completion#GetCompletions() + function! ale#completion#GetCompletions(source) let g:get_completions_called = 1 endfunction @@ -53,10 +53,11 @@ After: unlet! g:fake_mode unlet! g:get_completions_called unlet! b:ale_old_omnifunc - unlet! b:ale_old_completopt + unlet! b:ale_old_completeopt unlet! b:ale_completion_info unlet! b:ale_completion_response unlet! b:ale_completion_parser + unlet! b:ale_completion_result unlet! b:ale_complete_done_time delfunction CheckCompletionCalled @@ -86,7 +87,7 @@ Execute(ale#completion#GetCompletions should not be called when the cursor posit call setpos('.', [bufnr(''), 1, 2, 0]) " We just want to check if the function is called. - function! ale#completion#GetCompletions() + function! ale#completion#GetCompletions(source) let g:get_completions_called = 1 endfunction @@ -105,7 +106,7 @@ Execute(ale#completion#GetCompletions should not be called if you switch to norm let g:fake_mode = 'n' " We just want to check if the function is called. - function! ale#completion#GetCompletions() + function! ale#completion#GetCompletions(source) let g:get_completions_called = 1 endfunction @@ -124,6 +125,7 @@ Execute(Completion should not be done shortly after the CompleteDone function): Execute(ale#completion#Show() should remember the omnifunc setting and replace it): let &l:omnifunc = 'FooBar' + let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#Show('Response', 'Parser') AssertEqual 'FooBar', b:ale_old_omnifunc @@ -136,9 +138,10 @@ Execute(ale#completion#Show() should remember the omnifunc setting and replace i Execute(ale#completion#Show() should remember the completeopt setting and replace it): let &l:completeopt = 'menu' + let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#Show('Response', 'Parser') - AssertEqual 'menu', b:ale_old_completopt + AssertEqual 'menu', b:ale_old_completeopt AssertEqual 'menu,menuone,noselect,noinsert', &l:completeopt AssertEqual [], g:feedkeys_calls @@ -148,38 +151,70 @@ Execute(ale#completion#Show() should remember the completeopt setting and replac Execute(ale#completion#Show() should set the preview option if it's set): let &l:completeopt = 'menu,preview' + let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#Show('Response', 'Parser') - AssertEqual 'menu,preview', b:ale_old_completopt + AssertEqual 'menu,preview', b:ale_old_completeopt AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt AssertEqual [], g:feedkeys_calls sleep 1ms AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls +Execute(ale#completion#Show() should not replace the completeopt setting for manual completion): + let b:ale_completion_info = {'source': 'ale-manual'} + + let &l:completeopt = 'menu,preview' + + call ale#completion#Show('Response', 'Parser') + + Assert !exists('b:ale_old_completeopt') + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls + Execute(ale#completion#OmniFunc() should also remember the completeopt setting and replace it): let &l:completeopt = 'menu' + let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#OmniFunc(0, '') - AssertEqual 'menu', b:ale_old_completopt + AssertEqual 'menu', b:ale_old_completeopt AssertEqual 'menu,menuone,noselect,noinsert', &l:completeopt Execute(ale#completion#OmniFunc() should set the preview option if it's set): let &l:completeopt = 'menu,preview' + let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#OmniFunc(0, '') - AssertEqual 'menu,preview', b:ale_old_completopt + AssertEqual 'menu,preview', b:ale_old_completeopt AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt -Execute(ale#completion#Show() should make the correct feedkeys() call): +Execute(ale#completion#Show() should make the correct feedkeys() call for automatic completion): + let b:ale_completion_info = {'source': 'ale-automatic'} + call ale#completion#Show('Response', 'Parser') + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls + +Execute(ale#completion#Show() should make the correct feedkeys() call for manual completion): + let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#Show('Response', 'Parser') AssertEqual [], g:feedkeys_calls sleep 1ms AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls +Execute(ale#completion#Show() should not call feedkeys() for other sources): + let b:ale_completion_info = {'source': 'deoplete'} + call ale#completion#Show('Response', 'Parser') + + sleep 1ms + AssertEqual [], g:feedkeys_calls + Execute(ale#completion#Show() shouldn't do anything if you switch back to normal mode): let &l:completeopt = 'menu,preview' let g:fake_mode = 'n' @@ -188,7 +223,7 @@ Execute(ale#completion#Show() shouldn't do anything if you switch back to normal AssertEqual 'menu,preview', &l:completeopt Assert !exists('b:ale_old_omnifunc') - Assert !exists('b:ale_old_completopt') + Assert !exists('b:ale_old_completeopt') Assert !exists('b:ale_completion_response') Assert !exists('b:ale_completion_parser') AssertEqual [], g:feedkeys_calls @@ -209,12 +244,12 @@ Execute(ale#completion#Done() should restore old omnifunc values): Assert !has_key(b:, 'ale_old_omnifunc') Execute(ale#completion#Done() should restore the old completeopt setting): - let b:ale_old_completopt = 'menu' + let b:ale_old_completeopt = 'menu' call ale#completion#Done() AssertEqual 'menu', &l:completeopt - Assert !has_key(b:, 'ale_old_completopt') + Assert !has_key(b:, 'ale_old_completeopt') Execute(ale#completion#Done() should leave settings alone when none were remembered): let &l:omnifunc = 'BazBoz' @@ -234,9 +269,46 @@ Execute(The completion request_id should be reset when queuing again): AssertEqual 0, b:ale_completion_info.request_id -Execute(b:ale_completion_info should be set up correctly when requesting completions): +Execute(b:ale_completion_info should be set up correctly when requesting completions automatically): + let b:ale_completion_result = [] + call setpos('.', [bufnr(''), 3, 14, 0]) + call ale#completion#GetCompletions('ale-automatic') + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line_length': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ 'source': 'ale-automatic', + \ }, + \ b:ale_completion_info + Assert !exists('b:ale_completion_result') + +Execute(b:ale_completion_info should be set up correctly when requesting completions manually): + let b:ale_completion_result = [] + call setpos('.', [bufnr(''), 3, 14, 0]) + ALEComplete + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line_length': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ 'source': 'ale-manual', + \ }, + \ b:ale_completion_info + Assert !exists('b:ale_completion_result') + +Execute(b:ale_completion_info should be set up correctly for other sources): + let b:ale_completion_result = [] call setpos('.', [bufnr(''), 3, 14, 0]) - call ale#completion#GetCompletions() + call ale#completion#GetCompletions('deoplete') AssertEqual \ { @@ -246,8 +318,10 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet \ 'line_length': 14, \ 'line': 3, \ 'prefix': 'ab', + \ 'source': 'deoplete', \ }, \ b:ale_completion_info + Assert !exists('b:ale_completion_result') Execute(The correct keybinds should be configured): redir => g:output @@ -264,7 +338,7 @@ Execute(The correct keybinds should be configured): Execute(Running the normal mode <Plug> keybind should reset the settings): let b:ale_old_omnifunc = 'FooBar' - let b:ale_old_completopt = 'menu' + let b:ale_old_completeopt = 'menu' " We can't run the keybind, but we can call the function. call ale#completion#RestoreCompletionOptions() @@ -272,4 +346,4 @@ Execute(Running the normal mode <Plug> keybind should reset the settings): AssertEqual 'FooBar', &l:omnifunc AssertEqual 'menu', &l:completeopt Assert !has_key(b:, 'ale_old_omnifunc') - Assert !has_key(b:, 'ale_old_completopt') + Assert !has_key(b:, 'ale_old_completeopt') diff --git a/test/completion/test_completion_filtering.vader b/test/completion/test_completion_filtering.vader index ffb313ef..c5f14266 100644 --- a/test/completion/test_completion_filtering.vader +++ b/test/completion/test_completion_filtering.vader @@ -16,6 +16,9 @@ Execute(Prefix filtering should work for Lists of strings): AssertEqual \ ['FooBar', 'FongBar', 'baz', 'foo'], \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.') + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '') Execute(Prefix filtering should work for completion items): AssertEqual @@ -102,6 +105,9 @@ Execute(Excluding words from completion results should work with lists of String AssertEqual \ ['Deutsch'], \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], '.') + AssertEqual + \ ['Deutsch'], + \ ale#completion#Filter(bufnr(''), '', ['Deutsch'], '') Execute(Filtering shouldn't modify the original list): let b:ale_completion_excluded_words = ['it', 'describe'] diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader index 130f31b9..25536436 100644 --- a/test/completion/test_lsp_completion_messages.vader +++ b/test/completion/test_lsp_completion_messages.vader @@ -16,18 +16,20 @@ Before: let g:capability_checked = '' let g:conn_id = v:null let g:Callback = '' - let g:wait_callback_list = [] + let g:init_callback_list = [] - function! ale#lsp_linter#StartLSP(buffer, linter) abort + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) - return { + let l:details = { + \ 'command': 'foobar', \ 'buffer': a:buffer, \ 'connection_id': g:conn_id, \ 'project_root': '/foo/bar', - \ 'language_id': 'python', \} + + call add(g:init_callback_list, {-> a:Callback(a:linter, l:details)}) endfunction " Pretend we're in insert mode for most tests. @@ -35,9 +37,10 @@ Before: return 'i' endfunction - function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort + function! ale#lsp#HasCapability(conn_id, capability) abort let g:capability_checked = a:capability - call add(g:wait_callback_list, a:callback) + + return 1 endfunction function! ale#lsp#RegisterCallback(conn_id, callback) abort @@ -60,11 +63,11 @@ After: unlet! g:message_list unlet! g:capability_checked - unlet! g:wait_callback_list + unlet! g:init_callback_list unlet! g:conn_id unlet! g:Callback unlet! b:ale_old_omnifunc - unlet! b:ale_old_completopt + unlet! b:ale_old_completeopt unlet! b:ale_completion_info unlet! b:ale_completion_response unlet! b:ale_completion_parser @@ -99,14 +102,15 @@ Execute(The right message should be sent for the initial tsserver request): " The cursor position needs to match what was saved before. call setpos('.', [bufnr(''), 1, 3, 0]) - call ale#completion#GetCompletions() + call ale#completion#GetCompletions('ale-automatic') " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual 1, len(g:wait_callback_list) + AssertEqual 1, len(g:init_callback_list) + call map(g:init_callback_list, 'v:val()') + AssertEqual 'completion', g:capability_checked - call map(g:wait_callback_list, 'v:val([g:conn_id, ''/foo/bar''])') " We should send the right callback. AssertEqual @@ -125,6 +129,7 @@ Execute(The right message should be sent for the initial tsserver request): \ 'request_id': 1, \ 'line': 1, \ 'prefix': 'fo', + \ 'source': 'ale-automatic', \ }, \ get(b:, 'ale_completion_info', {}) @@ -186,14 +191,15 @@ Execute(The right message should be sent for the initial LSP request): " The cursor position needs to match what was saved before. call setpos('.', [bufnr(''), 1, 5, 0]) - call ale#completion#GetCompletions() + call ale#completion#GetCompletions('ale-automatic') " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual 1, len(g:wait_callback_list) + AssertEqual 1, len(g:init_callback_list) + call map(g:init_callback_list, 'v:val()') + AssertEqual 'completion', g:capability_checked - call map(g:wait_callback_list, 'v:val([g:conn_id, ''/foo/bar''])') " We should send the right callback. AssertEqual @@ -228,6 +234,7 @@ Execute(The right message should be sent for the initial LSP request): \ 'request_id': 1, \ 'line': 1, \ 'prefix': 'fo', + \ 'source': 'ale-automatic', \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', \ }, \ get(b:, 'ale_completion_info', {}) @@ -253,14 +260,15 @@ Execute(Two completion requests shouldn't be sent in a row): " The cursor position needs to match what was saved before. call setpos('.', [bufnr(''), 1, 5, 0]) - call ale#completion#GetCompletions() + call ale#completion#GetCompletions('ale-automatic') " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual 2, len(g:wait_callback_list) + AssertEqual 2, len(g:init_callback_list) + call map(g:init_callback_list, 'v:val()') + AssertEqual 'completion', g:capability_checked - call map(g:wait_callback_list, 'v:val([347, ''/foo/bar''])') " We should only send one completion message for two LSP servers. AssertEqual diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader index 71e53ab6..a8e00260 100644 --- a/test/completion/test_lsp_completion_parsing.vader +++ b/test/completion/test_lsp_completion_parsing.vader @@ -471,3 +471,34 @@ Execute(Should handle documentation in the markdown format): \ ], \ }, \ }) + +Execute(Should handle completion messages with textEdit objects): + AssertEqual + \ [ + \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'id': 226, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'PlayTimeCallback', + \ 'filterText': 'next_callback', + \ 'insertText': 'next_callback', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' next_callback', + \ 'sortText': '3ee19999next_callback', + \ 'textEdit': { + \ 'newText': 'next_callback', + \ 'range': { + \ 'end': {'character': 13, 'line': 12}, + \ 'start': {'character': 4, 'line': 12}, + \ }, + \ }, + \ }, + \ ], + \ }, + \ }) diff --git a/test/elm-test-files/newapp-notests/elm.json b/test/elm-test-files/newapp-notests/elm.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp-notests/elm.json diff --git a/test/elm-test-files/newapp-notests/node_modules/.bin/elm b/test/elm-test-files/newapp-notests/node_modules/.bin/elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp-notests/node_modules/.bin/elm diff --git a/test/elm-test-files/newapp-notests/tests/TestMain.elm b/test/elm-test-files/newapp-notests/tests/TestMain.elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp-notests/tests/TestMain.elm diff --git a/test/elm-test-files/newapp/elm.json b/test/elm-test-files/newapp/elm.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp/elm.json diff --git a/test/elm-test-files/newapp/node_modules/.bin/elm b/test/elm-test-files/newapp/node_modules/.bin/elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp/node_modules/.bin/elm diff --git a/test/elm-test-files/newapp/node_modules/.bin/elm-test b/test/elm-test-files/newapp/node_modules/.bin/elm-test new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp/node_modules/.bin/elm-test diff --git a/test/elm-test-files/newapp/src/Main.elm b/test/elm-test-files/newapp/src/Main.elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp/src/Main.elm diff --git a/test/elm-test-files/newapp/tests/TestSuite.elm b/test/elm-test-files/newapp/tests/TestSuite.elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/newapp/tests/TestSuite.elm diff --git a/test/elm-test-files/oldapp/elm-package.json b/test/elm-test-files/oldapp/elm-package.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/oldapp/elm-package.json diff --git a/test/elm-test-files/oldapp/node_modules/.bin/elm b/test/elm-test-files/oldapp/node_modules/.bin/elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/oldapp/node_modules/.bin/elm diff --git a/test/elm-test-files/oldapp/node_modules/.bin/elm-test b/test/elm-test-files/oldapp/node_modules/.bin/elm-test new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/oldapp/node_modules/.bin/elm-test diff --git a/test/elm-test-files/oldapp/src/Main.elm b/test/elm-test-files/oldapp/src/Main.elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/oldapp/src/Main.elm diff --git a/test/elm-test-files/oldapp/tests/TestSuite.elm b/test/elm-test-files/oldapp/tests/TestSuite.elm new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/elm-test-files/oldapp/tests/TestSuite.elm diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader index 90407681..9e5ad027 100644 --- a/test/fix/test_ale_fix.vader +++ b/test/fix/test_ale_fix.vader @@ -55,10 +55,20 @@ Before: return {'command': 'cat - <(echo d)'} endfunction + function CatLineDeferred(buffer, lines) abort + return ale#command#Run(a:buffer, 'echo', { + \ -> ale#command#Run(a:buffer, 'echo', {-> {'command': 'cat - <(echo d)'}}) + \}) + endfunction + function ReplaceWithTempFile(buffer, lines) abort return {'command': 'echo x > %t', 'read_temporary_file': 1} endfunction + function CatWithTempFile(buffer, lines) abort + return {'command': 'cat %t <(echo d)'} + endfunction + function RemoveLastLine(buffer, lines) abort return ['a', 'b'] endfunction @@ -175,8 +185,10 @@ Before: After: Restore + unlet! g:test_filename unlet! g:ale_run_synchronously unlet! g:ale_set_lists_synchronously + unlet! g:ale_run_synchronously_callbacks unlet! g:ale_emulate_job_failure unlet! b:ale_fixers unlet! b:ale_fix_on_save @@ -186,7 +198,9 @@ After: delfunction DoNothing delfunction CatLine delfunction CatLineOneArg + delfunction CatLineDeferred delfunction ReplaceWithTempFile + delfunction CatWithTempFile delfunction RemoveLastLine delfunction RemoveLastLineOneArg delfunction TestCallback @@ -217,8 +231,8 @@ After: setlocal buftype=nofile - if filereadable('fix_test_file') - call delete('fix_test_file') + if exists('g:test_filename') && filereadable(g:test_filename) + call delete(g:test_filename) endif call setloclist(0, []) @@ -235,11 +249,13 @@ Given testft (A file with three lines): Execute(ALEFix should complain when there are no functions to call): ALEFix + call ale#test#FlushJobs() AssertEqual 'No fixers have been defined. Try :ALEFixSuggest', GetLastMessage() Execute(ALEFix should apply simple functions): let g:ale_fixers.testft = ['AddCarets'] ALEFix + call ale#test#FlushJobs() Expect(The first function should be used): ^a @@ -249,6 +265,7 @@ Expect(The first function should be used): Execute(ALEFix should apply simple functions in a chain): let g:ale_fixers.testft = ['AddCarets', 'AddDollars'] ALEFix + call ale#test#FlushJobs() Expect(Both functions should be used): $^a @@ -258,6 +275,7 @@ Expect(Both functions should be used): Execute(ALEFix should allow 0 to be returned to skip functions): let g:ale_fixers.testft = ['DoNothing', 'AddDollars'] ALEFix + call ale#test#FlushJobs() Expect(Only the second function should be applied): $a @@ -268,6 +286,7 @@ 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 + call ale#test#FlushJobs() Expect(Nothing should be changed): a @@ -277,6 +296,7 @@ Expect(Nothing should be changed): Execute(* fixers should be used if no filetype is matched): let g:ale_fixers = {'*': ['AddDollars']} ALEFix + call ale#test#FlushJobs() Expect(The file should be changed): $a @@ -290,6 +310,7 @@ Execute(ALEFix should allow commands to be run): else let g:ale_fixers.testft = ['CatLine'] ALEFix + call ale#test#FlushJobs() endif Expect(An extra line should be added): @@ -301,6 +322,7 @@ Expect(An extra line should be added): Execute(ALEFix should use fixers passed in commandline when provided): let g:ale_fixers.testft = ['RemoveLastLine'] ALEFix AddCarets AddDollars + call ale#test#FlushJobs() Expect(Only fixers passed via command line should be run): $^a @@ -315,11 +337,28 @@ Execute(ALEFix should allow temporary files to be read): else let g:ale_fixers.testft = ['ReplaceWithTempFile'] ALEFix + call ale#test#FlushJobs() endif Expect(The line we wrote to the temporary file should be used here): x +Execute(ALEFix should not read the temporary file when the option is not set): + if has('win32') + " Just skip this test on Windows, we can't run it. + call setline(1, ['a', 'b', 'c', 'd']) + else + let g:ale_fixers.testft = ['CatWithTempFile'] + ALEFix + call ale#test#FlushJobs() + endif + +Expect(An extra line should be added): + a + b + c + d + Execute(ALEFix should allow jobs and simple functions to be combined): if has('win32') " Just skip this test on Windows, we can't run it. @@ -328,6 +367,7 @@ Execute(ALEFix should allow jobs and simple functions to be combined): else let g:ale_fixers.testft = ['ReplaceWithTempFile', 'AddDollars'] ALEFix + call ale#test#FlushJobs() endif Expect(The lines from the temporary file should be modified): @@ -340,6 +380,7 @@ Execute(ALEFix should send lines modified by functions to jobs): else let g:ale_fixers.testft = ['AddDollars', 'CatLine'] ALEFix + call ale#test#FlushJobs() endif Expect(The lines should first be modified by the function, then the job): @@ -352,6 +393,7 @@ Execute(ALEFix should skip commands when jobs fail to run): let g:ale_emulate_job_failure = 1 let g:ale_fixers.testft = ['CatLine', 'AddDollars'] ALEFix + call ale#test#FlushJobs() Expect(Only the second function should be applied): $a @@ -361,6 +403,7 @@ Expect(Only the second function should be applied): Execute(ALEFix should handle strings for selecting a single function): let g:ale_fixers.testft = 'AddCarets' ALEFix + call ale#test#FlushJobs() Expect(The first function should be used): ^a @@ -371,6 +414,7 @@ Execute(ALEFix should use functions from the registry): call ale#fix#registry#Add('add_carets', 'AddCarets', [], 'Add some carets') let g:ale_fixers.testft = ['add_carets'] ALEFix + call ale#test#FlushJobs() Expect(The registry function should be used): ^a @@ -380,6 +424,7 @@ Expect(The registry function should be used): Execute(ALEFix should be able to remove the last line for files): let g:ale_fixers.testft = ['RemoveLastLine'] ALEFix + call ale#test#FlushJobs() Expect(There should be only two lines): a @@ -388,6 +433,7 @@ Expect(There should be only two lines): Execute(ALEFix should accept funcrefs): let g:ale_fixers.testft = [function('RemoveLastLine')] ALEFix + call ale#test#FlushJobs() Expect(There should be only two lines): a @@ -401,6 +447,7 @@ Execute(ALEFix should accept lambdas): else let g:ale_fixers.testft = [{buffer, lines -> lines + ['d']}] ALEFix + call ale#test#FlushJobs() endif Expect(There should be an extra line): @@ -413,6 +460,7 @@ Execute(ALEFix should user buffer-local fixer settings): let g:ale_fixers.testft = ['AddCarets', 'AddDollars'] let b:ale_fixers = {'testft': ['RemoveLastLine']} ALEFix + call ale#test#FlushJobs() Expect(There should be only two lines): a @@ -422,6 +470,7 @@ Execute(ALEFix should allow Lists to be used for buffer-local fixer settings): let g:ale_fixers.testft = ['AddCarets', 'AddDollars'] let b:ale_fixers = ['RemoveLastLine'] ALEFix + call ale#test#FlushJobs() Expect(There should be only two lines): a @@ -437,8 +486,9 @@ Execute(ALEFix should fix files on the save event): let g:ale_lint_on_save = 1 let g:ale_enabled = 1 - noautocmd silent file fix_test_file - call writefile(getline(1, '$'), 'fix_test_file') + let g:test_filename = tempname() + execute 'noautocmd silent file ' . fnameescape(g:test_filename) + call writefile(getline(1, '$'), g:test_filename) let g:ale_fixers.testft = ['AddDollars'] @@ -447,10 +497,11 @@ Execute(ALEFix should fix files on the save event): call SetUpLinters() call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() " We should save the file. - AssertEqual ['$a', '$b', '$c'], readfile('fix_test_file') - Assert !&modified, 'The was marked as ''modified''' + AssertEqual ['$a', '$b', '$c'], readfile(g:test_filename) + Assert !&modified, 'The file was marked as ''modified''' if !has('win32') " We should have run the linter. @@ -477,8 +528,9 @@ Execute(ALEFix should not fix files on :wq): let g:ale_lint_on_save = 1 let g:ale_enabled = 1 - noautocmd silent file fix_test_file - call writefile(getline(1, '$'), 'fix_test_file') + let g:test_filename = tempname() + execute 'noautocmd silent file ' . fnameescape(g:test_filename) + call writefile(getline(1, '$'), g:test_filename) let g:ale_fixers.testft = ['AddDollars'] @@ -491,7 +543,7 @@ Execute(ALEFix should not fix files on :wq): call ale#events#SaveEvent(bufnr('')) " We should save the file. - AssertEqual ['a', 'b', 'c'], readfile('fix_test_file') + AssertEqual ['a', 'b', 'c'], readfile(g:test_filename) Assert &modified, 'The was not marked as ''modified''' " We should not run the linter. @@ -512,14 +564,16 @@ Execute(ALEFix should still lint with no linters to be applied): let g:ale_lint_on_save = 1 let g:ale_enabled = 1 - noautocmd silent file fix_test_file + let g:test_filename = tempname() + execute 'noautocmd silent file ' . fnameescape(g:test_filename) let g:ale_fixers.testft = [] call SetUpLinters() call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() - Assert !filereadable('fix_test_file'), 'The file should not have been saved' + Assert !filereadable(g:test_filename), 'The file should not have been saved' if !has('win32') " We have run the linter. @@ -546,14 +600,16 @@ Execute(ALEFix should still lint when nothing was fixed on save): let g:ale_lint_on_save = 1 let g:ale_enabled = 1 - noautocmd silent file fix_test_file + let g:test_filename = tempname() + execute 'noautocmd silent file ' . fnameescape(g:test_filename) let g:ale_fixers.testft = ['DoNothing'] call SetUpLinters() call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() - Assert !filereadable('fix_test_file'), 'The file should not have been saved' + Assert !filereadable(g:test_filename), 'The file should not have been saved' if !has('win32') " We should have run the linter. @@ -581,7 +637,8 @@ Given testft (A file with three lines): c Execute(ale#fix#InitBufferData() should set up the correct data): - noautocmd silent file fix_test_file + let g:test_filename = tempname() + execute 'noautocmd silent file ' . fnameescape(g:test_filename) call ale#fix#InitBufferData(bufnr(''), 'save_file') @@ -597,6 +654,7 @@ Execute(ale#fix#InitBufferData() should set up the correct data): Execute(ALEFix simple functions should be able to accept one argument, the buffer): let g:ale_fixers.testft = ['RemoveLastLineOneArg'] ALEFix + call ale#test#FlushJobs() Expect(There should be only two lines): a @@ -632,6 +690,7 @@ Execute(ALEFix functions returning jobs should be able to accept one argument): else let g:ale_fixers.testft = ['CatLine'] ALEFix + call ale#test#FlushJobs() endif Expect(An extra line should be added): @@ -643,22 +702,26 @@ Expect(An extra line should be added): Execute(ALE should print a message telling you something isn't a valid fixer when you type some nonsense): let g:ale_fixers.testft = ['CatLine', 'invalidname'] ALEFix + call ale#test#FlushJobs() 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 + call ale#test#FlushJobs() 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 + call ale#test#FlushJobs() Execute(Test fixing with chained callbacks): let g:ale_fixers.testft = ['FirstChainCallback'] ALEFix + call ale#test#FlushJobs() " The buffer shouldn't be piped in for earlier commands in the chain. AssertEqual @@ -677,6 +740,7 @@ Expect(The echoed line should be added): Execute(Test fixing with chained callback where the first command is skipped): let g:ale_fixers.testft = ['FirstChainCallbackSkipped'] ALEFix + call ale#test#FlushJobs() Expect(The default line should be added): a @@ -687,6 +751,7 @@ Expect(The default line should be added): Execute(Test fixing with chained callback where the second command is skipped): let g:ale_fixers.testft = ['FirstChainCallbackSecondSkipped'] ALEFix + call ale#test#FlushJobs() Expect(The default line should be added): a @@ -697,6 +762,7 @@ Expect(The default line should be added): Execute(Test fixing with chained callback where the final callback is skipped): let g:ale_fixers.testft = ['ChainWhereLastIsSkipped'] ALEFix + call ale#test#FlushJobs() Expect(The lines should be the same): a @@ -706,6 +772,7 @@ Expect(The lines should be the same): Execute(Empty output should be ignored): let g:ale_fixers.testft = ['IgnoredEmptyOutput'] ALEFix + call ale#test#FlushJobs() Expect(The lines should be the same): a @@ -715,6 +782,7 @@ Expect(The lines should be the same): Execute(A temporary file shouldn't be piped into the command when disabled): let g:ale_fixers.testft = ['EchoLineNoPipe'] ALEFix + call ale#test#FlushJobs() AssertEqual \ string(ale#job#PrepareCommand(bufnr(''), 'echo new line')), @@ -731,6 +799,7 @@ Expect(The new line should be used): Execute(Post-processing should work): let g:ale_fixers.testft = ['FixWithJSONPostProcessing'] ALEFix + call ale#test#FlushJobs() Expect(The lines in the JSON should be used): x @@ -740,5 +809,23 @@ Expect(The lines in the JSON should be used): Execute(ALEFix should apply autocmds): let g:ale_fixers.testft = ['AddCarets'] ALEFix + call ale#test#FlushJobs() + AssertEqual g:pre_success, 1 AssertEqual g:post_success, 1 + +Execute(ALEFix should support ale#command#Run): + if has('win32') + " Just skip this test on Windows, we can't run it. + call setline(1, ['a', 'b', 'c', 'd']) + else + let g:ale_fixers.testft = ['CatLineDeferred'] + ALEFix + call ale#test#FlushJobs() + endif + +Expect(The extra line should be added): + a + b + c + d diff --git a/test/fix/test_ale_fix_ignore.vader b/test/fix/test_ale_fix_ignore.vader new file mode 100644 index 00000000..5eb9b9ab --- /dev/null +++ b/test/fix/test_ale_fix_ignore.vader @@ -0,0 +1,110 @@ +Before: + Save g:ale_fixers + Save g:ale_fix_on_save + Save g:ale_fix_on_save_ignore + + let g:ale_fix_on_save = 1 + let g:ale_fixers = {'abc': ['a', 'b'], 'xyz': ['c', 'd']} + unlet! b:ale_fixers + unlet! b:ale_fix_on_save_ignore + + function FixerA(buffer, lines) abort + return a:lines + ['a'] + endfunction + + function FixerB(buffer, lines) abort + return a:lines + ['b'] + endfunction + + function FixerC(buffer, lines) abort + return a:lines + ['c'] + endfunction + + function FixerD(buffer, lines) abort + return a:lines + ['d'] + endfunction + + set filetype=abc.xyz + let g:test_filename = tempname() + execute 'noautocmd silent file ' . fnameescape(g:test_filename) + + call ale#fix#registry#Add('a', 'FixerA', ['abc'], '') + call ale#fix#registry#Add('b', 'FixerB', ['abc'], '') + call ale#fix#registry#Add('c', 'FixerC', ['xyz'], '') + call ale#fix#registry#Add('d', 'FixerD', ['xyz'], '') + +After: + Restore + + if exists('g:test_filename') && filereadable(g:test_filename) + call delete(g:test_filename) + endif + + unlet! b:ale_fixers + unlet! b:ale_fix_on_save_ignore + unlet! g:test_filename + + delfunction FixerA + delfunction FixerB + delfunction FixerC + delfunction FixerD + + call ale#fix#registry#ResetToDefaults() + +Given abc.xyz (An empty file): +Execute(Ignoring with a filetype in a global Dictionary should work): + let g:ale_fix_on_save_ignore = {'abc': ['b'], 'xyz': ['c']} + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual ['', 'a', 'd'], getline(1, '$') + +Execute(Ignoring with a filetype in a global List should work): + let g:ale_fix_on_save_ignore = ['b', 'c'] + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual ['', 'a', 'd'], getline(1, '$') + +Execute(Ignoring with a filetype in a local Dictionary should work): + let g:ale_fix_on_save_ignore = {'abc': ['b'], 'xyz': ['c']} + " The local Dictionary should entirely replace the global one. + let b:ale_fix_on_save_ignore = {'abc': ['b']} + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual ['', 'a', 'c', 'd'], getline(1, '$') + +Execute(Ignoring with a filetype in a local List should work): + let g:ale_fix_on_save_ignore = {'abc': ['b'], 'xyz': ['c']} + " The local List should entirely replace the global Dictionary. + let b:ale_fix_on_save_ignore = ['b'] + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual ['', 'a', 'c', 'd'], getline(1, '$') + +Execute(Ignoring functions by reference with a Dictionary should work): + let g:ale_fixers = { + \ 'abc': [function('FixerA'), function('FixerB')], + \ 'xyz': [function('FixerC'), function('FixerD')], + \} + let b:ale_fix_on_save_ignore = { + \ 'abc': [function('FixerB')], + \ 'xyz': [function('FixerC')], + \} + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual ['', 'a', 'd'], getline(1, '$') + +Execute(Ignoring functions by reference with a List should work): + let g:ale_fixers = { + \ 'abc': [function('FixerA'), function('FixerB')], + \ 'xyz': [function('FixerC'), function('FixerD')], + \} + let b:ale_fix_on_save_ignore = [function('FixerB'), function('FixerC')] + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual ['', 'a', 'd'], getline(1, '$') diff --git a/test/fixers/test_black_fixer_callback.vader b/test/fixers/test_black_fixer_callback.vader index 365b0fa6..25ad05db 100644 --- a/test/fixers/test_black_fixer_callback.vader +++ b/test/fixers/test_black_fixer_callback.vader @@ -5,6 +5,7 @@ Before: " Use an invalid global executable, so we don't match it. let g:ale_python_black_executable = 'xxxinvalid' let g:ale_python_black_options = '' + let g:ale_python_black_auto_pipenv = 0 call ale#test#SetDirectory('/testplugin/test/fixers') silent cd .. @@ -21,19 +22,26 @@ After: 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')) . ' -'}, + \ {'command': ale#path#BufferCdString(bufnr('')) . 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' + let g:ale_python_black_change_directory = 0 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('')) + +Execute(Pipenv is detected when python_black_auto_pipenv is set): + let g:ale_python_black_auto_pipenv = 1 + let g:ale_python_black_change_directory = 0 + + call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + + AssertEqual + \ {'command': ale#Escape('pipenv') . ' run black -'}, + \ ale#fixers#black#Fix(bufnr('')) diff --git a/test/fixers/test_cmakeformat_fixer_callback.vader b/test/fixers/test_cmakeformat_fixer_callback.vader new file mode 100644 index 00000000..9263d6fa --- /dev/null +++ b/test/fixers/test_cmakeformat_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_cmake_cmakeformat_executable + Save g:ale_cmake_cmakeformat_options + + " Use an invalid global executable, so we don't match it. + let g:ale_cmake_cmakeformat_executable = 'xxxinvalid' + let g:ale_cmake_cmakeformat_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The cmakeformat callback should return the correct default values): + call ale#test#SetFilename('../cmake_files/CMakeList.txt') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i ' + \ . ' %t', + \ }, + \ ale#fixers#cmakeformat#Fix(bufnr('')) + +Execute(The cmakeformat callback should include custom cmakeformat options): + let g:ale_cmake_cmakeformat_options = "-r '(a) -> a'" + call ale#test#SetFilename('../cmake_files/CMakeList.txt') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i ' + \ . ' ' . g:ale_cmake_cmakeformat_options + \ . ' %t', + \ }, + \ ale#fixers#cmakeformat#Fix(bufnr('')) diff --git a/test/fixers/test_eslint_fixer_callback.vader b/test/fixers/test_eslint_fixer_callback.vader index 774595e3..7ea9c2cf 100644 --- a/test/fixers/test_eslint_fixer_callback.vader +++ b/test/fixers/test_eslint_fixer_callback.vader @@ -1,143 +1,129 @@ Before: - call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#assert#SetUpFixerTest('javascript', 'eslint') + runtime autoload/ale/handlers/eslint.vim After: - call ale#test#RestoreDirectory() - call ale#semver#ResetVersionCache() + call ale#assert#TearDownFixerTest() Execute(The executable path should be correct): call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') " eslint_d output with an older eslint version is used here. - AssertEqual + GivenCommandOutput ['v4.4.1 (eslint_d v5.1.0)'] + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) \ . ' --fix %t', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['v4.4.1 (eslint_d v5.1.0)']) + \ } Execute(The lower priority configuration file in a nested directory should be preferred): call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-config/testfile.js') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/subdir-with-config/.eslintrc')) \ . ' --fix %t', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + \ } Execute(package.json should be used as a last resort): call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-package-json/testfile.js') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) \ . ' --fix %t', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + \ } call ale#test#SetFilename('../eslint-test-files/package.json') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/node_modules/.bin/eslint')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/package.json')) \ . ' --fix %t', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + \ } Execute(The version check should be correct): - call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') + call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-config/testfile.js') - AssertEqual + " We should run the command to get the version the first time. + GivenCommandOutput ['4.9.0'] + AssertFixer [ + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --version', \ { - \ 'chain_with': 'ale#fixers#eslint#ApplyFixForVersion', \ 'command': (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) - \ . ' --version' + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', \ }, - \ ale#fixers#eslint#Fix(bufnr('')) + \] + + AssertFixer [ + \ { + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ }, + \] Execute(--fix-dry-run should be used for 4.9.0 and up): call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') - AssertEqual + GivenCommandOutput ['4.9.0'] + AssertFixer \ { \ 'command': (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['4.9.0']) + \ } Execute(--fix-to-stdout should be used for eslint_d): call ale#test#SetFilename('../eslint-test-files/app-with-eslint-d/testfile.js') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/package.json')) \ . ' --fix %t', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['']) + \ } " The option should be used when eslint_d is new enough. " We look at the ESLint version instead of the eslint_d version. - AssertEqual + GivenCommandOutput ['v3.19.0 (eslint_d v4.2.0)'] + AssertFixer \ { \ 'command': \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) \ . ' --stdin-filename %s --stdin --fix-to-stdout', \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['v3.19.0 (eslint_d v4.2.0)']) + \ } " The option should be used for new versions too. - AssertEqual + GivenCommandOutput ['4.9.0'] + AssertFixer \ { \ 'command': \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) \ . ' --stdin-filename %s --stdin --fix-to-stdout', \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['4.9.0']) - -Execute(The version number should be cached): - call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-config/testfile.js') - - " Call the second callback with the version output. - call ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['4.9.0']) - - " The version command should be skipped. - AssertEqual - \ { - \ 'chain_with': 'ale#fixers#eslint#ApplyFixForVersion', - \ 'command': '', - \ }, - \ ale#fixers#eslint#Fix(bufnr('')) - - " Call it again without the version output. We should use the newer command. - AssertEqual - \ { - \ 'command': (has('win32') ? 'node.exe ' : '') - \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) - \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', - \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', - \ }, - \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + \ } Execute(The --fix-dry-run post-processor should handle JSON output correctly): AssertEqual diff --git a/test/fixers/test_fecs_fixer_callback.vader b/test/fixers/test_fecs_fixer_callback.vader new file mode 100644 index 00000000..809b4d46 --- /dev/null +++ b/test/fixers/test_fecs_fixer_callback.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpFixerTest('javascript', 'fecs') + runtime autoload/ale/handlers/fecs.vim + +After: + call ale#assert#TearDownFixerTest() + +Execute(The fecs fixer should respect to g:ale_javascript_fecs_executable): + let g:ale_javascript_fecs_executable = 'fecs_paths/fecs' + let g:ale_javascript_fecs_use_global = 1 + silent cd ../command_callback + let g:dir = getcwd() + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_javascript_fecs_executable) . ' format --replace=true %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#fecs#Fix(bufnr('')) + +Execute(The fecs fixer should return 0 when executable not found): + let g:ale_javascript_fecs_executable = 'fecs-invalid' + let g:ale_javascript_fecs_use_global = 1 + AssertEqual + \ 0, + \ ale#fixers#fecs#Fix(bufnr('')) diff --git a/test/fixers/test_floskell_fixer_callback.vader b/test/fixers/test_floskell_fixer_callback.vader new file mode 100644 index 00000000..66fe9200 --- /dev/null +++ b/test/fixers/test_floskell_fixer_callback.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_haskell_floskell_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_haskell_floskell_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The floskell callback should return the correct default values): + call ale#test#SetFilename('../haskell_files/testfile.hs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' %t', + \ }, + \ ale#fixers#floskell#Fix(bufnr('')) diff --git a/test/fixers/test_ktlint_fixer_callback.vader b/test/fixers/test_ktlint_fixer_callback.vader new file mode 100644 index 00000000..47b37788 --- /dev/null +++ b/test/fixers/test_ktlint_fixer_callback.vader @@ -0,0 +1,44 @@ +Before: + Save g:ale_kotlin_ktlint_executable + Save g:ale_kotlin_ktlint_options + Save g:ale_kotlin_ktlint_rulesets + + " Use an invalid global executable, so we don't match it. + let g:ale_kotlin_ktlint_executable = 'xxxinvalid' + let g:ale_kotlin_ktlint_options = '' + let g:ale_kotlin_ktlint_rulesets = [] + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The ktlint callback should return the correct default values): + call ale#test#SetFilename('../kotlin_files/testfile.kt') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' %t' + \ . ' --format', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#ktlint#Fix(bufnr('')) + +Execute(The ktlint callback should include custom ktlint options): + let g:ale_kotlin_ktlint_options = "--android" + let g:ale_kotlin_ktlint_rulesets = ['/path/to/custom/ruleset.jar'] + call ale#test#SetFilename('../kotlin_files/testfile.kt') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_kotlin_ktlint_options + \ . ' --ruleset /path/to/custom/ruleset.jar' + \ . ' %t' + \ . ' --format', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#ktlint#Fix(bufnr('')) diff --git a/test/fixers/test_latexindent_fixer_callback.vader b/test/fixers/test_latexindent_fixer_callback.vader new file mode 100644 index 00000000..d0da94a1 --- /dev/null +++ b/test/fixers/test_latexindent_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_tex_latexindent_executable + Save g:ale_tex_latexindent_options + + " Use an invalid global executable, so we don't match it. + let g:ale_tex_latexindent_executable = 'xxxinvalid' + let g:ale_tex_latexindent_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The latexindent callback should return the correct default values): + call ale#test#SetFilename('../tex_files/testfile.tex') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -l -w' + \ . ' %t', + \ }, + \ ale#fixers#latexindent#Fix(bufnr('')) + +Execute(The latexindent callback should include custom gofmt options): + let g:ale_tex_latexindent_options = "-l '~/.indentconfig.yaml'" + call ale#test#SetFilename('../tex_files/testfile.tex') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -l -w' + \ . ' ' . g:ale_tex_latexindent_options + \ . ' %t', + \ }, + \ ale#fixers#latexindent#Fix(bufnr('')) diff --git a/test/fixers/test_ocp_indent_fixer_callback.vader b/test/fixers/test_ocp_indent_fixer_callback.vader new file mode 100644 index 00000000..1f61f383 --- /dev/null +++ b/test/fixers/test_ocp_indent_fixer_callback.vader @@ -0,0 +1,34 @@ +Before: + Save g:ale_ocaml_ocp_indent_executable + Save g:ale_ocaml_ocpindent_options + + " Use an invalid global executable + let g:ale_ocaml_ocp_indent_executable = 'xxxinvalid' + let g:ale_ocaml_ocp_indent_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The ocp_indent callback should return the correct default values): + call ale#test#SetFilename('../ocaml-test-files/ocp_inden_testfile.re') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ }, + \ ale#fixers#ocp_indent#Fix(bufnr('')) + +Execute(The ocp_indent callback should include custom ocp_indent options): + let g:ale_ocaml_ocp_indent_config = "base=4, type=4" + call ale#test#SetFilename('../ocaml-test-files/ocp_inden_testfile.re') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --config=' . ale#Escape(g:ale_ocaml_ocp_indent_config) + \ }, + \ ale#fixers#ocp_indent#Fix(bufnr('')) diff --git a/test/fixers/test_prettier_eslint_fixer.callback.vader b/test/fixers/test_prettier_eslint_fixer.callback.vader index 5c899d86..90e11672 100644 --- a/test/fixers/test_prettier_eslint_fixer.callback.vader +++ b/test/fixers/test_prettier_eslint_fixer.callback.vader @@ -1,54 +1,36 @@ Before: - call ale#test#SetDirectory('/testplugin/test/fixers') - - Save g:ale_javascript_prettier_eslint_executable - Save g:ale_javascript_prettier_eslint_use_global - Save g:ale_javascript_prettier_eslint_options - - unlet! g:ale_javascript_prettier_eslint_executable - unlet! g:ale_javascript_prettier_eslint_use_global - unlet! g:ale_javascript_prettier_eslint_options - - call ale#fixers#prettier_eslint#SetOptionDefaults() + call ale#assert#SetUpFixerTest('javascript', 'prettier_eslint') After: - Restore - - unlet! b:ale_javascript_prettier_eslint_executable - unlet! b:ale_javascript_prettier_eslint_use_global - unlet! b:ale_javascript_prettier_eslint_options - - call ale#test#RestoreDirectory() - call ale#semver#ResetVersionCache() + call ale#assert#TearDownFixerTest() Execute(The default command should be correct): - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': \ ale#Escape('prettier-eslint') \ . ' %t' \ . ' --write' - \ }, - \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) + \ } Execute(Additional options should be used when set): let b:ale_javascript_prettier_eslint_options = '--foobar' - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': \ ale#Escape('prettier-eslint') \ . ' %t' \ . ' --foobar --write' - \ }, - \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) + \ } Execute(--eslint-config-path should be set for 4.2.0 and up): call ale#test#SetFilename('eslint-test-files/react-app/foo/bar.js') - AssertEqual + GivenCommandOutput ['4.2.0'] + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': @@ -56,58 +38,57 @@ Execute(--eslint-config-path should be set for 4.2.0 and up): \ . ' %t' \ . ' --eslint-config-path ' . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/.eslintrc.js')) \ . ' --write' - \ }, - \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), ['4.2.0']) + \ } Execute(--eslint-config-path shouldn't be used for older versions): call ale#test#SetFilename('eslint-test-files/react-app/foo/bar.js') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': \ ale#Escape('prettier-eslint') \ . ' %t' \ . ' --write' - \ }, - \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) + \ } Execute(The version check should be correct): - AssertEqual + AssertFixer [ + \ ale#Escape('prettier-eslint') . ' --version', \ { - \ 'chain_with': 'ale#fixers#prettier_eslint#ApplyFixForVersion', - \ 'command': ale#Escape('prettier-eslint') . ' --version', - \ }, - \ ale#fixers#prettier_eslint#Fix(bufnr('')) + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --write' + \ } + \] Execute(The new --stdin-filepath option should be used when the version is new enough): call ale#test#SetFilename('eslint-test-files/react-app/foo/bar.js') - AssertEqual + GivenCommandOutput ['4.4.0'] + AssertFixer \ { \ 'command': ale#path#CdString(expand('%:p:h')) \ . ale#Escape('prettier-eslint') \ . ' --eslint-config-path ' . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/.eslintrc.js')) \ . ' --stdin-filepath %s --stdin', - \ }, - \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), ['4.4.0']) + \ } Execute(The version number should be cached): - call ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), ['4.4.0']) - - " The version command should be skipped. - AssertEqual + GivenCommandOutput ['4.4.0'] + AssertFixer \ { - \ 'chain_with': 'ale#fixers#prettier_eslint#ApplyFixForVersion', - \ 'command': '', - \ }, - \ ale#fixers#prettier_eslint#Fix(bufnr('')) + \ 'command': ale#path#CdString(expand('%:p:h')) + \ . ale#Escape('prettier-eslint') + \ . ' --stdin-filepath %s --stdin', + \ } - " The newer command should be used. - AssertEqual + GivenCommandOutput [] + AssertFixer \ { \ 'command': ale#path#CdString(expand('%:p:h')) \ . ale#Escape('prettier-eslint') \ . ' --stdin-filepath %s --stdin', - \ }, - \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) + \ } diff --git a/test/fixers/test_prettier_fixer_callback.vader b/test/fixers/test_prettier_fixer_callback.vader index 7f25471b..9be161ac 100644 --- a/test/fixers/test_prettier_fixer_callback.vader +++ b/test/fixers/test_prettier_fixer_callback.vader @@ -1,268 +1,284 @@ Before: - call ale#test#SetDirectory('/testplugin/test/fixers') - Save g:ale_javascript_prettier_executable - Save g:ale_javascript_prettier_options + call ale#assert#SetUpFixerTest('javascript', 'prettier') - " Use an invalid global executable, so we don't match it. - let g:ale_javascript_prettier_executable = 'xxxinvalid' - let g:ale_javascript_prettier_options = '' - - call ale#test#SetDirectory('/testplugin/test/fixers') silent cd .. silent cd command_callback let g:dir = getcwd() After: - let g:ale_has_override = {} - - call ale#test#RestoreDirectory() - call ale#semver#ResetVersionCache() + call ale#assert#TearDownFixerTest() Execute(The prettier callback should return the correct default values): call ale#test#SetFilename('../prettier-test-files/testfile.js') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': ale#Escape(g:ale_javascript_prettier_executable) \ . ' %t' \ . ' --write', - \ }, - \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + \ } Execute(The --config option should not be set automatically): let g:ale_javascript_prettier_use_local_config = 1 call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': ale#Escape(g:ale_javascript_prettier_executable) \ . ' %t' \ . ' --write', - \ }, - \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + \ } Execute(The prettier callback should include custom prettier options): let g:ale_javascript_prettier_options = '--no-semi' call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, \ 'command': ale#Escape(g:ale_javascript_prettier_executable) \ . ' %t' \ . ' --no-semi' \ . ' --write', - \ }, - \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + \ } Execute(The version check should be correct): call ale#test#SetFilename('../prettier-test-files/testfile.js') - AssertEqual - \ { - \ 'chain_with': 'ale#fixers#prettier#ApplyFixForVersion', - \ 'command': ale#Escape(g:ale_javascript_prettier_executable) - \ . ' --version', - \ }, - \ ale#fixers#prettier#Fix(bufnr('')) + AssertFixer [ + \ ale#Escape('prettier') . ' --version', + \ {'read_temporary_file': 1, 'command': ale#Escape('prettier') . ' %t --write'} + \] Execute(--stdin-filepath should be used when prettier is new enough): let g:ale_javascript_prettier_options = '--no-semi' call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') - AssertEqual + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(expand('%:p:h')) \ . ale#Escape(g:ale_javascript_prettier_executable) \ . ' --no-semi' \ . ' --stdin-filepath %s --stdin', - \ }, - \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + \ } Execute(The version number should be cached): call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') - " Call the second callback with the version output. - call ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'command': ale#path#CdString(expand('%:p:h')) + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --stdin-filepath %s --stdin', + \ } - " Call it again without the version output. We should use the newer command. - AssertEqual + GivenCommandOutput [] + AssertFixer + \ { + \ 'command': ale#path#CdString(expand('%:p:h')) + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser to `babylon` by default, < 1.16.0): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=javascript + + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(expand('%:p:h')) \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser babylon' \ . ' --stdin-filepath %s --stdin', - \ }, - \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + \ } + +Execute(Should set --parser to `babel` by default, >= 1.16.0): + call ale#test#SetFilename('../prettier-test-files/testfile') + + set filetype=javascript + + GivenCommandOutput ['1.16.0'] + AssertFixer + \ { + \ 'command': ale#path#CdString(expand('%:p:h')) + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser babel' + \ . ' --stdin-filepath %s --stdin', + \ } Execute(Should set --parser based on filetype, TypeScript): call ale#test#SetFilename('../prettier-test-files/testfile') set filetype=typescript - AssertEqual + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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 filetype, YAML): call ale#test#SetFilename('../prettier-test-files/testfile') set filetype=yaml - AssertEqual + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(expand('%:p:h')) \ . ale#Escape(g:ale_javascript_prettier_executable) \ . ' --parser yaml' \ . ' --stdin-filepath %s --stdin', - \ }, - \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + \ } Execute(Should set --parser based on filetype, HTML): call ale#test#SetFilename('../prettier-test-files/testfile') set filetype=html - AssertEqual + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(expand('%:p:h')) \ . ale#Escape(g:ale_javascript_prettier_executable) \ . ' --parser html' \ . ' --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 + GivenCommandOutput ['1.6.0'] + AssertFixer \ { \ 'command': ale#path#CdString(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(The prettier_d post-processor should permit regular JavaScript content): AssertEqual diff --git a/test/fixers/test_standard_fixer_callback.vader b/test/fixers/test_standard_fixer_callback.vader index 38f2d54a..db9f20f6 100644 --- a/test/fixers/test_standard_fixer_callback.vader +++ b/test/fixers/test_standard_fixer_callback.vader @@ -1,6 +1,9 @@ Before: call ale#test#SetDirectory('/testplugin/test/fixers') + unlet! b:ale_javascript_standard_executable + unlet! b:ale_javascript_standard_options + After: call ale#test#RestoreDirectory() @@ -15,3 +18,14 @@ Execute(The executable path should be correct): \ . ' --fix %t', \ }, \ ale#fixers#standard#Fix(bufnr('')) + +Execute(Custom options should be supported): + let b:ale_javascript_standard_use_global = 1 + let b:ale_javascript_standard_options = '--foo-bar' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('standard') . ' --foo-bar --fix %t', + \ }, + \ ale#fixers#standard#Fix(bufnr('')) diff --git a/test/fixers/test_standardrb_fixer_callback.vader b/test/fixers/test_standardrb_fixer_callback.vader new file mode 100644 index 00000000..99234b79 --- /dev/null +++ b/test/fixers/test_standardrb_fixer_callback.vader @@ -0,0 +1,54 @@ +Before: + Save g:ale_ruby_standardrb_executable + Save g:ale_ruby_standardrb_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_standardrb_executable = 'xxxinvalid' + let g:ale_ruby_standardrb_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 standardrb callback should return the correct default values): + call ale#test#SetFilename('ruby_paths/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --fix --force-exclusion %t', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) + +Execute(The standardrb callback should include configuration files): + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.standard.yml')) + \ . ' --fix --force-exclusion %t', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) + +Execute(The standardrb callback should include custom rubocop options): + let g:ale_ruby_standardrb_options = '--except Lint/Debugger' + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.standard.yml')) + \ . ' --except Lint/Debugger' + \ . ' --fix --force-exclusion %t', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) diff --git a/test/fixers/test_styler_fixer_callback.vader b/test/fixers/test_styler_fixer_callback.vader new file mode 100644 index 00000000..85e45c1d --- /dev/null +++ b/test/fixers/test_styler_fixer_callback.vader @@ -0,0 +1,21 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The styler callback should include custom styler options): + let g:ale_r_styler_options = "a_custom_option" + + AssertEqual + \ { + \ 'command': 'Rscript --vanilla -e ' + \ . '"suppressPackageStartupMessages(library(styler));' + \ . 'style_file(commandArgs(TRUE), style = ' + \ . 'a_custom_option)"' + \ . ' %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#styler#Fix(bufnr('')) diff --git a/test/fixers/test_textlint_fixer_callback.vader b/test/fixers/test_textlint_fixer_callback.vader new file mode 100644 index 00000000..2848cfa5 --- /dev/null +++ b/test/fixers/test_textlint_fixer_callback.vader @@ -0,0 +1,42 @@ +Before: + Save g:ale_textlint_executable + Save g:ale_textlint_options + Save g:ale_textlint_use_global + + " Use an invalid global executable, so we don't match it. + let g:ale_textlint_executable = 'xxxinvalid' + let g:ale_textlint_options = '' + let g:ale_textlint_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The textlint callback should return the correct default values): + call ale#test#SetFilename('../markdown_files/testfile.md') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' --fix' + \ . ' %t', + \ }, + \ ale#fixers#textlint#Fix(bufnr('')) + +Execute(The textlint callback should include custom textlint options): + let g:ale_textlint_options = "--quiet" + call ale#test#SetFilename('../markdown_files/testfile.md') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' --fix' + \ . ' ' . g:ale_textlint_options + \ . ' %t', + \ }, + \ ale#fixers#textlint#Fix(bufnr('')) diff --git a/test/handler/test_ameba_handler.vader b/test/handler/test_ameba_handler.vader new file mode 100644 index 00000000..a6f43170 --- /dev/null +++ b/test/handler/test_ameba_handler.vader @@ -0,0 +1,44 @@ +Before: + runtime ale_linters/crystal/ameba.vim + +After: + unlet! g:lines + call ale#linter#Reset() + +Execute(The ameba handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 24, + \ 'col': 28, + \ 'end_col': 29, + \ 'text': 'Trailing whitespace detected', + \ 'code': 'Layout/TrailingWhitespace', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#crystal#ameba#HandleAmebaOutput(123, [ + \ '{"sources":[{"path":"my_file_with_issues.cr","issues":[{"rule_name":"Layout/TrailingWhitespace","message":"Trailing whitespace detected","location":{"line":24,"column":28},"end_location":{"line":null,"column":null}}]},{"path":"my_file_without_issues.cr","issues":[]}],"metadata":{"ameba_version":"0.8.1","crystal_version":"0.26.1"},"summary":{"target_sources_count":2,"issues_count":1}}' + \ ]) + +Execute(The ameba handler should handle when files are checked and no offenses are found): + AssertEqual + \ [], + \ ale_linters#crystal#ameba#HandleAmebaOutput(123, [ + \ '{"sources":[{"path":"my_file_with_issues.cr",issues":[]},{"path":"my_file_without_issues.cr",issues":[]}],"metadata":{ameba_version":"0.8.1",crystal_version":"0.26.1"},"summary":{target_sources_count":2,issues_count":0}}' + \ ]) + +Execute(The ameba handler should handle when no files are checked): + AssertEqual + \ [], + \ ale_linters#crystal#ameba#HandleAmebaOutput(123, [ + \ '{"sources":[],"metadata":{ameba_version":"0.8.1",crystal_version":"0.26.1"},"summary":{target_sources_count":0,issues_count":0}}' + \ ]) + +Execute(The ameba handler should handle blank output without any errors): + AssertEqual + \ [], + \ ale_linters#crystal#ameba#HandleAmebaOutput(123, ['{}']) + AssertEqual + \ [], + \ ale_linters#crystal#ameba#HandleAmebaOutput(123, []) diff --git a/test/handler/test_bandit_handler.vader b/test/handler/test_bandit_handler.vader new file mode 100644 index 00000000..a2793a46 --- /dev/null +++ b/test/handler/test_bandit_handler.vader @@ -0,0 +1,42 @@ +Before: + runtime ale_linters/python/bandit.vim + +After: + call ale#linter#Reset() + +Execute(The bandit handler for Python should parse input correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 0, + \ 'lnum': 2, + \ 'code': 'B404', + \ 'type': 'I', + \ 'text': 'Consider possible security implications associated with subprocess module.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 4, + \ 'code': 'B305', + \ 'type': 'W', + \ 'text': 'Use of insecure cipher mode cryptography.hazmat.primitives.ciphers.modes.ECB.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 6, + \ 'code': 'B609', + \ 'type': 'E', + \ 'text': 'Possible wildcard injection in call: subprocess.Popen', + \ }, + \ ], + \ ale_linters#python#bandit#Handle(0, [ + \ '[main] INFO profile include tests: None', + \ '[main] INFO profile exclude tests: None', + \ '[main] INFO cli include tests: None', + \ '[main] INFO cli exclude tests: None', + \ '[main] INFO running on Python 3.7.2', + \ '[node_visitor] INFO Unable to find qualified name for module: <stdin>', + \ '2:B404:LOW:Consider possible security implications associated with subprocess module.', + \ '4:B305:MEDIUM:Use of insecure cipher mode cryptography.hazmat.primitives.ciphers.modes.ECB.', + \ '6:B609:HIGH:Possible wildcard injection in call: subprocess.Popen', + \ ]) diff --git a/test/handler/test_clojure_clj_kondo_handler.vader b/test/handler/test_clojure_clj_kondo_handler.vader new file mode 100644 index 00000000..45db9049 --- /dev/null +++ b/test/handler/test_clojure_clj_kondo_handler.vader @@ -0,0 +1,75 @@ +Before: + runtime ale_linters/clojure/clj_kondo.vim + +After: + call ale#linter#Reset() + +Execute(the clojure clj-kondo handler should be able to handle errors): + AssertEqual + \ [ + \ { + \ 'lnum': 123, + \ 'col': 44, + \ 'type': 'E', + \ 'text': 'error: Unexpected )', + \ }, + \ ], + \ ale_linters#clojure#clj_kondo#HandleCljKondoFormat(0, [ + \ 'test.clj:123:44: error: Unexpected )', + \ ]) + +Execute(the clojure clj-kondo handler should be able to handle warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 654, + \ 'col': 321, + \ 'type': 'W', + \ 'text': 'warning: inline def', + \ } + \ ], + \ ale_linters#clojure#clj_kondo#HandleCljKondoFormat(0, [ + \ 'test.clj:654:321: warning: inline def' + \ ]) + +Execute(the clojure clj-kondo handler should be able to handle exceptions): + AssertEqual + \ [ + \ { + \ 'lnum': 123, + \ 'col': 321, + \ 'type': 'E', + \ 'text': 'Exception: something horrible happen', + \ } + \ ], + \ ale_linters#clojure#clj_kondo#HandleCljKondoFormat(0, [ + \ 'test.clj:123:321: Exception: something horrible happen' + \ ]) + +Execute(the clojure clj-kondo handler should be able to handle errors from stdin): + AssertEqual + \ [ + \ { + \ 'lnum': 16, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'error: Unexpected )', + \ }, + \ ], + \ ale_linters#clojure#clj_kondo#HandleCljKondoFormat(0, [ + \ '<stdin>:16:1: error: Unexpected )', + \ ]) + +Execute(the clojure clj-kondo handler should be able to handle windows files): + AssertEqual + \ [ + \ { + \ 'lnum': 123, + \ 'col': 44, + \ 'type': 'E', + \ 'text': 'error: Unexpected )', + \ } + \ ], + \ ale_linters#clojure#clj_kondo#HandleCljKondoFormat(0, [ + \ 'C:\my\operating\system\is\silly\core.clj:123:44: error: Unexpected )', + \ ]) diff --git a/test/handler/test_cookstyle_handler.vader b/test/handler/test_cookstyle_handler.vader new file mode 100644 index 00000000..7d705a19 --- /dev/null +++ b/test/handler/test_cookstyle_handler.vader @@ -0,0 +1,22 @@ +Before: + runtime ale_linters/chef/cookstyle.vim + +After: + call ale#linter#Reset() + +Execute(Basic warnings should be handled): + AssertEqual + \ [ + \ { + \ 'lnum': 58, + \ 'col': 24, + \ 'code': 'Style/UnneededInterpolation', + \ 'type': 'W', + \ 'end_col': 40, + \ 'text': 'Style/UnneededInterpolation: Prefer `to_s` over string interpolation.', + \ } + \ ], + \ ale_linters#chef#cookstyle#Handle(bufnr(''), [ + \ '{"metadata":{"rubocop_version":"0.62.0","ruby_engine":"ruby","ruby_version":"2.6.0","ruby_patchlevel":"0","ruby_platform":"x86_64-linux"},"files":[{"path":"recipes/default.rb","offenses":[{"severity":"convention","message":"Style/UnneededInterpolation: Prefer `to_s` over string interpolation.","cop_name":"Style/UnneededInterpolation","corrected":false,"location":{"start_line":58,"start_column":24,"last_line":58,"last_column":40,"length":17,"line":58,"column":24}}]}],"summary":{"offense_count":1,"target_file_count":1,"inspected_file_count":1}}' + \ ]) + diff --git a/test/handler/test_cypher_lint_handler.vader b/test/handler/test_cypher_lint_handler.vader new file mode 100644 index 00000000..066adae4 --- /dev/null +++ b/test/handler/test_cypher_lint_handler.vader @@ -0,0 +1,21 @@ +Before: + runtime ale_linters/cypher/cypher_lint.vim + +After: + call ale#linter#Reset() + +Execute(The cypher-lint handler should handle errors for the current file correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 75, + \ 'type': 'E', + \ 'text': "Invalid input ',': expected an identifier, shortestPath, allShortestPaths or '('", + \ }, + \ ], + \ ale_linters#cypher#cypher_lint#Handle(bufnr(''), [ + \ "shakespeare.cql:1:75: Invalid input ',': expected an identifier, shortestPath, allShortestPaths or '('", + \ "CREATE (shakespeare:Author {firstname:'William', lastname:'Shakespeare'}),,", + \ " ^", + \ ]) diff --git a/test/handler/test_eslint_handler.vader b/test/handler/test_eslint_handler.vader index 4a57927b..1bb438a6 100644 --- a/test/handler/test_eslint_handler.vader +++ b/test/handler/test_eslint_handler.vader @@ -342,6 +342,17 @@ Execute(eslint should warn about ignored files by default): \ '/path/to/some/ignored.js:0:0: File ignored because of a matching ignore pattern. Use "--no-ignore" to override. [Warning]', \ ]) + AssertEqual + \ [{ + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'File ignored by default. Use "--ignore-pattern ''!node_modules/*''" to override.', + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ '/path/to/some/ignored.js:0:0: File ignored by default. Use "--ignore-pattern ''!node_modules/*''" to override. [Warning]', + \ ]) + Execute(eslint should not warn about ignored files when explicitly disabled): let g:ale_javascript_eslint_suppress_eslintignore = 1 @@ -351,6 +362,12 @@ Execute(eslint should not warn about ignored files when explicitly disabled): \ '/path/to/some/ignored.js:0:0: File ignored because of a matching ignore pattern. Use "--no-ignore" to override. [Warning]', \ ]) + AssertEqual + \ [], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ '/path/to/some/ignored.js:0:0: File ignored by default. Use "--ignore-pattern ''!node_modules/*''" to override. [Warning]', + \ ]) + Execute(eslint should handle react errors correctly): AssertEqual \ [ diff --git a/test/handler/test_fecs_handler.vader b/test/handler/test_fecs_handler.vader new file mode 100644 index 00000000..7c216b8d --- /dev/null +++ b/test/handler/test_fecs_handler.vader @@ -0,0 +1,35 @@ +Before: + runtime autoload/ale/handlers/fecs.vim + +After: + call ale#linter#Reset() + +Execute(fecs should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 20, + \ 'col': 25, + \ 'text': 'Unexpected console statement.', + \ 'code': 'no-console', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 24, + \ 'col': 36, + \ 'text': 'Missing radix parameter.', + \ 'code': 'radix', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 25, + \ 'col': 6, + \ 'text': 'Missing static property value.', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#fecs#Handle(347, [ + \ 'fecs WARN → line 20, col 25: Unexpected console statement. (no-console)', + \ 'fecs ERROR → line 24, col 36: Missing radix parameter. (radix)', + \ 'fecs ERROR → line 25, col 6: Missing static property value.', + \ ]) diff --git a/test/handler/test_flake8_handler.vader b/test/handler/test_flake8_handler.vader index cdf20bc0..1c9956fa 100644 --- a/test/handler/test_flake8_handler.vader +++ b/test/handler/test_flake8_handler.vader @@ -113,7 +113,7 @@ Execute(The flake8 handler should handle stack traces): \ [ \ { \ 'lnum': 1, - \ 'text': 'An exception was thrown. See :ALEDetail', + \ 'text': 'ImportError: No module named parser (See :ALEDetail)', \ 'detail': join([ \ 'Traceback (most recent call last):', \ ' File "/usr/local/bin/flake8", line 7, in <module>', @@ -258,3 +258,19 @@ Execute(E112 should be a syntax error): \ ale_linters#python#flake8#Handle(bufnr(''), [ \ 'foo.py:6:1: E112 expected an indented block', \ ]) + +Execute(Compatibility with hacking which uses older style flake8): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'H306', + \ 'type': 'W', + \ 'text': 'imports not in alphabetical order (smtplib, io)', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: H306: imports not in alphabetical order (smtplib, io)', + \ ]) diff --git a/test/handler/test_flow_handler.vader b/test/handler/test_flow_handler.vader index 3a575a01..055ba026 100644 --- a/test/handler/test_flow_handler.vader +++ b/test/handler/test_flow_handler.vader @@ -499,7 +499,8 @@ Execute(The flow handler should handle extra errors): \ 'col': 35, \ 'type': 'E', \ 'text': 'props of React element `New`: This type is incompatible with object type', - \ 'detail': 'Property `setVector` is incompatible: number This type is incompatible with function type ', + \ 'detail': 'props of React element `New`: This type is incompatible with object type' + \ . "\nProperty `setVector` is incompatible: number This type is incompatible with function type ", \ } \] diff --git a/test/handler/test_ghdl_handler.vader b/test/handler/test_ghdl_handler.vader new file mode 100644 index 00000000..a0f5edac --- /dev/null +++ b/test/handler/test_ghdl_handler.vader @@ -0,0 +1,26 @@ +Before: + runtime ale_linters/vhdl/ghdl.vim + +After: + call ale#linter#Reset() + +Execute(The ghdl handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 41, + \ 'col' : 5, + \ 'type': 'E', + \ 'text': "error: 'begin' is expected instead of 'if'" + \ }, + \ { + \ 'lnum': 12, + \ 'col' : 8, + \ 'type': 'E', + \ 'text': ' no declaration for "i0"' + \ }, + \ ], + \ ale_linters#vhdl#ghdl#Handle(bufnr(''), [ + \ "dff_en.vhd:41:5:error: 'begin' is expected instead of 'if'", + \ '/path/to/file.vhdl:12:8: no declaration for "i0"', + \ ]) diff --git a/test/handler/test_ktlint_handler.vader b/test/handler/test_ktlint_handler.vader new file mode 100644 index 00000000..f0d634e6 --- /dev/null +++ b/test/handler/test_ktlint_handler.vader @@ -0,0 +1,21 @@ +Before: + Save g:ale_kotlin_ktlint_rulesets + + let g:ale_kotlin_ktlint_rulesets = [] + +After: + Restore + +Execute(The ktlint handler method GetRulesets should properly parse custom rulesets): + let g:ale_kotlin_ktlint_rulesets = ['/path/to/custom/ruleset.jar', '/path/to/other/ruleset.jar'] + + AssertEqual + \ '--ruleset /path/to/custom/ruleset.jar --ruleset /path/to/other/ruleset.jar', + \ ale#handlers#ktlint#GetRulesets(bufnr('')) + +Execute(The ktlint handler method GetRulesets should return an empty string when no rulesets have been configured): + let g:ale_kotlin_ktlint_rulesets = [] + + AssertEqual + \ '', + \ ale#handlers#ktlint#GetRulesets(bufnr('')) diff --git a/test/handler/test_lacheck_handler.vader b/test/handler/test_lacheck_handler.vader new file mode 100644 index 00000000..0bcc3be8 --- /dev/null +++ b/test/handler/test_lacheck_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/tex/lacheck.vim + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The lacheck handler should parse lines correctly): + + call ale#test#SetFilename('command_callback/tex_paths/sample1.tex') + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'W', + \ 'text': 'perhaps you should insert a `~'' before "\ref"' + \ } + \ ], + \ ale_linters#tex#lacheck#Handle(bufnr(''), [ + \ "** sample1:", + \ "\"sample1.tex\", line 1: perhaps you should insert a `~' before \"\\ref\"" + \ ]) + +Execute(The lacheck handler should ignore errors from input files): + + call ale#test#SetFilename('ale_test.tex') + + AssertEqual + \ [ + \ ], + \ ale_linters#tex#lacheck#Handle(255, [ + \ "** ale_input:", + \ "\"ale_input.tex\", line 1: perhaps you should insert a `~' before \"\\ref\"" + \ ]) diff --git a/test/handler/test_languagetool_handler.vader b/test/handler/test_languagetool_handler.vader new file mode 100644 index 00000000..61d3abfd --- /dev/null +++ b/test/handler/test_languagetool_handler.vader @@ -0,0 +1,62 @@ +Before: + runtime! ale_linters/text/languagetool.vim + +After: + call ale#linter#Reset() + +Execute(languagetool handler should report 3 errors): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 19, + \ 'end_col': 20, + \ 'text': 'This sentence does not start with an uppercase letter', + \ 'type': 'W', + \ 'code': 'UPPERCASE_SENTENCE_START', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 36, + \ 'end_col': 42, + \ 'text': "Did you mean 'to see'?", + \ 'type': 'W', + \ 'code': 'TOO_TO[1]', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 44, + \ 'end_col': 45, + \ 'text': "Use 'a' instead of 'an' if the following word doesn't start with a vowel sound, e.g. 'a sentence', 'a university'", + \ 'type': 'W', + \ 'code': 'EN_A_VS_AN', + \ } + \ ], + \ ale#handlers#languagetool#HandleOutput(bufnr(''), [ + \ '1.) Line 3, column 19, Rule ID: UPPERCASE_SENTENCE_START', + \ 'Message: This sentence does not start with an uppercase letter', + \ 'Suggestion: Or', + \ '...red phrases for details on potential errors. or use this text too see an few of of the probl...', + \ ' ^^ ', + \ '', + \ '2.) Line 3, column 36, Rule ID: TOO_TO[1]', + \ "Message: Did you mean 'to see'?", + \ 'Suggestion: to see', + \ '...etails on potential errors. or use this text too see an few of of the problems that LanguageTool ...', + \ ' ^^^^^^^ ', + \ '', + \ '3.) Line 3, column 44, Rule ID: EN_A_VS_AN', + \ "Message: Use 'a' instead of 'an' if the following word doesn't start with a vowel sound, e.g. 'a sentence', 'a university'", + \ 'Suggestion: a', + \ '...n potential errors. or use this text too see an few of of the problems that LanguageTool can...', + \ ' ^^ ', + \ 'Time: 2629ms for 8 sentences (3.0 sentences/sec)' + \ ]) + +Execute(languagetool handler should report no errors on empty input): + AssertEqual + \ [], + \ ale#handlers#languagetool#HandleOutput(bufnr(''), [ + \ '', + \ 'Time: 2629ms for 8 sentences (3.0 sentences/sec)' + \ ]) diff --git a/test/handler/test_perl_handler.vader b/test/handler/test_perl_handler.vader index e769550c..060b1ffe 100644 --- a/test/handler/test_perl_handler.vader +++ b/test/handler/test_perl_handler.vader @@ -91,3 +91,19 @@ Execute(The Perl linter reports errors even when mixed with warnings): \ 'syntax error at - line 8, at EOF', \ 'Execution of t.pl aborted due to compilation errors.', \ ]) + +Execute(The Perl linter reports errors even when an additional file location is included): + AssertEqual + \ [ + \ {'lnum': '5', 'type': 'E', 'text': '"my" variable $foo masks earlier declaration in same scope'}, + \ {'lnum': '6', 'type': 'E', 'text': '"my" variable $foo masks earlier declaration in same scope'}, + \ {'lnum': '11', 'type': 'E', 'text': 'Global symbol "$asdf" requires explicit package name (did you forget to declare "my $asdf"?)'}, + \ {'lnum': '12', 'type': 'E', 'text': 'Global symbol "$asdf" requires explicit package name (did you forget to declare "my $asdf"?)'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ '"my" variable $foo masks earlier declaration in same scope at - line 5.', + \ '"my" variable $foo masks earlier declaration in same scope at - line 6, at <DATA> line 1.', + \ 'Global symbol "$asdf" requires explicit package name (did you forget to declare "my $asdf"?) at - line 11.', + \ 'Global symbol "$asdf" requires explicit package name (did you forget to declare "my $asdf"?) at - line 12, <DATA> line 1.', + \ 'Execution of t.pl aborted due to compilation errors.', + \ ]) diff --git a/test/handler/test_phpstan_handler.vader b/test/handler/test_phpstan_handler.vader index 207a7758..67fdb759 100644 --- a/test/handler/test_phpstan_handler.vader +++ b/test/handler/test_phpstan_handler.vader @@ -14,7 +14,7 @@ Execute(Output without errors should be parsed correctly): AssertEqual \ [], - \ ale_linters#php#phpstan#Handle(bufnr(''), [" 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%"]) + \ ale_linters#php#phpstan#Handle(bufnr(''), []) Execute(Output with some errors should be parsed correctly): call ale#test#SetFilename('phpstan-test-files/foo/test.php') @@ -24,21 +24,20 @@ Execute(Output with some errors should be parsed correctly): \ { \ 'lnum': 9, \ 'text': 'Call to method format() on an unknown class DateTimeImutable.', - \ 'type': 'W' + \ 'type': 'E' \ }, \ { \ 'lnum': 16, \ 'text': 'Sample message.', - \ 'type': 'W' + \ 'type': 'E' \ }, \ { \ 'lnum': 192, \ 'text': 'Invalid command testCommand.', - \ 'type': 'W' + \ 'type': 'E' \ } \ ], \ ale_linters#php#phpstan#Handle(bufnr(''), [ - \ ' 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%', \ 'phpstan-test-files/foo/test.php:9:Call to method format() on an unknown class DateTimeImutable.', \ 'phpstan-test-files/foo/test.php:16:Sample message.', \ 'phpstan-test-files/foo/test.php:192:Invalid command testCommand.', @@ -52,11 +51,10 @@ Execute(Output should be parsed correctly with Windows paths): \ { \ 'lnum': 9, \ 'text': 'Access to an undefined property Test::$var.', - \ 'type': 'W' + \ 'type': 'E' \ } \ ], \ ale_linters#php#phpstan#Handle(bufnr(''), [ - \ ' 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%', \ 'D:\phpstan-test-files\foo\test.php:9:Access to an undefined property Test::$var.', \]) @@ -68,10 +66,9 @@ Execute(Output for .inc files should be parsed correctly): \ { \ 'lnum': 9, \ 'text': 'Access to an undefined property Test::$var.', - \ 'type': 'W' + \ 'type': 'E' \ } \ ], \ ale_linters#php#phpstan#Handle(bufnr(''), [ - \ ' 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%', \ '/phpstan-test-files/foo/test.inc:9:Access to an undefined property Test::$var.', \]) diff --git a/test/handler/test_powershell_handler.vader b/test/handler/test_powershell_handler.vader new file mode 100755 index 00000000..635bcd20 --- /dev/null +++ b/test/handler/test_powershell_handler.vader @@ -0,0 +1,62 @@ +Before: + runtime ale_linters/powershell/powershell.vim + +After: + call ale#linter#Reset() + +Execute(The powershell handler should process syntax errors from parsing a powershell script): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'col': 29, + \ 'type': 'E', + \ 'text': 'Missing closing ''}'' in statement block or type definition.', + \ 'code': 'ParseException', + \ }, + \ ], + \ ale_linters#powershell#powershell#Handle(bufnr(''), [ + \ "At line:8 char:29", + \ "+ Invoke-Command -ScriptBlock {", + \ "+ ~", + \ "Missing closing '}' in statement block or type definition.", + \ "At /home/harrisj/tester.ps1:5 char:5", + \ "+ [void]$ExecutionContext.InvokeCommand.NewScriptBlock($Contents);", + \ "+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + \ "+ CategoryInfo : NotSpecified: (:) [], ParseException", + \ "+ FullyQualifiedErrorId : ParseException" + \ ]) + +Execute(The powershell handler should process multiple syntax errors from parsing a powershell script): + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'col': 31, + \ 'type': 'E', + \ 'text': 'The string is missing the terminator: ".', + \ 'code': 'ParseException' + \ }, + \ { + \ 'lnum': 3, + \ 'col': 16, + \ 'type': 'E', + \ 'text': 'Missing closing ''}'' in statement block or type definition.', + \ 'code': 'ParseException' + \ }, + \ ], + \ ale_linters#powershell#powershell#Handle(bufnr(''), [ + \ 'At line:11 char:31', + \ '+ write-verbose ''deleted''', + \ '+ ~', + \ 'The string is missing the terminator: ".', + \ 'At line:3 char:16', + \ '+ invoke-command {', + \ '+ ~', + \ 'Missing closing ''}'' in statement block or type definition.', + \ 'At /var/folders/qv/15ybvt050v9cgwrm7c95x4r4zc4qsg/T/vwhzIc8/1/script.ps1:1 char:150', + \ '+ ... ontents); [void]$ExecutionContext.InvokeCommand.NewScriptBlock($Con ...', + \ '+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', + \ '+ CategoryInfo : NotSpecified: (:) [], ParseException', + \ '+ FullyQualifiedErrorId : ParseException' + \ ]) diff --git a/test/handler/test_psscriptanalyzer_handler.vader b/test/handler/test_psscriptanalyzer_handler.vader new file mode 100644 index 00000000..060d5941 --- /dev/null +++ b/test/handler/test_psscriptanalyzer_handler.vader @@ -0,0 +1,42 @@ +Before: + runtime ale_linters/powershell/psscriptanalyzer.vim + +After: + call ale#linter#Reset() + +Execute(The psscriptanalyzer handler should handle basic information or warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'I', + \ 'text': 'The cmdlet ''Get-GithubRepo'' does not have a help comment.', + \ 'code': 'PSProvideCommentHelp', + \ }, + \ { + \ 'lnum': 9, + \ 'type': 'W', + \ 'text': '''%'' is an alias of ''ForEach-Object''. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content.', + \ 'code': 'PSAvoidUsingCmdletAliases', + \ }, + \ { + \ 'lnum': 23, + \ 'type': 'E', + \ 'text': 'The ComputerName parameter of a cmdlet should not be hardcoded as this will expose sensitive information about the system.', + \ 'code': 'PSAvoidUsingComputerNameHardcoded', + \ }, + \ ], + \ ale_linters#powershell#psscriptanalyzer#Handle(bufnr(''), [ + \ '1', + \ 'Information', + \ 'The cmdlet ''Get-GithubRepo'' does not have a help comment.', + \ 'PSProvideCommentHelp', + \ '9', + \ 'Warning', + \ '''%'' is an alias of ''ForEach-Object''. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content.', + \ 'PSAvoidUsingCmdletAliases', + \ '23', + \ 'Error', + \ 'The ComputerName parameter of a cmdlet should not be hardcoded as this will expose sensitive information about the system.', + \ 'PSAvoidUsingComputerNameHardcoded', + \ ]) diff --git a/test/handler/test_pydocstyle_handler.vader b/test/handler/test_pydocstyle_handler.vader index d155dc9a..cfb75307 100644 --- a/test/handler/test_pydocstyle_handler.vader +++ b/test/handler/test_pydocstyle_handler.vader @@ -66,7 +66,7 @@ Execute(Basic pydocstyle warnings should be handled): \ ], \ ale_linters#python#pydocstyle#Handle(bufnr(''), [ \ 'Checking file ' . fnamemodify(bufname(bufnr('')), ':p') . '.', - \ ale#Escape(fnamemodify(bufname(bufnr('')), ':t')) . ':1 at module level:', + \ './mydir/myfile.py:1 at module level:', \ ' D100: Missing docstring in public module', \ '', \ ' All modules should normally have docstrings. [...] all functions and', @@ -80,7 +80,7 @@ Execute(Basic pydocstyle warnings should be handled): \ ' 1: # 2: 3: s 4: a 5: m 6: p 7: l ...', \ '', \ '', - \ ale#Escape(fnamemodify(bufname(bufnr('')), ':t')) . ':4 in public function `main`:', + \ 'C:\mydir\myfile.py:4 in public function `main`:', \ ' D205: 1 blank line required between summary line and description (found 0)', \ '', \ ' Multi-line docstrings consist of a summary line just like a one-line', @@ -92,7 +92,7 @@ Execute(Basic pydocstyle warnings should be handled): \ ' 3: d 4: e 5: f 6: 7: m 8: a 9: i ...', \ '', \ '', - \ ale#Escape(fnamemodify(bufname(bufnr('')), ':t')) . ':4 in public function `main`:', + \ 'myfile.py:4 in public function `main`:', \ ' D400: First line should end with a period (not ''e'')', \ '', \ ' The [first line of a] docstring is a phrase ending in a period.', diff --git a/test/handler/test_pylama_handler.vader b/test/handler/test_pylama_handler.vader new file mode 100644 index 00000000..d21c65d3 --- /dev/null +++ b/test/handler/test_pylama_handler.vader @@ -0,0 +1,193 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/python/pylama.vim + +After: + Restore + + call ale#linter#Reset() + + silent file something_else.py + +Execute(The pylama handler should handle no messages): + AssertEqual [], ale_linters#python#pylama#Handle(bufnr(''), []) + +Execute(The pylama handler should handle basic warnings and syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'col': 1, + \ 'code': 'W0611', + \ 'type': 'W', + \ 'sub_type': '', + \ 'text': '''foo'' imported but unused [pyflakes]', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 0, + \ 'code': 'E0401', + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'Unable to import ''foo'' [pylint]', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 1, + \ 'code': 'E302', + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'expected 2 blank lines, found 1 [pycodestyle]', + \ }, + \ { + \ 'lnum': 11, + \ 'col': 1, + \ 'code': 'D401', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'First line should be in imperative mood (''Get'', not ''Gets'') [pydocstyle]', + \ }, + \ { + \ 'lnum': 15, + \ 'col': 81, + \ 'code': 'E501', + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'line too long (96 > 80 characters) [pycodestyle]', + \ }, + \ { + \ 'lnum': 16, + \ 'col': 1, + \ 'code': 'D203', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': '1 blank line required before class docstring (found 0) [pydocstyle]', + \ }, + \ { + \ 'lnum': 18, + \ 'col': 1, + \ 'code': 'D107', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'Missing docstring in __init__ [pydocstyle]', + \ }, + \ { + \ 'lnum': 20, + \ 'col': 0, + \ 'code': 'C4001', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'Invalid string quote ", should be '' [pylint]', + \ }, + \ ], + \ ale_linters#python#pylama#Handle(bufnr(''), [ + \ 'No config file found, using default configuration', + \ 'index.py:8:1: W0611 ''foo'' imported but unused [pyflakes]', + \ 'index.py:8:0: E0401 Unable to import ''foo'' [pylint]', + \ 'index.py:10:1: E302 expected 2 blank lines, found 1 [pycodestyle]', + \ 'index.py:11:1: D401 First line should be in imperative mood (''Get'', not ''Gets'') [pydocstyle]', + \ 'index.py:15:81: E501 line too long (96 > 80 characters) [pycodestyle]', + \ 'index.py:16:1: D203 1 blank line required before class docstring (found 0) [pydocstyle]', + \ 'index.py:18:1: D107 Missing docstring in __init__ [pydocstyle]', + \ 'index.py:20:0: C4001 Invalid string quote ", should be '' [pylint]', + \ ]) + +Execute(The pylama handler should handle tracebacks with parsable messages): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'ParseError: Cannot parse file. (See :ALEDetail)', + \ 'detail': join([ + \ 'Traceback (most recent call last):', + \ ' File "/usr/local/lib/python2.7/site-packages/pylama/core.py", line 66, in run', + \ ' path, code=code, ignore=ignore, select=select, params=lparams)', + \ ' File "/usr/local/lib/python2.7/site-packages/pylama/lint/pylama_pydocstyle.py", line 37, in run', + \ ' } for e in PyDocChecker().check_source(*check_source_args)]', + \ ' File "/usr/local/lib/python2.7/site-packages/pydocstyle/checker.py", line 64, in check_source', + \ ' module = parse(StringIO(source), filename)', + \ ' File "/usr/local/lib/python2.7/site-packages/pydocstyle/parser.py", line 340, in __call__', + \ ' return self.parse(*args, **kwargs)', + \ ' File "/usr/local/lib/python2.7/site-packages/pydocstyle/parser.py", line 328, in parse', + \ ' six.raise_from(ParseError(), error)', + \ ' File "/usr/local/lib/python2.7/site-packages/six.py", line 737, in raise_from', + \ ' raise value', + \ 'ParseError: Cannot parse file.', + \ ], "\n"), + \ }, + \ { + \ 'lnum': 11, + \ 'col': 1, + \ 'code': 'E302', + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'expected 2 blank lines, found 1 [pycodestyle]', + \ }, + \ { + \ 'lnum': 16, + \ 'col': 81, + \ 'code': 'E501', + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'line too long (96 > 80 characters) [pycodestyle]', + \ }, + \ ], + \ ale_linters#python#pylama#Handle(bufnr(''), [ + \ 'Traceback (most recent call last):', + \ ' File "/usr/local/lib/python2.7/site-packages/pylama/core.py", line 66, in run', + \ ' path, code=code, ignore=ignore, select=select, params=lparams)', + \ ' File "/usr/local/lib/python2.7/site-packages/pylama/lint/pylama_pydocstyle.py", line 37, in run', + \ ' } for e in PyDocChecker().check_source(*check_source_args)]', + \ ' File "/usr/local/lib/python2.7/site-packages/pydocstyle/checker.py", line 64, in check_source', + \ ' module = parse(StringIO(source), filename)', + \ ' File "/usr/local/lib/python2.7/site-packages/pydocstyle/parser.py", line 340, in __call__', + \ ' return self.parse(*args, **kwargs)', + \ ' File "/usr/local/lib/python2.7/site-packages/pydocstyle/parser.py", line 328, in parse', + \ ' six.raise_from(ParseError(), error)', + \ ' File "/usr/local/lib/python2.7/site-packages/six.py", line 737, in raise_from', + \ ' raise value', + \ 'ParseError: Cannot parse file.', + \ '', + \ 'index.py:11:1: E302 expected 2 blank lines, found 1 [pycodestyle]', + \ 'index.py:16:81: E501 line too long (96 > 80 characters) [pycodestyle]', + \ ]) + +" Note: This is probably a bug, since all pylama plugins produce codes, but +" should be handled for compatibility. +" Note: The pylama isort plugin is distributed in the isort package. +Execute(The pylama handler should handle messages without codes): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'code': '', + \ 'type': 'W', + \ 'sub_type': '', + \ 'text': 'Incorrectly sorted imports. [isort]' + \ }, + \ ], + \ ale_linters#python#pylama#Handle(bufnr(''), [ + \ 'index.py:0:0: Incorrectly sorted imports. [isort]', + \ ]) + +" Note: This is a pylama bug, but should be handled for compatibility. +" See https://github.com/klen/pylama/pull/146 +Execute(The pylama handler should handle message codes followed by a colon): + AssertEqual + \ [ + \ { + \ 'lnum': 31, + \ 'col': 1, + \ 'code': 'E800', + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'Found commented out code: # needs_sphinx = ''1.0'' [eradicate]', + \ }, + \ ], + \ ale_linters#python#pylama#Handle(bufnr(''), [ + \ 'index.py:31:1: E800: Found commented out code: # needs_sphinx = ''1.0'' [eradicate]', + \ ]) diff --git a/test/handler/test_raco_handler.vader b/test/handler/test_raco_handler.vader new file mode 100644 index 00000000..217fe2f9 --- /dev/null +++ b/test/handler/test_raco_handler.vader @@ -0,0 +1,26 @@ +Before: + runtime ale_linters/racket/raco.vim + +After: + call ale#linter#Reset() + +Execute(The raco handler should handle errors for the current file correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'dfine: unbound identifier in modulemessage', + \ }, + \ ], + \ ale_linters#racket#raco#Handle(bufnr(''), [ + \ 'foo.rkt:4:1: dfine: unbound identifier in modulemessage', + \ ' in: dfine', + \ ' context...:', + \ ' /usr/local/Cellar/racket/6.5/share/racket/pkgs/compiler-lib/compiler/commands/expand.rkt:34:15: loop', + \ ' /usr/local/Cellar/racket/6.5/share/racket/pkgs/compiler-lib/compiler/commands/expand.rkt:10:2: show-program', + \ ' /usr/local/Cellar/racket/6.5/share/racket/pkgs/compiler-lib/compiler/commands/expand.rkt: [running body]', + \ ' /usr/local/Cellar/minimal-racket/6.6/share/racket/collects/raco/raco.rkt: [running body]', + \ ' /usr/local/Cellar/minimal-racket/6.6/share/racket/collects/raco/main.rkt: [running body]', + \ ]) diff --git a/test/handler/test_redpen_handler.vader b/test/handler/test_redpen_handler.vader index 4490bcba..0b030e2d 100644 --- a/test/handler/test_redpen_handler.vader +++ b/test/handler/test_redpen_handler.vader @@ -80,7 +80,7 @@ Execute(redpen handler should handle errors output): \ ']', \ ]) -Execute(redpen handler should no error output): +Execute(The redpen handler should handle an empty error list): AssertEqual \ [], \ ale#handlers#redpen#HandleRedpenOutput(bufnr(''), [ @@ -91,3 +91,8 @@ Execute(redpen handler should no error output): \ ' }', \ ']', \ ]) + +Execute(The redpen handler should handle totally empty output): + AssertEqual + \ [], + \ ale#handlers#redpen#HandleRedpenOutput(bufnr(''), []) diff --git a/test/handler/test_rubocop_handler.vader b/test/handler/test_rubocop_handler.vader index ef0137d6..d7868f26 100644 --- a/test/handler/test_rubocop_handler.vader +++ b/test/handler/test_rubocop_handler.vader @@ -41,21 +41,21 @@ Execute(The rubocop handler should parse lines correctly): \ 'type': 'E', \ }, \ ], - \ ale_linters#ruby#rubocop#Handle(347, [ + \ ale#ruby#HandleRubocopOutput(347, [ \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[{"path":"my_great_file.rb","offenses":[{"severity":"convention","message":"Prefer single-quoted strings...","cop_name":"Style/SomeCop","corrected":false,"location":{"line":83,"column":29,"length":7}},{"severity":"fatal","message":"Some error","cop_name":"Style/SomeOtherCop","corrected":false,"location":{"line":12,"column":2,"length":1}},{"severity":"warning","message":"Regular warning","cop_name":"Style/WarningCop","corrected":false,"location":{"line":10,"column":5,"length":8}},{"severity":"error","message":"Another error","cop_name":"Style/SpaceBeforeBlockBraces","corrected":false,"location":{"line":11,"column":1,"length":1}}]}],"summary":{"offense_count":4,"target_file_count":1,"inspected_file_count":1}}' \ ]) Execute(The rubocop handler should handle when files are checked and no offenses are found): AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, [ + \ ale#ruby#HandleRubocopOutput(347, [ \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[{"path":"my_great_file.rb","offenses":[]}],"summary":{"offense_count":0,"target_file_count":1,"inspected_file_count":1}}' \ ]) Execute(The rubocop handler should handle when no files are checked): AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, [ + \ ale#ruby#HandleRubocopOutput(347, [ \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[],"summary":{"offense_count":0,"target_file_count":0,"inspected_file_count":0}}' \ ]) @@ -66,11 +66,11 @@ Execute(The rubocop handler should handle output without any errors): AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, g:lines) + \ ale#ruby#HandleRubocopOutput(347, g:lines) \ AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, ['{}']) + \ ale#ruby#HandleRubocopOutput(347, ['{}']) AssertEqual \ [], - \ ale_linters#ruby#rubocop#Handle(347, []) + \ ale#ruby#HandleRubocopOutput(347, []) diff --git a/test/handler/test_rust_handler.vader b/test/handler/test_rust_handler.vader index 4764e713..56db9b36 100644 --- a/test/handler/test_rust_handler.vader +++ b/test/handler/test_rust_handler.vader @@ -8,7 +8,7 @@ Execute(The Rust handler should handle rustc output): \ 'end_lnum': 15, \ 'type': 'E', \ 'col': 5, - \ 'end_col': 8, + \ 'end_col': 7, \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', \ }, \ { @@ -16,7 +16,7 @@ Execute(The Rust handler should handle rustc output): \ 'end_lnum': 13, \ 'type': 'E', \ 'col': 7, - \ 'end_col': 10, + \ 'end_col': 9, \ 'text': 'no method named `wat` found for type `std::string::String` in the current scope', \ }, \ ], @@ -84,7 +84,7 @@ Execute(The Rust handler should handle cargo output): \ 'end_lnum': 15, \ 'type': 'E', \ 'col': 5, - \ 'end_col': 8, + \ 'end_col': 7, \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', \ }, \ { @@ -92,7 +92,7 @@ Execute(The Rust handler should handle cargo output): \ 'end_lnum': 13, \ 'type': 'E', \ 'col': 7, - \ 'end_col': 10, + \ 'end_col': 9, \ 'text': 'no method named `wat` found for type `std::string::String` in the current scope', \ }, \ ], @@ -158,7 +158,7 @@ Execute(The Rust handler should should errors from expansion spans): \ 'end_lnum': 4, \ 'type': 'E', \ 'col': 21, - \ 'end_col': 23, + \ 'end_col': 22, \ 'text': 'mismatched types: expected bool, found integral variable', \ }, \ ], @@ -208,7 +208,7 @@ Execute(The Rust handler should show detailed errors): \ 'end_lnum': 4, \ 'type': 'E', \ 'col': 21, - \ 'end_col': 23, + \ 'end_col': 22, \ 'text': 'mismatched types: expected bool, found integral variable', \ }, \ ], @@ -296,7 +296,7 @@ Execute(The Rust handler should remove secondary spans if set): \ 'lnum': 1, \ 'end_lnum': 1, \ 'type': 'E', - \ 'end_col': 21, + \ 'end_col': 20, \ 'col': 1, \ 'text': 'this function takes 1 parameter but 0 were supplied: defined here', \ }, @@ -304,7 +304,7 @@ Execute(The Rust handler should remove secondary spans if set): \ 'lnum': 1, \ 'end_lnum': 1, \ 'type': 'E', - \ 'end_col': 46, + \ 'end_col': 45, \ 'col': 40, \ 'text': 'this function takes 1 parameter but 0 were supplied: expected 1 parameter', \ }, @@ -371,7 +371,7 @@ Execute(The Rust handler should remove secondary spans if set): \ 'lnum': 1, \ 'end_lnum': 1, \ 'type': 'E', - \ 'end_col': 46, + \ 'end_col': 45, \ 'col': 40, \ 'text': 'this function takes 1 parameter but 0 were supplied: expected 1 parameter', \ }, diff --git a/test/handler/test_vcom_handler.vader b/test/handler/test_vcom_handler.vader new file mode 100644 index 00000000..943b525a --- /dev/null +++ b/test/handler/test_vcom_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/vhdl/vcom.vim + +After: + call ale#linter#Reset() + +Execute(The vcom handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 218, + \ 'type': 'W', + \ 'text': '(vcom-1236) Shared variables must be of a protected type.' + \ }, + \ { + \ 'lnum': 73, + \ 'type': 'E', + \ 'text': '(vcom-1136) Unknown identifier "aresetn".' + \ }, + \ { + \ 'lnum': 73, + \ 'type': 'E', + \ 'text': 'Bad resolution function (STD_LOGIC) for type (error).' + \ }, + \ { + \ 'lnum': 73, + \ 'type': 'E', + \ 'text': 'near ":": (vcom-1576) expecting ";" or ")".' + \ }, + \ ], + \ ale_linters#vhdl#vcom#Handle(bufnr(''), [ + \ '** Warning: ../path/to/file.vhd(218): (vcom-1236) Shared variables must be of a protected type.', + \ '** Error: tb_file.vhd(73): (vcom-1136) Unknown identifier "aresetn".', + \ '** Error: tb_file.vhd(73): Bad resolution function (STD_LOGIC) for type (error).', + \ '** Error: tb_file.vhd(73): near ":": (vcom-1576) expecting ";" or ")".', + \ ]) diff --git a/test/handler/test_vlog_handler.vader b/test/handler/test_vlog_handler.vader new file mode 100644 index 00000000..a70665db --- /dev/null +++ b/test/handler/test_vlog_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/verilog/vlog.vim + +After: + call ale#linter#Reset() + +Execute(The vlog handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'type': 'W', + \ 'text': '(vlog-2623) Undefined variable: C.' + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.' + \ }, + \ ], + \ ale_linters#verilog#vlog#Handle(bufnr(''), [ + \ '** Warning: add.v(7): (vlog-2623) Undefined variable: C.', + \ '** Error: file.v(1): (vlog-13294) Identifier must be declared with a port mode: C.', + \ ]) diff --git a/test/handler/test_vulture_handler.vader b/test/handler/test_vulture_handler.vader index c6bd7643..b28055db 100644 --- a/test/handler/test_vulture_handler.vader +++ b/test/handler/test_vulture_handler.vader @@ -70,7 +70,7 @@ Execute(Vulture exception should be handled): \ [ \ { \ 'lnum': 1, - \ 'text': 'An exception was thrown. See :ALEDetail', + \ 'text': 'BaddestException: Everything gone wrong (See :ALEDetail)', \ 'detail': join([ \ 'Traceback (most recent call last):', \ ' File "/usr/lib/python3.6/site-packages/vulture/__init__.py", line 13, in <module>', diff --git a/test/handler/test_xvhdl_handler.vader b/test/handler/test_xvhdl_handler.vader new file mode 100644 index 00000000..b90539b8 --- /dev/null +++ b/test/handler/test_xvhdl_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/vhdl/xvhdl.vim + +After: + call ale#linter#Reset() + +Execute(The xvhdl handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 17, + \ 'type': 'E', + \ 'text': '[VRFC 10-91] aresetn is not declared ' + \ }, + \ { + \ 'lnum': 128, + \ 'type': 'E', + \ 'text': '[VRFC 10-91] m_axis_tx_tdata is not declared ' + \ }, + \ ], + \ ale_linters#vhdl#xvhdl#Handle(bufnr(''), [ + \ 'ERROR: [VRFC 10-91] aresetn is not declared [/path/to/file.vhd:17]', + \ 'ERROR: [VRFC 10-91] m_axis_tx_tdata is not declared [/home/user/tx_data.vhd:128]', + \ ]) diff --git a/test/handler/test_xvlog_handler.vader b/test/handler/test_xvlog_handler.vader new file mode 100644 index 00000000..2e1f83fc --- /dev/null +++ b/test/handler/test_xvlog_handler.vader @@ -0,0 +1,18 @@ +Before: + runtime ale_linters/verilog/xvlog.vim + +After: + call ale#linter#Reset() + +Execute(The xvlog handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'type': 'E', + \ 'text': '[VRFC 10-1412] syntax error near output ' + \ }, + \ ], + \ ale_linters#verilog#xvlog#Handle(bufnr(''), [ + \ 'ERROR: [VRFC 10-1412] syntax error near output [/path/to/file.v:5]', + \ ]) diff --git a/test/jsonlint-test-files/app-without-jsonlint/src/app.json b/test/jsonlint-test-files/app-without-jsonlint/src/app.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/jsonlint-test-files/app-without-jsonlint/src/app.json diff --git a/test/jsonlint-test-files/app/node_modules/.bin/jsonlint b/test/jsonlint-test-files/app/node_modules/.bin/jsonlint new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/jsonlint-test-files/app/node_modules/.bin/jsonlint diff --git a/test/jsonlint-test-files/app/src/app.json b/test/jsonlint-test-files/app/src/app.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/jsonlint-test-files/app/src/app.json diff --git a/test/jsonlint-test-files/node_modules/jsonlint/lib/cli.js b/test/jsonlint-test-files/node_modules/jsonlint/lib/cli.js new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/jsonlint-test-files/node_modules/jsonlint/lib/cli.js diff --git a/test/kotlin_files/testfile.kt b/test/kotlin_files/testfile.kt new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/kotlin_files/testfile.kt diff --git a/test/lsp/test_did_save_event.vader b/test/lsp/test_did_save_event.vader index f8ff8f70..423138af 100644 --- a/test/lsp/test_did_save_event.vader +++ b/test/lsp/test_did_save_event.vader @@ -34,16 +34,19 @@ Before: \ }) let g:ale_linters = {'foobar': ['dummy_linter']} - function! ale#lsp_linter#StartLSP(buffer, linter) abort + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) - - return { + let l:details = { + \ 'command': 'foobar', \ 'buffer': a:buffer, \ 'connection_id': g:conn_id, \ 'project_root': '/foo/bar', - \ 'language_id': 'foobar', \} + + call a:Callback(a:linter, l:details) + + return 1 endfunction " Replace the Send function for LSP, so we can monitor calls to it. @@ -61,6 +64,7 @@ After: unlet! b:ale_enabled unlet! b:ale_linters unlet! g:message_list + unlet! b:ale_save_event_fired delfunction LanguageCallback delfunction ProjectRootCallback diff --git a/test/lsp/test_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader index 71768ce5..2abdf6ca 100644 --- a/test/lsp/test_lsp_client_messages.vader +++ b/test/lsp/test_lsp_client_messages.vader @@ -23,7 +23,7 @@ Execute(ale#lsp#message#Initialize() should return correct messages): \ ale#lsp#message#Initialize('/foo/bar', {'foo': 'bar'}) Execute(ale#lsp#message#Initialized() should return correct messages): - AssertEqual [1, 'initialized'], ale#lsp#message#Initialized() + AssertEqual [1, 'initialized', {}], ale#lsp#message#Initialized() Execute(ale#lsp#message#Shutdown() should return correct messages): AssertEqual [0, 'shutdown'], ale#lsp#message#Shutdown() @@ -112,7 +112,7 @@ Execute(ale#lsp#message#Completion() should return correct messages): \ 'textDocument': { \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), \ }, - \ 'position': {'line': 11, 'character': 34}, + \ 'position': {'line': 11, 'character': 33}, \ } \ ], \ ale#lsp#message#Completion(bufnr(''), 12, 34, '') @@ -126,7 +126,7 @@ Execute(ale#lsp#message#Completion() should return correct messages with a trigg \ 'textDocument': { \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), \ }, - \ 'position': {'line': 11, 'character': 34}, + \ 'position': {'line': 11, 'character': 33}, \ 'context': {'triggerKind': 2, 'triggerCharacter': '.'}, \ } \ ], @@ -141,11 +141,25 @@ Execute(ale#lsp#message#Definition() should return correct messages): \ 'textDocument': { \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), \ }, - \ 'position': {'line': 11, 'character': 34}, + \ 'position': {'line': 11, 'character': 33}, \ } \ ], \ ale#lsp#message#Definition(bufnr(''), 12, 34) +Execute(ale#lsp#message#TypeDefinition() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/typeDefinition', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ } + \ ], + \ ale#lsp#message#TypeDefinition(bufnr(''), 12, 34) + Execute(ale#lsp#message#References() should return correct messages): AssertEqual \ [ @@ -155,7 +169,7 @@ Execute(ale#lsp#message#References() should return correct messages): \ 'textDocument': { \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), \ }, - \ 'position': {'line': 11, 'character': 34}, + \ 'position': {'line': 11, 'character': 33}, \ 'context': {'includeDeclaration': v:false}, \ } \ ], @@ -181,7 +195,7 @@ Execute(ale#lsp#message#Hover() should return correct messages): \ 'textDocument': { \ 'uri': ale#path#ToURI(g:dir . '/foo/bar.ts'), \ }, - \ 'position': {'line': 11, 'character': 34}, + \ 'position': {'line': 11, 'character': 33}, \ } \ ], \ ale#lsp#message#Hover(bufnr(''), 12, 34) @@ -192,7 +206,7 @@ Execute(ale#lsp#message#DidChangeConfiguration() should return correct messages) \ } AssertEqual \ [ - \ 0, + \ 1, \ 'workspace/didChangeConfiguration', \ { \ 'settings': { diff --git a/test/lsp/test_lsp_command_formatting.vader b/test/lsp/test_lsp_command_formatting.vader index 9721f37f..ec3b4120 100644 --- a/test/lsp/test_lsp_command_formatting.vader +++ b/test/lsp/test_lsp_command_formatting.vader @@ -17,12 +17,14 @@ Execute(Command formatting should be applied correctly for LSP linters): call ale#lsp_linter#StartLSP( \ bufnr(''), \ { + \ 'name': 'linter', \ 'language_callback': {-> 'x'}, \ 'project_root_callback': {-> '/foo/bar'}, \ 'lsp': 'stdio', \ 'executable': has('win32') ? 'cmd': 'true', \ 'command': '%e --foo', \ }, + \ {-> 0} \) if has('win32') diff --git a/test/lsp/test_lsp_root_detection.vader b/test/lsp/test_lsp_root_detection.vader new file mode 100644 index 00000000..b7827248 --- /dev/null +++ b/test/lsp/test_lsp_root_detection.vader @@ -0,0 +1,62 @@ +Before: + call ale#assert#SetUpLinterTest('c', 'clangd') + + function! Hook1(buffer) + return 'abc123' + endfunction + +After: + let g:ale_lsp_root = {} + unlet! b:ale_lsp_root + delfunction Hook1 + + call ale#assert#TearDownLinterTest() + +Execute(The buffer-specific variable can be a string): + let b:ale_lsp_root = '/some/path' + call ale#test#SetFilename('other-file.c') + + AssertLSPProject '/some/path' + +Execute(The buffer-specific variable can be a dictionary): + let b:ale_lsp_root = {'clangd': '/some/path', 'golangserver': '/other/path'} + call ale#test#SetFilename('other-file.c') + + AssertLSPProject '/some/path' + +Execute(The buffer-specific variable can have funcrefs): + let b:ale_lsp_root = {'clangd': function('Hook1'), 'golangserver': '/path'} + call ale#test#SetFilename('other-file.c') + + AssertLSPProject 'abc123' + +Execute(The global variable can be a dictionary): + let g:ale_lsp_root = {'clangd': '/some/path', 'golangserver': '/other/path'} + call ale#test#SetFilename('other-file.c') + + AssertLSPProject '/some/path' + +Execute(The global variable can have funcrefs): + let g:ale_lsp_root = {'clangd': function('Hook1'), 'golangserver': '/path'} + call ale#test#SetFilename('other-file.c') + + AssertLSPProject 'abc123' + +Execute(The buffer-specific variable overrides the global variable): + let b:ale_lsp_root = {'clangd': '/some/path', 'golangserver': '/other/path'} + let g:ale_lsp_root = {'clangd': '/not/this/path', 'golangserver': '/elsewhere'} + call ale#test#SetFilename('other-file.c') + + AssertLSPProject '/some/path' + +Execute(The global variable is queried if the buffer-specific has no value): + let b:ale_lsp_root = {'golangserver': '/other/path'} + let g:ale_lsp_root = {'clangd': '/some/path', 'golangserver': '/elsewhere'} + call ale#test#SetFilename('other-file.c') + + AssertLSPProject '/some/path' + +Execute(The default hook value is acceptable): + call ale#test#SetFilename('other-file.c') + + AssertLSPProject '' diff --git a/test/lsp/test_lsp_startup.vader b/test/lsp/test_lsp_startup.vader new file mode 100644 index 00000000..028ec9b1 --- /dev/null +++ b/test/lsp/test_lsp_startup.vader @@ -0,0 +1,366 @@ +Before: + Save g:ale_run_synchronously + + let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks + unlet! g:ale_run_synchronously_emulate_commands + + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/engine.vim + runtime autoload/ale/job.vim + runtime autoload/ale/socket.vim + + let g:job_map = {} + let g:emulate_job_failure = 0 + let g:next_job_id = 1 + + let g:socket_map = {} + let g:emulate_socket_failure = 0 + let g:next_channel_id = 0 + + let g:message_buffer = '' + let g:calls = [] + + function! ale#engine#IsExecutable(buffer, executable) abort + return !empty(a:executable) + endfunction + + function! ale#job#HasOpenChannel(job_id) abort + return has_key(g:job_map, a:job_id) + endfunction + + function! ale#job#Stop(job_id) abort + if has_key(g:job_map, a:job_id) + call remove(g:job_map, a:job_id) + endif + endfunction + + function! ale#job#Start(command, options) abort + if g:emulate_job_failure + return 0 + endif + + let l:job_id = g:next_job_id + let g:next_job_id += 1 + let g:job_map[l:job_id] = [a:command, a:options] + + return l:job_id + endfunction + + function! ale#job#SendRaw(job_id, data) abort + let g:message_buffer .= a:data + endfunction + + function! ale#socket#IsOpen(channel_id) abort + return has_key(g:socket_map, a:channel_id) + endfunction + + function! ale#socket#Close(channel_id) abort + if has_key(g:socket_map, a:channel_id) + call remove(g:socket_map, a:channel_id) + endif + endfunction + + function! ale#socket#Open(address, options) abort + if g:emulate_socket_failure + return -1 + endif + + let l:channel_id = g:next_channel_id + let g:next_channel_id += 1 + let g:socket_map[l:channel_id] = [a:address, a:options] + + return l:channel_id + endfunction + + function! ale#socket#Send(channel_id, data) abort + let g:message_buffer .= a:data + endfunction + + function! PopMessages() abort + let l:message_list = [] + + for l:line in split(g:message_buffer, '\(\r\|\n\|Content-Length\)\+') + if l:line[:0] is '{' + let l:data = json_decode(l:line) + + call add(l:message_list, l:data) + endif + endfor + + let g:message_buffer = '' + + return l:message_list + endfunction + + function! SendMessage(message) abort + let l:conn_id = keys(ale#lsp#GetConnections())[0] + let l:body = json_encode(a:message) + let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body + + call ale#lsp#HandleMessage(l:conn_id, l:data) + endfunction + + function! Start() abort + let l:linter = values(ale#linter#GetLintersLoaded())[0][0] + + return ale#lsp_linter#StartLSP( + \ bufnr(''), + \ l:linter, + \ {linter, details -> add(g:calls, [linter.name, details])}, + \) + endfunction + + function! AssertInitSuccess(linter_name, conn_prefix, language, root, command) abort + let l:messages = PopMessages() + + if a:linter_name is# 'tsserver' + AssertEqual + \ [ + \ { + \ 'seq': v:null, + \ 'arguments': { + \ 'file': expand('%:p'), + \ }, + \ 'type': 'request', + \ 'command': 'open', + \ }, + \ ], + \ l:messages + else + AssertEqual + \ [ + \ { + \ 'method': 'initialize', + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'params': { + \ 'initializationOptions': {}, + \ 'rootUri': ale#path#ToURI(a:root), + \ 'capabilities': {}, + \ 'rootPath': a:root, + \ 'processId': getpid(), + \ }, + \ }, + \ ], + \ l:messages + + call SendMessage({ + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'result': { + \ 'capabilities': { + \ 'renameProvider': v:true, + \ 'executeCommandProvider': { + \ 'commands': [], + \ }, + \ 'hoverProvider': v:true, + \ 'documentSymbolProvider': v:true, + \ 'documentRangeFormattingProvider': v:true, + \ 'codeLensProvider': { + \ 'resolveProvider': v:false + \ }, + \ 'referencesProvider': v:true, + \ 'textDocumentSync': 2, + \ 'documentFormattingProvider': v:true, + \ 'codeActionProvider': v:true, + \ 'signatureHelpProvider': { + \ 'triggerCharacters': ['(', ','], + \ }, + \ 'completionProvider': { + \ 'triggerCharacters': ['.'], + \ 'resolveProvider': v:false + \ }, + \ 'definitionProvider': v:true, + \ 'experimental': {}, + \ 'documentHighlightProvider': v:true, + \ 'workspaceSymbolProvider': v:true, + \ }, + \ }, + \}) + + let l:messages = PopMessages() + + AssertEqual + \ [ + \ { + \ 'method': 'initialized', + \ 'jsonrpc': '2.0', + \ 'params': {}, + \ }, + \ { + \ 'method': 'textDocument/didOpen', + \ 'jsonrpc': '2.0', + \ 'params': { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': ale#lsp#message#GetNextVersionID() - 1, + \ 'languageId': a:language, + \ 'text': "\n", + \ }, + \ }, + \ }, + \ ], + \ l:messages + endif + + AssertEqual + \ [ + \ [ + \ a:linter_name, + \ { + \ 'connection_id': a:conn_prefix . ':' . a:root, + \ 'project_root': a:root, + \ 'buffer': bufnr(''), + \ 'command': !empty(a:command) ? ale#job#PrepareCommand(bufnr(''), a:command) : '', + \ }, + \ ], + \ ], + \ g:calls + endfunction + + function! AssertInitFailure() abort + let l:messages = PopMessages() + + AssertEqual [], l:messages + AssertEqual [], g:calls + endfunction + + call ale#linter#Reset() + +After: + Restore + + call ale#linter#Reset() + call ale#lsp#ResetConnections() + + unlet! g:ale_run_synchronously_callbacks + unlet! g:job_map + unlet! g:emulate_job_failure + unlet! g:next_job_id + + unlet! g:socket_map + unlet! g:emulate_socket_failure + unlet! g:next_channel_id + + unlet! g:message_buffer + unlet! g:calls + + delfunction PopMessages + delfunction Start + delfunction AssertInitSuccess + delfunction AssertInitFailure + + runtime autoload/ale/engine.vim + runtime autoload/ale/job.vim + runtime autoload/ale/socket.vim + +Execute(tsserver should be started correctly): + runtime ale_linters/typescript/tsserver.vim + + Assert Start() + call AssertInitSuccess('tsserver', 'tsserver', '', '', ale#Escape('tsserver')) + +Execute(tsserver failures should be handled appropriately): + runtime ale_linters/typescript/tsserver.vim + + let g:emulate_job_failure = 1 + + Assert !Start() + call AssertInitFailure() + +Execute(LSP jobs should start correctly): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'stdio', + \ 'executable': 'foo', + \ 'command': 'foo', + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + Assert Start() + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', 'foo') + +Execute(LSP job failures should be handled): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'stdio', + \ 'executable': 'foo', + \ 'command': 'foo', + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + let g:emulate_job_failure = 1 + + Assert !Start() + call AssertInitFailure() + +Execute(LSP TCP connections should start correctly): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address': 'foo', + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + Assert Start() + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '') + +Execute(LSP TCP connection failures should be handled): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address': 'foo', + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + let g:emulate_socket_failure = 1 + + Assert !Start() + call AssertInitFailure() + +Execute(Deferred executables should be handled correctly): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'stdio', + \ 'executable': {b -> ale#command#Run(b, 'echo', {-> 'foo'})}, + \ 'command': '%e -c', + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + Assert Start() + call ale#test#FlushJobs() + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c') + +Execute(Deferred commands should be handled correctly): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'stdio', + \ 'executable': 'foo', + \ 'command': {b -> ale#command#Run(b, 'echo', {-> '%e -c'})}, + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + Assert Start() + call ale#test#FlushJobs() + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c') + +Execute(Deferred addresses should be handled correctly): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address': {b -> ale#command#Run(b, 'echo', {-> 'localhost:1234'})}, + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + Assert Start() + call ale#test#FlushJobs() + call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '') diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader index 2f59535d..0372765d 100644 --- a/test/lsp/test_other_initialize_message_handling.vader +++ b/test/lsp/test_other_initialize_message_handling.vader @@ -1,5 +1,9 @@ Before: + runtime autoload/ale/lsp.vim + + let g:message_list = [] let b:conn = { + \ 'id': 1, \ 'is_tsserver': 0, \ 'data': '', \ 'root': '/foo/bar', @@ -10,7 +14,7 @@ Before: \ 'config': {}, \ 'callback_list': [], \ 'message_queue': [], - \ 'capabilities_queue': [], + \ 'init_queue': [], \ 'capabilities': { \ 'hover': 0, \ 'references': 0, @@ -21,8 +25,17 @@ Before: \ }, \} + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + After: unlet! b:conn + unlet! g:message_list + + runtime autoload/ale/lsp.vim Execute(Messages with no method and capabilities should initialize projects): call ale#lsp#HandleInitResponse(b:conn, { @@ -30,15 +43,18 @@ Execute(Messages with no method and capabilities should initialize projects): \}) AssertEqual 1, b:conn.initialized + AssertEqual [[1, 'initialized', {}]], g:message_list Execute(Other messages should not initialize projects): call ale#lsp#HandleInitResponse(b:conn, {'method': 'lolwat'}) AssertEqual 0, b:conn.initialized + AssertEqual [], g:message_list call ale#lsp#HandleInitResponse(b:conn, {'result': {'x': {}}}) AssertEqual 0, b:conn.initialized + AssertEqual [], g:message_list Execute(Capabilities should bet set up correctly): call ale#lsp#HandleInitResponse(b:conn, { @@ -86,6 +102,7 @@ Execute(Capabilities should bet set up correctly): \ 'symbol_search': 1, \ }, \ b:conn.capabilities + AssertEqual [[1, 'initialized', {}]], g:message_list Execute(Disabled capabilities should be recognised correctly): call ale#lsp#HandleInitResponse(b:conn, { @@ -128,6 +145,7 @@ Execute(Disabled capabilities should be recognised correctly): \ 'symbol_search': 0, \ }, \ b:conn.capabilities + AssertEqual [[1, 'initialized', {}]], g:message_list Execute(Results that are not dictionaries should be handled correctly): call ale#lsp#HandleInitResponse(b:conn, { @@ -135,3 +153,4 @@ Execute(Results that are not dictionaries should be handled correctly): \ 'id': 1, \ 'result': v:null, \}) + AssertEqual [], g:message_list diff --git a/test/lsp/test_read_lsp_diagnostics.vader b/test/lsp/test_read_lsp_diagnostics.vader index a5c5ded3..377e73d9 100644 --- a/test/lsp/test_read_lsp_diagnostics.vader +++ b/test/lsp/test_read_lsp_diagnostics.vader @@ -17,7 +17,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should handle errors): \ 'lnum': 3, \ 'col': 11, \ 'end_lnum': 5, - \ 'end_col': 16, + \ 'end_col': 15, \ 'code': 'some-error', \ } \ ], @@ -38,7 +38,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should handle warnings): \ 'lnum': 2, \ 'col': 4, \ 'end_lnum': 2, - \ 'end_col': 4, + \ 'end_col': 3, \ 'code': 'some-warning', \ } \ ], @@ -59,7 +59,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should treat messages with missing se \ 'lnum': 3, \ 'col': 11, \ 'end_lnum': 5, - \ 'end_col': 16, + \ 'end_col': 15, \ 'code': 'some-error', \ } \ ], @@ -79,7 +79,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should handle messages without codes) \ 'lnum': 3, \ 'col': 11, \ 'end_lnum': 5, - \ 'end_col': 16, + \ 'end_col': 15, \ } \ ], \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ @@ -98,7 +98,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should include sources in detail): \ 'lnum': 10, \ 'col': 15, \ 'end_lnum': 12, - \ 'end_col': 23, + \ 'end_col': 22, \ } \ ], \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ @@ -109,6 +109,26 @@ Execute(ale#lsp#response#ReadDiagnostics() should include sources in detail): \ } \ ]}}) +Execute(ale#lsp#response#ReadDiagnostics() should keep detail with line breaks but replace with spaces in text): + AssertEqual [ + \ { + \ 'type': 'E', + \ 'text': 'cannot borrow `cap` as mutable more than once at a time mutable borrow starts here in previous iteration of loop', + \ 'detail': "[rustc] cannot borrow `cap` as mutable\r\nmore than once at a time\n\nmutable borrow starts here\rin previous iteration of loop", + \ 'lnum': 10, + \ 'col': 15, + \ 'end_lnum': 12, + \ 'end_col': 22, + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ + \ { + \ 'range': Range(9, 14, 11, 22), + \ 'message': "cannot borrow `cap` as mutable\r\nmore than once at a time\n\nmutable borrow starts here\rin previous iteration of loop", + \ 'source': 'rustc', + \ } + \ ]}}) + Execute(ale#lsp#response#ReadDiagnostics() should consider -1 to be a meaningless code): AssertEqual [ \ { @@ -117,7 +137,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should consider -1 to be a meaningles \ 'lnum': 3, \ 'col': 11, \ 'end_lnum': 5, - \ 'end_col': 16, + \ 'end_col': 15, \ } \ ], \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ @@ -136,7 +156,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should handle multiple messages): \ 'lnum': 1, \ 'col': 3, \ 'end_lnum': 1, - \ 'end_col': 3, + \ 'end_col': 2, \ }, \ { \ 'type': 'W', @@ -144,7 +164,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should handle multiple messages): \ 'lnum': 2, \ 'col': 5, \ 'end_lnum': 2, - \ 'end_col': 5, + \ 'end_col': 4, \ }, \ ], \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ @@ -167,7 +187,7 @@ Execute(ale#lsp#response#ReadDiagnostics() should use relatedInformation for det \ 'lnum': 1, \ 'col': 3, \ 'end_lnum': 1, - \ 'end_col': 3, + \ 'end_col': 2, \ 'detail': "Something went wrong!\n/tmp/someotherfile.txt:43:80:\n\tmight be this" \ } \ ], diff --git a/test/lsp/test_reset_lsp.vader b/test/lsp/test_reset_lsp.vader index 2bec13dc..310b3d62 100644 --- a/test/lsp/test_reset_lsp.vader +++ b/test/lsp/test_reset_lsp.vader @@ -18,14 +18,17 @@ Before: endfunction call ale#engine#InitBufferInfo(bufnr('')) + " Call this function first, so we can be sure the module is loaded before we + " check if it exists. + call ale#lsp_linter#ClearLSPData() call ale#linter#Define('testft', { \ 'name': 'lsplinter', \ 'lsp': 'tsserver', - \ 'executable_callback': 'EmptyString', - \ 'command_callback': 'EmptyString', - \ 'project_root_callback': 'EmptyString', - \ 'language_callback': 'EmptyString', + \ 'executable': function('EmptyString'), + \ 'command': function('EmptyString'), + \ 'project_root': function('EmptyString'), + \ 'language': function('EmptyString'), \}) call ale#linter#Define('testft', { @@ -68,7 +71,10 @@ Execute(ALEStopAllLSPs should clear the loclist): \ 'linter_name': 'otherlinter', \ }, \] - let g:ale_buffer_info[bufnr('')].active_linter_list = ['lsplinter', 'otherlinter'] + let g:ale_buffer_info[bufnr('')].active_linter_list = [ + \ {'name': 'lsplinter'}, + \ {'name': 'otherlinter'}, + \] ALEStopAllLSPs @@ -87,4 +93,6 @@ Execute(ALEStopAllLSPs should clear the loclist): \] " The LSP linter should be removed from the active linter list. - AssertEqual g:ale_buffer_info[bufnr('')].active_linter_list, ['otherlinter'] + AssertEqual + \ ['otherlinter'], + \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name') diff --git a/test/lsp/test_update_config.vader b/test/lsp/test_update_config.vader index 07068bc8..698477ec 100644 --- a/test/lsp/test_update_config.vader +++ b/test/lsp/test_update_config.vader @@ -3,6 +3,10 @@ Before: let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) + " Stub out this function, so we test updating configs. + function! ale#lsp#Send(conn_id, message) abort + endfunction + After: Restore diff --git a/test/markdown_files/testfile.md b/test/markdown_files/testfile.md new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/markdown_files/testfile.md diff --git a/test/python/test_deoplete_source.py b/test/python/test_deoplete_source.py new file mode 100644 index 00000000..960abe3a --- /dev/null +++ b/test/python/test_deoplete_source.py @@ -0,0 +1,130 @@ +import unittest +import imp + +ale_module = imp.load_source( + 'deoplete.sources.ale', + '/testplugin/rplugin/python3/deoplete/sources/ale.py', +) + + +class VimMock(object): + def __init__(self, call_list, call_results): + self.__call_list = call_list + self.__call_results = call_results + + def call(self, function, *args): + self.__call_list.append((function, args)) + + return self.__call_results.get(function, 0) + + +class DeopleteSourceTest(unittest.TestCase): + def setUp(self): + super(DeopleteSourceTest, self).setUp() + + self.call_list = [] + self.call_results = {} + self.source = ale_module.Source('vim') + self.source.vim = VimMock(self.call_list, self.call_results) + + def test_attributes(self): + """ + Check all of the attributes we set. + """ + attributes = dict( + (key, getattr(self.source, key)) + for key in + dir(self.source) + if not key.startswith('__') + and key != 'vim' + and not hasattr(getattr(self.source, key), '__self__') + ) + + self.assertEqual(attributes, { + 'is_bytepos': True, + 'mark': '[L]', + 'min_pattern_length': 1, + 'name': 'ale', + 'rank': 100, + }) + + def test_completion_position(self): + self.call_results['ale#completion#GetCompletionPosition'] = 2 + + self.assertEqual(self.source.get_completion_position(), 2) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletionPosition', ()), + ]) + + def test_request_completion_results(self): + context = {'is_async': False} + + self.assertEqual(self.source.gather_candidates(context), []) + self.assertEqual(context, {'is_async': True}) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletions', ('deoplete',)), + ]) + + def test_refresh_completion_results(self): + context = {'is_async': False} + + self.assertEqual(self.source.gather_candidates(context), []) + self.assertEqual(context, {'is_async': True}) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletions', ('deoplete',)), + ]) + + context = {'is_async': True, 'is_refresh': True} + + self.assertEqual(self.source.gather_candidates(context), []) + self.assertEqual(context, {'is_async': True, 'is_refresh': True}) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletions', ('deoplete',)), + ('ale#completion#GetCompletions', ('deoplete',)), + ]) + + def test_poll_no_result(self): + context = {'is_async': True} + self.call_results['ale#completion#GetCompletionResult'] = None + + self.assertEqual(self.source.gather_candidates(context), []) + self.assertEqual(context, {'is_async': True}) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletionResult', ()), + ]) + + def test_poll_empty_result_ready(self): + context = {'is_async': True} + self.call_results['ale#completion#GetCompletionResult'] = [] + + self.assertEqual(self.source.gather_candidates(context), []) + self.assertEqual(context, {'is_async': False}) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletionResult', ()), + ]) + + def test_poll_non_empty_result_ready(self): + context = {'is_async': True} + self.call_results['ale#completion#GetCompletionResult'] = [ + { + 'word': 'foobar', + 'kind': 'v', + 'icase': 1, + 'menu': '', + 'info': '', + }, + ] + + self.assertEqual(self.source.gather_candidates(context), [ + { + 'word': 'foobar', + 'kind': 'v', + 'icase': 1, + 'menu': '', + 'info': '', + }, + ]) + self.assertEqual(context, {'is_async': False}) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletionResult', ()), + ]) diff --git a/test/script/block-padding-checker b/test/script/block-padding-checker index b13c9b92..2feab6d0 100755 --- a/test/script/block-padding-checker +++ b/test/script/block-padding-checker @@ -10,7 +10,8 @@ import re INDENTATION_RE = re.compile(r'^ *') COMMENT_LINE_RE = re.compile(r'^ *"') -COMMAND_RE = re.compile(r'^ *([a-zA-Z]+)') +COMMAND_RE = re.compile(r'^ *([a-zA-Z\\]+)') +OPERATOR_END_RE = re.compile(r'(&&|\|\||\+|-|\*\| /)$') START_BLOCKS = set(['if', 'for', 'while', 'try', 'function']) END_BLOCKS = set(['endif', 'endfor', 'endwhile', 'endtry', 'endfunction']) @@ -21,6 +22,7 @@ WHITESPACE_BEFORE_SET = START_BLOCKS | TERMINATORS WHITESPACE_FORBIDDEN_BEFORE_SET = END_BLOCKS | MIDDLE_BLOCKS WHITESPACE_AFTER_SET = END_BLOCKS WHITESPACE_FORBIDDEN_AFTER_SET = START_BLOCKS | MIDDLE_BLOCKS +SAME_INDENTATION_SET = set(['\\']) def remove_comment_lines(line_iter): @@ -44,7 +46,7 @@ def check_lines(line_iter): ): yield ( line_number, - 'Blank line forbidden after `%s`' % (command,) + 'Blank line forbidden after `%s`' % (previous_command,) ) previous_line_blank = True @@ -56,6 +58,26 @@ def check_lines(line_iter): if command_match: command = command_match.group(1) + if ( + command in SAME_INDENTATION_SET + and previous_indentation_level is not None + and indentation_level != previous_indentation_level + ): + yield ( + line_number, + 'Line continuation should match previous indentation' + ) + + if ( + previous_indentation_level is not None + and indentation_level != previous_indentation_level + and abs(indentation_level - previous_indentation_level) != 4 # noqa + ): + yield ( + line_number, + 'Indentation should be 4 spaces' + ) + # Check for commands requiring blank lines before them, if they # aren't at the start of a block. if ( @@ -98,6 +120,12 @@ def check_lines(line_iter): previous_line_blank = False previous_indentation_level = indentation_level + if OPERATOR_END_RE.search(line): + yield ( + line_number, + 'Put operators at the start of lines instead' + ) + def main(): status = 0 diff --git a/test/script/check-supported-tools-tables b/test/script/check-supported-tools-tables index 220c7427..beb580d7 100755 --- a/test/script/check-supported-tools-tables +++ b/test/script/check-supported-tools-tables @@ -6,66 +6,56 @@ 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. -# Find the start and end lines for the help section. -ale_help_start_line="$( \ - grep -m1 -n '^[0-9][0-9]*\. *Supported Languages' doc/ale.txt \ - | sed 's/\([0-9]*\).*/\1/' \ -)" -ale_help_section_size="$( \ - tail -n +"$ale_help_start_line" doc/ale.txt \ - | grep -m1 -n '================' \ - | sed 's/\([0-9]*\).*/\1/' \ -)" -# -- shellcheck complains about expr, but it works better. -# shellcheck disable=SC2003 -ale_help_end_line="$(expr "$ale_help_start_line" + "$ale_help_section_size")" - -# Find the start and end lines for the same section in the README. -readme_start_line="$( \ - grep -m1 -n '^.*[0-9][0-9]*\. *Supported Languages' README.md \ - | sed 's/\([0-9]*\).*/\1/' \ -)" -readme_section_size="$( \ - tail -n +"$readme_start_line" README.md \ - | grep -m1 -n '^##.*Usage' \ - | sed 's/\([0-9]*\).*/\1/' \ -)" -# shellcheck disable=SC2003 -readme_end_line="$(expr "$readme_start_line" + "$readme_section_size")" - doc_file="$(mktemp -t doc.XXXXXXXX)" +doc_sorted_file="$(mktemp -t doc-sorted.XXXXXXXX)" readme_file="$(mktemp -t readme.XXXXXXXX)" -sed -n "$ale_help_start_line,$ale_help_end_line"p doc/ale.txt \ - | grep '\* .*: ' \ - | sed 's/^*//' \ - | sed 's/[`!^]//g;s/([^)]*)//g' \ - | sed 's/ *\([,:]\)/\1/g' \ - | sed 's/ */ /g' \ - | sed 's/^ *//;s/ *$//' \ - | sed 's/^/ /' \ - > "$doc_file" - -sed -n "$readme_start_line,$readme_end_line"p README.md \ - | grep '| .* |' \ - | sed '/^| Language/d;/^| ---/d' \ - | sed 's/^|//' \ - | sed 's/ \{0,1\}|/:/' \ - | sed 's/[`!^|]//g;s/([^)]*)//g' \ - | sed 's/\[//g;s/\]//g' \ - | sed 's/see[^,]*//g' \ - | sed 's/ *\([,:]\)/\1/g' \ - | sed 's/ */ /g' \ - | sed 's/^ *//;s/ *$//' \ - | sed 's/^/ /' \ - | sed 's/ *-n flag//g' \ - > "$readme_file" +while read -r; do + if [[ "$REPLY" =~ ^! ]]; then + language="${REPLY/!/}" + else + # shellcheck disable=SC2001 + echo "$language - $REPLY" + fi +done < <( + grep '^\*\|^ *\*' doc/ale-supported-languages-and-tools.txt \ + | sed -e '1,2d' \ + | sed 's/^\* */!/' \ + | sed 's/^ *\* *\|!!\|\^\|(.*)\|`//g' \ + | sed 's/ *$//' +) > "$doc_file" + +while read -r; do + if [[ "$REPLY" =~ ^! ]]; then + language="${REPLY/!/}" + else + # shellcheck disable=SC2001 + echo "$language - $REPLY" + fi +done < <( + grep '^\*\|^ *\*' supported-tools.md \ + | sed 's/^\* */!/' \ + | sed 's/^ *\* *\|:floppy_disk:\|:warning:\|(.*)\|\[\|\].*\|-n flag//g' \ + | sed 's/ *$//' +) > "$readme_file" exit_code=0 +# Sort the tools ignoring case, and complain when things are out of order. +sort -f -k1,2 "$doc_file" -o "$doc_sorted_file" + +diff -U0 "$doc_sorted_file" "$doc_file" || exit_code=$? + +if ((exit_code)); then + echo + echo "The supported tools list isn't sorted properly" + echo +fi + diff -U0 "$readme_file" "$doc_file" || exit_code=$? rm "$doc_file" +rm "$doc_sorted_file" rm "$readme_file" exit "$exit_code" diff --git a/test/script/check-toc b/test/script/check-toc index 09d794ee..f3a57ed2 100755 --- a/test/script/check-toc +++ b/test/script/check-toc @@ -6,19 +6,24 @@ set -u # This script checks that the table of contents for the supported tools is # sorted, and that the table matches the files. -toc_start_line="$( \ - grep -m1 -n 'Integration Documentation.*|ale-integrations|' doc/ale.txt \ +toc_section_start_line="$( + grep -m1 -n '^7\..*\*ale-other-integration-options\*' doc/ale.txt \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +toc_start_offset="$( \ + tail -n +"$toc_section_start_line" doc/ale.txt \ + | grep -m1 -n '^ .*\.\.\.' \ | sed 's/\([0-9]*\).*/\1/' \ )" # shellcheck disable=SC2003 -toc_start_line="$(expr "$toc_start_line" + 1)" +toc_start_line="$(expr "$toc_section_start_line" + "$toc_start_offset" - 1)" toc_section_size="$( \ tail -n +"$toc_start_line" doc/ale.txt \ - | grep -m1 -n '^ [0-9]\+\.' \ + | grep -m1 -n '^===*$' \ | sed 's/\([0-9]*\).*/\1/' \ )" # shellcheck disable=SC2003 -toc_end_line="$(expr "$toc_start_line" + "$toc_section_size" - 2)" +toc_end_line="$(expr "$toc_start_line" + "$toc_section_size" - 4)" toc_file="$(mktemp -t table-of-contents.XXXXXXXX)" heading_file="$(mktemp -t headings.XXXXXXXX)" @@ -26,7 +31,7 @@ tagged_toc_file="$(mktemp -t ale.txt.XXXXXXXX)" sorted_toc_file="$(mktemp -t sorted-ale.txt.XXXXXXXX)" sed -n "$toc_start_line,$toc_end_line"p doc/ale.txt \ - | sed 's/^ \( *[^.][^.]*\)\.\.*|\(..*\)|/\1, \2/' \ + | sed 's/^ \( *[^.][^.]*\)\.\.*|\(..*\)|/\1, \2/' \ > "$toc_file" # Get all of the doc files in a natural sorted order. diff --git a/test/script/custom-checks b/test/script/custom-checks index d4027fec..20dbfb80 100755 --- a/test/script/custom-checks +++ b/test/script/custom-checks @@ -67,4 +67,14 @@ echo test/script/check-toc || exit_code=$? +echo '========================================' +echo 'Check Python code' +echo '========================================' +echo + +docker run --rm -v "$PWD:/testplugin" "$DOCKER_RUN_IMAGE" \ + python -W ignore -m unittest discover /testplugin/test/python \ + || exit_code=$? +echo + exit $exit_code diff --git a/test/script/custom-linting-rules b/test/script/custom-linting-rules index 77e87db4..981a9459 100755 --- a/test/script/custom-linting-rules +++ b/test/script/custom-linting-rules @@ -106,6 +106,7 @@ check_errors 'let g:ale_\w\+_\w\+_args =' 'Name your option g:ale_<filetype>_<li check_errors 'shellescape(' 'Use ale#Escape instead of shellescape' check_errors 'simplify(' 'Use ale#path#Simplify instead of simplify' check_errors 'tempname(' 'Use ale#util#Tempname instead of tempname' +check_errors 'getcurpos(' "Use getpos('.') instead of getcurpos() if you don't need curswant, to avoid a bug that changes curswant" check_errors "expand(['\"]%" "Use expand('#' . a:buffer . '...') instead. You might get a filename for the wrong buffer." check_errors 'getcwd()' "Do not use getcwd(), as it could run from the wrong buffer. Use expand('#' . a:buffer . ':p:h') instead." check_errors '==#' "Use 'is#' instead of '==#'. 0 ==# 'foobar' is true" diff --git a/test/sign/test_linting_sets_signs.vader b/test/sign/test_linting_sets_signs.vader index 3ccecf45..a8d5761f 100644 --- a/test/sign/test_linting_sets_signs.vader +++ b/test/sign/test_linting_sets_signs.vader @@ -13,6 +13,7 @@ Before: let g:ale_buffer_info = {} let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks let g:ale_set_signs = 1 " Disable features we don't need for these tests. let g:ale_set_quickfix = 0 @@ -58,10 +59,12 @@ After: delfunction TestCallback delfunction CollectSigns + unlet! g:ale_run_synchronously_callbacks sign unplace * call ale#linter#Reset() Execute(The signs should be updated after linting is done): ALELint + call ale#test#FlushJobs() AssertEqual [['1', 'ALEWarningSign'], ['2', 'ALEErrorSign']], CollectSigns() diff --git a/test/sign/test_sign_placement.vader b/test/sign/test_sign_placement.vader index e2d95ff0..f0b3ba2f 100644 --- a/test/sign/test_sign_placement.vader +++ b/test/sign/test_sign_placement.vader @@ -87,6 +87,7 @@ Before: After: Restore + unlet! g:ale_run_synchronously_callbacks unlet! g:loclist delfunction GenerateResults delfunction ParseSigns @@ -134,6 +135,7 @@ Given testft(A file with warnings/errors): Execute(The current signs should be set for running a job): ALELint + call ale#test#FlushJobs() AssertEqual \ [ diff --git a/test/smoke_test.vader b/test/smoke_test.vader index c87f95b2..53e08a8d 100644 --- a/test/smoke_test.vader +++ b/test/smoke_test.vader @@ -66,7 +66,7 @@ Execute(Linters should run with the default options): " where tests fail randomly. for g:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) call ale#Queue(0, '') - call ale#engine#WaitForJobs(2000) + call ale#test#WaitForJobs(2000) let g:results = ale#test#GetLoclistWithoutModule() @@ -110,7 +110,7 @@ Execute(Linters should run in PowerShell too): \}) call ale#Queue(0, '') - call ale#engine#WaitForJobs(4000) + call ale#test#WaitForJobs(4000) AssertEqual [ \ { @@ -140,7 +140,7 @@ Execute(Linters should run in PowerShell too): Execute(Previous errors should be removed when linters change): call ale#Queue(0, '') - call ale#engine#WaitForJobs(2000) + call ale#test#WaitForJobs(2000) call ale#linter#Reset() @@ -167,7 +167,7 @@ Execute(Previous errors should be removed when linters change): " where tests fail randomly. for g:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) call ale#Queue(0, '') - call ale#engine#WaitForJobs(2000) + call ale#test#WaitForJobs(2000) let g:results = ale#test#GetLoclistWithoutModule() diff --git a/test/swift-test-files/non-swift-package-project/src/folder/dummy.swift b/test/swift-test-files/non-swift-package-project/src/folder/dummy.swift new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/swift-test-files/non-swift-package-project/src/folder/dummy.swift diff --git a/test/swift-test-files/swift-package-project/Package.swift b/test/swift-test-files/swift-package-project/Package.swift new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/swift-test-files/swift-package-project/Package.swift diff --git a/test/swift-test-files/swift-package-project/src/folder/dummy.swift b/test/swift-test-files/swift-package-project/src/folder/dummy.swift new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/swift-test-files/swift-package-project/src/folder/dummy.swift diff --git a/test/test_ale_has.vader b/test/test_ale_has.vader new file mode 100644 index 00000000..eb1da039 --- /dev/null +++ b/test/test_ale_has.vader @@ -0,0 +1,7 @@ +Execute(Checks for versions below the current version should succeed): + AssertEqual 1, ale#Has('ale-2.4.0') + AssertEqual 1, ale#Has('ALE-2.2.1') + AssertEqual 1, ale#Has('ALE-1.0.0') + +Execute(Checks for newer versions should fail): + AssertEqual 0, ale#Has('ALE-20.0.0') diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index 325c2aa8..29c19b8e 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -97,6 +97,7 @@ Before: \ 'let g:ale_list_vertical = 0', \ 'let g:ale_list_window_size = 10', \ 'let g:ale_loclist_msg_format = ''%code: %%s''', + \ 'let g:ale_lsp_root = {}', \ 'let g:ale_max_buffer_history_size = 20', \ 'let g:ale_max_signs = -1', \ 'let g:ale_maximum_file_size = 0', diff --git a/test/test_ale_lint_command.vader b/test/test_ale_lint_command.vader index bc2ebabe..ba7308dd 100644 --- a/test/test_ale_lint_command.vader +++ b/test/test_ale_lint_command.vader @@ -1,7 +1,9 @@ Before: Save g:ale_buffer_info + Save g:ale_enabled let g:ale_buffer_info = {} + let g:ale_enabled = 1 let g:expected_loclist = [{ \ 'bufnr': bufnr('%'), @@ -58,7 +60,7 @@ Execute(ALELint should run the linters): " Try to run the linter a few times, as it fails randomly in NeoVim. for b:i in range(5) ALELint - call ale#engine#WaitForJobs(2000) + call ale#test#WaitForJobs(2000) if !has('nvim') " Sleep so the delayed list function can run. diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader index db891009..d0bca329 100644 --- a/test/test_ale_toggle.vader +++ b/test/test_ale_toggle.vader @@ -10,6 +10,7 @@ Before: let g:ale_set_signs = 1 let g:ale_set_lists_synchronously = 1 let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks let g:ale_pattern_options = {} let g:ale_pattern_options_enabled = 1 let g:ale_set_balloons = @@ -85,6 +86,7 @@ Before: After: Restore + unlet! g:ale_run_synchronously_callbacks unlet! g:expected_loclist unlet! g:expected_groups unlet! b:ale_enabled @@ -113,6 +115,7 @@ Execute(ALEToggle should reset everything and then run again): AssertEqual 'foobar', &filetype ALELint + call ale#test#FlushJobs() " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() @@ -135,6 +138,7 @@ Execute(ALEToggle should reset everything and then run again): " Toggle ALE on, everything should be set up and run again. ALEToggle + call ale#test#FlushJobs() AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) @@ -157,6 +161,7 @@ Execute(ALEToggle should skip filename keys and preserve them): \} ALELint + call ale#test#FlushJobs() " Now Toggle ALE off. ALEToggle @@ -174,6 +179,7 @@ Execute(ALEToggle should skip filename keys and preserve them): " Toggle ALE on again. ALEToggle + call ale#test#FlushJobs() AssertEqual \ { @@ -188,15 +194,18 @@ Execute(ALEToggle should skip filename keys and preserve them): Execute(ALEDisable should reset everything and stay disabled): ALELint + call ale#test#FlushJobs() AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() ALEDisable + call ale#test#FlushJobs() AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 0, g:ale_enabled ALEDisable + call ale#test#FlushJobs() AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 0, g:ale_enabled @@ -205,6 +214,7 @@ Execute(ALEEnable should enable ALE and lint again): let g:ale_enabled = 0 ALEEnable + call ale#test#FlushJobs() AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual 1, g:ale_enabled @@ -213,6 +223,7 @@ Execute(ALEReset should reset everything for a buffer): AssertEqual 'foobar', &filetype ALELint + call ale#test#FlushJobs() " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() @@ -224,6 +235,7 @@ Execute(ALEReset should reset everything for a buffer): " Now Toggle ALE off. ALEReset + call ale#test#FlushJobs() " Everything should be cleared. Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' @@ -237,6 +249,7 @@ Execute(ALEToggleBuffer should reset everything and then run again): AssertEqual 'foobar', &filetype ALELint + call ale#test#FlushJobs() " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() @@ -257,6 +270,7 @@ Execute(ALEToggleBuffer should reset everything and then run again): " Toggle ALE on, everything should be set up and run again. ALEToggleBuffer + call ale#test#FlushJobs() AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) @@ -268,10 +282,12 @@ Execute(ALEToggleBuffer should reset everything and then run again): Execute(ALEDisableBuffer should reset everything and stay disabled): ALELint + call ale#test#FlushJobs() AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() ALEDisableBuffer + call ale#test#FlushJobs() AssertEqual [], ale#test#GetLoclistWithoutModule() AssertEqual 0, b:ale_enabled @@ -280,6 +296,7 @@ Execute(ALEEnableBuffer should enable ALE and lint again): let b:ale_enabled = 0 ALEEnableBuffer + call ale#test#FlushJobs() AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual 1, b:ale_enabled @@ -303,6 +320,7 @@ Execute(ALEResetBuffer should reset everything for a buffer): AssertEqual 'foobar', &filetype ALELint + call ale#test#FlushJobs() " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() @@ -314,6 +332,7 @@ Execute(ALEResetBuffer should reset everything for a buffer): " Now Toggle ALE off. ALEResetBuffer + call ale#test#FlushJobs() " Everything should be cleared. Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' diff --git a/test/test_c_flag_parsing.vader b/test/test_c_flag_parsing.vader index 8a9b7189..045554a3 100644 --- a/test/test_c_flag_parsing.vader +++ b/test/test_c_flag_parsing.vader @@ -14,14 +14,25 @@ Execute(The CFlags parser should be able to parse include directives): 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')), + \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir'), \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -c file.c']) + AssertEqual + \ '-isystem ' . '/usr/include/dir', + \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -isystem /usr/include/dir -c file.c']) + +Execute(ParseCFlags should ignore -c and -o): + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir'), + \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -c file.c -o a.out']) + Execute(The CFlags parser should be able to parse macro directives): 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')) + \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') \ . ' -DTEST=1', \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=1 -c file.c']) @@ -29,7 +40,7 @@ Execute(The CFlags parser should be able to parse macro directives with spaces): 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')) + \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') \ . ' -DTEST=$(( 2 * 4 ))', \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=$(( 2 * 4 )) -c file.c']) @@ -37,14 +48,14 @@ Execute(The CFlags parser should be able to parse shell directives with spaces): 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')) + \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=`date +%s` -c file.c']) Execute(ParseCFlags should be able to parse flags with relative paths): 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')) + \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), @@ -56,8 +67,8 @@ Execute(ParseCFlags should be able to parse flags with relative paths): Execute(ParseCFlags should be able to parse -Dgoal): 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')) + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), @@ -66,29 +77,16 @@ Execute(ParseCFlags should be able to parse -Dgoal): \ . ' -DTEST=`date +%s` -c file.c' \ ) -Execute(ParseCFlags should ignore -T and other arguments): - 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#ParseCFlags( - \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir --sysroot=subdir ' - \ . '-I'. ale#path#Simplify('kernel/include') - \ . ' -DTEST=`date +%s` -c file.c' - \ ) - Execute(ParseCFlags should handle paths with spaces in double quotes): 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')) + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/"dir with spaces"') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ 'gcc -Dgoal=9 -Isubdir ' \ . '-I"dir with spaces"' . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) @@ -96,29 +94,28 @@ Execute(ParseCFlags should handle paths with spaces in double quotes): Execute(ParseCFlags should handle paths with spaces in single quotes): 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')) + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. "/test_c_projects/makefile_project/'dir with spaces'") + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' - \ . '-I''dir with spaces''' . ' -I'. ale#path#Simplify('kernel/include') + \ 'gcc -Dgoal=9 -Isubdir ' + \ . "-I'dir with spaces'" . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) Execute(ParseCFlags should handle paths with minuses): 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')) + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' - \ . '-I''dir with spaces''' . ' -Idir-with-dash' + \ 'gcc -Dgoal=9 -Isubdir ' + \ . ' -Idir-with-dash' \ . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) @@ -126,17 +123,14 @@ Execute(ParseCFlags should handle paths with minuses): Execute(ParseCFlags should handle -D with minuses): AssertEqual \ '-Dgoal=9' - \ . ' ' . ale#Escape('-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) + \ . ' ' . '-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')) + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ 'gcc -Dgoal=9 -Isubdir ' \ . '-Dmacro-with-dash ' - \ . '-I''dir with spaces''' . ' -Idir-with-dash' \ . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) @@ -144,16 +138,11 @@ Execute(ParseCFlags should handle -D with minuses): Execute(ParseCFlags should handle flags at the end of the line): 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')), + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include'), \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' - \ . '-Dmacro-with-dash ' - \ . '-I''dir with spaces''' . ' -Idir-with-dash' + \ 'gcc -Dgoal=9 -Isubdir ' \ . ' -I'. ale#path#Simplify('kernel/include') \ ) @@ -161,19 +150,145 @@ Execute(FlagsFromCompileCommands should tolerate empty values): AssertEqual '', ale#c#FlagsFromCompileCommands(bufnr(''), '') Execute(ParseCompileCommandsFlags should tolerate empty values): - AssertEqual '', ale#c#ParseCompileCommandsFlags(bufnr(''), '', []) + AssertEqual '', ale#c#ParseCompileCommandsFlags(bufnr(''), {}, {}) Execute(ParseCompileCommandsFlags should parse some basic flags): - noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) AssertEqual - \ '-I' . ale#path#Simplify('/usr/include/xmms2'), - \ ale#c#ParseCompileCommandsFlags(bufnr(''), ale#path#Simplify('/foo/bar/xmms2-mpris'), [ + \ '-I/usr/include/xmms2', + \ ale#c#ParseCompileCommandsFlags(bufnr(''), { "xmms2-mpris.c": [ \ { - \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), - \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/xmms2') + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' - \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), - \ 'file': ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ 'file': '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ }, + \ ] }, {}) + +Execute(ParseCompileCommandsFlags should tolerate items without commands): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + + AssertEqual + \ '', + \ ale#c#ParseCompileCommandsFlags(bufnr(''), { "xmms2-mpris.c": [ + \ { + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'file': '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ }, + \ ] }, {}) + +Execute(ParseCompileCommandsFlags should fall back to files in the same directory): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + + AssertEqual + \ '-I/usr/include/xmms2', + \ ale#c#ParseCompileCommandsFlags(bufnr(''), {}, { "src": [ + \ { + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c', + \ }, + \ ] }) + +Execute(ParseCompileCommandsFlags should take commands from matching .c files for .h files): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.h')) + + AssertEqual + \ '-I/usr/include/xmms2', + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ { + \ 'xmms2-mpris.c': [ + \ { + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ }, + \ ], + \ }, + \ { + \ }, + \ ) + +Execute(ParseCompileCommandsFlags should take commands from matching .cpp files for .hpp files): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.hpp')) + + AssertEqual + \ '-I/usr/include/xmms2', + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ { + \ 'xmms2-mpris.cpp': [ + \ { + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp', + \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp', + \ }, + \ ], \ }, - \ ]) + \ { + \ }, + \ ) + +Execute(ParseCompileCommandsFlags should take commands from matching .cpp files for .h files): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.h')) + + AssertEqual + \ '-I/usr/include/xmms2', + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ { + \ 'xmms2-mpris.cpp': [ + \ { + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp', + \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.cpp', + \ }, + \ ], + \ }, + \ { + \ }, + \ ) + +Execute(ParseCompileCommandsFlags should not take commands from .c files for .h files with different names): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/other.h')) + + AssertEqual + \ '', + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ { + \ 'xmms2-mpris.c': [ + \ { + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ }, + \ ], + \ }, + \ { + \ }, + \ ) + +Execute(ParseCFlags should handle parenthesis and quotes): + AssertEqual + \ '-Dgoal=9 -Dtest1="('' '')" file1.o -Dtest2=''(` `)'' file2.o -Dtest3=`(" ")` file3.o', + \ ale#c#ParseCFlags( + \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ 'gcc -Dgoal=9 ' + \ . '-Dtest1="('' '')" file1.o ' + \ . '-Dtest2=''(` `)'' file2.o ' + \ . '-Dtest3=`(" ")` file3.o ' + \ ) diff --git a/test/test_command_chain.vader b/test/test_command_chain.vader index 591f6f40..329ebc97 100644 --- a/test/test_command_chain.vader +++ b/test/test_command_chain.vader @@ -1,6 +1,7 @@ Before: Save &shell, g:ale_run_synchronously let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks if !has('win32') set shell=/bin/sh @@ -47,6 +48,7 @@ Before: After: Restore + unlet! g:ale_run_synchronously_callbacks unlet! g:first_echo_called unlet! g:second_echo_called unlet! g:final_callback_called @@ -63,6 +65,7 @@ Given foobar (Some imaginary filetype): Execute(Check the results of running the chain): AssertEqual 'foobar', &filetype call ale#Queue(0) + call ale#test#FlushJobs() Assert g:first_echo_called, 'The first chain item was not called' Assert g:second_echo_called, 'The second chain item was not called' diff --git a/test/test_deferred_command_string.vader b/test/test_deferred_command_string.vader new file mode 100644 index 00000000..026be6fe --- /dev/null +++ b/test/test_deferred_command_string.vader @@ -0,0 +1,50 @@ +Before: + Save g:ale_run_synchronously + Save g:ale_emulate_job_failure + Save g:ale_buffer_info + + let g:ale_run_synchronously = 1 + let g:ale_buffer_info = {} + let b:ale_history = [] + + call ale#linter#Reset() + call ale#assert#SetUpLinterTestCommands() + call ale#linter#Define('foobar', { + \ 'name': 'lint_file_linter', + \ 'callback': 'LintFileCallback', + \ 'executable': 'echo', + \ 'command': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 'foo'})})}, + \ 'read_buffer': 0, + \}) + + " Run the test commands in the shell. + let g:ale_run_synchronously_emulate_commands = 0 + +After: + Restore + + call ale#assert#TearDownLinterTest() + unlet! g:ale_run_synchronously_callbacks + +Given foobar (Some imaginary filetype): +Execute(It should be possible to compute an executable to check based on the result of commands): + AssertLinter 'echo', 'foo' + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ 1, + \ len(filter(copy(b:ale_history), 'string(v:val.command) =~# ''foo''')) + +Execute(It handle the deferred command failing): + let g:ale_emulate_job_failure = 1 + + AssertLinter 'echo', 0 + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ 0, + \ len(filter(copy(b:ale_history), 'string(v:val.command) =~# ''foo''')) diff --git a/test/test_deferred_executable_string.vader b/test/test_deferred_executable_string.vader new file mode 100644 index 00000000..3bdc5251 --- /dev/null +++ b/test/test_deferred_executable_string.vader @@ -0,0 +1,46 @@ +Before: + Save g:ale_run_synchronously + Save g:ale_emulate_job_failure + Save g:ale_buffer_info + + let g:ale_run_synchronously = 1 + let g:ale_buffer_info = {} + let b:ale_history = [] + + call ale#linter#Reset() + call ale#assert#SetUpLinterTestCommands() + call ale#linter#Define('foobar', { + \ 'name': 'lint_file_linter', + \ 'callback': 'LintFileCallback', + \ 'executable': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 'foo'})})}, + \ 'command': 'echo', + \ 'read_buffer': 0, + \}) + +After: + Restore + + call ale#assert#TearDownLinterTest() + +Given foobar (Some imaginary filetype): +Execute(It should be possible to compute an executable to check based on the result of commands): + AssertLinter 'foo', 'echo' + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ [{'status': 0, 'job_id': 'executable', 'command': 'foo'}], + \ filter(copy(b:ale_history), 'v:val.job_id is# ''executable''') + +Execute(It handle the deferred command failing): + let g:ale_emulate_job_failure = 1 + + AssertLinter 0, 'echo' + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ [], + \ filter(copy(b:ale_history), 'v:val.job_id is# ''executable''') diff --git a/test/test_engine_invocation.vader b/test/test_engine_invocation.vader index c56895d3..af713953 100644 --- a/test/test_engine_invocation.vader +++ b/test/test_engine_invocation.vader @@ -34,7 +34,10 @@ Before: \} function! ProcessIndex(chain_index) - return ale#engine#ProcessChain(347, g:linter, a:chain_index, []) + let [l:command, l:options] = ale#engine#ProcessChain(347, '', g:linter, a:chain_index, []) + let l:options.command = l:command + + return l:options endfunction After: @@ -64,40 +67,6 @@ Execute(Engine invocation should return the command for the fourth item correctl AssertEqual 'fourth', g:result.command AssertEqual 4, g:result.next_chain_index -Execute(Engine invocation should return the command for a single callback correctly): - unlet g:linter.command_chain - let g:linter.command_callback = 'FirstChainFunction' - - let g:result = ProcessIndex(0) - - AssertEqual 'first', g:result.command - -Execute(Engine invocation should return the command for a command string correctly): - unlet g:linter.command_chain - let g:linter.command = 'foo bar' - - let g:result = ProcessIndex(0) - - AssertEqual 'foo bar', g:result.command - -Execute(Engine invocation should process read_buffer correctly for simple commands): - unlet g:linter.command_chain - let g:linter.command = 'foo bar' - let g:linter.read_buffer = 0 - - let g:result = ProcessIndex(0) - - AssertEqual 'foo bar', g:result.command - AssertEqual 0, g:result.read_buffer - - let g:linter.command_callback = 'FirstChainFunction' - unlet g:linter.command - - let g:result = ProcessIndex(0) - - AssertEqual 'first', g:result.command - AssertEqual 0, g:result.read_buffer - Execute(Engine invocation should allow read_buffer to be enabled for a command in the middle of a chain): let g:linter.command_chain[2].read_buffer = 1 diff --git a/test/test_engine_lsp_response_handling.vader b/test/test_engine_lsp_response_handling.vader index 04f12ad6..84febe39 100644 --- a/test/test_engine_lsp_response_handling.vader +++ b/test/test_engine_lsp_response_handling.vader @@ -5,12 +5,15 @@ Before: let g:ale_buffer_info = {} unlet! g:ale_lsp_error_messages + unlet! b:ale_linters call ale#test#SetDirectory('/testplugin/test') After: Restore + unlet! b:ale_linters + call ale#test#RestoreDirectory() call ale#linter#Reset() call ale#lsp_linter#ClearLSPData() @@ -162,6 +165,47 @@ Execute(tsserver semantic error responses should be handled correctly): \ ], \ ale#test#GetLoclistWithoutModule() +Execute(tsserver errors should mark tsserver no longer active): + let b:ale_linters = ['tsserver'] + runtime ale_linters/typescript/tsserver.vim + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('typescript') + Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list) + + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[], + \ }, + \}) + + AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list + +Execute(LSP errors should mark linters no longer active): + let b:ale_linters = ['pyls'] + runtime ale_linters/python/pyls.vim + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({1: 'pyls'}) + + let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('python') + Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list) + + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToURI(g:dir . '/filename.py'), + \ 'diagnostics': [], + \ }, + \}) + + AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list + Execute(LSP errors should be logged in the history): call ale#lsp_linter#SetLSPLinterMap({'347': 'foobar'}) call ale#lsp_linter#HandleLSPResponse(347, { diff --git a/test/test_errors_removed_after_filetype_changed.vader b/test/test_errors_removed_after_filetype_changed.vader index 651a74f2..7ad97f94 100644 --- a/test/test_errors_removed_after_filetype_changed.vader +++ b/test/test_errors_removed_after_filetype_changed.vader @@ -3,12 +3,13 @@ Before: Save g:ale_buffer_info Save g:ale_echo_cursor Save g:ale_run_synchronously - Save g:ale_run_synchronously Save g:ale_set_highlights Save g:ale_set_loclist Save g:ale_set_quickfix Save g:ale_set_signs + let g:ale_buffer_info = {} + " Enable only the one feature we need. let g:ale_set_signs = 0 let g:ale_set_quickfix = 0 @@ -17,6 +18,7 @@ Before: let g:ale_echo_cursor = 0 let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks call setloclist(0, []) noautocmd let &filetype = 'foobar' @@ -44,6 +46,8 @@ Before: After: Restore + + unlet! g:ale_run_synchronously_callbacks delfunction TestCallback call ale#linter#Reset() @@ -51,6 +55,7 @@ After: Execute(Error should be removed when the filetype changes to something else we cannot check): call ale#Queue(0) + call ale#test#FlushJobs() sleep 1ms AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) @@ -58,6 +63,7 @@ Execute(Error should be removed when the filetype changes to something else we c noautocmd let &filetype = 'foobar2' call ale#Queue(0) + call ale#test#FlushJobs() sleep 1ms " We should get some items from the second filetype. @@ -66,6 +72,7 @@ Execute(Error should be removed when the filetype changes to something else we c noautocmd let &filetype = 'xxx' call ale#Queue(0) + call ale#test#FlushJobs() sleep 1ms AssertEqual 0, len(ale#test#GetLoclistWithoutModule()) diff --git a/test/test_eslint_executable_detection.vader b/test/test_eslint_executable_detection.vader index c1438ed8..5599576e 100644 --- a/test/test_eslint_executable_detection.vader +++ b/test/test_eslint_executable_detection.vader @@ -6,7 +6,6 @@ Before: runtime ale_linters/javascript/eslint.vim After: - let g:ale_has_override = {} let g:ale_javascript_eslint_executable = 'eslint' let g:ale_javascript_eslint_use_global = 0 @@ -54,11 +53,17 @@ Execute(eslint_d should be detected correctly): Execute(eslint.js executables should be run with node on Windows): call ale#test#SetFilename('eslint-test-files/react-app/subdir/testfile.js') - let g:ale_has_override['win32'] = 1 " We have to execute the file with node. - AssertEqual - \ ale#Escape('node.exe') . ' ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) - \ . ' -f unix --stdin --stdin-filename %s', - \ ale#handlers#eslint#GetCommand(bufnr('')) + if has('win32') + AssertEqual + \ ale#Escape('node.exe') . ' ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -f unix --stdin --stdin-filename %s', + \ ale#handlers#eslint#GetCommand(bufnr('')) + else + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -f unix --stdin --stdin-filename %s', + \ ale#handlers#eslint#GetCommand(bufnr('')) + endif diff --git a/test/test_filetype_linter_defaults.vader b/test/test_filetype_linter_defaults.vader index 4f190226..af028041 100644 --- a/test/test_filetype_linter_defaults.vader +++ b/test/test_filetype_linter_defaults.vader @@ -61,7 +61,7 @@ Execute(The defaults for the zsh filetype should be correct): Execute(The defaults for the verilog filetype should be correct): " This filetype isn't configured with default, so we can test loading all " available linters with this. - AssertEqual ['iverilog', 'verilator'], GetLinterNames('verilog') + AssertEqual ['iverilog', 'verilator', 'vlog', 'xvlog'], GetLinterNames('verilog') let g:ale_linters_explicit = 1 diff --git a/test/test_find_references.vader b/test/test_find_references.vader index 88b2d762..1a147849 100644 --- a/test/test_find_references.vader +++ b/test/test_find_references.vader @@ -8,30 +8,38 @@ Before: let g:message_list = [] let g:preview_called = 0 let g:item_list = [] + let g:options = {} let g:capability_checked = '' let g:conn_id = v:null - let g:WaitCallback = v:null + let g:InitCallback = v:null - runtime autoload/ale/linter.vim + runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim runtime autoload/ale/preview.vim - function! ale#lsp_linter#StartLSP(buffer, linter) abort + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) - return { + if a:linter.lsp is# 'tsserver' + call ale#lsp#MarkConnectionAsTsserver(g:conn_id) + endif + + let l:details = { + \ 'command': 'foobar', \ 'buffer': a:buffer, \ 'connection_id': g:conn_id, \ 'project_root': '/foo/bar', - \ 'language_id': 'python', \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} endfunction - function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort + function! ale#lsp#HasCapability(conn_id, capability) abort let g:capability_checked = a:capability - let g:WaitCallback = a:callback + + return 1 endfunction function! ale#lsp#RegisterCallback(conn_id, callback) abort @@ -48,9 +56,10 @@ Before: call add(g:expr_list, a:expr) endfunction - function! ale#preview#ShowSelection(item_list) abort + function! ale#preview#ShowSelection(item_list, options) abort let g:preview_called = 1 let g:item_list = a:item_list + let g:options = a:options endfunction After: @@ -63,13 +72,14 @@ After: call ale#linter#Reset() unlet! g:capability_checked - unlet! g:WaitCallback + unlet! g:InitCallback unlet! g:old_filename unlet! g:conn_id unlet! g:Callback unlet! g:message_list unlet! g:expr_list unlet! b:ale_linters + unlet! g:options unlet! g:item_list unlet! g:preview_called @@ -135,9 +145,9 @@ Execute(Results should be shown for tsserver responses): 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}, + \ {'filename': '/foo/bar/app.ts', 'column': 9, 'line': 9, 'match': 'import {doSomething} from ''./whatever'''}, + \ {'filename': '/foo/bar/app.ts', 'column': 3, 'line': 804, 'match': 'doSomething()'}, + \ {'filename': '/foo/bar/other/app.ts', 'column': 3, 'line': 51, 'match': 'doSomething()'}, \ ], \ g:item_list AssertEqual {}, ale#references#GetMap() @@ -162,6 +172,8 @@ Execute(The preview window should not be opened for empty tsserver responses): AssertEqual ['echom ''No references found.'''], g:expr_list Execute(tsserver reference requests should be sent): + call ale#linter#Reset() + runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) @@ -170,17 +182,30 @@ Execute(tsserver reference requests should be sent): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) - AssertEqual 'references', g:capability_checked - call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + AssertEqual 'references', g:capability_checked AssertEqual \ 'function(''ale#references#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual - \ [[0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], \ g:message_list - AssertEqual {'42': {}}, ale#references#GetMap() + AssertEqual {'42': {'use_relative_paths': 0}}, ale#references#GetMap() + +Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServerResponse): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -relative + + call g:InitCallback() + + AssertEqual {'42': {'use_relative_paths': 1}}, ale#references#GetMap() Given python(Some Python file): foo @@ -228,14 +253,15 @@ Execute(LSP reference responses should be handled): 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': [ - \ ], - \ } - \) + 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 responses with a null result should be handled): + call ale#references#SetMap({3: {}}) + call ale#references#HandleLSPResponse(1, {'id': 3, 'result': v:null}) Assert !g:preview_called AssertEqual {}, ale#references#GetMap() @@ -251,10 +277,10 @@ Execute(LSP reference requests should be sent): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) - AssertEqual 'references', g:capability_checked - call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + AssertEqual 'references', g:capability_checked AssertEqual \ 'function(''ale#references#HandleLSPResponse'')', \ string(g:Callback) @@ -270,10 +296,21 @@ Execute(LSP reference requests should be sent): \ }], \ [0, 'textDocument/references', { \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, - \ 'position': {'line': 0, 'character': 3}, + \ 'position': {'line': 0, 'character': 2}, \ 'context': {'includeDeclaration': v:false}, \ }], \ ], \ g:message_list - AssertEqual {'42': {}}, ale#references#GetMap() + AssertEqual {'42': {'use_relative_paths': 0}}, ale#references#GetMap() + +Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResponse): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEFindReferences -relative + + call g:InitCallback() + + AssertEqual {'42': {'use_relative_paths': 1}}, ale#references#GetMap() diff --git a/test/test_flow_command.vader b/test/test_flow_command.vader index c673ce0a..4805e121 100644 --- a/test/test_flow_command.vader +++ b/test/test_flow_command.vader @@ -1,45 +1,46 @@ Before: runtime ale_linters/javascript/flow.vim - call ale#test#SetDirectory('/testplugin/test') + call ale#assert#SetUpLinterTest('javascript', 'flow') + 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() + call ale#assert#TearDownLinterTest() Execute(flow should return a command to run if a .flowconfig file exists): call ale#test#SetFilename('flow/a/sub/dummy') - AssertEqual + AssertLinter 'flow', \ ale#Escape('flow') - \ . ' check-contents --respect-pragma --json --from ale %s', - \ ale_linters#javascript#flow#GetCommand(bufnr('%'), []) + \ . ' check-contents --respect-pragma --json --from ale %s < %t' + \ . (!has('win32') ? '; echo' : '') 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 + AssertLinter 'flow', \ ale#Escape('flow') - \ . ' check-contents --json --from ale %s', - \ ale_linters#javascript#flow#GetCommand(bufnr('%'), []) + \ . ' check-contents --json --from ale %s < %t' + \ . (!has('win32') ? '; echo' : '') Execute(flow should should not use --respect-pragma for old versions): call ale#test#SetFilename('flow/a/sub/dummy') - AssertEqual + GivenCommandOutput [ + \ 'Warning: `flow --version` is deprecated in favor of `flow version`', + \ 'Flow, a static type checker for JavaScript, version 0.27.0', + \] + AssertLinter 'flow', [ + \ ale#Escape('flow') . ' --version', \ ale#Escape('flow') - \ . ' check-contents --json --from ale %s', - \ ale_linters#javascript#flow#GetCommand(bufnr('%'), [ - \ 'Warning: `flow --version` is deprecated in favor of `flow version`', - \ 'Flow, a static type checker for JavaScript, version 0.27.0', - \ ]) + \ . ' check-contents --json --from ale %s < %t' + \ . (!has('win32') ? '; echo' : ''), + \] Execute(flow should not return a command to run if no .flowconfig file exists): call ale#test#SetFilename('flow/b/sub/dummy') - AssertEqual '', ale_linters#javascript#flow#GetCommand(bufnr('%'), []) + AssertLinterNotExecuted diff --git a/test/test_format_command.vader b/test/test_format_command.vader index 71285efa..15435326 100644 --- a/test/test_format_command.vader +++ b/test/test_format_command.vader @@ -8,28 +8,41 @@ Before: AssertEqual 'dummy.txt', fnamemodify(a:filename, ':t') endfunction + runtime autoload/ale/command.vim + + function! ale#command#CreateTempFile(buffer, temporary_file, input) abort + return !empty(a:temporary_file) + endfunction + After: unlet! g:result unlet! g:match delfunction CheckTempFile + runtime autoload/ale/command.vim + Execute(FormatCommand should do nothing to basic command strings): - AssertEqual ['', 'awesome-linter do something'], ale#command#FormatCommand(bufnr('%'), '', 'awesome-linter do something', 0) + AssertEqual + \ ['', 'awesome-linter do something', 0], + \ ale#command#FormatCommand(bufnr('%'), '', 'awesome-linter do something', 0, v:null) Execute(FormatCommand should handle %%, and ignore other percents): - AssertEqual ['', '% %%d %%f %x %'], ale#command#FormatCommand(bufnr('%'), '', '%% %%%d %%%f %x %', 0) + AssertEqual + \ ['', '% %%d %%f %x %', 0], + \ ale#command#FormatCommand(bufnr('%'), '', '%% %%%d %%%f %x %', 0, v:null) Execute(FormatCommand should convert %s to the current filename): AssertEqual \ [ \ '', - \ 'foo ' . ale#Escape(expand('%:p')) . ' bar ' . ale#Escape(expand('%:p')) + \ 'foo ' . ale#Escape(expand('%:p')) . ' bar ' . ale#Escape(expand('%:p')), + \ 0, \ ], - \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s bar %s', 0) + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s bar %s', 0, v:null) Execute(FormatCommand should convert %t to a new temporary filename): - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0, v:null) call CheckTempFile(g:result[0]) @@ -42,8 +55,22 @@ Execute(FormatCommand should convert %t to a new temporary filename): " The two temporary filenames formatted in should be the same. AssertEqual g:match[1], g:match[2] +Execute(FormatCommand should not convert %t to a new temporary filename when the input is given as v:false): + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0, v:false) + + AssertEqual ['', 'foo %t bar %t', 0], g:result + +Execute(FormatCommand should signal that files are created when temporary files are needed): + AssertEqual + \ 1, + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %t', 0, v:null)[2] + + AssertEqual + \ 0, + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s', 0, v:null)[2] + Execute(FormatCommand should let you combine %s and %t): - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %s', 0) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %s', 0, v:null) call CheckTempFile(g:result[0]) @@ -59,31 +86,31 @@ Execute(FormatCommand should let you combine %s and %t): Execute(FormatCommand should replace %e with the escaped executable): if has('win32') AssertEqual - \ ['', 'foo foo'], - \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0) + \ ['', 'foo foo', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null) AssertEqual - \ ['', '"foo bar"'], - \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0) + \ ['', '"foo bar"', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null) AssertEqual - \ ['', '%e %e'], - \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0) + \ ['', '%e %e', 0], + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null) else AssertEqual - \ ['', '''foo'' ''foo'''], - \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0) + \ ['', '''foo'' ''foo''', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null) AssertEqual - \ ['', '''foo bar'''], - \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0) + \ ['', '''foo bar''', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null) AssertEqual - \ ['', '%e %e'], - \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0) + \ ['', '%e %e', 0], + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null) endif Execute(EscapeCommandPart should escape all percent signs): AssertEqual '%%s %%t %%%% %%s %%t %%%%', ale#engine#EscapeCommandPart('%s %t %% %s %t %%') Execute(EscapeCommandPart should pipe in temporary files appropriately): - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar', 1) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar', 1, v:null) call CheckTempFile(g:result[0]) @@ -91,7 +118,7 @@ Execute(EscapeCommandPart should pipe in temporary files appropriately): Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] AssertEqual ale#Escape(g:result[0]), g:match[1] - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar %t', 1) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar %t', 1, v:null) call CheckTempFile(g:result[0]) diff --git a/test/test_format_temporary_file_creation.vader b/test/test_format_temporary_file_creation.vader index 385af908..10409400 100644 --- a/test/test_format_temporary_file_creation.vader +++ b/test/test_format_temporary_file_creation.vader @@ -13,6 +13,7 @@ Before: let g:ale_echo_cursor = 0 let g:ale_enabled = 1 let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks let g:ale_set_highlights = 0 let g:ale_set_loclist = 0 let g:ale_set_quickfix = 0 @@ -41,6 +42,7 @@ Before: After: Restore + unlet! g:ale_run_synchronously_callbacks unlet! g:output delfunction TestCallback @@ -56,5 +58,6 @@ Execute(ALE should be able to read the %t file): AssertEqual 'foobar', &filetype ALELint + call ale#test#FlushJobs() AssertEqual ['foo', 'bar', 'baz'], g:output diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader index 66c24fb6..3479d7b5 100644 --- a/test/test_go_to_definition.vader +++ b/test/test_go_to_definition.vader @@ -8,27 +8,35 @@ Before: let g:expr_list = [] let g:capability_checked = '' let g:conn_id = v:null - let g:WaitCallback = v:null + let g:InitCallback = v:null runtime autoload/ale/linter.vim + runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim - function! ale#lsp_linter#StartLSP(buffer, linter) abort + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) - return { + if a:linter.lsp is# 'tsserver' + call ale#lsp#MarkConnectionAsTsserver(g:conn_id) + endif + + let l:details = { + \ 'command': 'foobar', \ 'buffer': a:buffer, \ 'connection_id': g:conn_id, \ 'project_root': '/foo/bar', - \ 'language_id': 'python', \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} endfunction - function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort + function! ale#lsp#HasCapability(conn_id, capability) abort let g:capability_checked = a:capability - let g:WaitCallback = a:callback + + return 1 endfunction function! ale#lsp#RegisterCallback(conn_id, callback) abort @@ -55,7 +63,7 @@ After: call ale#linter#Reset() unlet! g:capability_checked - unlet! g:WaitCallback + unlet! g:InitCallback unlet! g:old_filename unlet! g:conn_id unlet! g:Callback @@ -70,8 +78,17 @@ After: Execute(Other messages for the tsserver handler should be ignored): call ale#definition#HandleTSServerResponse(1, {'command': 'foo'}) +Execute(Tagstack should be incremented if supported): + if exists('*gettagstack') && exists('*settagstack') + let original_stack_depth = gettagstack().length + endif + call ale#definition#UpdateTagStack() + if exists('*gettagstack') && exists('*settagstack') + AssertEqual original_stack_depth + 1, gettagstack().length + endif + Execute(Failed definition responses should be handled correctly): - call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleTSServerResponse( \ 1, \ {'command': 'definition', 'request_seq': 3} @@ -79,7 +96,7 @@ 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#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleTSServerResponse( \ 1, \ { @@ -97,7 +114,7 @@ Given typescript(Some typescript file): bazxyzxyzxyz Execute(Other files should be jumped to for definition responses): - call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleTSServerResponse( \ 1, \ { @@ -122,7 +139,7 @@ Execute(Other files should be jumped to for definition responses): AssertEqual {}, ale#definition#GetMap() Execute(Other files should be jumped to for definition responses in tabs too): - call ale#definition#SetMap({3: {'open_in_tab': 1}}) + call ale#definition#SetMap({3: {'open_in': 'tab'}}) call ale#definition#HandleTSServerResponse( \ 1, \ { @@ -146,7 +163,57 @@ Execute(Other files should be jumped to for definition responses in tabs too): AssertEqual [3, 7], getpos('.')[1:2] AssertEqual {}, ale#definition#GetMap() -Execute(tsserver completion requests should be sent): +Execute(Other files should be jumped to for definition responses in splits too): + call ale#definition#SetMap({3: {'open_in': 'horizontal-split'}}) + call ale#definition#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'definition', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [ + \ { + \ 'file': ale#path#Simplify(g:dir . '/completion_dummy_file'), + \ 'start': {'line': 3, 'offset': 7}, + \ }, + \ ], + \ } + \) + + AssertEqual + \ [ + \ 'split +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 7], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Other files should be jumped to for definition responses in vsplits too): + call ale#definition#SetMap({3: {'open_in': 'vertical-split'}}) + call ale#definition#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'definition', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [ + \ { + \ 'file': ale#path#Simplify(g:dir . '/completion_dummy_file'), + \ 'start': {'line': 3, 'offset': 7}, + \ }, + \ ], + \ } + \) + + AssertEqual + \ [ + \ 'vsplit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 7], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(tsserver definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) @@ -155,19 +222,22 @@ Execute(tsserver completion requests should be sent): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) - AssertEqual 'definition', g:capability_checked - call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual - \ [[0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], \ g:message_list - AssertEqual {'42': {'open_in_tab': 0}}, ale#definition#GetMap() + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() -Execute(tsserver tab completion requests should be sent): +Execute(tsserver tab definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) @@ -176,17 +246,20 @@ Execute(tsserver tab completion requests should be sent): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) - AssertEqual 'definition', g:capability_checked - call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual - \ [[0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], \ g:message_list - AssertEqual {'42': {'open_in_tab': 1}}, ale#definition#GetMap() + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() Given python(Some Python file): foo @@ -194,7 +267,7 @@ Given python(Some Python file): bazxyzxyzxyz Execute(Other files should be jumped to for LSP definition responses): - call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleLSPResponse( \ 1, \ { @@ -217,7 +290,7 @@ Execute(Other files should be jumped to for LSP definition responses): 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#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleLSPResponse( \ 1, \ { @@ -239,7 +312,7 @@ Execute(Locations inside the same file should be jumped to without using :edit): 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#SetMap({3: {'open_in': 'tab'}}) call ale#definition#HandleLSPResponse( \ 1, \ { @@ -262,7 +335,7 @@ Execute(Other files should be jumped to in tabs for LSP definition responses): AssertEqual {}, ale#definition#GetMap() Execute(Definition responses with lists should be handled): - call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleLSPResponse( \ 1, \ { @@ -293,12 +366,12 @@ Execute(Definition responses with lists should be handled): AssertEqual {}, ale#definition#GetMap() Execute(Definition responses with null response should be handled): - call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) call ale#definition#HandleLSPResponse(1, {'id': 3, 'result': v:null}) AssertEqual [], g:expr_list -Execute(LSP completion requests should be sent): +Execute(LSP definition requests should be sent): runtime ale_linters/python/pyls.vim let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) @@ -308,10 +381,10 @@ Execute(LSP completion requests should be sent): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) - AssertEqual 'definition', g:capability_checked - call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) @@ -327,14 +400,50 @@ Execute(LSP completion requests should be sent): \ }], \ [0, 'textDocument/definition', { \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, - \ 'position': {'line': 0, 'character': 3}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() + +Execute(LSP type definition requests should be sent): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToTypeDefinition + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'typeDefinition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#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/typeDefinition', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list - AssertEqual {'42': {'open_in_tab': 0}}, ale#definition#GetMap() + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() -Execute(LSP tab completion requests should be sent): +Execute(LSP tab definition requests should be sent): runtime ale_linters/python/pyls.vim let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) @@ -344,10 +453,10 @@ Execute(LSP tab completion requests should be sent): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual type(function('type')), type(g:WaitCallback) - AssertEqual 'definition', g:capability_checked - call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + AssertEqual 'definition', g:capability_checked AssertEqual \ 'function(''ale#definition#HandleLSPResponse'')', \ string(g:Callback) @@ -363,9 +472,45 @@ Execute(LSP tab completion requests should be sent): \ }], \ [0, 'textDocument/definition', { \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, - \ 'position': {'line': 0, 'character': 3}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() + +Execute(LSP tab type definition requests should be sent): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToTypeDefinitionInTab + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'typeDefinition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#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/typeDefinition', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, \ }], \ ], \ g:message_list - AssertEqual {'42': {'open_in_tab': 1}}, ale#definition#GetMap() + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() diff --git a/test/test_highlight_placement.vader b/test/test_highlight_placement.vader index 53dcea06..619a964e 100644 --- a/test/test_highlight_placement.vader +++ b/test/test_highlight_placement.vader @@ -9,6 +9,7 @@ Before: Save g:ale_set_signs let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks let g:ale_set_highlights = 1 let g:ale_set_signs = 1 let g:ale_buffer_info = {} @@ -64,6 +65,7 @@ Before: After: Restore + unlet! g:ale_run_synchronously_callbacks unlet! g:items unlet! b:ale_enabled @@ -81,6 +83,7 @@ Given testft(A Javscript file with warnings/errors): Execute(Highlights should be set when a linter runs): ALELint + call ale#test#FlushJobs() AssertEqual \ [ diff --git a/test/test_history_saving.vader b/test/test_history_saving.vader index d7a307b5..18b64db5 100644 --- a/test/test_history_saving.vader +++ b/test/test_history_saving.vader @@ -2,6 +2,10 @@ Before: Save g:ale_max_buffer_history_size Save g:ale_history_log_output Save g:ale_run_synchronously + Save g:ale_enabled + + let g:ale_enabled = 1 + let g:ale_run_synchronously = 1 unlet! b:ale_fixers unlet! b:ale_enabled @@ -68,27 +72,19 @@ Given foobar (Some imaginary filetype): Execute(History should be set when commands are run): AssertEqual 'foobar', &filetype - let g:expected_results = ['command', 'exit_code', 'job_id', 'status'] - - " Retry this test until it works. This one can randomly fail. - for g:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) - let b:ale_history = [] - call ale#Queue(0) - call ale#engine#WaitForJobs(2000) + let b:ale_history = [] + ALELint + call ale#test#FlushJobs() - let g:history = filter( - \ copy(ale#history#Get(bufnr(''))), - \ 'v:val.job_id isnot# ''executable''', - \) + let g:history = filter( + \ copy(ale#history#Get(bufnr(''))), + \ 'v:val.job_id isnot# ''executable''', + \) - AssertEqual 1, len(g:history) - - if sort(keys(g:history[0])) == g:expected_results - break - endif - endfor - - AssertEqual g:expected_results, sort(keys(g:history[0])) + AssertEqual 1, len(g:history) + AssertEqual + \ ['command', 'exit_code', 'job_id', 'status'], + \ sort(keys(g:history[0])) if has('win32') AssertEqual 'cmd /s/c "echo command history test"', g:history[0].command @@ -106,8 +102,8 @@ Execute(History should be not set when disabled): let g:ale_history_enabled = 0 - call ale#Queue(0) - call ale#engine#WaitForJobs(2000) + ALELint + call ale#test#FlushJobs() AssertEqual [], ale#history#Get(bufnr('')) @@ -115,24 +111,21 @@ Execute(History should include command output if logging is enabled): AssertEqual 'foobar', &filetype let g:ale_history_log_output = 1 - let g:expected_results = ['command history test'] " Retry this test until it works. This one can randomly fail. - for g:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) - let b:ale_history = [] - call ale#Queue(0) - call ale#engine#WaitForJobs(2000) + let b:ale_history = [] + ALELint + call ale#test#FlushJobs() - let g:history = ale#history#Get(bufnr('')) + let g:history = ale#history#Get(bufnr('')) - AssertEqual 1, len(g:history) - - if get(g:history[0], 'output', []) == g:expected_results - break - endif - endfor - - AssertEqual g:expected_results, get(g:history[0], 'output', []) + AssertEqual 1, len(g:history) + AssertEqual + \ ['command history test'], + \ map( + \ copy(get(g:history[0], 'output', [])), + \ 'substitute(v:val, ''[\r ]*$'', '''', ''g'')' + \ ) Execute(History items should be popped after going over the max): let b:ale_history = map(range(20), '{''status'': ''started'', ''job_id'': v:val, ''command'': ''foobar''}') @@ -169,10 +162,13 @@ Execute(The history should be updated when fixers are run): let b:ale_fixers = {'foobar': ['TestFixer']} let b:ale_enabled = 0 - let g:ale_run_synchronously = 1 ALEFix + AssertEqual ['started'], map(copy(b:ale_history), 'v:val.status') + + call ale#test#FlushJobs() + AssertEqual ['finished'], map(copy(b:ale_history), 'v:val.status') if has('win32') diff --git a/test/test_hover.vader b/test/test_hover.vader index a18fc651..917694a2 100644 --- a/test/test_hover.vader +++ b/test/test_hover.vader @@ -15,6 +15,7 @@ Before: let g:Callback = a:callback return { + \ 'command': 'foobar', \ 'connection_id': 347, \ 'project_root': '/foo/bar', \} @@ -149,3 +150,23 @@ Execute(LSP hover response with lists of strings and marked strings should be ha AssertEqual ["foo\nbar\n"], g:echo_list AssertEqual {}, ale#hover#GetMap() + +Execute(tsserver responses for documentation requests should be handled): + call ale#hover#SetMap({3: {'show_documentation': 1}}) + + call ale#hover#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'quickinfo', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'documentation': 'foo is a very good method', + \ 'displayString': 'foo bar', + \ }, + \ } + \) + + " The preview window should show the text. + AssertEqual ['foo is a very good method'], ale#test#GetPreviewWindowText() + silent! pclose diff --git a/test/test_ignoring_linters.vader b/test/test_ignoring_linters.vader index 866f9e0d..2d9c67de 100644 --- a/test/test_ignoring_linters.vader +++ b/test/test_ignoring_linters.vader @@ -30,6 +30,7 @@ Execute(Exclude should ignore some invalid values): \ {'name': 'linter3', 'aliases': []}, \ ], \ 'foo', + \ 0, \ ) AssertEqual \ [ @@ -45,6 +46,7 @@ Execute(Exclude should ignore some invalid values): \ {'name': 'linter3', 'aliases': []}, \ ], \ 0, + \ 0, \ ) AssertEqual \ [ @@ -60,6 +62,7 @@ Execute(Exclude should ignore some invalid values): \ {'name': 'linter3', 'aliases': []}, \ ], \ v:null, + \ 0, \ ) Execute(Exclude should handle Lists): @@ -75,6 +78,7 @@ Execute(Exclude should handle Lists): \ {'name': 'linter3', 'aliases': []}, \ ], \ ['linter1', 'alias1'], + \ 0, \ ) Execute(Exclude should handle Dictionaries): @@ -90,11 +94,51 @@ Execute(Exclude should handle Dictionaries): \ {'name': 'linter3', 'aliases': []}, \ ], \ {'foo': ['linter1'], 'bar': ['alias1']}, + \ 0, + \ ) + +Execute(Exclude should filter LSP linters when g:ale_disable_lsp is set to 1): + let g:ale_disable_lsp = 1 + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': [], 'lsp': ''}, + \ {'name': 'linter2', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo', + \ [ + \ {'name': 'linter1', 'aliases': [], 'lsp': ''}, + \ {'name': 'linter2', 'aliases': []}, + \ {'name': 'linter3', 'aliases': [], 'lsp': 'stdio'}, + \ ], + \ [], + \ 1, + \ ) + +Execute(Exclude should filter LSP linters when b:ale_disable_lsp is set to 1): + let b:ale_disable_lsp = 1 + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': [], 'lsp': ''}, + \ {'name': 'linter2', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo', + \ [ + \ {'name': 'linter1', 'aliases': [], 'lsp': ''}, + \ {'name': 'linter2', 'aliases': []}, + \ {'name': 'linter3', 'aliases': [], 'lsp': 'stdio'}, + \ ], + \ [], + \ 1, \ ) Before: Save g:ale_linters_ignore Save g:ale_buffer_info + Save g:ale_disable_lsp + + let g:ale_disable_lsp = 0 let g:linters = [] let g:loclist = [] @@ -127,6 +171,7 @@ After: unlet! b:ale_linters_ignore unlet! b:ale_quitting unlet! b:ale_save_event_fired + unlet! b:ale_disable_lsp unlet! g:linters unlet! g:loclist unlet! g:lsp_message @@ -242,7 +287,7 @@ Execute(Buffer ignore lists should be applied for LSP linters): \ 'lnum': 1, \ 'col': 10, \ 'type': 'E', - \ 'end_col': 10, + \ 'end_col': 9, \ 'end_lnum': 1, \ 'text': 'x', \ } diff --git a/test/test_jsonlint_executable_detection.vader b/test/test_jsonlint_executable_detection.vader new file mode 100644 index 00000000..de52f846 --- /dev/null +++ b/test/test_jsonlint_executable_detection.vader @@ -0,0 +1,45 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + + runtime ale_linters/json/jsonlint.vim + +After: + let g:ale_json_jsonlint_executable = 'jsonlint' + let g:ale_json_jsonlint_use_global = 0 + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(local executable should be detected correctly): + call ale#test#SetFilename('jsonlint-test-files/app/src/app.json') + + AssertEqual + \ ale#path#Simplify(g:dir . '/jsonlint-test-files/app/node_modules/.bin/jsonlint'), + \ ale_linters#json#jsonlint#GetExecutable(bufnr('')) + +Execute(recursively executable should be detected correctly): + call ale#test#SetFilename('jsonlint-test-files/app-without-jsonlint/src/app.json') + + AssertEqual + \ ale#path#Simplify(g:dir . '/jsonlint-test-files/node_modules/jsonlint/lib/cli.js'), + \ ale_linters#json#jsonlint#GetExecutable(bufnr('')) + +Execute(use_global should override project executable): + let g:ale_json_jsonlint_use_global = 1 + + call ale#test#SetFilename('jsonlint-test-files/app/src/app.json') + + AssertEqual + \ 'jsonlint', + \ ale_linters#json#jsonlint#GetExecutable(bufnr('')) + +Execute(manually defined should override default executable): + let g:ale_json_jsonlint_use_global = 1 + let g:ale_json_jsonlint_executable = 'custom_jsonlint' + + call ale#test#SetFilename('jsonlint-test-files/app/src/app.json') + + AssertEqual + \ 'custom_jsonlint', + \ ale_linters#json#jsonlint#GetExecutable(bufnr('')) + diff --git a/test/test_lint_file_linters.vader b/test/test_lint_file_linters.vader index f67fad44..d16f4aa1 100644 --- a/test/test_lint_file_linters.vader +++ b/test/test_lint_file_linters.vader @@ -8,6 +8,7 @@ Before: let g:ale_buffer_info = {} let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks let g:ale_set_lists_synchronously = 1 let b:ale_save_event_fired = 0 @@ -89,6 +90,7 @@ After: Restore + unlet! g:ale_run_synchronously_callbacks unlet! b:ale_save_event_fired unlet! b:ale_enabled unlet g:buffer_result @@ -111,6 +113,7 @@ Given foobar (Some imaginary filetype): Execute(Running linters without 'lint_file' should run only buffer linters): call ale#Queue(0) + call ale#test#FlushJobs() AssertEqual [ \ { @@ -131,6 +134,7 @@ Execute(Running linters with 'lint_file' should run all linters): Assert filereadable(expand('%:p')), 'The file was not readable' call ale#Queue(0, 'lint_file') + call ale#test#FlushJobs() AssertEqual [ \ { @@ -163,6 +167,7 @@ Execute(Linter errors from files should be kept): Assert filereadable(expand('%:p')), 'The file was not readable' call ale#Queue(0, 'lint_file') + call ale#test#FlushJobs() " Change the results for the buffer callback. let g:buffer_result = [ @@ -175,6 +180,7 @@ Execute(Linter errors from files should be kept): \] call ale#Queue(0) + call ale#test#FlushJobs() AssertEqual [ \ { @@ -202,6 +208,7 @@ Execute(Linter errors from files should be kept when no other linters are run): Assert filereadable(expand('%:p')), 'The file was not readable' call ale#Queue(0, 'lint_file') + call ale#test#FlushJobs() AssertEqual [ \ { @@ -240,11 +247,13 @@ Execute(The Save event should respect the buffer number): Assert filereadable(expand('%:p')), 'The file was not readable' call ale#events#SaveEvent(bufnr('') + 1) + call ale#test#FlushJobs() " We shouldn't get any prblems yet. AssertEqual [], GetSimplerLoclist() call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() " We should get them now we used the right buffer number. AssertEqual [ @@ -268,6 +277,7 @@ Execute(The Save event should set b:ale_save_event_fired to 1): call ale#linter#Reset() call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() " This flag needs to be set so windows can be opened, etc. AssertEqual 1, b:ale_save_event_fired @@ -276,6 +286,7 @@ Execute(b:ale_save_event_fired should be set to 0 when results are set): let b:ale_save_event_fired = 1 call ale#engine#SetResults(bufnr(''), []) + call ale#test#FlushJobs() AssertEqual 0, b:ale_save_event_fired @@ -289,15 +300,18 @@ Execute(lint_file linters should stay running after checking without them): " The lint_file linter should still be running. AssertEqual \ ['lint_file_linter', 'buffer_linter'], - \ g:ale_buffer_info[bufnr('')].active_linter_list + \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name') " We should have 1 job for each linter. - AssertEqual 2, len(g:ale_buffer_info[bufnr('')].job_list) + AssertEqual + \ 2, + \ len(keys(get(get(ale#command#GetData(), bufnr(''), {}), 'jobs', {}))) - call ale#engine#WaitForJobs(2000) + call ale#test#WaitForJobs(2000) Execute(The save event should not lint the buffer when ALE is disabled): let g:ale_enabled = 0 call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual [], GetSimplerLoclist() AssertEqual 0, b:ale_save_event_fired diff --git a/test/test_lint_on_enter_when_file_changed.vader b/test/test_lint_on_enter_when_file_changed.vader index 67f43c17..88493005 100644 --- a/test/test_lint_on_enter_when_file_changed.vader +++ b/test/test_lint_on_enter_when_file_changed.vader @@ -50,6 +50,7 @@ Execute(The file changed event function should set b:ale_file_changed): Execute(The file changed event function should lint the current buffer when it has changed): set filetype=foobar call ale#events#FileChangedEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual [{ \ 'bufnr': bufnr(''), @@ -68,6 +69,7 @@ Execute(The buffer should be checked after entering it after the file has change set filetype=foobar call ale#events#ReadOrEnterEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual [{ \ 'bufnr': bufnr(''), diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index d967761d..cd32ebc8 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -1,4 +1,10 @@ Before: + Save g:ale_lsp_root + Save b:ale_lsp_root + + let g:ale_lsp_root = {} + unlet! b:ale_lsp_root + let g:linter = {} After: @@ -48,7 +54,7 @@ Execute (PreProcess should throw when executable is not a string): \ 'executable': 123, \ 'command': 'echo', \}) - AssertEqual '`executable` must be a string if defined', g:vader_exception + AssertEqual '`executable` must be a String or Function if defined', g:vader_exception Execute (PreProcess should throw when executable_callback is not a callback): AssertThrows call ale#linter#PreProcess('testft', { @@ -59,6 +65,14 @@ Execute (PreProcess should throw when executable_callback is not a callback): \}) AssertEqual '`executable_callback` must be a callback if defined', g:vader_exception +Execute (PreProcess should allow executable to be a callback): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': function('type'), + \ 'command': 'echo', + \}) + Execute (PreProcess should throw when there is no command): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'foo', @@ -74,7 +88,15 @@ Execute (PreProcess should throw when command is not a string): \ 'executable': 'echo', \ 'command': [], \}) - AssertEqual '`command` must be a string if defined', g:vader_exception + AssertEqual '`command` must be a String or Function if defined', g:vader_exception + +Execute (PreProcess should allow command to be a callback): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': function('type'), + \}) Execute (PreProcess should throw when command_callback is not a callback): AssertThrows call ale#linter#PreProcess('testft', { @@ -445,6 +467,18 @@ Execute(PreProcess should complain about using language and language_callback to AssertThrows call ale#linter#PreProcess('testft', g:linter) AssertEqual 'Only one of `language` or `language_callback` should be set', g:vader_exception +Execute(PreProcess should complain about invalid language values): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 0, + \ 'project_root_callback': 'x', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`language` must be a String or Funcref', g:vader_exception + Execute(PreProcess should use the filetype as the language string by default): let g:linter = { \ 'name': 'x', @@ -455,6 +489,17 @@ Execute(PreProcess should use the filetype as the language string by default): AssertEqual 'testft', ale#linter#PreProcess('testft', g:linter).language_callback(0) +Execute(PreProcess should allow language to be set to a callback): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': {-> 'foo'}, + \ 'project_root_callback': 'x', + \} + + AssertEqual 'foo', ale#linter#PreProcess('testft', g:linter).language_callback(0) + Execute(PreProcess should require an address_callback for LSP socket configurations): let g:linter = { \ 'name': 'x', @@ -462,7 +507,7 @@ Execute(PreProcess should require an address_callback for LSP socket configurati \} AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`address_callback` must be defined for getting the LSP address', g:vader_exception + AssertEqual '`address` or `address_callback` must be defined for getting the LSP address', g:vader_exception Execute(PreProcess should complain about address_callback for non-LSP linters): let g:linter = { @@ -474,7 +519,112 @@ Execute(PreProcess should complain about address_callback for non-LSP linters): \} AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`address_callback` cannot be used when lsp != ''socket''', g:vader_exception + AssertEqual '`address` or `address_callback` cannot be used when lsp != ''socket''', g:vader_exception + +Execute(PreProcess accept valid address_callback values): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': {-> 'foo:123'}, + \ 'language': 'x', + \ 'project_root_callback': 'x', + \}) + + AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter) + +Execute(PreProcess accept address as a String): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'foo:123', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \}) + + AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter) + +Execute(PreProcess accept address as a Function): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': {-> 'foo:123'}, + \ 'language': 'x', + \ 'project_root_callback': 'x', + \}) + + AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter) + +Execute(PreProcess should complain about invalid address values): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 0, + \ 'language': 'x', + \ 'project_root_callback': 'x', + \}) + AssertEqual '`address` must be a String or Function if defined', g:vader_exception + +Execute(PreProcess should accept allow the project root be set as a String): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'foo:123', + \ 'language': 'x', + \ 'project_root': '/foo/bar', + \}) + + AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) + +Execute(PreProcess should accept allow the project root be set as a Function): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'foo:123', + \ 'language': 'x', + \ 'project_root': {-> '/foo/bar'}, + \}) + + AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) + +Execute(PreProcess should complain when the project_root valid is invalid): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'foo:123', + \ 'language': 'x', + \ 'project_root': 0, + \}) + AssertEqual '`project_root` must be a String or Function if defined', g:vader_exception + +Execute(PreProcess should accept project_root_callback as a String): + call ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'foo:123', + \ 'language': 'x', + \ 'project_root_callback': 'Foobar', + \}) + +Execute(PreProcess should accept project_root_callback as a Function): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'foo:123', + \ 'language': 'x', + \ 'project_root_callback': {-> '/foo/bar'}, + \}) + + AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) + +Execute(PreProcess should complain when the project_root_callback valid is invalid): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'foo:123', + \ 'language': 'x', + \ 'project_root_callback': 0, + \}) + AssertEqual '`project_root_callback` must be a callback if defined', g:vader_exception Execute(PreProcess should complain about using initialization_options and initialization_options_callback together): let g:linter = { @@ -501,6 +651,41 @@ Execute(PreProcess should throw when initialization_options_callback is not a ca \}) AssertEqual '`initialization_options_callback` must be a callback if defined', g:vader_exception +Execute(PreProcess should throw when initialization_options is not a Dictionary or callback): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'initialization_options': 0, + \}) + AssertEqual '`initialization_options` must be a String or Function if defined', g:vader_exception + +Execute(PreProcess should accept initialization_options as a Dictionary): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'initialization_options': {'foo': v:true}, + \}) + + AssertEqual {'foo': v:true}, ale#lsp_linter#GetOptions(0, g:linter) + +Execute(PreProcess should accept initialization_options as a Funcref): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'initialization_options': {-> {'foo': v:true}}, + \}) + + AssertEqual {'foo': v:true}, ale#lsp_linter#GetOptions(0, g:linter) + Execute(PreProcess should complain about using lsp_config and lsp_config_callback together): let g:linter = { \ 'name': 'x', @@ -527,22 +712,30 @@ Execute(PreProcess should throw when lsp_config_callback is not a callback): AssertEqual '`lsp_config_callback` must be a callback if defined', g:vader_exception Execute(PreProcess should accept LSP configuration options via lsp_config): - let g:ale_lsp_configuration = { - \ 'foo': 'bar' + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language_callback': 'x', + \ 'project_root_callback': 'x', + \ 'lsp_config': {'foo': 'bar'}, \} + AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter) + +Execute(PreProcess should accept LSP configuration options via lsp_config as a function): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', \ 'address_callback': 'X', \ 'language_callback': 'x', \ 'project_root_callback': 'x', - \ 'lsp_config': g:ale_lsp_configuration, + \ 'lsp_config': {-> {'foo': 'bar'}}, \} - AssertEqual {'foo': 'bar'}, ale#linter#PreProcess('testft', g:linter).lsp_config + AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter) -Execute(PreProcess should throw when lsp_config is not a Dictionary): +Execute(PreProcess should throw when lsp_config is not a Dictionary or Function): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'foo', \ 'lsp': 'socket', @@ -551,4 +744,4 @@ Execute(PreProcess should throw when lsp_config is not a Dictionary): \ 'project_root_callback': 'x', \ 'lsp_config': 'x', \}) - AssertEqual '`lsp_config` must be a Dictionary', g:vader_exception + AssertEqual '`lsp_config` must be a Dictionary or Function if defined', g:vader_exception diff --git a/test/test_linter_retrieval.vader b/test/test_linter_retrieval.vader index a1c34622..6f9b3db4 100644 --- a/test/test_linter_retrieval.vader +++ b/test/test_linter_retrieval.vader @@ -2,8 +2,8 @@ Before: Save g:ale_linters Save g:ale_linter_aliases - 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} + let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout', 'read_buffer': 1, 'lint_file': 0, 'aliases': [], 'lsp': ''} + let g:testlinter2 = {'name': 'testlinter2', 'executable': 'testlinter2', 'command': 'testlinter2', 'callback': 'testCB2', 'output_stream': 'stdout', 'read_buffer': 0, 'lint_file': 1, 'aliases': [], 'lsp': ''} call ale#linter#Reset() call ale#linter#PreventLoading('testft') call ale#linter#PreventLoading('javascript') @@ -160,7 +160,7 @@ Execute (Buffer-local overrides for aliases should be used): 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') + AssertEqual [{'name': 'testlinter', 'output_stream': 'stdout', 'executable': 'testlinter', 'command': 'testlinter', 'callback': 'testCB', 'read_buffer': 1, 'lint_file': 0, 'aliases': [], 'lsp': ''}], ale#linter#Get('testft') Execute (Linters for later filetypes should replace the former ones): @@ -178,5 +178,5 @@ Execute (Linters for later filetypes should replace the former ones): \}) AssertEqual [ - \ {'output_stream': 'stdout', 'lint_file': 0, 'read_buffer': 1, 'name': 'eslint', 'executable': 'x', 'lsp': '', 'aliases': [], 'command': 'x', 'callback': 'x', 'add_newline': 0} + \ {'output_stream': 'stdout', 'lint_file': 0, 'read_buffer': 1, 'name': 'eslint', 'executable': 'x', 'lsp': '', 'aliases': [], 'command': 'x', 'callback': 'x'} \], ale#linter#Get('javascript.typescript') diff --git a/test/test_linting_blacklist.vader b/test/test_linting_blacklist.vader index 73190b7f..2bcc9576 100644 --- a/test/test_linting_blacklist.vader +++ b/test/test_linting_blacklist.vader @@ -11,6 +11,6 @@ Given unite (A Unite.vim file): Execute(Running ALE on a blacklisted file shouldn't change anything): call ale#Queue(0) - call ale#engine#WaitForJobs(2000) + call ale#test#WaitForJobs(2000) AssertEqual {}, g:ale_buffer_info diff --git a/test/test_linting_updates_loclist.vader b/test/test_linting_updates_loclist.vader index 921cdb08..8a162703 100644 --- a/test/test_linting_updates_loclist.vader +++ b/test/test_linting_updates_loclist.vader @@ -64,6 +64,7 @@ Given foobar (Some JavaScript with problems): Execute(The loclist should be updated after linting is done): ALELint + call ale#test#FlushJobs() AssertEqual \ [ diff --git a/test/test_loclist_jumping.vader b/test/test_loclist_jumping.vader index da9a1f57..3b6f0688 100644 --- a/test/test_loclist_jumping.vader +++ b/test/test_loclist_jumping.vader @@ -5,23 +5,24 @@ Before: \ {'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': 'W', 'sub_type': 'style', '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': 'W', 'sub_type': 'style', 'bufnr': bufnr(''), 'lnum': 2, 'col': 3}, + \ {'type': 'W', 'bufnr': bufnr(''), 'lnum': 2, 'col': 6}, \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 700}, \ {'type': 'E', 'bufnr': bufnr('') + 1, 'lnum': 3, 'col': 2}, \ ], \ }, \} - function! TestJump(position, wrap, pos) + function! TestJump(position, wrap, filter, subtype_filter, pos) call cursor(a:pos) if type(a:position) == type(0) call ale#loclist_jumping#JumpToIndex(a:position) else - call ale#loclist_jumping#Jump(a:position, a:wrap) + call ale#loclist_jumping#Jump(a:position, a:wrap, a:filter, + \ a:subtype_filter) endif return getcurpos()[1:2] @@ -36,46 +37,61 @@ Given foobar (Some imaginary filetype): 12345678 Execute(loclist jumping should jump correctly when not wrapping): - AssertEqual [2, 1], TestJump('before', 0, [2, 2]) - AssertEqual [1, 3], TestJump('before', 0, [2, 1]) - AssertEqual [2, 3], TestJump('after', 0, [2, 2]) - AssertEqual [2, 1], TestJump('after', 0, [1, 3]) - AssertEqual [2, 6], TestJump('after', 0, [2, 4]) - AssertEqual [2, 8], TestJump('after', 0, [2, 6]) + AssertEqual [2, 1], TestJump('before', 0, 'any', 'any', [2, 2]) + AssertEqual [1, 3], TestJump('before', 0, 'any', 'any', [2, 1]) + AssertEqual [2, 3], TestJump('after', 0, 'any', 'any', [2, 2]) + AssertEqual [2, 1], TestJump('after', 0, 'any', 'any', [1, 3]) + AssertEqual [2, 6], TestJump('after', 0, 'any', 'any', [2, 4]) + AssertEqual [2, 8], TestJump('after', 0, 'any', 'any', [2, 6]) Execute(loclist jumping should jump correctly when wrapping): - AssertEqual [2, 1], TestJump('before', 1, [2, 2]) - AssertEqual [1, 3], TestJump('before', 1, [2, 1]) - AssertEqual [2, 3], TestJump('after', 1, [2, 2]) - AssertEqual [2, 1], TestJump('after', 1, [1, 3]) - AssertEqual [2, 6], TestJump('after', 1, [2, 4]) - - AssertEqual [1, 2], TestJump('after', 1, [2, 8]) - AssertEqual [2, 8], TestJump('before', 1, [1, 2]) + AssertEqual [2, 1], TestJump('before', 1, 'any', 'any', [2, 2]) + AssertEqual [1, 3], TestJump('before', 1, 'any', 'any', [2, 1]) + AssertEqual [2, 3], TestJump('after', 1, 'any', 'any', [2, 2]) + AssertEqual [2, 1], TestJump('after', 1, 'any', 'any', [1, 3]) + AssertEqual [2, 6], TestJump('after', 1, 'any', 'any', [2, 4]) + + AssertEqual [1, 2], TestJump('after', 1, 'any', 'any', [2, 8]) + AssertEqual [2, 8], TestJump('before', 1, 'any', 'any', [1, 2]) + +Execute(loclist jumping should jump correctly with warning filters): + AssertEqual [2, 1], TestJump('after', 0, 'W', 'any', [1, 2]) + AssertEqual [2, 6], TestJump('after', 0, 'W', 'any', [2, 3]) + AssertEqual [2, 1], TestJump('after', 1, 'W', 'any', [2, 6]) + +Execute(loclist jumping should jump correctly with error filters): + AssertEqual [1, 2], TestJump('after', 1, 'E', 'any', [2, 700]) + AssertEqual [2, 2], TestJump('before', 0, 'E', 'any', [2, 700]) + AssertEqual [2, 2], TestJump('after', 1, 'E', 'any', [1, 3]) + +Execute(loclist jumping should jump correctly with sub type filters): + AssertEqual [2, 3], TestJump('after', 0, 'any', 'style', [2, 1]) + AssertEqual [2, 2], TestJump('after', 0, 'any', '', [1, 3]) + AssertEqual [2, 1], TestJump('after', 1, 'any', 'style', [2, 6]) Execute(loclist jumping not jump when the loclist is empty): let g:ale_buffer_info[bufnr('%')].loclist = [] - AssertEqual [1, 6], TestJump('before', 0, [1, 6]) - AssertEqual [1, 6], TestJump('before', 1, [1, 6]) - AssertEqual [1, 6], TestJump('after', 0, [1, 6]) - AssertEqual [1, 6], TestJump('after', 1, [1, 6]) + AssertEqual [1, 6], TestJump('before', 0, 'any', 'any', [1, 6]) + AssertEqual [1, 6], TestJump('before', 1, 'any', 'any', [1, 6]) + AssertEqual [1, 6], TestJump('after', 0, 'any', 'any', [1, 6]) + AssertEqual [1, 6], TestJump('after', 1, 'any', 'any', [1, 6]) Execute(We should be able to jump to the last item): - AssertEqual [2, 8], TestJump(-1, 0, [1, 6]) + AssertEqual [2, 8], TestJump(-1, 0, 'any', 'any', [1, 6]) Execute(We shouldn't move when jumping to the last item where there are none): let g:ale_buffer_info[bufnr('%')].loclist = [] - AssertEqual [1, 6], TestJump(-1, 0, [1, 6]) + AssertEqual [1, 6], TestJump(-1, 0, 'any', 'any', [1, 6]) Execute(We should be able to jump to the first item): - AssertEqual [1, 2], TestJump(0, 0, [1, 6]) + AssertEqual [1, 2], TestJump(0, 0, 'any', 'any', [1, 6]) Execute(We shouldn't move when jumping to the first item where there are none): let g:ale_buffer_info[bufnr('%')].loclist = [] - AssertEqual [1, 6], TestJump(0, 0, [1, 6]) + AssertEqual [1, 6], TestJump(0, 0, 'any', 'any', [1, 6]) Execute(We should be able to jump when the error line is blank): " Add a blank line at the end. @@ -84,7 +100,7 @@ Execute(We should be able to jump when the error line is blank): 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]) - AssertEqual [2, 8], TestJump('before', 1, [3, 1]) - AssertEqual [3, 1], TestJump('after', 0, [3, 1]) - AssertEqual [1, 2], TestJump('after', 1, [3, 1]) + AssertEqual [2, 8], TestJump('before', 0, 'any', 'any', [3, 1]) + AssertEqual [2, 8], TestJump('before', 1, 'any', 'any', [3, 1]) + AssertEqual [3, 1], TestJump('after', 0, 'any', 'any', [3, 1]) + AssertEqual [1, 2], TestJump('after', 1, 'any', 'any', [3, 1]) diff --git a/test/test_no_linting_on_write_quit.vader b/test/test_no_linting_on_write_quit.vader index 12ef38ed..bbd80d8f 100644 --- a/test/test_no_linting_on_write_quit.vader +++ b/test/test_no_linting_on_write_quit.vader @@ -58,6 +58,7 @@ Execute(No linting should be done on :wq or :x): " First try just the SaveEvent, to be sure that we set errors in the test. call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) @@ -65,6 +66,7 @@ Execute(No linting should be done on :wq or :x): call setloclist(0, []) call ale#events#QuitEvent(bufnr('')) call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual [], ale#test#GetLoclistWithoutModule() @@ -73,11 +75,13 @@ Execute(No linting should be for :w after :q fails): let g:ale_fix_on_save = 0 call ale#events#QuitEvent(bufnr('')) + call ale#test#FlushJobs() " Simulate 2 seconds passing. let b:ale_quitting -= 1000 call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) @@ -86,6 +90,7 @@ Execute(No linting should be done on :wq or :x after fixing files): let g:ale_fix_on_save = 1 call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) @@ -93,6 +98,7 @@ Execute(No linting should be done on :wq or :x after fixing files): call setloclist(0, []) call ale#events#QuitEvent(bufnr('')) call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual [], ale#test#GetLoclistWithoutModule() @@ -101,10 +107,12 @@ Execute(Linting should be done after :q fails and fixing files): let g:ale_fix_on_save = 1 call ale#events#QuitEvent(bufnr('')) + call ale#test#FlushJobs() " Simulate 2 seconds passing. let b:ale_quitting -= 1000 call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() AssertEqual 1, len(ale#test#GetLoclistWithoutModule()) diff --git a/test/test_nvim_api_highlight.vader b/test/test_nvim_api_highlight.vader new file mode 100644 index 00000000..829c3ad7 --- /dev/null +++ b/test/test_nvim_api_highlight.vader @@ -0,0 +1,160 @@ +Before: + Save g:ale_enabled + Save g:ale_set_signs + + let g:nvim_buf_clear_namespace_calls = [] + let g:nvim_buf_add_highlight_calls = [] + + call ale#test#SetDirectory('/testplugin/test/highlight') + call ale#test#SetFilename('dummy.txt') + + runtime autoload/ale/highlight.vim + + let g:ale_set_signs = 1 + let g:ale_enabled = 1 + let b:ale_nvim_highlight_id = 42 + let b:ale_highlight_items = [] + + function! ale#highlight#nvim_buf_clear_namespace(...) abort + call add(g:nvim_buf_clear_namespace_calls, a:000) + endfunction + + function! ale#highlight#nvim_buf_add_highlight(...) abort + call add(g:nvim_buf_add_highlight_calls, a:000) + return 42 " returns namespace id + endfunction + +After: + Restore + + unlet! b:ale_enabled + unlet! b:ale_nvim_highlight_id + unlet! b:ale_highlight_items + + unlet! g:nvim_buf_clear_namespace_calls + unlet! g:nvim_buf_add_highlight_calls + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + runtime autoload/ale/highlight.vim + +Given foobar (Some imaginary filetype): + <contents> + +Execute(Check usage of nvim_buf_clear_namespace): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), []) + + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual + \ [[bufnr(''), 42, 0, -1]], + \ g:nvim_buf_clear_namespace_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / single char): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Should highlight a single char by lnum, col + AssertEqual 1, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [[bufnr(''), 42, 'ALEError', 1, 3, 4]], + \ g:nvim_buf_add_highlight_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / single line span): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 2, + \ 'end_col': 10, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Should highlight a span between col and end_col on lnum + AssertEqual 1, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [[bufnr(''), 42, 'ALEError', 1, 3, 10]], + \ g:nvim_buf_add_highlight_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / multiple lines span): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 5, + \ 'end_col': 10, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Should highlight all lines from lnum till end_lnum + AssertEqual 4, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [ + \ [bufnr(''), 42, 'ALEError', 1, 3, -1], + \ [bufnr(''), 42, 'ALEError', 2, 0, -1], + \ [bufnr(''), 42, 'ALEError', 3, 0, -1], + \ [bufnr(''), 42, 'ALEError', 4, 0, 10] + \ ], + \ g:nvim_buf_add_highlight_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / line highights): + let g:ale_set_signs = 0 + + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 5, + \ 'end_col': 10, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Now the last highlight should be put on the entire line + AssertEqual 5, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [ + \ [bufnr(''), 42, 'ALEError', 1, 3, -1], + \ [bufnr(''), 42, 'ALEError', 2, 0, -1], + \ [bufnr(''), 42, 'ALEError', 3, 0, -1], + \ [bufnr(''), 42, 'ALEError', 4, 0, 10], + \ [bufnr(''), 42, 'ALEErrorLine', 1, 0, -1] + \ ], + \ g:nvim_buf_add_highlight_calls + endif diff --git a/test/test_parse_command_args.vader b/test/test_parse_command_args.vader new file mode 100644 index 00000000..0103b967 --- /dev/null +++ b/test/test_parse_command_args.vader @@ -0,0 +1,52 @@ +After: + unlet! b:parse_result + + if exists(':ParseTest') + delcommand ParseTest + endif + +Execute(ale#args#Parse should handle empty input): + AssertEqual + \ [{}, ''], + \ ale#args#Parse([], '') + AssertEqual + \ [{}, ''], + \ ale#args#Parse(['foo', 'bar'], '') + +Execute(ale#args#Parse should parse commands correctly): + AssertEqual + \ [{'foo': '', 'bar': ''}, 'leave these alone'], + \ ale#args#Parse(['foo', 'bar'], '-foo -bar leave these alone') + AssertEqual + \ [{'foo': ''}, 'leave these alone'], + \ ale#args#Parse(['foo', 'bar'], '-foo leave these alone') + +Execute(ale#args#Parse should raise errors for unknown arguments): + AssertThrows call ale#args#Parse(['foo', 'bar'], '-nope leave these alone') + AssertEqual 'Invalid argument: -nope', g:vader_exception + +Execute(ale#args#Parse should stop parsing arguments after --): + AssertEqual + \ [{'foo': ''}, ' --nope leave these alone'], + \ ale#args#Parse(['foo', 'bar'], '-foo -- --nope leave these alone') + AssertEqual + \ [{}, '--'], + \ ale#args#Parse(['foo', 'bar'], '-- --') + AssertEqual + \ [{}, ''], + \ ale#args#Parse(['foo', 'bar'], '--') + +Execute(ale#args#Parse should work for an example command): + command! -nargs=* ParseTest let b:parse_result = ale#args#Parse(['foo', 'bar'], <q-args>) + + ParseTest + AssertEqual [{}, ''], b:parse_result + + ParseTest -foo + AssertEqual [{'foo': ''}, ''], b:parse_result + + ParseTest -foo -bar + AssertEqual [{'foo': '', 'bar': ''}, ''], b:parse_result + + ParseTest -foo -bar leave these alone + AssertEqual [{'foo': '', 'bar': ''}, 'leave these alone'], b:parse_result diff --git a/test/test_path_uri.vader b/test/test_path_uri.vader index a3e68d98..cc2287cb 100644 --- a/test/test_path_uri.vader +++ b/test/test_path_uri.vader @@ -2,8 +2,53 @@ Execute(ale#path#ToURI should work for Windows paths): AssertEqual 'file:///C:/foo/bar/baz.tst', ale#path#ToURI('C:\foo\bar\baz.tst') AssertEqual 'foo/bar/baz.tst', ale#path#ToURI('foo\bar\baz.tst') +Execute(ale#path#FromURI should work for Unix paths): + AssertEqual '/foo/bar/baz.tst', ale#path#FromURI('file:///foo/bar/baz.tst') + AssertEqual '/foo/bar/baz.tst', ale#path#FromURI('file:/foo/bar/baz.tst') + AssertEqual '/foo/bar/baz.tst', ale#path#FromURI('FILE:///foo/bar/baz.tst') + AssertEqual '/foo/bar/baz.tst', ale#path#FromURI('FILE:/foo/bar/baz.tst') + Execute(ale#path#FromURI should work for Windows paths): - AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('file:///C:/foo/bar/baz.tst') + if has('win32') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('file:///C:/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('file:/C:/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromURI('file:///c:/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromURI('file:/c:/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('FILE:///C:/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('FILE:/C:/foo/bar/baz.tst') + else + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromURI('file:///C:/foo/bar/baz.tst') + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromURI('file:/C:/foo/bar/baz.tst') + AssertEqual '/c:/foo/bar/baz.tst', ale#path#FromURI('file:///c:/foo/bar/baz.tst') + AssertEqual '/c:/foo/bar/baz.tst', ale#path#FromURI('file:/c:/foo/bar/baz.tst') + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromURI('FILE:///C:/foo/bar/baz.tst') + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromURI('FILE:/C:/foo/bar/baz.tst') + endif + +Execute(ale#path#FromURI parse Windows paths with a pipe): + if has('win32') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('file:///C|/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('file:/C|/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromURI('file:///c|/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromURI('file:/c|/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('FILE:///C|/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('FILE:/C|/foo/bar/baz.tst') + else + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromURI('file:///C|/foo/bar/baz.tst') + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromURI('file:/C|/foo/bar/baz.tst') + AssertEqual '/c|/foo/bar/baz.tst', ale#path#FromURI('file:///c|/foo/bar/baz.tst') + AssertEqual '/c|/foo/bar/baz.tst', ale#path#FromURI('file:/c|/foo/bar/baz.tst') + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromURI('FILE:///C|/foo/bar/baz.tst') + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromURI('FILE:/C|/foo/bar/baz.tst') + endif + +Execute(ale#path#FromURI should handle the colon for the drive letter being encoded): + " These URIs shouldn't be created, but we'll handle them anyway. + if has('win32') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('file:///C%3A/foo/bar/baz.tst') + else + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromURI('file:///C%3A/foo/bar/baz.tst') + endif Execute(ale#path#ToURI should work for Unix paths): AssertEqual 'file:///foo/bar/baz.tst', ale#path#ToURI('/foo/bar/baz.tst') diff --git a/test/test_prepare_command.vader b/test/test_prepare_command.vader index 75e4c0c6..6a71eaed 100644 --- a/test/test_prepare_command.vader +++ b/test/test_prepare_command.vader @@ -1,6 +1,11 @@ Before: Save &shell Save &shellcmdflag + Save g:ale_shell + Save g:ale_shell_arguments + + unlet! g:ale_shell + unlet! g:ale_shell_arguments After: Restore @@ -43,6 +48,7 @@ Execute(Other shells should be used when set): if !has('win32') let &shell = '/bin/bash' let &shellcmdflag = '-c' + let g:ale_shell = &shell AssertEqual ['/bin/bash', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') endif @@ -54,3 +60,16 @@ Execute(cmd /s/c as a string should be used on Windows): AssertEqual 'cmd /s/c "foobar"', ale#job#PrepareCommand(bufnr(''), 'foobar') endif + +Execute(Setting ale_shell should cause ale#job#PrepareCommand to use set shell): + let g:ale_shell = '/foo/bar' + + if has('win32') + AssertEqual ['/foo/bar', '/c', 'foobar'], ale#job#PrepareCommand(bufnr(''), "foobar") + else + AssertEqual ['/foo/bar', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), "foobar") + endif + + let g:ale_shell_arguments = '-x' + + AssertEqual ['/foo/bar', '-x', 'foobar'], ale#job#PrepareCommand(bufnr(''), "foobar") diff --git a/test/test_python_traceback.vader b/test/test_python_traceback.vader new file mode 100644 index 00000000..6a659986 --- /dev/null +++ b/test/test_python_traceback.vader @@ -0,0 +1,79 @@ +Execute(ale#python#HandleTraceback returns empty List for empty lines): + AssertEqual + \ [], + \ ale#python#HandleTraceback([], 10) + +Execute(ale#python#HandleTraceback returns traceback, when present): + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'Exception: Example error (See :ALEDetail)', + \ 'detail': join([ + \ 'Traceback (most recent call last):', + \ ' File "./example.py", line 5, in <module>', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ ], "\n"), + \ }], + \ ale#python#HandleTraceback([ + \ 'Traceback (most recent call last):', + \ ' File "./example.py", line 5, in <module>', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ ], 1) + +" SyntaxError has extra output lines about the source +Execute(ale#python#HandleTraceback returns SyntaxError traceback): + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'SyntaxError: invalid syntax (See :ALEDetail)', + \ 'detail': join([ + \ 'Traceback (most recent call last):', + \ ' File "<string>", line 1, in <module>', + \ ' File "example.py", line 5', + \ ' +', + \ ' ^', + \ 'SyntaxError: invalid syntax', + \ ], "\n"), + \ }], + \ ale#python#HandleTraceback([ + \ 'Traceback (most recent call last):', + \ ' File "<string>", line 1, in <module>', + \ ' File "example.py", line 5', + \ ' +', + \ ' ^', + \ 'SyntaxError: invalid syntax', + \ ], 1) + +Execute(ale#python#HandleTraceback ignores traceback after line limit): + AssertEqual + \ [], + \ ale#python#HandleTraceback([ + \ '', + \ 'Traceback (most recent call last):', + \ ' File "./example.py", line 5, in <module>', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ ], 1) + +Execute(ale#python#HandleTraceback doesn't include later lines in detail): + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'Exception: Example error (See :ALEDetail)', + \ 'detail': join([ + \ 'Traceback (most recent call last):', + \ ' File "./example.py", line 5, in <module>', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ ], "\n"), + \ }], + \ ale#python#HandleTraceback([ + \ 'Traceback (most recent call last):', + \ ' File "./example.py", line 5, in <module>', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ 'file:1:2: Style issue', + \ 'file:3:4: Non-style issue', + \ ], 1) diff --git a/test/test_redundant_tsserver_rendering_avoided.vader b/test/test_redundant_tsserver_rendering_avoided.vader index a292b014..6e0b2d30 100644 --- a/test/test_redundant_tsserver_rendering_avoided.vader +++ b/test/test_redundant_tsserver_rendering_avoided.vader @@ -42,7 +42,7 @@ Before: runtime autoload/ale/engine.vim - let g:ale_buffer_info = {bufnr(''): {'loclist': []}} + let g:ale_buffer_info = {bufnr(''): {'loclist': [], 'active_linter_list': []}} let g:ale_handle_loclist_called = 0 function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort diff --git a/test/test_sandbox_execution.vader b/test/test_sandbox_execution.vader index 5a4974ba..cf994ce8 100644 --- a/test/test_sandbox_execution.vader +++ b/test/test_sandbox_execution.vader @@ -59,7 +59,7 @@ Execute(ALE shouldn't blow up if file cleanup happens in a sandbox): \ 'temporary_file_list': ['/tmp/foo'], \ 'temporary_directory_list': ['/tmp/bar'], \} - sandbox call ale#engine#RemoveManagedFiles(3) + sandbox call ale#command#RemoveManagedFiles(3) AssertEqual ['/tmp/foo'], g:ale_buffer_info[3].temporary_file_list AssertEqual ['/tmp/bar'], g:ale_buffer_info[3].temporary_directory_list diff --git a/test/test_semver_utils.vader b/test/test_semver_utils.vader index 30e9e818..b38feb06 100644 --- a/test/test_semver_utils.vader +++ b/test/test_semver_utils.vader @@ -1,27 +1,20 @@ After: call ale#semver#ResetVersionCache() -Execute(GetVersion should return the version from the lines of output): +Execute(ParseVersion should return the version from the lines of output): " We should be able to parse the semver string from flake8 - AssertEqual [3, 0, 4], ale#semver#GetVersion('dummy', [ + AssertEqual [3, 0, 4], ale#semver#ParseVersion([ \ '3.0.4 (mccabe: 0.5.2, pyflakes: 1.2.3, pycodestyle: 2.0.0) CPython 2.7.12 on Linux', \ '1.2.3', \]) -Execute(GetVersion should return an empty list when no vesrion can be found): - AssertEqual [], ale#semver#GetVersion('dummy', ['x']) - AssertEqual [], ale#semver#GetVersion('dummy', []) +Execute(ParseVersion should return an empty list when no vesrion can be found): + AssertEqual [], ale#semver#ParseVersion(['x']) + AssertEqual [], ale#semver#ParseVersion([]) -Execute(GetVersion should cache the version): - AssertEqual [], ale#semver#GetVersion('dummy', []) - AssertEqual [3, 4, 7], ale#semver#GetVersion('dummy', ['Version 3.4.7']) - AssertEqual [3, 4, 7], ale#semver#GetVersion('dummy', []) - -Execute(HasVersion should return 1 when the version has been cached): - call ale#semver#GetVersion('dummy', []) - AssertEqual 0, ale#semver#HasVersion('dummy') - call ale#semver#GetVersion('dummy', ['3.4.7']) - AssertEqual 1, ale#semver#HasVersion('dummy') +Execute(ParseVersion should tolerate missing patch numbers): + " This goes against the semver spec, but we handle it anyway. + AssertEqual [3, 4, 0], ale#semver#ParseVersion(['Version 3.4']) Execute(GTE should compare triples correctly): Assert ale#semver#GTE([3, 0, 4], [3, 0, 0]) diff --git a/test/test_shell_detection.vader b/test/test_shell_detection.vader index adb8d70d..88ee462d 100644 --- a/test/test_shell_detection.vader +++ b/test/test_shell_detection.vader @@ -52,11 +52,27 @@ Execute(zsh should be detected appropriately): Given(A file with a csh hash bang and arguments): #!/usr/bin/env csh -eu --foobar -Execute(zsh should be detected appropriately): +Execute(csh should be detected appropriately): AssertEqual 'csh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'csh', ale_linters#sh#shell#GetExecutable(bufnr('')) AssertEqual 'csh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) +Given(A file with a ksh hashbang): + #!/bin/ksh + +Execute(/bin/ksh should be detected appropriately): + AssertEqual 'ksh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'ksh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a ksh as an argument to env): + #!/usr/bin/env ksh + +Execute(ksh should be detected appropriately): + AssertEqual 'ksh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'ksh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + Given(A file with a sh hash bang and arguments): #!/usr/bin/env sh -eu --foobar diff --git a/test/test_should_do_nothing_conditions.vader b/test/test_should_do_nothing_conditions.vader index de2e2782..6dfed555 100644 --- a/test/test_should_do_nothing_conditions.vader +++ b/test/test_should_do_nothing_conditions.vader @@ -35,6 +35,10 @@ After: unlet! b:funky_command_created unlet! b:fake_mode + if &diff is 1 + let &diff = 0 + endif + runtime autoload/ale/util.vim Given foobar(An empty file): @@ -70,6 +74,11 @@ Execute(DoNothing should return 1 when an operator is pending): AssertEqual 1, ale#ShouldDoNothing(bufnr('')) +Execute(DoNothing should return 1 for diff buffers): + let &diff = 1 + + 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 diff --git a/test/test_statusline.vader b/test/test_statusline.vader index d928e7ee..f76cbfa9 100644 --- a/test/test_statusline.vader +++ b/test/test_statusline.vader @@ -28,25 +28,9 @@ Before: return l:res endfunction -After: - Restore - - delfunction Counts - -Execute (Count should be 0 when data is empty): - AssertEqual Counts({}), ale#statusline#Count(bufnr('')) - -Execute (Count should read data from the cache): - let g:ale_buffer_info = {'44': {'count': Counts({'error': 1, 'warning': 2})}} - AssertEqual Counts({'error': 1, 'warning': 2}), ale#statusline#Count(44) - -Execute (The count should be correct after an update): - let g:ale_buffer_info = {'44': {}} - call ale#statusline#Update(44, []) - AssertEqual Counts({}), ale#statusline#Count(44) - -Execute (Count should be match the loclist): - let g:ale_buffer_info = { + " A test simplified loclist that will be used for some of the + " tests in this module. + let g:test_buffer_info = { \ bufnr(''): { \ 'loclist': [ \ {'bufnr': bufnr('') - 1, 'type': 'E'}, @@ -77,6 +61,61 @@ Execute (Count should be match the loclist): \ ], \ }, \} +After: + Restore + + delfunction Counts + unlet g:test_buffer_info + +Execute (Count should be 0 when data is empty): + AssertEqual Counts({}), ale#statusline#Count(bufnr('')) + +Execute (FirstProblem should be 0 when data is empty): + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'error') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'warning') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'style_error') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'style_warning') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'info') + +Execute (Count should read data from the cache): + let g:ale_buffer_info = {'44': {'count': Counts({'error': 1, 'warning': 2})}} + AssertEqual Counts({'error': 1, 'warning': 2}), ale#statusline#Count(44) + +Execute (FirstProblem should read data from the cache): + let g:ale_buffer_info = + \{"44": + \{'count': 0, + \'first_problems': + \{'error': {'lnum': 3}, + \'warning': {'lnum': 44}, + \'style_error': {'lnum': 22}, + \'style_warning': {'lnum': 223}, + \'info': {'lnum': 2} + \} + \} + \} + AssertEqual {'lnum': 3}, ale#statusline#FirstProblem(44, 'error') + AssertEqual {'lnum': 44}, ale#statusline#FirstProblem(44, 'warning') + AssertEqual {'lnum': 223}, ale#statusline#FirstProblem(44, 'style_warning') + AssertEqual {'lnum': 22}, ale#statusline#FirstProblem(44, 'style_error') + AssertEqual {'lnum': 2}, ale#statusline#FirstProblem(44, 'info') + +Execute (The count should be correct after an update): + let g:ale_buffer_info = {'44': {}} + call ale#statusline#Update(44, []) + AssertEqual Counts({}), ale#statusline#Count(44) + +Execute (FirstProblem should be correct after an update): + let g:ale_buffer_info = {'44': {}} + call ale#statusline#Update(44, []) + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'error') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'warning') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'style_error') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'style_warning') + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'info') + +Execute (Count should match the loclist): + let g:ale_buffer_info = g:test_buffer_info AssertEqual { \ 'error': 1, \ 'style_error': 2, @@ -88,8 +127,22 @@ Execute (Count should be match the loclist): \ 'total': 15, \}, ale#statusline#Count(bufnr('')) +Execute (FirstProblem should pull the first matching value from the loclist): + let g:ale_buffer_info = g:test_buffer_info + AssertEqual {'bufnr': bufnr(''), 'type': 'E'}, ale#statusline#FirstProblem(bufnr(''), 'error') + AssertEqual {'bufnr': bufnr(''), 'type': 'W'}, ale#statusline#FirstProblem(bufnr(''), 'warning') + AssertEqual {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, ale#statusline#FirstProblem(bufnr(''), 'style_error') + AssertEqual {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, ale#statusline#FirstProblem(bufnr(''), 'style_warning') + AssertEqual {'bufnr': bufnr(''), 'type': 'I'}, ale#statusline#FirstProblem(bufnr(''), 'info') + Execute (Output should be empty for non-existent buffer): + let g:ale_buffer_info = g:test_buffer_info AssertEqual Counts({}), ale#statusline#Count(9001) + AssertEqual {}, ale#statusline#FirstProblem(9001, 'error') + AssertEqual {}, ale#statusline#FirstProblem(9001, 'warning') + AssertEqual {}, ale#statusline#FirstProblem(9001, 'style_error') + AssertEqual {}, ale#statusline#FirstProblem(9001, 'style_warning') + AssertEqual {}, ale#statusline#FirstProblem(9001, 'info') Execute(ale#statusline#Update shouldn't blow up when globals are undefined): unlet! g:ale_statusline_format @@ -98,3 +151,7 @@ Execute(ale#statusline#Update shouldn't blow up when globals are undefined): Execute(ale#statusline#Count should return 0 counts when globals are undefined): unlet! g:ale_statusline_format AssertEqual Counts({}), ale#statusline#Count(1) + +Execute(FirstProblem should return an empty dict when globals are undefined): + unlet! g:ale_statusline_format + AssertEqual {}, ale#statusline#FirstProblem(bufnr(''), 'info') diff --git a/test/test_swift_find_project_root.vader b/test/test_swift_find_project_root.vader new file mode 100644 index 00000000..7cb1cc29 --- /dev/null +++ b/test/test_swift_find_project_root.vader @@ -0,0 +1,18 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(Detect root of Swift project with Package.swift correctly): + call ale#test#SetFilename('swift-test-files/swift-package-project/src/folder/dummy.swift') + AssertEqual + \ ale#path#Simplify(g:dir . '/swift-test-files/swift-package-project'), + \ ale#swift#FindProjectRoot(bufnr('')) + +Execute(Detect no root in case of non-Package.swift project): + call ale#test#SetFilename('swift-test-files/non-swift-package-project/src/folder/dummy.swift') + AssertEqual + \ '', + \ ale#swift#FindProjectRoot(bufnr('')) + diff --git a/test/test_swiftlint_executable_detection.vader b/test/test_swiftlint_executable_detection.vader index a8e14c84..dfd4930b 100644 --- a/test/test_swiftlint_executable_detection.vader +++ b/test/test_swiftlint_executable_detection.vader @@ -6,7 +6,6 @@ Before: runtime ale_linters/swift/swiftlint.vim After: - let g:ale_has_override = {} let g:ale_swift_swiftlint_executable = 'swiftlint' let g:ale_swift_swiftlint_use_global = 0 diff --git a/test/test_symbol_search.vader b/test/test_symbol_search.vader index d8b7a4a6..053a8b04 100644 --- a/test/test_symbol_search.vader +++ b/test/test_symbol_search.vader @@ -7,30 +7,33 @@ Before: let g:message_list = [] let g:preview_called = 0 let g:item_list = [] + let g:options = {} let g:capability_checked = '' let g:conn_id = v:null - let g:WaitCallback = v:null + let g:InitCallback = v:null runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim runtime autoload/ale/preview.vim - function! ale#lsp_linter#StartLSP(buffer, linter) abort + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) - - return { + let l:details = { \ 'buffer': a:buffer, \ 'connection_id': g:conn_id, \ 'project_root': '/foo/bar', \ 'language_id': 'python', \} + + let g:InitCallback = {-> a:Callback(a:linter, l:details)} endfunction - function! ale#lsp#WaitForCapability(conn_id, capability, callback) abort + function! ale#lsp#HasCapability(conn_id, capability) abort let g:capability_checked = a:capability - let g:WaitCallback = a:callback + + return 1 endfunction function! ale#lsp#RegisterCallback(conn_id, callback) abort @@ -47,9 +50,10 @@ Before: call add(g:expr_list, a:expr) endfunction - function! ale#preview#ShowSelection(item_list) abort + function! ale#preview#ShowSelection(item_list, options) abort let g:preview_called = 1 let g:item_list = a:item_list + let g:options = a:options endfunction After: @@ -57,12 +61,13 @@ After: call ale#linter#Reset() unlet! g:capability_checked - unlet! g:WaitCallback + unlet! g:InitCallback unlet! g:conn_id unlet! g:Callback unlet! g:message_list unlet! g:expr_list unlet! b:ale_linters + unlet! g:options unlet! g:item_list unlet! g:preview_called @@ -156,10 +161,10 @@ Execute(LSP symbol requests should be sent): " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) - AssertEqual 'symbol_search', g:capability_checked - AssertEqual type(function('type')), type(g:WaitCallback) - call call(g:WaitCallback, [g:conn_id, '/foo/bar']) + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + AssertEqual 'symbol_search', g:capability_checked AssertEqual \ 'function(''ale#symbol#HandleLSPResponse'')', \ string(g:Callback) @@ -170,4 +175,15 @@ Execute(LSP symbol requests should be sent): \ ], \ g:message_list - AssertEqual {'42': {'buffer': bufnr('')}}, ale#symbol#GetMap() + AssertEqual {'42': {'buffer': bufnr(''), 'use_relative_paths': 0}}, ale#symbol#GetMap() + +Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResponse): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALESymbolSearch -relative foo bar + + call g:InitCallback() + + AssertEqual {'42': {'buffer': bufnr(''), 'use_relative_paths': 1}}, ale#symbol#GetMap() diff --git a/test/test_temporary_file_management.vader b/test/test_temporary_file_management.vader index 4847706a..9fff1ace 100644 --- a/test/test_temporary_file_management.vader +++ b/test/test_temporary_file_management.vader @@ -13,21 +13,21 @@ Before: " We are registering a temporary file, so we should delete it. let g:filename = tempname() call writefile(['foo'], g:filename) - call ale#engine#ManageFile(a:buffer, g:filename) + call ale#command#ManageFile(a:buffer, g:filename) " We are registering this directory appropriately, so we should delete " the whole thing. let g:directory = tempname() call mkdir(g:directory) call writefile(['foo'], g:directory . '/bar') - call ale#engine#ManageDirectory(a:buffer, g:directory) + call ale#command#ManageDirectory(a:buffer, g:directory) " We are registering this directory as temporary file, so we " shouldn't delete it. let g:preserved_directory = tempname() call mkdir(g:preserved_directory) call writefile(['foo'], g:preserved_directory . '/bar') - call ale#engine#ManageFile(a:buffer, g:preserved_directory) + call ale#command#ManageFile(a:buffer, g:preserved_directory) return g:command endfunction @@ -42,6 +42,7 @@ Before: \ 'callback': 'TestCallback', \ 'command_callback': 'TestCommandCallback', \}) + call ale#command#ClearData() After: Restore @@ -58,6 +59,7 @@ After: delfunction TestCommandCallback delfunction TestCallback call ale#linter#Reset() + call ale#command#ClearData() Given foobar (Some imaginary filetype): foo @@ -68,7 +70,7 @@ Execute(ALE should delete managed files/directories appropriately after linting) AssertEqual 'foobar', &filetype call ale#Queue(0) - call ale#engine#WaitForJobs(2000) + call ale#test#FlushJobs() Assert !filereadable(g:filename), 'The temporary file was not deleted' Assert !isdirectory(g:directory), 'The temporary directory was not deleted' @@ -80,7 +82,7 @@ Execute(ALE should delete managed files even if no command is run): let g:command = '' call ale#Queue(0) - call ale#engine#WaitForJobs(2000) + call ale#test#WaitForJobs(2000) Assert !filereadable(g:filename), 'The temporary file was not deleted' Assert !isdirectory(g:directory), 'The temporary directory was not deleted' @@ -95,11 +97,11 @@ Execute(ALE should delete managed files when the buffer is removed): Assert !isdirectory(g:directory), 'The temporary directory was not deleted' Assert isdirectory(g:preserved_directory), 'The tempoary directory was not kept' -Execute(ALE should create and delete directories for ale#engine#CreateDirectory()): +Execute(ALE should create and delete directories for ale#command#CreateDirectory()): call ale#engine#InitBufferInfo(bufnr('%')) - let b:dir = ale#engine#CreateDirectory(bufnr('%')) - let b:dir2 = ale#engine#CreateDirectory(bufnr('%')) + let b:dir = ale#command#CreateDirectory(bufnr('%')) + let b:dir2 = ale#command#CreateDirectory(bufnr('%')) Assert isdirectory(b:dir), 'The directory was not created' @@ -117,16 +119,28 @@ 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') +Execute(ale#command#ManageFile should add the file even if the buffer info hasn't been set yet): + call ale#command#ManageFile(bufnr(''), '/foo/bar') + AssertEqual - \ ['/foo/bar'], - \ g:ale_buffer_info[bufnr('')].temporary_file_list + \ { + \ bufnr(''): { + \ 'jobs': {}, + \ 'file_list': ['/foo/bar'], + \ 'directory_list': [], + \ }, + \ }, + \ ale#command#GetData() + +Execute(ale#command#ManageDirectory should add the directory even if the buffer info hasn't been set yet): + call ale#command#ManageDirectory(bufnr(''), '/foo/bar') -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 + \ { + \ bufnr(''): { + \ 'jobs': {}, + \ 'file_list': [], + \ 'directory_list': ['/foo/bar'], + \ }, + \ }, + \ ale#command#GetData() diff --git a/test/tex_files/testfile.tex b/test/tex_files/testfile.tex new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/tex_files/testfile.tex @@ -35,3 +35,5 @@ set ttimeoutlen=0 execute 'set encoding=utf-8' let g:mapleader=',' + +let g:ale_ignore_2_4_warnings = 1 |