From 76265755a1add77121c8f9dabb3e9bb70fe9a972 Mon Sep 17 00:00:00 2001 From: "martin f. krafft" Date: Tue, 8 Apr 2025 17:03:36 +0200 Subject: [PATCH] Squashed '.vim/bundle/ale/' content from commit 22185c4c git-subtree-dir: .vim/bundle/ale git-subtree-split: 22185c4c5c8b8ff52c701020c309a4cefc3a04fa --- .appveyor.yml | 62 + .editorconfig | 18 + .gitattributes | 13 + .github/CODE_OF_CONDUCT.md | 3 + .github/CONTRIBUTING.md | 25 + .github/ISSUE_TEMPLATE/config.yml | 6 + .github/ISSUE_TEMPLATE/report-a-bug.md | 48 + .../suggest-a-new-linter-or-fixer.md | 21 + .../ISSUE_TEMPLATE/suggest-an-improvement.md | 8 + .github/PULL_REQUEST_TEMPLATE.md | 13 + .github/stale.yml | 17 + .github/workflows/main.yml | 38 + .gitignore | 12 + .luarc.json | 20 + .vintrc.yaml | 5 + Dockerfile | 32 + LICENSE | 22 + README.md | 903 +++ ale_linters/ada/adals.vim | 26 + ale_linters/ada/cspell.vim | 5 + ale_linters/ada/gcc.vim | 54 + ale_linters/ansible/ansible_lint.vim | 138 + ale_linters/ansible/language_server.vim | 47 + ale_linters/apiblueprint/drafter.vim | 38 + ale_linters/apkbuild/apkbuild_lint.vim | 12 + ale_linters/apkbuild/secfixes_check.vim | 12 + ale_linters/asciidoc/alex.vim | 4 + ale_linters/asciidoc/cspell.vim | 5 + ale_linters/asciidoc/languagetool.vim | 5 + ale_linters/asciidoc/proselint.vim | 9 + ale_linters/asciidoc/redpen.vim | 9 + ale_linters/asciidoc/textlint.vim | 9 + ale_linters/asciidoc/vale.vim | 9 + ale_linters/asciidoc/writegood.vim | 4 + ale_linters/asm/gcc.vim | 37 + ale_linters/asm/llvm_mc.vim | 37 + ale_linters/astro/eslint.vim | 11 + ale_linters/avra/avra.vim | 36 + ale_linters/awk/gawk.vim | 23 + ale_linters/bats/shellcheck.vim | 4 + ale_linters/bib/bibclean.vim | 80 + ale_linters/bicep/az_bicep.vim | 69 + ale_linters/bicep/bicep.vim | 65 + ale_linters/bitbake/oelint_adv.vim | 47 + ale_linters/bzl/buildifier.vim | 40 + ale_linters/c/cc.vim | 67 + ale_linters/c/ccls.vim | 15 + ale_linters/c/clangcheck.vim | 38 + ale_linters/c/clangd.vim | 22 + ale_linters/c/clangtidy.vim | 52 + ale_linters/c/cppcheck.vim | 29 + ale_linters/c/cpplint.vim | 20 + ale_linters/c/cquery.vim | 30 + ale_linters/c/cspell.vim | 5 + ale_linters/c/flawfinder.vim | 25 + ale_linters/c3/c3lsp.vim | 22 + ale_linters/cairo/scarb.vim | 31 + ale_linters/cairo/sierra.vim | 54 + ale_linters/cairo/starknet.vim | 39 + ale_linters/chef/cookstyle.vim | 54 + ale_linters/chef/foodcritic.vim | 41 + ale_linters/clojure/clj_kondo.vim | 47 + ale_linters/clojure/joker.vim | 34 + .../cloudformation/cfn_python_lint.vim | 36 + ale_linters/cmake/cmake_lint.vim | 43 + ale_linters/cmake/cmakelint.vim | 24 + ale_linters/coffee/coffee.vim | 23 + ale_linters/coffee/coffeelint.vim | 43 + ale_linters/cpp/cc.vim | 67 + ale_linters/cpp/ccls.vim | 15 + ale_linters/cpp/clangcheck.vim | 35 + ale_linters/cpp/clangd.vim | 22 + ale_linters/cpp/clangtidy.vim | 53 + ale_linters/cpp/clazy.vim | 32 + ale_linters/cpp/cppcheck.vim | 29 + ale_linters/cpp/cpplint.vim | 20 + ale_linters/cpp/cquery.vim | 30 + ale_linters/cpp/cspell.vim | 5 + ale_linters/cpp/flawfinder.vim | 25 + ale_linters/crystal/ameba.vim | 57 + ale_linters/crystal/crystal.vim | 35 + ale_linters/cs/csc.vim | 90 + ale_linters/cs/cspell.vim | 5 + ale_linters/cs/mcs.vim | 37 + ale_linters/cs/mcsc.vim | 91 + ale_linters/css/cspell.vim | 5 + ale_linters/css/csslint.vim | 18 + ale_linters/css/fecs.vim | 9 + ale_linters/css/stylelint.vim | 20 + ale_linters/css/vscodecss.vim | 16 + ale_linters/cucumber/cucumber.vim | 46 + ale_linters/cuda/clangd.vim | 23 + ale_linters/cuda/nvcc.vim | 46 + ale_linters/cypher/cypher_lint.vim | 26 + ale_linters/d/dls.vim | 22 + ale_linters/d/dmd.vim | 116 + ale_linters/dafny/dafny.vim | 41 + ale_linters/dart/analysis_server.vim | 37 + ale_linters/dart/dart_analyze.vim | 29 + ale_linters/dart/language_server.vim | 20 + ale_linters/desktop/desktop_file_validate.vim | 31 + ale_linters/dockerfile/dockerfile_lint.vim | 76 + ale_linters/dockerfile/dockerlinter.vim | 69 + ale_linters/dockerfile/hadolint.vim | 122 + ale_linters/elixir/credo.vim | 71 + ale_linters/elixir/cspell.vim | 5 + ale_linters/elixir/dialyxir.vim | 34 + ale_linters/elixir/dogma.vim | 39 + ale_linters/elixir/elixir_ls.vim | 22 + ale_linters/elixir/lexical.vim | 19 + ale_linters/elixir/mix.vim | 45 + ale_linters/elm/ls.vim | 41 + ale_linters/elm/make.vim | 242 + ale_linters/erlang/dialyzer.vim | 97 + ale_linters/erlang/elvis.vim | 59 + ale_linters/erlang/erlang_ls.vim | 57 + ale_linters/erlang/erlc.vim | 104 + ale_linters/erlang/syntaxerl.vim | 44 + ale_linters/eruby/erb.vim | 25 + ale_linters/eruby/erblint.vim | 51 + ale_linters/eruby/erubi.vim | 32 + ale_linters/eruby/erubis.vim | 23 + ale_linters/eruby/ruumba.vim | 62 + ale_linters/fish/fish.vim | 67 + ale_linters/fortran/gcc.vim | 72 + ale_linters/fortran/language_server.vim | 20 + ale_linters/fountain/proselint.vim | 9 + ale_linters/fuse/fusionlint.vim | 33 + ale_linters/gitcommit/gitlint.vim | 51 + ale_linters/gleam/gleamlsp.vim | 18 + ale_linters/glimmer/embertemplatelint.vim | 6 + ale_linters/glsl/glslang.vim | 42 + ale_linters/glsl/glslls.vim | 30 + ale_linters/go/bingo.vim | 31 + ale_linters/go/cspell.vim | 5 + ale_linters/go/gobuild.vim | 52 + ale_linters/go/gofmt.vim | 15 + ale_linters/go/golangci_lint.vim | 83 + ale_linters/go/gopls.vim | 39 + ale_linters/go/gosimple.vim | 12 + ale_linters/go/gotype.vim | 24 + ale_linters/go/govet.vim | 21 + ale_linters/go/langserver.vim | 29 + ale_linters/go/revive.vim | 21 + ale_linters/go/staticcheck.vim | 34 + ale_linters/gohtmltmpl/djlint.vim | 12 + ale_linters/graphql/eslint.vim | 10 + ale_linters/graphql/gqlint.vim | 10 + ale_linters/groovy/npmgroovylint.vim | 46 + ale_linters/hack/hack.vim | 22 + ale_linters/hack/hhast.vim | 40 + ale_linters/haml/hamllint.vim | 57 + ale_linters/handlebars/djlint.vim | 12 + ale_linters/handlebars/embertemplatelint.vim | 6 + ale_linters/haskell/cabal_ghc.vim | 20 + ale_linters/haskell/cspell.vim | 5 + ale_linters/haskell/ghc.vim | 18 + ale_linters/haskell/ghc_mod.vim | 19 + ale_linters/haskell/hdevtools.vim | 20 + ale_linters/haskell/hie.vim | 41 + ale_linters/haskell/hlint.vim | 46 + ale_linters/haskell/hls.vim | 66 + ale_linters/haskell/stack_build.vim | 23 + ale_linters/haskell/stack_ghc.vim | 21 + ale_linters/help/alex.vim | 4 + ale_linters/help/cspell.vim | 5 + ale_linters/help/proselint.vim | 9 + ale_linters/help/writegood.vim | 4 + ale_linters/html/alex.vim | 4 + ale_linters/html/angular.vim | 56 + ale_linters/html/cspell.vim | 5 + ale_linters/html/djlint.vim | 14 + ale_linters/html/eslint.vim | 12 + ale_linters/html/fecs.vim | 9 + ale_linters/html/htmlhint.vim | 32 + ale_linters/html/proselint.vim | 9 + ale_linters/html/stylelint.vim | 28 + ale_linters/html/tidy.vim | 70 + ale_linters/html/vscodehtml.vim | 16 + ale_linters/html/writegood.vim | 4 + ale_linters/htmlangular/djlint.vim | 12 + ale_linters/htmldjango/djlint.vim | 12 + ale_linters/hurl/hurlfmt.vim | 69 + ale_linters/idris/idris.vim | 81 + ale_linters/ink/ls.vim | 35 + ale_linters/inko/inko.vim | 33 + ale_linters/ispc/ispc.vim | 45 + ale_linters/java/checkstyle.vim | 73 + ale_linters/java/cspell.vim | 5 + ale_linters/java/eclipselsp.vim | 200 + ale_linters/java/javac.vim | 163 + ale_linters/java/javalsp.vim | 56 + ale_linters/java/pmd.vim | 36 + ale_linters/javascript/biome.vim | 11 + ale_linters/javascript/cspell.vim | 5 + ale_linters/javascript/deno.vim | 11 + ale_linters/javascript/eslint.vim | 11 + ale_linters/javascript/fecs.vim | 10 + ale_linters/javascript/flow.vim | 160 + ale_linters/javascript/flow_ls.vim | 29 + ale_linters/javascript/jscs.vim | 61 + ale_linters/javascript/jshint.vim | 33 + ale_linters/javascript/standard.vim | 32 + ale_linters/javascript/tsserver.vim | 17 + ale_linters/javascript/xo.vim | 9 + ale_linters/jinja/djlint.vim | 12 + ale_linters/json/biome.vim | 10 + ale_linters/json/cspell.vim | 5 + ale_linters/json/eslint.vim | 16 + ale_linters/json/jq.vim | 24 + ale_linters/json/jsonlint.vim | 43 + ale_linters/json/spectral.vim | 14 + ale_linters/json/vscodejson.vim | 32 + ale_linters/json5/eslint.vim | 16 + ale_linters/jsonc/biome.vim | 10 + ale_linters/jsonc/eslint.vim | 16 + ale_linters/jsonnet/jsonnet_lint.vim | 59 + ale_linters/jsonnet/jsonnetfmt.vim | 52 + ale_linters/julia/languageserver.vim | 22 + ale_linters/kotlin/kotlinc.vim | 177 + ale_linters/kotlin/ktlint.vim | 10 + ale_linters/kotlin/languageserver.vim | 30 + ale_linters/less/lessc.vim | 47 + ale_linters/less/stylelint.vim | 21 + ale_linters/llvm/llc.vim | 24 + ale_linters/lua/cspell.vim | 5 + ale_linters/lua/lua_language_server.vim | 15 + ale_linters/lua/luac.vim | 31 + ale_linters/lua/luacheck.vim | 79 + ale_linters/lua/selene.vim | 46 + ale_linters/mail/alex.vim | 4 + ale_linters/mail/languagetool.vim | 5 + ale_linters/mail/proselint.vim | 9 + ale_linters/mail/vale.vim | 9 + ale_linters/make/checkmake.vim | 37 + ale_linters/markdown/alex.vim | 4 + ale_linters/markdown/cspell.vim | 5 + ale_linters/markdown/languagetool.vim | 5 + ale_linters/markdown/markdownlint.vim | 27 + ale_linters/markdown/marksman.vim | 35 + ale_linters/markdown/mdl.vim | 43 + ale_linters/markdown/proselint.vim | 9 + ale_linters/markdown/pymarkdown.vim | 73 + ale_linters/markdown/redpen.vim | 9 + ale_linters/markdown/remark_lint.vim | 48 + ale_linters/markdown/textlint.vim | 9 + ale_linters/markdown/vale.vim | 24 + ale_linters/markdown/writegood.vim | 4 + ale_linters/matlab/mlint.vim | 44 + ale_linters/mercury/mmc.vim | 38 + ale_linters/nasm/nasm.vim | 41 + ale_linters/nim/nimcheck.vim | 79 + ale_linters/nim/nimlsp.vim | 33 + ale_linters/nix/deadnix.vim | 13 + ale_linters/nix/nix.vim | 63 + ale_linters/nix/rnix_lsp.vim | 17 + ale_linters/nix/statix.vim | 18 + ale_linters/nroff/alex.vim | 4 + ale_linters/nroff/proselint.vim | 9 + ale_linters/nroff/writegood.vim | 4 + ale_linters/nunjucks/djlint.vim | 12 + ale_linters/objc/ccls.vim | 15 + ale_linters/objc/clang.vim | 23 + ale_linters/objc/clangd.vim | 17 + ale_linters/objcpp/clang.vim | 23 + ale_linters/objcpp/clangd.vim | 17 + ale_linters/ocaml/merlin.vim | 17 + ale_linters/ocaml/ocamllsp.vim | 13 + ale_linters/ocaml/ols.vim | 15 + ale_linters/ocamlinterface/merlin.vim | 17 + ale_linters/ocamlinterface/ocamllsp.vim | 13 + ale_linters/odin/ols.vim | 19 + ale_linters/openapi/ibm_validator.vim | 58 + ale_linters/openapi/yamllint.vim | 9 + ale_linters/openscad/sca2d.vim | 24 + ale_linters/perl/perl.vim | 64 + ale_linters/perl/perlcritic.vim | 61 + ale_linters/perl6/perl6.vim | 166 + ale_linters/php/cspell.vim | 5 + ale_linters/php/intelephense.vim | 32 + ale_linters/php/langserver.vim | 27 + ale_linters/php/phan.vim | 75 + ale_linters/php/php.vim | 39 + ale_linters/php/phpactor.vim | 23 + ale_linters/php/phpcs.vim | 54 + ale_linters/php/phpmd.vim | 38 + ale_linters/php/phpstan.vim | 106 + ale_linters/php/psalm.vim | 32 + ale_linters/php/tlint.vim | 80 + ale_linters/po/alex.vim | 4 + ale_linters/po/msgfmt.vim | 30 + ale_linters/po/proselint.vim | 9 + ale_linters/po/writegood.vim | 4 + ale_linters/pod/alex.vim | 4 + ale_linters/pod/proselint.vim | 9 + ale_linters/pod/writegood.vim | 4 + ale_linters/pony/ponyc.vim | 16 + ale_linters/powershell/cspell.vim | 5 + ale_linters/powershell/powershell.vim | 100 + ale_linters/powershell/psscriptanalyzer.vim | 76 + ale_linters/prolog/swipl.vim | 110 + ale_linters/proto/buf_lint.vim | 26 + ale_linters/proto/protoc_gen_lint.vim | 27 + ale_linters/proto/protolint.vim | 24 + ale_linters/pug/puglint.vim | 56 + ale_linters/puppet/languageserver.vim | 38 + ale_linters/puppet/puppet.vim | 39 + ale_linters/puppet/puppetlint.vim | 18 + ale_linters/purescript/ls.vim | 50 + ale_linters/pyrex/cython.vim | 36 + ale_linters/python/bandit.vim | 82 + ale_linters/python/cspell.vim | 5 + ale_linters/python/flake8.vim | 176 + ale_linters/python/flakehell.vim | 181 + ale_linters/python/jedils.vim | 51 + ale_linters/python/mypy.vim | 109 + ale_linters/python/prospector.vim | 112 + ale_linters/python/pycln.vim | 92 + ale_linters/python/pycodestyle.vim | 87 + ale_linters/python/pydocstyle.vim | 83 + ale_linters/python/pyflakes.vim | 62 + ale_linters/python/pylama.vim | 160 + ale_linters/python/pylint.vim | 126 + ale_linters/python/pylsp.vim | 67 + ale_linters/python/pyre.vim | 50 + ale_linters/python/pyright.vim | 94 + ale_linters/python/refurb.vim | 79 + ale_linters/python/ruff.vim | 107 + ale_linters/python/unimport.vim | 81 + ale_linters/python/vulture.vim | 97 + ale_linters/qml/qmlfmt.vim | 25 + ale_linters/qml/qmllint.vim | 29 + ale_linters/r/languageserver.vim | 28 + ale_linters/r/lintr.vim | 35 + ale_linters/racket/langserver.vim | 7 + ale_linters/racket/raco.vim | 34 + ale_linters/reason/ls.vim | 24 + ale_linters/reason/merlin.vim | 17 + ale_linters/reason/ols.vim | 15 + ale_linters/rego/cspell.vim | 4 + ale_linters/rego/opacheck.vim | 56 + ale_linters/review/redpen.vim | 9 + ale_linters/robot/rflint.vim | 46 + ale_linters/rst/alex.vim | 4 + ale_linters/rst/cspell.vim | 5 + ale_linters/rst/proselint.vim | 9 + ale_linters/rst/redpen.vim | 9 + ale_linters/rst/rstcheck.vim | 31 + ale_linters/rst/textlint.vim | 9 + ale_linters/rst/vale.vim | 9 + ale_linters/rst/writegood.vim | 4 + ale_linters/ruby/brakeman.vim | 51 + ale_linters/ruby/cspell.vim | 5 + ale_linters/ruby/debride.vim | 42 + ale_linters/ruby/packwerk.vim | 55 + ale_linters/ruby/rails_best_practices.vim | 49 + ale_linters/ruby/reek.vim | 69 + ale_linters/ruby/rubocop.vim | 31 + ale_linters/ruby/ruby.vim | 12 + ale_linters/ruby/solargraph.vim | 22 + ale_linters/ruby/sorbet.vim | 26 + ale_linters/ruby/standardrb.vim | 23 + ale_linters/ruby/steep.vim | 172 + ale_linters/rust/analyzer.vim | 37 + ale_linters/rust/cargo.vim | 110 + ale_linters/rust/cspell.vim | 5 + ale_linters/rust/rls.vim | 27 + ale_linters/rust/rustc.vim | 33 + ale_linters/salt/salt_lint.vim | 33 + ale_linters/sass/sasslint.vim | 28 + ale_linters/sass/stylelint.vim | 14 + ale_linters/scala/cspell.vim | 5 + ale_linters/scala/fsc.vim | 14 + ale_linters/scala/metals.vim | 50 + ale_linters/scala/sbtserver.vim | 31 + ale_linters/scala/scalac.vim | 15 + ale_linters/scala/scalastyle.vim | 86 + ale_linters/scss/sasslint.vim | 28 + ale_linters/scss/scsslint.vim | 34 + ale_linters/scss/stylelint.vim | 20 + ale_linters/sh/bashate.vim | 43 + ale_linters/sh/cspell.vim | 5 + ale_linters/sh/language_server.vim | 32 + ale_linters/sh/shell.vim | 54 + ale_linters/sh/shellcheck.vim | 4 + ale_linters/slim/slimlint.vim | 55 + ale_linters/sml/smlnj.vim | 9 + ale_linters/sml/smlnj_cm.vim | 21 + ale_linters/solidity/solc.vim | 53 + ale_linters/solidity/solhint.vim | 83 + ale_linters/solidity/solium.vim | 9 + ale_linters/spec/rpmlint.vim | 90 + ale_linters/sql/sqlfluff.vim | 108 + ale_linters/sql/sqlint.vim | 28 + ale_linters/sql/sqllint.vim | 33 + ale_linters/stylus/stylelint.vim | 21 + ale_linters/sugarss/stylelint.vim | 22 + ale_linters/svelte/svelteserver.vim | 21 + ale_linters/swift/appleswiftformat.vim | 43 + ale_linters/swift/cspell.vim | 5 + ale_linters/swift/sourcekitlsp.vim | 14 + ale_linters/swift/swiftlint.vim | 69 + ale_linters/systemd/systemd_analyze.vim | 18 + ale_linters/tcl/nagelfar.vim | 39 + ale_linters/terraform/checkov.vim | 41 + ale_linters/terraform/terraform.vim | 69 + ale_linters/terraform/terraform_ls.vim | 39 + ale_linters/terraform/terraform_lsp.vim | 25 + ale_linters/terraform/tflint.vim | 105 + ale_linters/terraform/tfsec.vim | 87 + ale_linters/testft/testlinter.vim | 10 + ale_linters/tex/alex.vim | 4 + ale_linters/tex/chktex.vim | 64 + ale_linters/tex/cspell.vim | 5 + ale_linters/tex/lacheck.vim | 43 + ale_linters/tex/proselint.vim | 9 + ale_linters/tex/redpen.vim | 9 + ale_linters/tex/texlab.vim | 26 + ale_linters/tex/textlint.vim | 9 + ale_linters/tex/vale.vim | 9 + ale_linters/tex/writegood.vim | 4 + ale_linters/texinfo/alex.vim | 4 + ale_linters/texinfo/cspell.vim | 5 + ale_linters/texinfo/proselint.vim | 9 + ale_linters/texinfo/writegood.vim | 4 + ale_linters/text/alex.vim | 4 + ale_linters/text/cspell.vim | 5 + ale_linters/text/languagetool.vim | 4 + ale_linters/text/proselint.vim | 9 + ale_linters/text/redpen.vim | 9 + ale_linters/text/textlint.vim | 9 + ale_linters/text/vale.vim | 9 + ale_linters/text/writegood.vim | 4 + ale_linters/thrift/thrift.vim | 87 + ale_linters/thrift/thriftcheck.vim | 46 + ale_linters/typescript/biome.vim | 11 + ale_linters/typescript/cspell.vim | 5 + ale_linters/typescript/deno.vim | 12 + ale_linters/typescript/eslint.vim | 10 + ale_linters/typescript/standard.vim | 31 + ale_linters/typescript/tslint.vim | 75 + ale_linters/typescript/tsserver.vim | 18 + ale_linters/typescript/typecheck.vim | 33 + ale_linters/typescript/xo.vim | 6 + ale_linters/v/v.vim | 73 + ale_linters/vala/vala_lint.vim | 66 + ale_linters/verilog/hdl_checker.vim | 5 + ale_linters/verilog/iverilog.vim | 44 + ale_linters/verilog/slang.vim | 53 + ale_linters/verilog/verilator.vim | 60 + ale_linters/verilog/vlog.vim | 52 + ale_linters/verilog/xvlog.vim | 35 + ale_linters/verilog/yosys.vim | 42 + ale_linters/vhdl/ghdl.vim | 37 + ale_linters/vhdl/hdl_checker.vim | 5 + ale_linters/vhdl/vcom.vim | 38 + ale_linters/vhdl/xvhdl.vim | 37 + ale_linters/vim/ale_custom_linting_rules.vim | 70 + ale_linters/vim/vimls.vim | 61 + ale_linters/vim/vint.vim | 65 + ale_linters/vue/cspell.vim | 5 + ale_linters/vue/vls.vim | 23 + ale_linters/vue/volar.vim | 59 + ale_linters/wgsl/naga.vim | 12 + ale_linters/xhtml/alex.vim | 4 + ale_linters/xhtml/cspell.vim | 5 + ale_linters/xhtml/proselint.vim | 9 + ale_linters/xhtml/writegood.vim | 4 + ale_linters/xml/xmllint.vim | 65 + ale_linters/yaml/actionlint.vim | 87 + ale_linters/yaml/circleci.vim | 35 + ale_linters/yaml/gitlablint.vim | 49 + ale_linters/yaml/ls.vim | 35 + ale_linters/yaml/spectral.vim | 14 + ale_linters/yaml/swaglint.vim | 40 + ale_linters/yaml/yamllint.vim | 11 + ale_linters/yaml/yq.vim | 22 + ale_linters/yang/yang_lsp.vim | 15 + ale_linters/yara/yls.vim | 18 + ale_linters/zeek/zeek.vim | 23 + ale_linters/zig/zlint.vim | 30 + ale_linters/zig/zls.vim | 20 + autoload/ale.vim | 299 + autoload/ale/ant.vim | 45 + autoload/ale/args.vim | 43 + autoload/ale/assert.vim | 427 ++ autoload/ale/balloon.vim | 74 + autoload/ale/c.vim | 622 ++ autoload/ale/code_action.vim | 379 ++ autoload/ale/codefix.vim | 491 ++ autoload/ale/command.vim | 473 ++ autoload/ale/completion.vim | 1068 ++++ autoload/ale/completion/python.vim | 3 + autoload/ale/cursor.vim | 191 + autoload/ale/d.vim | 16 + autoload/ale/debugging.vim | 368 ++ autoload/ale/definition.vim | 305 + autoload/ale/dhall.vim | 24 + autoload/ale/engine.vim | 763 +++ autoload/ale/engine/ignore.vim | 97 + autoload/ale/events.vim | 253 + autoload/ale/filename_mapping.vim | 22 + autoload/ale/filerename.vim | 133 + autoload/ale/filetypes.vim | 58 + autoload/ale/fix.vim | 399 ++ autoload/ale/fix/registry.vim | 931 +++ autoload/ale/fixers/alejandra.vim | 13 + autoload/ale/fixers/apkbuild_fixer.vim | 19 + autoload/ale/fixers/appleswiftformat.vim | 16 + autoload/ale/fixers/astyle.vim | 59 + autoload/ale/fixers/autoflake.vim | 46 + autoload/ale/fixers/autoimport.vim | 45 + autoload/ale/fixers/autopep8.vim | 44 + autoload/ale/fixers/bibclean.vim | 15 + autoload/ale/fixers/biome.vim | 12 + autoload/ale/fixers/black.vim | 61 + autoload/ale/fixers/brittany.vim | 22 + autoload/ale/fixers/buf_format.vim | 12 + autoload/ale/fixers/buildifier.vim | 26 + autoload/ale/fixers/clangformat.vim | 47 + autoload/ale/fixers/clangtidy.vim | 52 + autoload/ale/fixers/cljfmt.vim | 14 + autoload/ale/fixers/cmakeformat.vim | 16 + autoload/ale/fixers/crystal.vim | 14 + autoload/ale/fixers/css_beautify.vim | 20 + autoload/ale/fixers/dart_format.vim | 18 + autoload/ale/fixers/dartfmt.vim | 18 + autoload/ale/fixers/deno.vim | 17 + autoload/ale/fixers/dfmt.vim | 18 + autoload/ale/fixers/dhall_format.vim | 11 + autoload/ale/fixers/dhall_freeze.vim | 14 + autoload/ale/fixers/dhall_lint.vim | 11 + autoload/ale/fixers/djlint.vim | 48 + autoload/ale/fixers/dotnet_format.vim | 18 + autoload/ale/fixers/dprint.vim | 29 + autoload/ale/fixers/dune.vim | 16 + autoload/ale/fixers/elm_format.vim | 23 + autoload/ale/fixers/erbformatter.vim | 13 + autoload/ale/fixers/erblint.vim | 40 + autoload/ale/fixers/erlang_mode.vim | 50 + autoload/ale/fixers/erlfmt.vim | 19 + autoload/ale/fixers/eslint.vim | 83 + autoload/ale/fixers/fecs.vim | 17 + autoload/ale/fixers/fish_indent.vim | 19 + autoload/ale/fixers/fixjson.vim | 28 + autoload/ale/fixers/floskell.vim | 20 + autoload/ale/fixers/forge.vim | 11 + autoload/ale/fixers/fourmolu.vim | 20 + autoload/ale/fixers/generic.vim | 25 + autoload/ale/fixers/generic_python.vim | 75 + autoload/ale/fixers/gleam_format.vim | 19 + autoload/ale/fixers/gnatpp.vim | 17 + autoload/ale/fixers/gofmt.vim | 16 + autoload/ale/fixers/gofumpt.vim | 17 + autoload/ale/fixers/goimports.vim | 23 + autoload/ale/fixers/golangci_lint.vim | 32 + autoload/ale/fixers/golines.vim | 21 + autoload/ale/fixers/gomod.vim | 11 + autoload/ale/fixers/google_java_format.vim | 23 + autoload/ale/fixers/gopls.vim | 23 + autoload/ale/fixers/hackfmt.vim | 18 + autoload/ale/fixers/help.vim | 24 + autoload/ale/fixers/hfmt.vim | 16 + autoload/ale/fixers/hindent.vim | 20 + autoload/ale/fixers/hlint.vim | 13 + autoload/ale/fixers/html_beautify.vim | 20 + autoload/ale/fixers/htmlbeautifier.vim | 13 + autoload/ale/fixers/hurlfmt.vim | 15 + autoload/ale/fixers/importjs.vim | 25 + autoload/ale/fixers/isort.vim | 77 + autoload/ale/fixers/jq.vim | 22 + autoload/ale/fixers/json_pytool.vim | 20 + autoload/ale/fixers/jsonnetfmt.vim | 18 + autoload/ale/fixers/ktlint.vim | 8 + autoload/ale/fixers/kulala_fmt.vim | 11 + autoload/ale/fixers/latexindent.vim | 16 + autoload/ale/fixers/lua_format.vim | 16 + autoload/ale/fixers/luafmt.vim | 13 + autoload/ale/fixers/mix_format.vim | 25 + autoload/ale/fixers/nickel_format.vim | 16 + autoload/ale/fixers/nimpretty.vim | 15 + autoload/ale/fixers/nixfmt.vim | 15 + autoload/ale/fixers/nixpkgsfmt.vim | 12 + autoload/ale/fixers/npmgroovylint.vim | 16 + autoload/ale/fixers/ocamlformat.vim | 17 + autoload/ale/fixers/ocp_indent.vim | 18 + autoload/ale/fixers/opafmt.vim | 15 + autoload/ale/fixers/ormolu.vim | 12 + autoload/ale/fixers/packer.vim | 17 + autoload/ale/fixers/pandoc.vim | 16 + autoload/ale/fixers/perltidy.vim | 18 + autoload/ale/fixers/pgformatter.vim | 12 + autoload/ale/fixers/php_cs_fixer.vim | 26 + autoload/ale/fixers/phpcbf.vim | 26 + autoload/ale/fixers/pint.vim | 25 + autoload/ale/fixers/prettier.vim | 125 + autoload/ale/fixers/prettier_eslint.vim | 56 + autoload/ale/fixers/prettier_standard.vim | 24 + autoload/ale/fixers/protolint.vim | 26 + autoload/ale/fixers/ptop.vim | 17 + autoload/ale/fixers/puppetlint.vim | 22 + autoload/ale/fixers/purs_tidy.vim | 24 + autoload/ale/fixers/purty.vim | 22 + autoload/ale/fixers/pycln.vim | 96 + autoload/ale/fixers/pyflyby.vim | 47 + autoload/ale/fixers/qmlfmt.vim | 11 + autoload/ale/fixers/raco_fmt.vim | 15 + autoload/ale/fixers/refmt.vim | 18 + autoload/ale/fixers/remark_lint.vim | 24 + .../ale/fixers/reorder_python_imports.vim | 43 + autoload/ale/fixers/rubocop.vim | 38 + autoload/ale/fixers/rubyfmt.vim | 16 + autoload/ale/fixers/ruff.vim | 100 + autoload/ale/fixers/ruff_format.vim | 78 + autoload/ale/fixers/rufo.vim | 20 + autoload/ale/fixers/rustfmt.vim | 15 + autoload/ale/fixers/rustywind.vim | 17 + autoload/ale/fixers/scadformat.vim | 15 + autoload/ale/fixers/scalafmt.vim | 25 + autoload/ale/fixers/shfmt.vim | 17 + autoload/ale/fixers/sorbet.vim | 19 + autoload/ale/fixers/sqlfluff.vim | 25 + autoload/ale/fixers/sqlfmt.vim | 13 + autoload/ale/fixers/sqlformat.vim | 16 + autoload/ale/fixers/standard.vim | 33 + autoload/ale/fixers/standardrb.vim | 23 + autoload/ale/fixers/statix.vim | 17 + autoload/ale/fixers/stylelint.vim | 26 + autoload/ale/fixers/styler.vim | 16 + autoload/ale/fixers/stylish_haskell.vim | 21 + autoload/ale/fixers/stylua.vim | 27 + autoload/ale/fixers/swiftformat.vim | 25 + autoload/ale/fixers/syntax_tree.vim | 18 + autoload/ale/fixers/terraform.vim | 17 + autoload/ale/fixers/textlint.vim | 15 + autoload/ale/fixers/tidy.vim | 26 + autoload/ale/fixers/tslint.vim | 22 + autoload/ale/fixers/typstyle.vim | 20 + autoload/ale/fixers/uncrustify.vim | 33 + autoload/ale/fixers/vfmt.vim | 13 + autoload/ale/fixers/xmllint.vim | 29 + autoload/ale/fixers/xo.vim | 36 + autoload/ale/fixers/yamlfix.vim | 25 + autoload/ale/fixers/yamlfmt.vim | 20 + autoload/ale/fixers/yapf.vim | 44 + autoload/ale/fixers/yq.vim | 22 + autoload/ale/fixers/zigfmt.vim | 14 + autoload/ale/floating_preview.vim | 221 + autoload/ale/go.vim | 58 + autoload/ale/gradle.vim | 74 + autoload/ale/gradle/init.gradle | 23 + autoload/ale/handlers/alex.vim | 55 + autoload/ale/handlers/atools.vim | 41 + autoload/ale/handlers/biome.vim | 58 + autoload/ale/handlers/c3lsp.vim | 19 + autoload/ale/handlers/cairo.vim | 41 + autoload/ale/handlers/ccls.vim | 26 + autoload/ale/handlers/cppcheck.vim | 89 + autoload/ale/handlers/cpplint.vim | 21 + autoload/ale/handlers/cspell.vim | 78 + autoload/ale/handlers/css.vim | 70 + autoload/ale/handlers/deadnix.vim | 33 + autoload/ale/handlers/deno.vim | 87 + autoload/ale/handlers/djlint.vim | 64 + autoload/ale/handlers/elixir.vim | 28 + autoload/ale/handlers/embertemplatelint.vim | 66 + autoload/ale/handlers/eslint.vim | 260 + autoload/ale/handlers/fecs.vim | 52 + autoload/ale/handlers/flawfinder.vim | 48 + autoload/ale/handlers/gawk.vim | 27 + autoload/ale/handlers/gcc.vim | 176 + autoload/ale/handlers/go.vim | 29 + autoload/ale/handlers/haskell.vim | 119 + autoload/ale/handlers/haskell_stack.vim | 7 + autoload/ale/handlers/hdl_checker.vim | 73 + autoload/ale/handlers/hlint.vim | 8 + autoload/ale/handlers/inko.vim | 37 + autoload/ale/handlers/ktlint.vim | 45 + autoload/ale/handlers/languagetool.vim | 77 + autoload/ale/handlers/markdownlint.vim | 24 + autoload/ale/handlers/naga.vim | 30 + autoload/ale/handlers/ocamllsp.vim | 30 + autoload/ale/handlers/ols.vim | 26 + autoload/ale/handlers/openscad.vim | 73 + autoload/ale/handlers/pony.vim | 33 + autoload/ale/handlers/redpen.vim | 65 + autoload/ale/handlers/ruby.vim | 38 + autoload/ale/handlers/rust.vim | 78 + autoload/ale/handlers/scala.vim | 37 + autoload/ale/handlers/sh.vim | 37 + autoload/ale/handlers/shellcheck.vim | 198 + autoload/ale/handlers/sml.vim | 102 + autoload/ale/handlers/spectral.vim | 31 + autoload/ale/handlers/statix.vim | 24 + autoload/ale/handlers/textlint.vim | 39 + autoload/ale/handlers/tslint.vim | 13 + autoload/ale/handlers/tsserver.vim | 8 + autoload/ale/handlers/unix.vim | 26 + autoload/ale/handlers/vale.vim | 39 + autoload/ale/handlers/writegood.vim | 72 + autoload/ale/handlers/xo.vim | 44 + autoload/ale/handlers/yamllint.vim | 39 + autoload/ale/highlight.vim | 222 + autoload/ale/history.vim | 62 + autoload/ale/hover.vim | 380 ++ autoload/ale/java.vim | 26 + autoload/ale/job.vim | 385 ++ autoload/ale/julia.vim | 19 + autoload/ale/linter.vim | 448 ++ autoload/ale/list.vim | 274 + autoload/ale/loclist_jumping.vim | 163 + autoload/ale/lsp.vim | 851 +++ autoload/ale/lsp/message.vim | 216 + autoload/ale/lsp/reset.vim | 92 + autoload/ale/lsp/response.vim | 152 + autoload/ale/lsp/tsserver_message.vim | 165 + autoload/ale/lsp_linter.vim | 653 ++ autoload/ale/lsp_window.vim | 58 + autoload/ale/lua.vim | 29 + autoload/ale/maven.vim | 57 + autoload/ale/node.vim | 22 + autoload/ale/organize_imports.vim | 63 + autoload/ale/other_source.vim | 21 + autoload/ale/path.vim | 257 + autoload/ale/pattern_options.vim | 47 + autoload/ale/powershell.vim | 32 + autoload/ale/preview.vim | 137 + autoload/ale/python.vim | 206 + autoload/ale/racket.vim | 12 + autoload/ale/references.vim | 188 + autoload/ale/rename.vim | 204 + autoload/ale/ruby.vim | 83 + autoload/ale/semver.vim | 78 + autoload/ale/sign.vim | 496 ++ autoload/ale/socket.vim | 151 + autoload/ale/statusline.vim | 135 + autoload/ale/swift.vim | 70 + autoload/ale/symbol.vim | 110 + autoload/ale/test.vim | 205 + autoload/ale/toggle.vim | 101 + autoload/ale/uri.vim | 43 + autoload/ale/uri/jdt.vim | 112 + autoload/ale/util.vim | 585 ++ autoload/ale/virtualtext.vim | 325 + autoload/asyncomplete/sources/ale.vim | 26 + doc/ale-ada.txt | 87 + doc/ale-ansible.txt | 48 + doc/ale-apkbuild.txt | 71 + doc/ale-asciidoc.txt | 24 + doc/ale-asm.txt | 54 + doc/ale-astro.txt | 16 + doc/ale-avra.txt | 30 + doc/ale-awk.txt | 31 + doc/ale-bats.txt | 13 + doc/ale-bazel.txt | 39 + doc/ale-bib.txt | 26 + doc/ale-bicep.txt | 54 + doc/ale-bitbake.txt | 40 + doc/ale-c.txt | 569 ++ doc/ale-c3.txt | 41 + doc/ale-cairo.txt | 38 + doc/ale-chef.txt | 56 + doc/ale-clojure.txt | 55 + doc/ale-cloudformation.txt | 45 + doc/ale-cmake.txt | 78 + doc/ale-cpp.txt | 480 ++ doc/ale-cs.txt | 273 + doc/ale-css.txt | 108 + doc/ale-cuda.txt | 62 + doc/ale-d.txt | 39 + doc/ale-dafny.txt | 20 + doc/ale-dart.txt | 157 + doc/ale-desktop.txt | 26 + doc/ale-development.txt | 546 ++ doc/ale-dhall.txt | 57 + doc/ale-dockerfile.txt | 115 + doc/ale-elixir.txt | 146 + doc/ale-elm.txt | 128 + doc/ale-erlang.txt | 232 + doc/ale-eruby.txt | 100 + doc/ale-fish.txt | 39 + doc/ale-fortran.txt | 68 + doc/ale-fountain.txt | 6 + doc/ale-fuse.txt | 30 + doc/ale-gitcommit.txt | 52 + doc/ale-gleam.txt | 36 + doc/ale-glsl.txt | 67 + doc/ale-go.txt | 398 ++ doc/ale-gohtmltmpl.txt | 11 + doc/ale-graphql.txt | 22 + doc/ale-groovy.txt | 49 + doc/ale-hack.txt | 61 + doc/ale-handlebars.txt | 43 + doc/ale-haskell.txt | 305 + doc/ale-hcl.txt | 16 + doc/ale-help.txt | 12 + doc/ale-html.txt | 289 + doc/ale-htmlangular.txt | 12 + doc/ale-htmldjango.txt | 12 + doc/ale-http.txt | 20 + doc/ale-hurl.txt | 20 + doc/ale-idris.txt | 30 + doc/ale-ink.txt | 46 + doc/ale-inko.txt | 21 + doc/ale-ispc.txt | 30 + doc/ale-java.txt | 339 ++ doc/ale-javascript.txt | 433 ++ doc/ale-jinja.txt | 12 + doc/ale-json.txt | 252 + doc/ale-json5.txt | 15 + doc/ale-jsonc.txt | 21 + doc/ale-jsonnet.txt | 54 + doc/ale-julia.txt | 23 + doc/ale-kotlin.txt | 157 + doc/ale-latex.txt | 21 + doc/ale-less.txt | 80 + doc/ale-llvm.txt | 21 + doc/ale-lua.txt | 175 + doc/ale-make.txt | 22 + doc/ale-markdown.txt | 229 + doc/ale-mercury.txt | 30 + doc/ale-nasm.txt | 30 + doc/ale-nickel.txt | 30 + doc/ale-nim.txt | 51 + doc/ale-nix.txt | 151 + doc/ale-nroff.txt | 12 + doc/ale-nunjucks.txt | 12 + doc/ale-objc.txt | 94 + doc/ale-objcpp.txt | 50 + doc/ale-ocaml.txt | 151 + doc/ale-odin.txt | 30 + doc/ale-openapi.txt | 81 + doc/ale-openscad.txt | 54 + doc/ale-packer.txt | 30 + doc/ale-pascal.txt | 30 + doc/ale-pawn.txt | 12 + doc/ale-perl.txt | 110 + doc/ale-perl6.txt | 49 + doc/ale-php.txt | 451 ++ doc/ale-po.txt | 12 + doc/ale-pod.txt | 12 + doc/ale-pony.txt | 30 + doc/ale-powershell.txt | 86 + doc/ale-prolog.txt | 71 + doc/ale-proto.txt | 122 + doc/ale-pug.txt | 51 + doc/ale-puppet.txt | 71 + doc/ale-purescript.txt | 93 + doc/ale-pyrex.txt | 30 + doc/ale-python.txt | 2258 +++++++ doc/ale-qml.txt | 20 + doc/ale-r.txt | 81 + doc/ale-racket.txt | 49 + doc/ale-reasonml.txt | 90 + doc/ale-rego.txt | 58 + doc/ale-rest.txt | 12 + doc/ale-restructuredtext.txt | 33 + doc/ale-robot.txt | 18 + doc/ale-ruby.txt | 346 ++ doc/ale-rust.txt | 369 ++ doc/ale-salt.tmt | 47 + doc/ale-sass.txt | 36 + doc/ale-scala.txt | 146 + doc/ale-scss.txt | 80 + doc/ale-sh.txt | 169 + doc/ale-sml.txt | 39 + doc/ale-solidity.txt | 58 + doc/ale-spec.txt | 48 + doc/ale-sql.txt | 109 + doc/ale-stylus.txt | 40 + doc/ale-sugarss.txt | 40 + doc/ale-supported-languages-and-tools.txt | 769 +++ doc/ale-svelte.txt | 36 + doc/ale-swift.txt | 71 + doc/ale-systemd.txt | 14 + doc/ale-tcl.txt | 30 + doc/ale-terraform.txt | 167 + doc/ale-tex.txt | 115 + doc/ale-texinfo.txt | 18 + doc/ale-text.txt | 55 + doc/ale-thrift.txt | 84 + doc/ale-toml.txt | 12 + doc/ale-typescript.txt | 332 + doc/ale-typst.html | 29 + doc/ale-v.txt | 53 + doc/ale-vala.txt | 36 + doc/ale-verilog.txt | 176 + doc/ale-vhdl.txt | 177 + doc/ale-vim-help.txt | 12 + doc/ale-vim.txt | 93 + doc/ale-vue.txt | 82 + doc/ale-wgsl.txt | 20 + doc/ale-xhtml.txt | 18 + doc/ale-xml.txt | 40 + doc/ale-yaml.txt | 474 ++ doc/ale-yang.txt | 20 + doc/ale-yara.txt | 25 + doc/ale-zeek.txt | 26 + doc/ale-zig.txt | 70 + doc/ale.txt | 5355 +++++++++++++++++ ftplugin/ale-fix-suggest.vim | 5 + ftplugin/ale-info.vim | 22 + ftplugin/ale-preview-selection.vim | 31 + ftplugin/ale-preview.vim | 5 + lspconfig.vim | 3 + lua/ale/diagnostics.lua | 95 + lua/ale/init.lua | 168 + lua/ale/lsp.lua | 171 + lua/ale/util.lua | 14 + plugin/ale.vim | 399 ++ rplugin/python3/deoplete/sources/ale.py | 62 + run-tests | 317 + run-tests.bat | 30 + supported-tools.md | 775 +++ syntax/ale-fix-suggest.vim | 13 + syntax/ale-info.vim | 30 + syntax/ale-preview-selection.vim | 11 + test-files/python/no_uv/whatever.py | 0 test/.config/nvim/init.vim | 1 + test/completion/test_ale_import_command.vader | 568 ++ test/completion/test_complete_events.vader | 35 + test/completion/test_completion_events.vader | 486 ++ .../test_completion_filtering.vader | 142 + .../completion/test_completion_prefixes.vader | 65 + .../test_lsp_completion_messages.vader | 310 + .../test_lsp_completion_parsing.vader | 736 +++ .../completion/test_omnifunc_completion.vader | 60 + .../test_public_completion_api.vader | 47 + .../test_tsserver_completion_parsing.vader | 311 + test/fix/test_ale_fix.vader | 884 +++ test/fix/test_ale_fix_aliases.vader | 5 + test/fix/test_ale_fix_completion.vader | 23 + test/fix/test_ale_fix_completion_filter.vader | 14 + test/fix/test_ale_fix_ignore.vader | 110 + test/fix/test_ale_fix_suggest.vader | 102 + .../test_alejandra_fixer_callback.vader | 24 + .../fixers/test_apkbuild_fixer_callback.vader | 20 + ...test_appleswiftformat_fixer_callback.vader | 47 + test/fixers/test_astyle_fixer_callback.vader | 96 + .../test_autoflake_fixer_callback.vader | 69 + .../test_autoimport_fixer_callback.vader | 77 + .../fixers/test_autopep8_fixer_callback.vader | 68 + .../fixers/test_bibclean_fixer_callback.vader | 30 + test/fixers/test_biome_fixer_callback.vader | 43 + test/fixers/test_black_fixer_callback.vader | 94 + ...est_break_up_long_lines_python_fixer.vader | 39 + .../fixers/test_brittany_fixer_callback.vader | 24 + .../test_buf_format_fixer_callback.vader | 21 + .../test_buildifier_fixer_callback.vader | 29 + .../test_clangformat_fixer_callback.vader | 64 + .../test_clangtidy_fixer_callback.vader | 47 + test/fixers/test_cljfmt_fixer_callback.vader | 12 + .../test_cmakeformat_fixer_callback.vader | 36 + .../test_crystal_format_fixer_callback.vader | 33 + .../test_css_beautify_fixer_callback.vader | 12 + .../test_dart_format_fixer_callback.vader | 40 + test/fixers/test_dartfmt_fixer_callback.vader | 40 + test/fixers/test_dfmt_fixer_callback.vader | 40 + .../test_dhall_format_fixer_callback.vader | 22 + .../test_dhall_freeze_fixer_callback.vader | 22 + .../test_dhall_lint_fixer_callback.vader | 20 + test/fixers/test_djlint_fixer_callback.vader | 98 + .../test_dotnet_format_fixer_callback.vader | 41 + test/fixers/test_dprint_fixer_callback.vader | 44 + test/fixers/test_dune_fixer_callback.vader | 36 + .../test_elm_format_fixer_callback.vader | 74 + .../test_erbformatter_fixer_callback.vader | 19 + test/fixers/test_erblint_fixer_callback.vader | 55 + .../test_erlang_mode_fixer_callback.vader | 72 + test/fixers/test_erlfmt_fixer_callback.vader | 34 + test/fixers/test_eslint_fixer_callback.vader | 321 + test/fixers/test_fecs_fixer_callback.vader | 24 + .../test_fish_indent_fixer_callback.vader | 40 + test/fixers/test_fixjson_fixer_callback.vader | 50 + .../fixers/test_floskell_fixer_callback.vader | 23 + test/fixers/test_forge_fixer_callback.vader | 15 + .../fixers/test_fourmolu_fixer_callback.vader | 29 + .../test_gleam_format_fixer_callback.vader | 28 + test/fixers/test_gnatpp_fixer_callback.vader | 28 + test/fixers/test_gofmt_fixer_callback.vader | 50 + test/fixers/test_gofumpt_fixer.vader | 27 + .../test_goimports_fixer_callback.vader | 57 + .../test_golangci_lint_fixer_callback.vader | 48 + test/fixers/test_golines_fixer_callback.vader | 54 + test/fixers/test_gomod_fixer_callback.vader | 41 + ...st_goofle_java_format_fixer_callback.vader | 27 + test/fixers/test_gopls_fixer_callback.vader | 57 + test/fixers/test_hackfmt_fixer_callback.vader | 37 + test/fixers/test_hfmt_fixer_callback.vader | 24 + test/fixers/test_hindent_fixer_callback.vader | 18 + test/fixers/test_hlint_fixer_callback.vader | 20 + .../test_html_beautify_fixer_callback.vader | 12 + .../test_htmlbeautifier_fixer_callback.vader | 19 + test/fixers/test_hurlfmt_fixer_callback.vader | 23 + .../fixers/test_importjs_fixer_callback.vader | 35 + test/fixers/test_isort_fixer_callback.vader | 83 + test/fixers/test_jq_fixer_callback.vader | 26 + .../test_jsonnetfmt_fixer_callback.vader | 38 + test/fixers/test_ktlint_fixer_callback.vader | 42 + .../test_kulala_fmt_fixer_callback.vader | 23 + .../test_latexindent_fixer_callback.vader | 36 + .../test_lua_format_fixer_callback.vader | 35 + test/fixers/test_luafmt_fixer_callback.vader | 35 + .../test_mix_format_fixer_callback.vader | 36 + .../test_nickel_format_fixer_callback.vader | 27 + .../test_nimpretty_fixer_callback.vader | 23 + test/fixers/test_nixfmt_fixer_callback.vader | 24 + .../test_nixpkgsfmt_fixer_callback.vader | 24 + .../test_npmgroovylint_fixer_callback.vader | 23 + .../test_ocamlformat_fixer_callback.vader | 36 + .../test_ocp_indent_fixer_callback.vader | 34 + test/fixers/test_opa_fmt_fixer_callback.vader | 33 + test/fixers/test_ormolu_fixer_callback.vader | 24 + .../test_packer_fmt_fixer_callback.vader | 34 + test/fixers/test_pandoc_fixer_callback.vader | 23 + .../fixers/test_perltidy_fixer_callback.vader | 40 + .../test_pgformatter_fixer_callback.vader | 24 + test/fixers/test_php_cs_fixer.vader | 66 + test/fixers/test_phpcbf_fixer_callback.vader | 117 + test/fixers/test_pint_fixer.vader | 62 + .../test_prettier_eslint_fixer.callback.vader | 97 + .../fixers/test_prettier_fixer_callback.vader | 351 ++ .../test_prettier_standard_callback.vader | 15 + .../test_protolint_fixer_callback.vader | 28 + test/fixers/test_ptop_fixer_callback.vader | 38 + .../test_puppetlint_fixer_callback.vader | 24 + .../test_purs_tidy_fixer_callback.vader | 20 + test/fixers/test_purty_fixer_callback.vader | 24 + test/fixers/test_pycln_fixer_callback.vader | 154 + test/fixers/test_pyflyby_fixer_callback.vader | 49 + .../test_python_add_blank_lines_fixer.vader | 167 + test/fixers/test_qmlfmt_fixer_callback.vader | 12 + .../fixers/test_raco_fmt_fixer_callback.vader | 17 + test/fixers/test_refmt_fixer_callback.vader | 41 + .../test_remark_lint_fixer_callback.vader | 24 + ...eorder_python_imports_fixer_callback.vader | 73 + test/fixers/test_rubocop_fixer_callback.vader | 89 + test/fixers/test_rubyfmt_fixer_callback.vader | 26 + test/fixers/test_ruff_fixer_callback.vader | 150 + .../test_ruff_format_fixer_callback.vader | 99 + test/fixers/test_rufo_fixer_callback.vader | 30 + test/fixers/test_rustfmt_fixer_callback.vader | 16 + .../test_rustywind_fixer_callback.vader | 36 + test/fixers/test_scadformat_fixer.vader | 22 + .../fixers/test_scalafmt_fixer_callback.vader | 66 + test/fixers/test_shfmt_fixer_callback.vader | 27 + test/fixers/test_sorbet_fixer_callback.vader | 38 + test/fixers/test_sqlfmt_fixer_callback.vader | 26 + .../test_sqlformat_fixer_callback.vader | 24 + .../fixers/test_standard_fixer_callback.vader | 31 + .../test_standardrb_fixer_callback.vader | 51 + test/fixers/test_statix_fixer.vader | 18 + .../test_stylelint_fixer_callback.vader | 34 + test/fixers/test_styler_fixer_callback.vader | 21 + .../test_stylish_haskell_fixer_callback.vader | 24 + test/fixers/test_stylua_fixer_callback.vader | 38 + .../test_swiftformat_fixer_callback.vader | 35 + .../test_syntax_tree_fixer_callback.vader | 35 + .../test_terraform_fmt_fixer_callback.vader | 34 + .../fixers/test_textlint_fixer_callback.vader | 42 + test/fixers/test_tidy_fixer_callback.vader | 25 + test/fixers/test_trim_whitespace.vader | 28 + test/fixers/test_tslint_fixer_callback.vader | 42 + .../fixers/test_typstyle_fixer_callback.vader | 21 + .../test_uncrustify_fixer_callback.vader | 108 + test/fixers/test_vfmt_fixer_callback.vader | 44 + .../test_vim_help_tags_alignment_fixer.vader | 19 + test/fixers/test_xmllint_fixer_callback.vader | 56 + test/fixers/test_xo_fixer_callback.vader | 45 + test/fixers/test_xots_fixer_callback.vader | 45 + test/fixers/test_yamlfix_fixer_callback.vader | 33 + test/fixers/test_yamlfmt_fixer_callback.vader | 12 + test/fixers/test_yapf_fixer_callback.vader | 61 + test/fixers/test_zigfmt_fixer_callback.vader | 20 + test/handler/test_actionlint_handler.vader | 43 + test/handler/test_ada_gcc_handler.vader | 36 + test/handler/test_alex_handler.vader | 54 + test/handler/test_ameba_handler.vader | 44 + test/handler/test_ansible_lint_handler.vader | 170 + .../test_appleswiftformat_handler.vader | 28 + test/handler/test_asm_handler.vader | 25 + test/handler/test_atools_handler.vader | 85 + test/handler/test_avra_handler.vader | 24 + test/handler/test_bandit_handler.vader | 42 + test/handler/test_bashate_handler.vader | 36 + test/handler/test_bibclean_handler.vader | 86 + .../handler/test_bicep_az_bicep_handler.vader | 39 + test/handler/test_bicep_bicep_handler.vader | 30 + .../test_bitbake_oelint_adv_handler.vader | 28 + test/handler/test_brakeman_handler.vader | 83 + test/handler/test_buildifier_handler.vader | 26 + .../test_cfn_python_lint_handler.vader | 33 + test/handler/test_checkmake_handler.vader | 21 + test/handler/test_checkov_handler.vader | 66 + test/handler/test_checkstyle_handler.vader | 53 + test/handler/test_circleci_handler.vader | 39 + test/handler/test_clang_handler.vader | 30 + .../test_clojure_clj_kondo_handler.vader | 89 + test/handler/test_clojure_joker_handler.vader | 75 + test/handler/test_cmake_lint_handler.vader | 28 + test/handler/test_coffeelint_handler.vader | 20 + test/handler/test_common_handlers.vader | 181 + test/handler/test_cookstyle_handler.vader | 21 + test/handler/test_cppcheck_handler.vader | 93 + test/handler/test_cpplint_handler.vader | 28 + test/handler/test_credo_handler.vader | 53 + test/handler/test_crystal_handler.vader | 28 + test/handler/test_csc_handler.vader | 98 + test/handler/test_cspell_handler.vader | 14 + test/handler/test_cucumber_handler.vader | 18 + test/handler/test_cuda_nvcc_handler.vader | 41 + test/handler/test_cypher_lint_handler.vader | 21 + test/handler/test_dafny_handler.vader | 36 + test/handler/test_dart_analyze_handler.vader | 35 + test/handler/test_deadnix_handler.vader | 25 + test/handler/test_debride_handler.vader | 27 + .../test_desktop_file_validate_handler.vader | 26 + test/handler/test_djlint_handler.vader | 21 + test/handler/test_dmd_handler.vader | 41 + .../test_dockerfile_lint_handler.vader | 112 + test/handler/test_dockerlinter_handler.vader | 77 + test/handler/test_dogma_handler.vader | 29 + test/handler/test_drafter_handler.vader | 37 + test/handler/test_elmmake_handler.vader | 299 + .../test_embertemplatelint_handler.vader | 81 + test/handler/test_erblint_handler.vader | 64 + .../test_erlang_dialyzer_handler.vader | 21 + test/handler/test_erlang_elvis_handler.vader | 40 + test/handler/test_eslint_handler.vader | 438 ++ test/handler/test_eslint_json_handler.vader | 376 ++ test/handler/test_fecs_handler.vader | 35 + test/handler/test_fish_handler.vader | 61 + test/handler/test_flake8_handler.vader | 276 + test/handler/test_flakehell_handler.vader | 276 + test/handler/test_flawfinder_handler.vader | 47 + test/handler/test_flow_handler.vader | 507 ++ test/handler/test_foodcritic_handler.vader | 44 + test/handler/test_fortran_handler.vader | 32 + test/handler/test_gawk_handler.vader | 38 + test/handler/test_gcc_handler.vader | 312 + test/handler/test_ghc_handler.vader | 180 + test/handler/test_ghc_mod_handler.vader | 37 + test/handler/test_ghdl_handler.vader | 26 + test/handler/test_gitlablint_handler.vader | 34 + test/handler/test_gitlint_handler.vader | 89 + test/handler/test_glslang_handler.vader | 48 + test/handler/test_go_generic_handler.vader | 38 + test/handler/test_gobuild_handler.vader | 45 + test/handler/test_golangci_lint_handler.vader | 146 + test/handler/test_hadolint.vader | 59 + test/handler/test_haskell_stack_handler.vader | 7 + test/handler/test_hlint_handler.vader | 80 + test/handler/test_hurlfmt_handler.vader | 29 + .../test_ibm_openapi_validator_handler.vader | 49 + test/handler/test_idris_handler.vader | 66 + test/handler/test_inko_handler.vader | 54 + test/handler/test_ispc_ispc_handler.vader | 90 + test/handler/test_javac_handler.vader | 97 + test/handler/test_jq_handler.vader | 30 + test/handler/test_jscs_handler.vader | 39 + test/handler/test_ktlint_handler.vader | 21 + test/handler/test_lacheck_handler.vader | 34 + test/handler/test_languagetool_handler.vader | 62 + test/handler/test_lessc_handler.vader | 69 + test/handler/test_llc_handler.vader | 58 + test/handler/test_llvm_mc_handler.vader | 27 + test/handler/test_lua_selene_handler.vader | 38 + test/handler/test_luac_handler.vader | 35 + test/handler/test_luacheck_handler.vader | 62 + test/handler/test_markdownlint_handler.vader | 34 + test/handler/test_mcs_handler.vader | 37 + test/handler/test_mcsc_handler.vader | 88 + test/handler/test_mdl_handler.vader | 25 + test/handler/test_mercury_mmc_handler.vader | 58 + test/handler/test_mix_handler.vader | 20 + test/handler/test_msgfmt_hander.vader | 24 + test/handler/test_mypy_handler.vader | 141 + test/handler/test_naga_handler.vader | 23 + test/handler/test_nagelfar_handler.vader | 174 + test/handler/test_nasm_handler.vader | 30 + test/handler/test_nim_handler.vader | 80 + test/handler/test_nix_handler.vader | 117 + test/handler/test_npmgroovylint_handler.vader | 63 + test/handler/test_openscad_handler.vader | 76 + test/handler/test_packwerk_handler.vader | 29 + test/handler/test_perl6_handler.vader | 277 + test/handler/test_perl_handler.vader | 109 + test/handler/test_perlcritic_handler.vader | 19 + test/handler/test_php_handler.vader | 93 + test/handler/test_php_phan_handler.vader | 26 + test/handler/test_php_phpmd_handler.vader | 24 + test/handler/test_phpcs_handler.vader | 28 + test/handler/test_phpstan_handler.vader | 49 + test/handler/test_pmd_handler.vader | 42 + test/handler/test_pony_handler.vader | 21 + test/handler/test_powershell_handler.vader | 109 + test/handler/test_prospector_handler.vader | 161 + .../test_psscriptanalyzer_handler.vader | 42 + test/handler/test_puglint_handler.vader | 45 + test/handler/test_puppet_handler.vader | 77 + test/handler/test_pycodestyle_handler.vader | 153 + test/handler/test_pydocstyle_handler.vader | 116 + test/handler/test_pyflakes_handler.vader | 24 + test/handler/test_pylama_handler.vader | 268 + test/handler/test_pylint_handler.vader | 135 + test/handler/test_pyrex_cython_handler.vader | 26 + test/handler/test_qmlfmt_handler.vader | 19 + test/handler/test_qmllint_handler.vader | 19 + test/handler/test_raco_handler.vader | 27 + .../test_rails_best_practices_handler.vader | 52 + test/handler/test_redpen_handler.vader | 98 + test/handler/test_reek_handler.vader | 85 + test/handler/test_remark_lint_handler.vader | 39 + test/handler/test_rflint_handler.vader | 32 + test/handler/test_rpmlint_handler.vader | 33 + test/handler/test_rstcheck_lint_handler.vader | 42 + test/handler/test_rubocop_handler.vader | 63 + test/handler/test_ruby_handler.vader | 38 + test/handler/test_ruff_handler.vader | 43 + test/handler/test_rust_handler.vader | 491 ++ test/handler/test_salt_salt_lint.vader | 34 + test/handler/test_scala_handler.vader | 29 + test/handler/test_scalastyle_handler.vader | 53 + test/handler/test_scarb_handler.vader | 20 + test/handler/test_shell_handler.vader | 177 + test/handler/test_shellcheck_handler.vader | 152 + test/handler/test_sierra_handler.vader | 20 + test/handler/test_slang_handler.vader | 26 + test/handler/test_slim_handler.vader | 33 + test/handler/test_sml_handler.vader | 119 + test/handler/test_solc_handler.vader | 34 + test/handler/test_solhint_handler.vader | 83 + test/handler/test_spectral_handler.vader | 52 + test/handler/test_sql_sqlfluff_handler.vader | 83 + test/handler/test_sqlint_handler.vader | 34 + test/handler/test_sqllint_handler.vader | 23 + test/handler/test_standard_handler.vader | 37 + test/handler/test_starknet_handler.vader | 36 + test/handler/test_statix_handler.vader | 16 + test/handler/test_steep_handler.vader | 100 + test/handler/test_stylelint_handler.vader | 43 + test/handler/test_swaglint_handler.vader | 68 + test/handler/test_swiftlint_handler.vader | 30 + test/handler/test_swipl_handler.vader | 155 + test/handler/test_syntaxerl_handler.vader | 24 + .../test_systemd_analyze_handler.vader | 19 + test/handler/test_terraform_handler.vader | 138 + test/handler/test_textlint_handler.vader | 40 + test/handler/test_tflint_handler.vader | 73 + test/handler/test_tfsec_handler.vader | 52 + test/handler/test_thrift_handler.vader | 63 + test/handler/test_thriftcheck_handler.vader | 28 + test/handler/test_tlint_handler.vader | 34 + test/handler/test_tslint_handler.vader | 315 + test/handler/test_typecheck_handler.vader | 24 + test/handler/test_unimport_handler.vader | 18 + test/handler/test_v_handler.vader | 54 + test/handler/test_vala_lint_handler.vader | 54 + test/handler/test_vale_handler.vader | 88 + test/handler/test_vcom_handler.vader | 36 + test/handler/test_verilator_handler.vader | 49 + test/handler/test_vint_handler.vader | 65 + test/handler/test_vlog_handler.vader | 47 + test/handler/test_vulture_handler.vader | 90 + test/handler/test_write_good_handler.vader | 37 + test/handler/test_xmllint_handler.vader | 30 + test/handler/test_xvhdl_handler.vader | 24 + test/handler/test_xvlog_handler.vader | 18 + test/handler/test_yamllint_handler.vader | 59 + test/handler/test_yosys_handler.vader | 27 + test/handler/test_yq_handler.vader | 19 + test/handler/test_zeek_handler.vader | 31 + test/handler/test_zlint_handler.vader | 44 + test/jsonnet_files/testfile.jsonnet | 1 + test/linter/test_ada_gcc.vader | 42 + test/linter/test_adals.vader | 17 + test/linter/test_alex.vader | 34 + test/linter/test_ameba.vader | 20 + test/linter/test_angular.vader | 28 + .../linter/test_ansible_language_server.vader | 18 + test/linter/test_ansible_lint.vader | 26 + test/linter/test_asciidoc_textlint.vader | 59 + test/linter/test_asm_gcc.vader | 20 + test/linter/test_avra_avra.vader | 29 + test/linter/test_bandit.vader | 100 + test/linter/test_bashate.vader | 15 + test/linter/test_bib_bibclean.vader | 24 + test/linter/test_bicep_az_bicep.vader | 21 + test/linter/test_bicep_bicep.vader | 21 + test/linter/test_bingo.vader | 60 + test/linter/test_biome.vader | 49 + test/linter/test_bitbake.vader | 13 + test/linter/test_brakeman.vader | 37 + test/linter/test_buf_lint.vader | 32 + test/linter/test_bzl_buildifier.vader | 29 + test/linter/test_c3_c3lsp.vader | 16 + test/linter/test_c_cc.vader | 94 + test/linter/test_c_ccls.vader | 69 + test/linter/test_c_clang_tidy.vader | 71 + test/linter/test_c_clangcheck.vader | 37 + test/linter/test_c_clangd.vader | 47 + test/linter/test_c_cppcheck.vader | 63 + test/linter/test_c_cquery.vader | 37 + test/linter/test_c_flawfinder.vader | 19 + test/linter/test_c_import_paths.vader | 162 + test/linter/test_cargo.vader | 222 + test/linter/test_checkmake.vader | 39 + test/linter/test_checkov.vader | 14 + test/linter/test_checkstyle.vader | 72 + test/linter/test_circleci.vader | 13 + test/linter/test_clang_tidy.vader | 78 + test/linter/test_clj_kondo.vader | 15 + test/linter/test_cmake_cmake_lint.vader | 13 + test/linter/test_cookstyle.vader | 14 + test/linter/test_cpp_cc.vader | 94 + test/linter/test_cpp_ccls.vader | 69 + test/linter/test_cpp_clangcheck.vader | 35 + test/linter/test_cpp_clazy.vader | 56 + test/linter/test_cpp_cppcheck.vader | 83 + test/linter/test_cpp_cquery.vader | 40 + test/linter/test_cpp_flawfinder.vader | 26 + test/linter/test_cpplint.vader | 17 + test/linter/test_cs_csc.vader | 42 + test/linter/test_cs_mcs.vader | 13 + test/linter/test_cs_mcsc.vader | 42 + test/linter/test_cspell.vader | 110 + test/linter/test_css_csslint.vader | 17 + test/linter/test_cucumber.vader | 18 + test/linter/test_cuda_nvcc.vader | 20 + test/linter/test_cypher_lint.vader | 8 + test/linter/test_d_dls.vader | 19 + test/linter/test_dart_analysis_server.vader | 21 + test/linter/test_dart_language_server.vader | 8 + test/linter/test_desktop_file_validate.vader | 15 + test/linter/test_dialyxir.vader | 16 + test/linter/test_djlint.vader | 69 + test/linter/test_dmd_commandline.vader | 96 + test/linter/test_dockerfile_lint.vader | 19 + test/linter/test_dockerlinter.vader | 19 + test/linter/test_dogma.vader | 16 + test/linter/test_eclipselsp.vader | 111 + test/linter/test_elixir_credo.vader | 43 + test/linter/test_elixir_ls.vader | 34 + test/linter/test_elixir_mix.vader | 19 + test/linter/test_elm_ls.vader | 29 + test/linter/test_elm_make.vader | 63 + test/linter/test_embertemplatelint.vader | 17 + test/linter/test_erb.vader | 16 + test/linter/test_erblint.vader | 26 + test/linter/test_erlang_dialyzer.vader | 45 + test/linter/test_erlang_elvis.vader | 52 + test/linter/test_erlang_erlang_ls.vader | 70 + test/linter/test_erlang_erlc.vader | 62 + test/linter/test_erlang_syntaxerl.vader | 45 + test/linter/test_erubi.vader | 32 + test/linter/test_erubis.vader | 16 + test/linter/test_eslint.vader | 87 + test/linter/test_fecs.vader | 9 + test/linter/test_flake8.vader | 226 + test/linter/test_flakehell.vader | 210 + test/linter/test_flow.vader | 42 + test/linter/test_foodcritic.vader | 15 + test/linter/test_fortran_fortls.vader | 18 + test/linter/test_fsc.vader | 13 + test/linter/test_fusionlint.vader | 19 + test/linter/test_gawk.vader | 25 + test/linter/test_gfortran.vader | 20 + test/linter/test_ghdl.vader | 19 + test/linter/test_gitlint.vader | 43 + test/linter/test_gleam_gleamlsp.vader | 15 + test/linter/test_glslang.vader | 19 + test/linter/test_glslls.vader | 19 + test/linter/test_gobuild.vader | 26 + test/linter/test_gofmt.vader | 26 + test/linter/test_golangci_lint.vader | 74 + test/linter/test_golangserver.vader | 76 + test/linter/test_gopls.vader | 96 + test/linter/test_gosimple.vader | 19 + test/linter/test_gotype.vader | 24 + test/linter/test_govet.vader | 28 + test/linter/test_graphql_gqlint.vader | 9 + test/linter/test_hadolint.vader | 25 + test/linter/test_haml_hamllint.vader | 43 + test/linter/test_haskell_cabal_ghc.vader | 13 + test/linter/test_haskell_ghc.vader | 13 + test/linter/test_haskell_ghc_mod.vader | 10 + test/linter/test_haskell_hdevtools.vader | 16 + test/linter/test_haskell_hie.vader | 23 + test/linter/test_haskell_hlint.vader | 17 + test/linter/test_haskell_hls.vader | 42 + test/linter/test_haskell_stack_build.vader | 13 + test/linter/test_haskell_stack_ghc.vader | 18 + test/linter/test_hdl_checker_options.vader | 86 + test/linter/test_html_stylelint.vader | 60 + test/linter/test_htmlhint.vader | 51 + test/linter/test_hurlfmt.vader | 19 + test/linter/test_ibm_openapi_validator.vader | 15 + test/linter/test_idris.vader | 21 + test/linter/test_ink_ls.vader | 22 + test/linter/test_inko_inko.vader | 20 + test/linter/test_ispc_ispc.vader | 14 + test/linter/test_iverilog.vader | 14 + test/linter/test_javac.vader | 326 + test/linter/test_javalsp.vader | 67 + test/linter/test_javascript_deno_lsp.vader | 84 + test/linter/test_javascript_tsserver.vader | 16 + test/linter/test_jedils.vader | 63 + test/linter/test_jq.vader | 8 + test/linter/test_jscs.vader | 15 + test/linter/test_jshint.vader | 17 + test/linter/test_json_vscodejson.vader | 33 + test/linter/test_jsonlint.vader | 31 + test/linter/test_jsonnet_lint.vader | 19 + test/linter/test_jsonnetfmt.vader | 19 + test/linter/test_julia_languageserver.vader | 30 + test/linter/test_kotlin_languageserver.vader | 23 + test/linter/test_kotlinc.vader | 9 + test/linter/test_languagetool.vader | 22 + test/linter/test_less_stylelint.vader | 32 + test/linter/test_lessc.vader | 46 + test/linter/test_lexical.vader | 28 + test/linter/test_lintr.vader | 34 + test/linter/test_llc.vader | 21 + test/linter/test_llvm_mc.vader | 20 + test/linter/test_lua_language_server.vader | 21 + test/linter/test_lua_selene.vader | 19 + test/linter/test_luac.vader | 13 + test/linter/test_luacheck.vader | 39 + test/linter/test_markdown_markdownlint.vader | 14 + test/linter/test_markdown_marksman.vader | 8 + test/linter/test_markdown_mdl.vader | 19 + test/linter/test_markdown_vale.vader | 32 + test/linter/test_mercury_mmc.vader | 22 + test/linter/test_mypy.vader | 114 + test/linter/test_naga.vader | 10 + test/linter/test_nagelfar.vader | 16 + test/linter/test_nasm_nasm.vader | 19 + test/linter/test_nimlsp.vader | 12 + test/linter/test_nix_deadnix.vader | 19 + test/linter/test_nix_statix.vader | 14 + test/linter/test_npmgroovylint.vader | 18 + test/linter/test_objc_ccls.vader | 66 + test/linter/test_ocaml_ocamllsp.vader | 29 + test/linter/test_ocaml_ols.vader | 41 + .../linter/test_ocamlinterface_ocamllsp.vader | 29 + test/linter/test_odin_ols.vader | 16 + test/linter/test_openscad_sca2d.vader | 12 + test/linter/test_packwerk.vader | 38 + test/linter/test_perl.vader | 14 + test/linter/test_perl6.vader | 14 + test/linter/test_perlcritic.vader | 36 + test/linter/test_php.vader | 15 + test/linter/test_php_intelephense.vader | 26 + test/linter/test_php_langserver.vader | 32 + test/linter/test_phpactor.vader | 20 + test/linter/test_phpcs.vader | 42 + test/linter/test_phpmd.vader | 12 + test/linter/test_phpstan.vader | 135 + test/linter/test_pony_ponyc.vader | 14 + test/linter/test_prospector.vader | 43 + test/linter/test_proto.vader | 16 + test/linter/test_protolint.vader | 24 + test/linter/test_psalm.vader | 44 + test/linter/test_puglint.vader | 48 + test/linter/test_puppet_languageserver.vader | 19 + test/linter/test_purescript_ls.vader | 31 + test/linter/test_pycln.vader | 124 + test/linter/test_pycodestyle.vader | 53 + test/linter/test_pydocstyle.vader | 51 + test/linter/test_pyflakes.vader | 67 + test/linter/test_pylama.vader | 94 + test/linter/test_pylint.vader | 104 + test/linter/test_pylsp.vader | 98 + test/linter/test_pymarkdown.vader | 50 + test/linter/test_pymarkdown_handler.vader | 52 + test/linter/test_pyre.vader | 76 + test/linter/test_pyrex_cython.vader | 30 + test/linter/test_pyright.vader | 188 + test/linter/test_qmlfmt.vader | 13 + test/linter/test_r_languageserver.vader | 22 + test/linter/test_racket_langserver.vader | 45 + test/linter/test_racket_raco.vader | 10 + test/linter/test_rails_best_practices.vader | 42 + test/linter/test_reason_ls.vader | 21 + test/linter/test_reason_ols.vader | 42 + test/linter/test_reek.vader | 49 + test/linter/test_refurb.vader | 92 + test/linter/test_rego_opacheck.vader | 16 + test/linter/test_remark_lint.vader | 37 + test/linter/test_revive.vader | 30 + test/linter/test_rflint.vader | 20 + test/linter/test_rnix.vader | 12 + test/linter/test_rst_textlint.vader | 58 + test/linter/test_rubocop.vader | 26 + test/linter/test_ruby.vader | 13 + test/linter/test_ruby_debride.vader | 8 + test/linter/test_ruby_solargraph.vader | 43 + test/linter/test_ruby_steep.vader | 69 + test/linter/test_ruff.vader | 127 + test/linter/test_rust_analyzer.vader | 28 + test/linter/test_rust_rls.vader | 33 + test/linter/test_rustc.vader | 25 + test/linter/test_ruumba.vader | 26 + test/linter/test_sass_sasslint.vader | 43 + test/linter/test_scala_metals.vader | 21 + test/linter/test_scala_sbtserver.vader | 23 + test/linter/test_scalac.vader | 13 + test/linter/test_scalastyle.vader | 34 + test/linter/test_scss_sasslint.vader | 43 + test/linter/test_scss_stylelint.vader | 33 + test/linter/test_shellcheck.vader | 106 + test/linter/test_slang.vader | 14 + test/linter/test_slimlint.vader | 19 + test/linter/test_solc.vader | 13 + test/linter/test_solc_commit.vader | 14 + test/linter/test_solhint.vader | 26 + test/linter/test_sorbet.vader | 34 + test/linter/test_spectral.vader | 31 + test/linter/test_sql_sqlfluff.vader | 18 + test/linter/test_sqllint.vader | 12 + test/linter/test_standard.vader | 43 + test/linter/test_standardrb.vader | 26 + test/linter/test_standardts.vader | 43 + test/linter/test_starknet.vader | 13 + test/linter/test_staticcheck.vader | 49 + test/linter/test_sugarss_stylelint.vader | 33 + test/linter/test_svelteserver.vader | 8 + test/linter/test_swaglint.vader | 29 + test/linter/test_swift_appleswiftformat.vader | 42 + test/linter/test_swift_sourcekitlsp.vader | 21 + test/linter/test_swiftlint.vader | 43 + test/linter/test_systemd_analyze.vader | 9 + test/linter/test_terraform_ls.vader | 37 + test/linter/test_terraform_lsp.vader | 29 + test/linter/test_terraform_terraform.vader | 15 + test/linter/test_terraform_tflint.vader | 23 + test/linter/test_terraform_tfsec.vader | 38 + test/linter/test_tex_chktex.vader | 50 + test/linter/test_tex_lacheck.vader | 13 + test/linter/test_tex_textlint.vader | 65 + test/linter/test_texlab.vader | 35 + test/linter/test_textlint.vader | 60 + test/linter/test_thrift.vader | 53 + test/linter/test_thriftcheck.vader | 16 + test/linter/test_tslint.vader | 23 + test/linter/test_typescript_deno_lsp.vader | 86 + test/linter/test_typescript_tsserver.vader | 8 + test/linter/test_unimport.vader | 78 + test/linter/test_v_command_callback.vader | 14 + test/linter/test_vcom.vader | 19 + test/linter/test_verilator.vader | 14 + test/linter/test_vim_vimls.vader | 77 + test/linter/test_vint.vader | 34 + test/linter/test_vlog.vader | 19 + test/linter/test_volar.vader | 27 + test/linter/test_vulture.vader | 85 + test/linter/test_write_good.vader | 55 + test/linter/test_xmllint.vader | 20 + test/linter/test_xo.vader | 20 + test/linter/test_xots.vader | 23 + test/linter/test_xvhdl.vader | 14 + test/linter/test_xvlog.vader | 17 + test/linter/test_yaml_actionlint.vader | 20 + test/linter/test_yaml_ls.vader | 21 + test/linter/test_yang_lsp.vader | 12 + test/linter/test_yara_yls.vader | 8 + test/linter/test_yq.vader | 8 + test/linter/test_zeek.vader | 17 + test/linter/test_zig_zlint.vader | 15 + test/linter/test_zig_zls.vader | 15 + test/lsp/test_closing_documents.vader | 176 + test/lsp/test_did_save_event.vader | 147 + .../test_engine_lsp_response_handling.vader | 552 ++ test/lsp/test_handling_window_requests.vader | 91 + test/lsp/test_lsp_address_split.vader | 9 + test/lsp/test_lsp_client_messages.vader | 405 ++ test/lsp/test_lsp_command_formatting.vader | 44 + test/lsp/test_lsp_connections.vader | 227 + test/lsp/test_lsp_custom_request.vader | 158 + test/lsp/test_lsp_error_parsing.vader | 74 + test/lsp/test_lsp_root_detection.vader | 67 + test/lsp/test_lsp_startup.vader | 496 ++ ...st_other_initialize_message_handling.vader | 223 + test/lsp/test_read_lsp_diagnostics.vader | 257 + test/lsp/test_reset_lsp.vader | 169 + test/lsp/test_update_config.vader | 21 + test/lua/.luarc.json | 40 + test/lua/ale_diagnostics_spec.lua | 232 + test/lua/ale_env_spec.lua | 56 + test/lua/ale_get_filename_mappings_spec.lua | 77 + test/lua/ale_has_spec.lua | 27 + test/lua/ale_lsp_spec.lua | 362 ++ test/lua/ale_pad_spec.lua | 13 + test/lua/ale_queue_spec.lua | 40 + test/lua/ale_var_spec.lua | 51 + test/lua/windows_escaping_spec.lua | 61 + test/python/test_deoplete_source.py | 121 + test/script/block-padding-checker | 145 + test/script/check-duplicate-tags | 5 + test/script/check-supported-tools-tables | 60 + test/script/check-tag-alignment | 11 + test/script/check-tag-references | 30 + test/script/check-toc | 90 + test/script/custom-checks | 80 + test/script/custom-linting-rules | 166 + test/script/dumb_named_pipe_server.py | 42 + test/script/dumb_tcp_client.py | 33 + test/script/dumb_tcp_server.py | 40 + test/script/run-lua-tests | 57 + test/script/run-vader-tests | 165 + test/script/run-vint | 20 + test/sign/test_linting_sets_signs.vader | 85 + test/sign/test_sign_column_highlighting.vader | 68 + test/sign/test_sign_limits.vader | 52 + test/sign/test_sign_parsing.vader | 88 + test/sign/test_sign_placement.vader | 298 + test/smoke_test.vader | 141 + test/test-files/.circleci/config.yml | 0 test/test-files/.gitignore | 2 + test/test-files/ada/testfile.adb | 0 .../node-modules-2/node_modules/alex/cli.js | 0 .../alex/node-modules/node_modules/.bin/alex | 0 .../@angular/language-server/bin/ngserver | 0 .../@angular/language-service/dummy | 0 test/test-files/ant/ant-project/build.xml | 0 test/test-files/ant/bin/ant | 0 test/test-files/ant/bin/ant.exe | 0 test/test-files/bazel/BUILD | 0 test/test-files/bazel/WORKSPACE | 0 test/test-files/bazel/defs.bzl | 0 test/test-files/bib/dummy.bib | 0 test/test-files/biome/json/biome.json | 0 test/test-files/biome/json/src/test.ts | 0 test/test-files/biome/jsonc/biome.jsonc | 0 test/test-files/biome/jsonc/src/test.ts | 0 test/test-files/bzl/bazel-package/BUILD.bazel | 0 .../build/bad_folder_to_test_priority | 0 .../build/compile_commands.json | 0 test/test-files/c/configure_project/Makefile | 0 test/test-files/c/configure_project/configure | 0 .../c/configure_project/include/test.h | 0 .../c/configure_project/subdir/Makefile | 0 test/test-files/c/dummy.c | 0 .../c/git_and_nested_makefiles/include/test.h | 0 .../c/git_and_nested_makefiles/src/Makefile | 0 .../c/gnumakefile_project/GNUmakefile | 0 test/test-files/c/gnumakefile_project/file.c | 0 test/test-files/c/h_file_project/Makefile | 0 test/test-files/c/h_file_project/subdir/dummy | 0 test/test-files/c/h_file_project/test.h | 0 test/test-files/c/hpp_file_project/Makefile | 0 .../c/hpp_file_project/subdir/dummy | 0 test/test-files/c/hpp_file_project/test.hpp | 0 .../json_project/build/compile_commands.json | 0 test/test-files/c/json_project/include/test.h | 0 test/test-files/c/json_project/subdir/dummy | 0 test/test-files/c/makefile_project/Makefile | 0 test/test-files/c/makefile_project/_astylerc | 0 test/test-files/c/makefile_project/args | 3 + .../c/makefile_project/include/test.h | 0 .../test-files/c/makefile_project/subdir/args | 1 + .../c/makefile_project/subdir/dummy | 0 .../c/makefile_project/subdir/file.c | 0 test/test-files/cargo/Cargo.toml | 0 .../cargo/workspace_paths/Cargo.toml | 0 .../cargo/workspace_paths/subpath/Cargo.toml | 0 .../compile_commands.json | 0 .../test-files/ccls/with_ccls-root/.ccls-root | 0 test/test-files/ccls/with_ccls/.ccls | 0 .../compile_commands.json | 0 test/test-files/checkstyle/other_config.xml | 0 .../compile_commands.json | 0 .../compile_commands.json | 0 .../with_clangformat/.clang-format | 0 test/test-files/cpp/.astylerc | 0 test/test-files/cpp/dummy.cpp | 0 .../cppcheck/one/compile_commands.json | 0 test/test-files/cppcheck/one/two/three/file.c | 0 .../cppcheck/one/two/three/file.cpp | 0 .../build/compile_commands.json | 0 .../cquery/build/compile_commands.json | 0 test/test-files/cquery/with_cquery/.cquery | 0 .../node-modules-2/node_modules/cspell/bin.js | 0 .../node-modules/node_modules/.bin/cspell | 0 .../test-files/csslint/other-app/testfile.css | 0 test/test-files/csslint/some-app/.csslintrc | 0 .../csslint/some-app/subdir/testfile.css | 0 .../test-files/cucumber/features/cuke.feature | 0 .../features/step_definitions/base_steps.rb | 0 test/test-files/d/test.d | 0 test/test-files/dart/.packages | 0 test/test-files/dart/testfile.dart | 0 test/test-files/dprint/blank.ts | 0 test/test-files/dprint/dprint.json | 0 test/test-files/elixir/mix_project/lib/app.ex | 0 test/test-files/elixir/mix_project/mix.exs | 3 + test/test-files/elixir/testfile.ex | 0 .../umbrella_project/apps/app1/lib/app.ex | 0 .../elixir/umbrella_project/apps/app1/mix.exs | 0 .../umbrella_project/apps/app2/lib/app.ex | 0 .../elixir/umbrella_project/apps/app2/mix.exs | 0 .../elixir/umbrella_project/mix.exs | 0 test/test-files/elm/newapp-notests/elm.json | 0 .../elm/newapp-notests/node_modules/.bin/elm | 0 .../elm/newapp-notests/tests/TestMain.elm | 0 test/test-files/elm/newapp/elm.json | 0 .../elm/newapp/node_modules/.bin/elm | 0 .../elm/newapp/node_modules/.bin/elm-test | 0 test/test-files/elm/newapp/src/Main.elm | 0 .../test-files/elm/newapp/tests/TestSuite.elm | 0 .../elm/node_modules/.bin/elm-format | 0 test/test-files/elm/oldapp/elm-package.json | 0 .../elm/oldapp/node_modules/.bin/elm | 0 .../elm/oldapp/node_modules/.bin/elm-test | 0 test/test-files/elm/oldapp/src/Main.elm | 0 .../test-files/elm/oldapp/tests/TestSuite.elm | 0 test/test-files/elm/src/subdir/testfile.elm | 0 .../erlang/app_with_elvis_config/elvis.config | 0 .../_build/default/lib/dep/erlang_ls.config | 0 .../deps/dep/erlang_ls.config | 0 .../erlang_ls.config | 0 test/test-files/erlang/app_with_erlfmt/erlfmt | 0 .../erlang/erlang_mk_app/deps/dep/erlang.mk | 0 .../test-files/erlang/erlang_mk_app/erlang.mk | 0 .../erlang/kerl_otp_root/.kerl_config | 0 .../_build/default/lib/dep/rebar.lock | 0 .../rebar3_app/_checkouts/dep/_build/.gitkeep | 0 .../rebar3_app/_checkouts/dep/rebar.lock | 0 test/test-files/erlang/rebar3_app/rebar.lock | 0 test/test-files/eruby/dummy.html.erb | 0 test/test-files/eslint/astro-app/.eslintrc.js | 0 .../node_modules/eslint/bin/eslint.js | 0 test/test-files/eslint/astro-app/package.json | 17 + .../eslint/astro-app/src/pages/index.astro | 16 + .../test-files/eslint/astro-app/tsconfig.json | 3 + .../eslint/node_modules/.bin/eslint | 0 .../eslint/other-app/subdir/testfile.js | 0 test/test-files/eslint/package.json | 0 test/test-files/eslint/react-app/.eslintrc.js | 0 .../node_modules/eslint/bin/eslint.js | 0 .../node_modules/standard/bin/cmd.js | 0 .../node_modules/stylelint/bin/stylelint.js | 0 .../eslint/react-app/node_modules/xo/cli.js | 0 .../react-app/subdir-with-config/.eslintrc | 0 .../node_modules/.gitkeep | 0 .../subdir-with-package-json/package.json | 0 .../eslint/react-app/subdir/testfile.css | 0 .../eslint/react-app/subdir/testfile.js | 0 .../eslint/react-app/subdir/testfile.ts | 0 test/test-files/eslint/yarn2-app/.eslintrc.js | 0 .../yarn2-app/.yarn/sdks/eslint/bin/eslint.js | 0 .../eslint/yarn2-app/subdir/testfile.js | 0 test/test-files/fecs/fecs | 0 test/test-files/fecs/fecs.exe | 0 test/test-files/fish/testfile.fish | 0 test/test-files/flow/a/.flowconfig | 0 test/test-files/flow/a/sub/dummy | 0 test/test-files/flow/b/sub/dummy | 0 test/test-files/fortls-project/.fortls | 2 + test/test-files/gleam/gleam.toml | 0 test/test-files/gleam/testfile.gleam | 0 test/test-files/go/go.mod | 1 + test/test-files/go/go1/prj1/file.go | 0 test/test-files/go/go2/prj2/file.go | 0 test/test-files/go/gopath/bin/gopls | 0 test/test-files/go/gopath/bin/staticcheck | 0 test/test-files/go/testfile.go | 0 test/test-files/go/testfile2.go | 0 .../gradle/build-gradle-project/build.gradle | 0 .../src/main/kotlin/dummy.kt | 0 test/test-files/gradle/gradle | 0 .../src/main/kotlin/dummy.kt | 0 .../settings-gradle-project/settings.gradle | 0 .../src/main/kotlin/dummy.kt | 0 .../gradle/unwrapped-project/build.gradle | 0 .../gradle/unwrapped-project/settings.gradle | 0 .../src/main/kotlin/dummy.kt | 0 .../gradle/wrapped-project/build.gradle | 0 .../test-files/gradle/wrapped-project/gradlew | 0 .../gradle/wrapped-project/settings.gradle | 0 .../wrapped-project/src/main/kotlin/dummy.kt | 0 .../haml-lint-and-rubocop/.haml-lint.yml | 0 .../haml-lint-and-rubocop/.rubocop.yml | 0 .../haml-lint-and-rubocop/subdir/file.haml | 0 .../hamllint/haml-lint-yml/.haml-lint.yml | 0 .../hamllint/haml-lint-yml/subdir/file.haml | 0 .../hamllint/rubocop-yml/.rubocop.yml | 0 .../hamllint/rubocop-yml/subdir/file.haml | 0 .../haskell-packages-project/cabal.project | 0 .../package-a/package-a.cabal | 0 .../package-a/src/folder/dummy.hs | 0 .../package-a/package-a.cabal | 0 .../package-a/src/folder/dummy.hs | 0 test/test-files/hdl_server/foo.vhd | 0 .../with_config_file/.hdl_checker.config | 0 .../with_config_file/_hdl_checker.config | 0 .../hdl_server/with_config_file/foo.vhd | 0 .../hdl_server/with_git/files/foo.vhd | 1 + test/test-files/hie_paths/file.hs | 0 test/test-files/html_beautify/html-beautify | 0 test/test-files/html_beautify/test.html | 0 .../htmlhint/node_modules/.bin/htmlhint | 0 .../htmlhint/with_config/.htmlhintrc | 0 test/test-files/ink/story/main.ink | 0 test/test-files/inko/test.inko | 0 test/test-files/inko/tests/test/test_foo.inko | 0 .../no_main/src/test/java/com/something/dummy | 0 .../src/main/java/com/something/dummy | 0 .../src/main/jaxb/com/something/dummy | 0 .../build/gen/main/java/com/something/dummy | 0 .../build/gen2/main/java/com/something/dummy | 0 .../src/main/java/com/something/dummy | 0 .../src/test/java/com/something/dummy | 0 test/test-files/javascript/test.js | 0 .../javascript_deno/custom_import_map.json | 3 + .../javascript_deno/import_map.json | 3 + test/test-files/javascript_deno/main.js | 1 + test/test-files/javascript_deno/tsconfig.json | 16 + test/test-files/json/testfile.json | 1 + .../app-without-jsonlint/src/app.json | 0 .../jsonlint/app/node_modules/.bin/jsonlint | 0 test/test-files/jsonlint/app/src/app.json | 0 .../jsonlint/node_modules/jsonlint/lib/cli.js | 0 test/test-files/julia/REQUIRE | 0 test/test-files/julia/test.jl | 0 test/test-files/kotlin/testfile.kt | 0 test/test-files/lessc/node_modules/.bin/lessc | 0 test/test-files/long-line/setup.cfg | 2 + test/test-files/lua/testfile.lua | 0 test/test-files/markdown/testfile.md | 0 .../maven/maven-java-project/module1/mvnw | 0 .../maven/maven-java-project/module1/mvnw.cmd | 0 .../maven/maven-java-project/module1/pom.xml | 1 + .../module1/src/main/java/dummy1.java | 0 .../maven/maven-java-project/module2/pom.xml | 1 + .../module2/src/main/java/dummy2.java | 0 .../maven/maven-kotlin-project/pom.xml | 1 + .../src/main/kotlin/dummy.kt | 1 + test/test-files/maven/mvn | 0 .../src/main/java/dummy.java | 0 test/test-files/nim/with-git/src/source.nim | 0 test/test-files/ocaml/testfile.ml | 0 test/test-files/ocamllsp/dune-project | 0 test/test-files/odin/main.odin | 0 test/test-files/ols/.merlin | 0 .../node_modules/.bin/ocaml-language-server | 0 test/test-files/openscad/dummy.scad | 0 test/test-files/pascal/test.pas | 0 .../php/project-with-php-cs-fixer/test.php | 0 .../vendor/bin/php-cs-fixer | 0 .../php/project-with-phpcbf/foo/test.php | 0 .../php/project-with-phpcbf/vendor/bin/phpcbf | 0 .../test-files/php/project-with-pint/test.php | 0 .../php/project-with-pint/vendor/bin/pint | 0 .../php/project-without-php-cs-fixer/test.php | 0 .../php/project-without-phpcbf/foo/test.php | 0 .../php/project-without-pint/test.php | 0 .../php/vendor/bin/php-language-server.php | 0 .../php/with-composer/composer.json | 0 .../vendor/bin/php-language-server.php | 0 .../vendor/bin/php-language-server.php | 0 .../phpcs/project-with-phpcs/foo/test.php | 0 .../phpcs/project-with-phpcs/vendor/bin/phpcs | 0 .../phpcs/project-without-phpcs/foo/test.php | 0 test/test-files/prettier/testfile | 0 test/test-files/prettier/testfile.css | 0 test/test-files/prettier/testfile.js | 0 test/test-files/prettier/testfile.json | 0 test/test-files/prettier/testfile.scss | 0 test/test-files/prettier/testfile.ts | 0 .../prettier/with_config/.prettierrc | 0 .../prettier/with_config/testfile.js | 0 .../with_prettierignore/.prettierignore | 0 .../with_prettierignore/src/testfile.js | 0 test/test-files/proto/testfile.proto | 0 test/test-files/psalm/vendor/bin/psalm | 0 .../puglint/node_modules/.bin/pug-lint | 0 test/test-files/puglint/package.json | 0 .../puglint/puglint_rc_dir/.pug-lintrc | 0 .../puglint/puglint_rc_js_dir/.pug-lintrc.js | 0 .../puglint_rc_json_dir/.pug-lintrc.json | 0 test/test-files/puppet/dummy.pp | 0 .../lib/puppet/types/exampletype.rb | 0 .../puppet/new-style-module/metadata.json | 0 .../new-style-module/template/template.epp | 0 .../puppet/old-style-module/manifests/init.pp | 0 .../old-style-module/templates/template.epp | 0 test/test-files/purescript/bower/Foo.purs | 0 test/test-files/purescript/bower/bower.json | 0 .../purescript/psc-package/Foo.purs | 0 .../purescript/psc-package/psc-package.json | 0 test/test-files/purescript/spago/Foo.purs | 0 test/test-files/purescript/spago/spago.dhall | 0 .../namespace_package_manifest/MANIFEST.in | 3 + .../namespace/foo/__init__.py | 0 .../namespace/foo/bar.py | 0 .../namespace/foo/__init__.py | 0 .../namespace/foo/bar.py | 0 .../namespace_package_pytest/pytest.ini | 2 + .../namespace/foo/__init__.py | 0 .../namespace/foo/bar.py | 0 .../python/namespace_package_setup/setup.cfg | 2 + .../namespace/foo/__init__.py | 0 .../namespace/foo/bar.py | 0 .../python/namespace_package_tox/tox.ini | 3 + .../no_virtualenv/subdir/foo/COMMIT_EDITMSG | 0 .../no_virtualenv/subdir/foo/__init__.py | 0 .../python/no_virtualenv/subdir/foo/bar.py | 0 test/test-files/python/pipenv/Pipfile.lock | 0 test/test-files/python/poetry/poetry.lock | 0 .../.pyre_configuration.local | 0 .../pyre_configuration_dir/foo/__init__.py | 0 .../python/pyre_configuration_dir/foo/bar.py | 0 .../python/python-package-project/.flake8 | 0 .../package-name/module.py | 0 test/test-files/python/uv/.gitkeep | 0 test/test-files/python/uv/uv.lock | 0 test/test-files/python/uv/whatever.py | 0 test/test-files/python/with_bandit/.bandit | 0 .../with_bandit/namespace/foo/__init__.py | 0 .../python/with_bandit/namespace/foo/bar.py | 0 .../with_mypy_ini_and_pytest_ini/mypy.ini | 0 .../tests/pytest.ini | 0 .../tests/testsubfolder/my_tests.py | 0 .../dir_with_yapf_config/.style.yapf | 0 .../with_virtualenv/env/Scripts/activate | 0 .../with_virtualenv/env/Scripts/autoflake.exe | 0 .../env/Scripts/autoimport.exe | 0 .../with_virtualenv/env/Scripts/autopep8.exe | 0 .../with_virtualenv/env/Scripts/black.exe | 0 .../with_virtualenv/env/Scripts/flake8.exe | 0 .../with_virtualenv/env/Scripts/flakehell.exe | 0 .../with_virtualenv/env/Scripts/gitlint.exe | 0 .../with_virtualenv/env/Scripts/isort.exe | 0 .../env/Scripts/jedi-language-server.exe | 0 .../with_virtualenv/env/Scripts/mypy.exe | 0 .../with_virtualenv/env/Scripts/pycln.exe | 0 .../with_virtualenv/env/Scripts/pyflakes.exe | 0 .../with_virtualenv/env/Scripts/pylama.exe | 0 .../with_virtualenv/env/Scripts/pylint.exe | 0 .../with_virtualenv/env/Scripts/pylsp.exe | 0 .../with_virtualenv/env/Scripts/pyre.exe | 0 .../env/Scripts/pyright-langserver.exe | 0 .../with_virtualenv/env/Scripts/refurb.exe | 0 .../env/Scripts/reorder-python-imports.exe | 0 .../with_virtualenv/env/Scripts/ruff.exe | 0 .../env/Scripts/tidy-imports.exe | 0 .../with_virtualenv/env/Scripts/unimport.exe | 0 .../with_virtualenv/env/Scripts/vulture.exe | 0 .../with_virtualenv/env/Scripts/yamlfix.exe | 0 .../with_virtualenv/env/Scripts/yapf.exe | 0 .../python/with_virtualenv/env/bin/activate | 0 .../python/with_virtualenv/env/bin/autoflake | 0 .../python/with_virtualenv/env/bin/autoimport | 0 .../python/with_virtualenv/env/bin/autopep8 | 0 .../python/with_virtualenv/env/bin/black | 0 .../python/with_virtualenv/env/bin/flake8 | 0 .../python/with_virtualenv/env/bin/flakehell | 0 .../python/with_virtualenv/env/bin/gitlint | 0 .../python/with_virtualenv/env/bin/isort | 0 .../env/bin/jedi-language-server | 0 .../python/with_virtualenv/env/bin/mypy | 0 .../python/with_virtualenv/env/bin/pycln | 0 .../python/with_virtualenv/env/bin/pyflakes | 0 .../python/with_virtualenv/env/bin/pylama | 0 .../python/with_virtualenv/env/bin/pylint | 0 .../python/with_virtualenv/env/bin/pylsp | 0 .../python/with_virtualenv/env/bin/pyre | 0 .../env/bin/pyright-langserver | 0 .../python/with_virtualenv/env/bin/refurb | 0 .../env/bin/reorder-python-imports | 0 .../python/with_virtualenv/env/bin/ruff | 0 .../with_virtualenv/env/bin/tidy-imports | 0 .../python/with_virtualenv/env/bin/unimport | 0 .../python/with_virtualenv/env/bin/vulture | 0 .../python/with_virtualenv/env/bin/yamlfix | 0 .../python/with_virtualenv/env/bin/yapf | 0 .../with_virtualenv/subdir/foo/COMMIT_EDITMSG | 0 .../with_virtualenv/subdir/foo/__init__.py | 0 .../python/with_virtualenv/subdir/foo/bar.py | 0 .../python/with_virtualenv/subdir/foo/bar.pyi | 0 test/test-files/r/.Rprofile | 0 .../racket/many-inits/a/b/c/foo.rkt | 3 + .../racket/many-inits/a/b/c/init.rkt | 1 + test/test-files/racket/many-inits/a/b/foo.rkt | 3 + .../test-files/racket/many-inits/a/b/init.rkt | 1 + test/test-files/racket/many-inits/a/foo.rkt | 3 + test/test-files/racket/many-inits/a/init.rkt | 1 + test/test-files/racket/many-inits/foo.rkt | 3 + test/test-files/racket/many-inits/init.rkt | 1 + test/test-files/racket/simple-script/foo.rkt | 3 + test/test-files/reasonml/bsconfig.json | 0 test/test-files/reasonml/testfile.re | 0 .../with_bin_path/node_modules/.bin/remark | 0 test/test-files/ruby/dummy.rb | 0 test/test-files/ruby/nested/dummy.rb | 0 test/test-files/ruby/nested/foo/Steepfile | 0 test/test-files/ruby/nested/foo/dummy.rb | 0 test/test-files/ruby/nested/foo/one/dummy.rb | 0 test/test-files/ruby/nested/foo/two/Steepfile | 0 test/test-files/ruby/nested/foo/two/dummmy.rb | 0 .../ruby/nested/foo/two/three/dummy.rb | 0 test/test-files/ruby/not_a_rails_app/file.rb | 0 .../ruby/valid_rails_app/app/dummy.rb | 0 .../ruby/valid_rails_app/app/models/thing.rb | 0 .../app/views/my_great_view.html.erb | 0 .../ruby/valid_rails_app/config/dummy.rb | 0 .../ruby/valid_rails_app/db/dummy.rb | 0 test/test-files/ruby/valid_ruby_app1/Rakefile | 0 .../ruby/valid_ruby_app1/lib/file.rb | 0 test/test-files/ruby/valid_ruby_app2/Gemfile | 0 .../ruby/valid_ruby_app2/lib/file.rb | 0 .../ruby/valid_ruby_app3/.solargraph.yml | 0 .../ruby/valid_ruby_app3/lib/file.rb | 0 test/test-files/ruby/with_config/.rubocop.yml | 0 .../test-files/ruby/with_config/.standard.yml | 0 test/test-files/rust/cargo/Cargo.toml | 0 test/test-files/rust/cargo/testfile.rs | 0 .../rust/rust-project/rust-project.json | 0 test/test-files/rust/rust-project/testfile.rs | 0 test/test-files/rustywind/test.html | 0 .../with-bin/node_modules/.bin/sass-lint | 0 .../node_modules/sass-lint/bin/sass-lint.js | 0 test/test-files/scala/dummy.scala | 0 .../scala/invalid_sbt_project/Main.scala | 0 .../scala/valid_sbt_project/Main.scala | 0 .../scala/valid_sbt_project/build.sbt | 0 test/test-files/slimlint/.rubocop.yml | 0 test/test-files/slimlint/subdir/file.slim | 0 test/test-files/smlnj/cm/foo.sml | 0 test/test-files/smlnj/cm/path/to/bar.sml | 0 test/test-files/smlnj/cm/sources.cm | 0 test/test-files/smlnj/file/qux.sml | 0 test/test-files/solhint/Contract.sol | 0 .../solhint/node_modules/.bin/solhint | 0 .../solhint/node_modules/solhint/index.js | 0 test/test-files/solhint/package.json | 0 .../spectral/node_modules/.bin/spectral | 0 test/test-files/spectral/openapi.yaml | 0 test/test-files/stack/stack.yaml | 0 .../with-bin/node_modules/.bin/standard | 0 .../with-cmd/node_modules/standard/bin/cmd.js | 0 .../stylelint/node_modules/.bin/stylelint | 0 .../stylua/stylua_config_dir/stylua.toml | 0 .../stylua/stylua_dot_config_dir/.stylua.toml | 0 test/test-files/swaglint/docs/swagger.yaml | 0 .../swaglint/node_modules/.bin/swaglint | 0 test/test-files/swift/dummy.swift | 0 .../src/folder/dummy.swift | 0 .../.swift-format | 10 + .../Package.swift | 0 .../src/folder/dummy.swift | 0 .../swift/swift-package-project/Package.swift | 0 .../src/folder/dummy.swift | 0 .../Pods/SwiftLint/swiftlint | 0 .../ios/Pods/SwiftLint/swiftlint | 0 .../cocoapods/Pods/SwiftLint/swiftlint | 0 .../react-native/ios/Pods/SwiftLint/swiftlint | 0 test/test-files/terraform/.terraform/dummy | 0 test/test-files/terraform/main.tf | 0 test/test-files/tex/sample1.tex | 0 test/test-files/tex/sample2.tex | 0 test/test-files/tex/testfile.tex | 0 .../with_bin_path/node_modules/.bin/textlint | 0 .../node_modules/textlint/bin/textlint.js | 0 test/test-files/tflint/foo/.tflint.hcl | 0 test/test-files/tflint/foo/bar.tf | 0 test/test-files/tfsec/json/.tfsec/config.json | 0 test/test-files/tfsec/json/main.tf | 0 test/test-files/tfsec/yml/.tfsec/config.yml | 0 test/test-files/tfsec/yml/main.tf | 0 test/test-files/tidy/.tidyrc | 0 test/test-files/tidy/test.html | 0 test/test-files/tidy/tidy | 0 test/test-files/tidy/tidy.exe | 0 .../empty-file | 0 test/test-files/top/example.ini | 0 test/test-files/top/middle/bottom/dummy.txt | 0 test/test-files/tsserver/src/file1.ts | 0 test/test-files/tsserver/src/level-1/file2.ts | 0 .../tsserver/src/level-1/level-2/file3.ts | 0 .../tsserver/src/level-1/tsconfig.json | 0 test/test-files/tsserver/tsconfig.json | 0 .../typescript/custom_import_map.json | 0 test/test-files/typescript/import_map.json | 0 test/test-files/typescript/test.ts | 0 test/test-files/typescript/tsconfig.json | 0 .../vim/invalid_vim_project/test.vim | 0 .../vim/node_modules/.bin/vim-language-server | 0 .../vim/path_with_autoload/autoload/test.vim | 0 .../vim/path_with_autoload/test.vim | 0 .../test-files/vim/path_with_initvim/init.vim | 0 .../vim/path_with_plugin/plugin/test.vim | 0 test/test-files/vim/path_with_plugin/test.vim | 0 test/test-files/vim/path_with_vimrc/.vimrc | 0 .../node_modules/.bin/vue-language-server | 0 .../typescript/lib/tsserverlibrary.js | 0 test/test-files/volar/package.json | 0 test/test-files/volar/src/App.vue | 0 .../node_modules/write-good/bin/write-good.js | 0 .../node-modules/node_modules/.bin/write-good | 0 test/test-files/xml/dummy.xml | 4 + .../xo/monorepo/node_modules/xo/cli.js | 0 test/test-files/xo/monorepo/package.json | 0 .../xo/monorepo/packages/a/index.js | 0 .../xo/monorepo/packages/a/index.ts | 0 .../xo/monorepo/packages/a/package.json | 0 test/test-files/yaml/test.yaml | 0 test/test-files/yara/dummy.yar | 5 + test/test-files/zig/build.zig | 0 test/test_ale_has.vader | 7 + test/test_ale_info.vader | 772 +++ test/test_ale_info_to_clipboard.vader | 15 + test/test_ale_lint_command.vader | 77 + test/test_ale_lint_stop_command.vader | 27 + test/test_ale_populate_command.vader | 96 + test/test_ale_toggle.vader | 444 ++ test/test_ale_var.vader | 24 + test/test_alejobstarted_autocmd.vader | 46 + test/test_alelint_autocmd.vader | 40 + test/test_ant_build_classpath_command.vader | 27 + test/test_ant_find_project_root.vader | 35 + test/test_autocmd_commands.vader | 286 + test/test_balloon_messages.vader | 87 + test/test_c_flag_parsing.vader | 689 +++ test/test_cleanup.vader | 14 + test/test_code_action.vader | 605 ++ test/test_code_action_corner_cases.vader | 141 + test/test_codefix.vader | 549 ++ test/test_computed_lint_file_values.vader | 150 + test/test_cursor_warnings.vader | 276 + test/test_deferred_command_string.vader | 50 + test/test_deferred_executable_string.vader | 46 + test/test_deno_executable_detection.vader | 20 + test/test_disabling_ale.vader | 105 + test/test_env_function.vader | 8 + ...rrors_removed_after_filetype_changed.vader | 78 + test/test_filename_mapping.vader | 62 + test/test_filerename.vader | 224 + test/test_filetype_guessing.vader | 20 + test/test_filetype_linter_defaults.vader | 140 + test/test_find_nearest_directory.vader | 17 + test/test_find_references.vader | 480 ++ test/test_floating_preview.vader | 93 + test/test_format_command.vader | 186 + .../test_format_temporary_file_creation.vader | 63 + test/test_function_arg_count.vader | 45 + test/test_fuzzy_json_decode.vader | 29 + test/test_get_abspath.vader | 29 + test/test_get_loclist.vader | 31 + test/test_getmatches.vader | 163 + test/test_go_to_definition.vader | 685 +++ .../test_gradle_build_classpath_command.vader | 52 + test/test_gradle_find_executable.vader | 37 + test/test_gradle_find_project_root.vader | 35 + test/test_helptags.vader | 2 + test/test_highlight_placement.vader | 465 ++ test/test_highlight_position_chunking.vader | 76 + test/test_history_saving.vader | 177 + test/test_hover.vader | 273 + test/test_hover_parsing.vader | 228 + test/test_ignoring_linters.vader | 613 ++ test/test_line_join.vader | 84 + test/test_lint_file_linters.vader | 317 + ...test_lint_on_enter_when_file_changed.vader | 84 + test/test_lint_on_filetype_changed.vader | 77 + test/test_linter_defintion_processing.vader | 501 ++ test/test_linter_retrieval.vader | 190 + test/test_linter_type_mapping.vader | 120 + test/test_linting_blacklist.vader | 16 + test/test_linting_updates_loclist.vader | 96 + test/test_list_formatting.vader | 188 + test/test_list_opening.vader | 284 + test/test_list_titles.vader | 77 + test/test_load_all_linters.vader | 6 + test/test_loclist_binary_search.vader | 66 + test/test_loclist_corrections.vader | 469 ++ test/test_loclist_jumping.vader | 121 + test/test_loclist_sorting.vader | 43 + test/test_maven_build_classpath_command.vader | 53 + test/test_maven_find_executable.vader | 46 + test/test_maven_find_project_root.vader | 28 + test/test_nearest_file_search.vader | 15 + test/test_neovim_diagnostics.vader | 71 + test/test_no_linting_on_write_quit.vader | 118 + test/test_organize_imports.vader | 172 + test/test_other_sources.vader | 153 + test/test_parse_command_args.vader | 31 + test/test_path_dirname.vader | 13 + test/test_path_equality.vader | 82 + test/test_path_upwards.vader | 48 + test/test_path_uri.vader | 73 + test/test_pattern_options.vader | 107 + test/test_prepare_command.vader | 96 + test/test_python_pipenv.vader | 19 + test/test_python_poetry.vader | 19 + test/test_python_traceback.vader | 79 + test/test_python_uv.vader | 19 + test/test_python_virtualenv.vader | 23 + test/test_quickfix_deduplication.vader | 50 + test/test_quitting_variable.vader | 39 + ...redundant_tsserver_rendering_avoided.vader | 178 + test/test_regex_escaping.vader | 4 + test/test_rename.vader | 494 ++ test/test_resolve_local_path.vader | 17 + ...lts_not_cleared_when_opening_loclist.vader | 30 + test/test_sandbox_execution.vader | 103 + test/test_semver_utils.vader | 41 + test/test_set_list_timers.vader | 29 + ..._setting_loclist_from_another_buffer.vader | 26 + ...g_problems_found_in_previous_buffers.vader | 98 + test/test_shell_detection.vader | 177 + test/test_should_do_nothing_conditions.vader | 88 + test/test_sml_command.vader | 45 + test/test_socket_connections.vader | 139 + test/test_statusline.vader | 144 + test/test_swift_find_project_root.vader | 18 + test/test_symbol_search.vader | 189 + test/test_temporary_file_management.vader | 146 + test/test_tmpdir_wrapper.vader | 32 + test/test_vim8_processid_parsing.vader | 5 + test/test_virtualtext.vader | 221 + test/test_windows_escaping.vader | 42 + test/test_wrap_comand.vader | 43 + test/test_writefile_function.vader | 117 + test/util/test_cd_string_commands.vader | 20 + test/v_files/testfile.v | 0 test/vimrc | 46 + 2228 files changed, 121524 insertions(+) create mode 100644 .appveyor.yml create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/report-a-bug.md create mode 100644 .github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md create mode 100644 .github/ISSUE_TEMPLATE/suggest-an-improvement.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/stale.yml create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .luarc.json create mode 100644 .vintrc.yaml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 ale_linters/ada/adals.vim create mode 100644 ale_linters/ada/cspell.vim create mode 100644 ale_linters/ada/gcc.vim create mode 100644 ale_linters/ansible/ansible_lint.vim create mode 100644 ale_linters/ansible/language_server.vim create mode 100644 ale_linters/apiblueprint/drafter.vim create mode 100644 ale_linters/apkbuild/apkbuild_lint.vim create mode 100644 ale_linters/apkbuild/secfixes_check.vim create mode 100644 ale_linters/asciidoc/alex.vim create mode 100644 ale_linters/asciidoc/cspell.vim create mode 100644 ale_linters/asciidoc/languagetool.vim create mode 100644 ale_linters/asciidoc/proselint.vim create mode 100644 ale_linters/asciidoc/redpen.vim create mode 100644 ale_linters/asciidoc/textlint.vim create mode 100644 ale_linters/asciidoc/vale.vim create mode 100644 ale_linters/asciidoc/writegood.vim create mode 100644 ale_linters/asm/gcc.vim create mode 100644 ale_linters/asm/llvm_mc.vim create mode 100644 ale_linters/astro/eslint.vim create mode 100644 ale_linters/avra/avra.vim create mode 100644 ale_linters/awk/gawk.vim create mode 100644 ale_linters/bats/shellcheck.vim create mode 100644 ale_linters/bib/bibclean.vim create mode 100644 ale_linters/bicep/az_bicep.vim create mode 100644 ale_linters/bicep/bicep.vim create mode 100644 ale_linters/bitbake/oelint_adv.vim create mode 100644 ale_linters/bzl/buildifier.vim create mode 100644 ale_linters/c/cc.vim create mode 100644 ale_linters/c/ccls.vim create mode 100644 ale_linters/c/clangcheck.vim create mode 100644 ale_linters/c/clangd.vim create mode 100644 ale_linters/c/clangtidy.vim create mode 100644 ale_linters/c/cppcheck.vim create mode 100644 ale_linters/c/cpplint.vim create mode 100644 ale_linters/c/cquery.vim create mode 100644 ale_linters/c/cspell.vim create mode 100644 ale_linters/c/flawfinder.vim create mode 100644 ale_linters/c3/c3lsp.vim create mode 100644 ale_linters/cairo/scarb.vim create mode 100644 ale_linters/cairo/sierra.vim create mode 100644 ale_linters/cairo/starknet.vim create mode 100644 ale_linters/chef/cookstyle.vim create mode 100644 ale_linters/chef/foodcritic.vim create mode 100644 ale_linters/clojure/clj_kondo.vim create mode 100644 ale_linters/clojure/joker.vim create mode 100644 ale_linters/cloudformation/cfn_python_lint.vim create mode 100644 ale_linters/cmake/cmake_lint.vim create mode 100644 ale_linters/cmake/cmakelint.vim create mode 100644 ale_linters/coffee/coffee.vim create mode 100644 ale_linters/coffee/coffeelint.vim create mode 100644 ale_linters/cpp/cc.vim create mode 100644 ale_linters/cpp/ccls.vim create mode 100644 ale_linters/cpp/clangcheck.vim create mode 100644 ale_linters/cpp/clangd.vim create mode 100644 ale_linters/cpp/clangtidy.vim create mode 100644 ale_linters/cpp/clazy.vim create mode 100644 ale_linters/cpp/cppcheck.vim create mode 100644 ale_linters/cpp/cpplint.vim create mode 100644 ale_linters/cpp/cquery.vim create mode 100644 ale_linters/cpp/cspell.vim create mode 100644 ale_linters/cpp/flawfinder.vim create mode 100644 ale_linters/crystal/ameba.vim create mode 100644 ale_linters/crystal/crystal.vim create mode 100644 ale_linters/cs/csc.vim create mode 100644 ale_linters/cs/cspell.vim create mode 100644 ale_linters/cs/mcs.vim create mode 100644 ale_linters/cs/mcsc.vim create mode 100644 ale_linters/css/cspell.vim create mode 100644 ale_linters/css/csslint.vim create mode 100644 ale_linters/css/fecs.vim create mode 100644 ale_linters/css/stylelint.vim create mode 100644 ale_linters/css/vscodecss.vim create mode 100644 ale_linters/cucumber/cucumber.vim create mode 100644 ale_linters/cuda/clangd.vim create mode 100644 ale_linters/cuda/nvcc.vim create mode 100644 ale_linters/cypher/cypher_lint.vim create mode 100644 ale_linters/d/dls.vim create mode 100644 ale_linters/d/dmd.vim create mode 100644 ale_linters/dafny/dafny.vim create mode 100644 ale_linters/dart/analysis_server.vim create mode 100644 ale_linters/dart/dart_analyze.vim create mode 100644 ale_linters/dart/language_server.vim create mode 100644 ale_linters/desktop/desktop_file_validate.vim create mode 100644 ale_linters/dockerfile/dockerfile_lint.vim create mode 100644 ale_linters/dockerfile/dockerlinter.vim create mode 100644 ale_linters/dockerfile/hadolint.vim create mode 100644 ale_linters/elixir/credo.vim create mode 100644 ale_linters/elixir/cspell.vim create mode 100644 ale_linters/elixir/dialyxir.vim create mode 100644 ale_linters/elixir/dogma.vim create mode 100644 ale_linters/elixir/elixir_ls.vim create mode 100644 ale_linters/elixir/lexical.vim create mode 100644 ale_linters/elixir/mix.vim create mode 100644 ale_linters/elm/ls.vim create mode 100644 ale_linters/elm/make.vim create mode 100644 ale_linters/erlang/dialyzer.vim create mode 100644 ale_linters/erlang/elvis.vim create mode 100644 ale_linters/erlang/erlang_ls.vim create mode 100644 ale_linters/erlang/erlc.vim create mode 100644 ale_linters/erlang/syntaxerl.vim create mode 100644 ale_linters/eruby/erb.vim create mode 100644 ale_linters/eruby/erblint.vim create mode 100644 ale_linters/eruby/erubi.vim create mode 100644 ale_linters/eruby/erubis.vim create mode 100644 ale_linters/eruby/ruumba.vim create mode 100644 ale_linters/fish/fish.vim create mode 100644 ale_linters/fortran/gcc.vim create mode 100644 ale_linters/fortran/language_server.vim create mode 100644 ale_linters/fountain/proselint.vim create mode 100644 ale_linters/fuse/fusionlint.vim create mode 100644 ale_linters/gitcommit/gitlint.vim create mode 100644 ale_linters/gleam/gleamlsp.vim create mode 100644 ale_linters/glimmer/embertemplatelint.vim create mode 100644 ale_linters/glsl/glslang.vim create mode 100644 ale_linters/glsl/glslls.vim create mode 100644 ale_linters/go/bingo.vim create mode 100644 ale_linters/go/cspell.vim create mode 100644 ale_linters/go/gobuild.vim create mode 100644 ale_linters/go/gofmt.vim create mode 100644 ale_linters/go/golangci_lint.vim create mode 100644 ale_linters/go/gopls.vim create mode 100644 ale_linters/go/gosimple.vim create mode 100644 ale_linters/go/gotype.vim create mode 100644 ale_linters/go/govet.vim create mode 100644 ale_linters/go/langserver.vim create mode 100644 ale_linters/go/revive.vim create mode 100644 ale_linters/go/staticcheck.vim create mode 100644 ale_linters/gohtmltmpl/djlint.vim create mode 100644 ale_linters/graphql/eslint.vim create mode 100644 ale_linters/graphql/gqlint.vim create mode 100644 ale_linters/groovy/npmgroovylint.vim create mode 100644 ale_linters/hack/hack.vim create mode 100644 ale_linters/hack/hhast.vim create mode 100644 ale_linters/haml/hamllint.vim create mode 100644 ale_linters/handlebars/djlint.vim create mode 100644 ale_linters/handlebars/embertemplatelint.vim create mode 100644 ale_linters/haskell/cabal_ghc.vim create mode 100644 ale_linters/haskell/cspell.vim create mode 100644 ale_linters/haskell/ghc.vim create mode 100644 ale_linters/haskell/ghc_mod.vim create mode 100644 ale_linters/haskell/hdevtools.vim create mode 100644 ale_linters/haskell/hie.vim create mode 100644 ale_linters/haskell/hlint.vim create mode 100644 ale_linters/haskell/hls.vim create mode 100644 ale_linters/haskell/stack_build.vim create mode 100644 ale_linters/haskell/stack_ghc.vim create mode 100644 ale_linters/help/alex.vim create mode 100644 ale_linters/help/cspell.vim create mode 100644 ale_linters/help/proselint.vim create mode 100644 ale_linters/help/writegood.vim create mode 100644 ale_linters/html/alex.vim create mode 100644 ale_linters/html/angular.vim create mode 100644 ale_linters/html/cspell.vim create mode 100644 ale_linters/html/djlint.vim create mode 100644 ale_linters/html/eslint.vim create mode 100644 ale_linters/html/fecs.vim create mode 100644 ale_linters/html/htmlhint.vim create mode 100644 ale_linters/html/proselint.vim create mode 100644 ale_linters/html/stylelint.vim create mode 100644 ale_linters/html/tidy.vim create mode 100644 ale_linters/html/vscodehtml.vim create mode 100644 ale_linters/html/writegood.vim create mode 100644 ale_linters/htmlangular/djlint.vim create mode 100644 ale_linters/htmldjango/djlint.vim create mode 100644 ale_linters/hurl/hurlfmt.vim create mode 100644 ale_linters/idris/idris.vim create mode 100644 ale_linters/ink/ls.vim create mode 100644 ale_linters/inko/inko.vim create mode 100644 ale_linters/ispc/ispc.vim create mode 100644 ale_linters/java/checkstyle.vim create mode 100644 ale_linters/java/cspell.vim create mode 100644 ale_linters/java/eclipselsp.vim create mode 100644 ale_linters/java/javac.vim create mode 100644 ale_linters/java/javalsp.vim create mode 100644 ale_linters/java/pmd.vim create mode 100644 ale_linters/javascript/biome.vim create mode 100644 ale_linters/javascript/cspell.vim create mode 100644 ale_linters/javascript/deno.vim create mode 100644 ale_linters/javascript/eslint.vim create mode 100644 ale_linters/javascript/fecs.vim create mode 100644 ale_linters/javascript/flow.vim create mode 100644 ale_linters/javascript/flow_ls.vim create mode 100644 ale_linters/javascript/jscs.vim create mode 100644 ale_linters/javascript/jshint.vim create mode 100644 ale_linters/javascript/standard.vim create mode 100644 ale_linters/javascript/tsserver.vim create mode 100644 ale_linters/javascript/xo.vim create mode 100644 ale_linters/jinja/djlint.vim create mode 100644 ale_linters/json/biome.vim create mode 100644 ale_linters/json/cspell.vim create mode 100644 ale_linters/json/eslint.vim create mode 100644 ale_linters/json/jq.vim create mode 100644 ale_linters/json/jsonlint.vim create mode 100644 ale_linters/json/spectral.vim create mode 100644 ale_linters/json/vscodejson.vim create mode 100644 ale_linters/json5/eslint.vim create mode 100644 ale_linters/jsonc/biome.vim create mode 100644 ale_linters/jsonc/eslint.vim create mode 100644 ale_linters/jsonnet/jsonnet_lint.vim create mode 100644 ale_linters/jsonnet/jsonnetfmt.vim create mode 100644 ale_linters/julia/languageserver.vim create mode 100644 ale_linters/kotlin/kotlinc.vim create mode 100644 ale_linters/kotlin/ktlint.vim create mode 100644 ale_linters/kotlin/languageserver.vim create mode 100644 ale_linters/less/lessc.vim create mode 100644 ale_linters/less/stylelint.vim create mode 100644 ale_linters/llvm/llc.vim create mode 100644 ale_linters/lua/cspell.vim create mode 100644 ale_linters/lua/lua_language_server.vim create mode 100644 ale_linters/lua/luac.vim create mode 100644 ale_linters/lua/luacheck.vim create mode 100644 ale_linters/lua/selene.vim create mode 100644 ale_linters/mail/alex.vim create mode 100644 ale_linters/mail/languagetool.vim create mode 100644 ale_linters/mail/proselint.vim create mode 100644 ale_linters/mail/vale.vim create mode 100644 ale_linters/make/checkmake.vim create mode 100644 ale_linters/markdown/alex.vim create mode 100644 ale_linters/markdown/cspell.vim create mode 100644 ale_linters/markdown/languagetool.vim create mode 100644 ale_linters/markdown/markdownlint.vim create mode 100644 ale_linters/markdown/marksman.vim create mode 100644 ale_linters/markdown/mdl.vim create mode 100644 ale_linters/markdown/proselint.vim create mode 100644 ale_linters/markdown/pymarkdown.vim create mode 100644 ale_linters/markdown/redpen.vim create mode 100644 ale_linters/markdown/remark_lint.vim create mode 100644 ale_linters/markdown/textlint.vim create mode 100644 ale_linters/markdown/vale.vim create mode 100644 ale_linters/markdown/writegood.vim create mode 100644 ale_linters/matlab/mlint.vim create mode 100644 ale_linters/mercury/mmc.vim create mode 100644 ale_linters/nasm/nasm.vim create mode 100644 ale_linters/nim/nimcheck.vim create mode 100644 ale_linters/nim/nimlsp.vim create mode 100644 ale_linters/nix/deadnix.vim create mode 100644 ale_linters/nix/nix.vim create mode 100644 ale_linters/nix/rnix_lsp.vim create mode 100644 ale_linters/nix/statix.vim create mode 100644 ale_linters/nroff/alex.vim create mode 100644 ale_linters/nroff/proselint.vim create mode 100644 ale_linters/nroff/writegood.vim create mode 100644 ale_linters/nunjucks/djlint.vim create mode 100644 ale_linters/objc/ccls.vim create mode 100644 ale_linters/objc/clang.vim create mode 100644 ale_linters/objc/clangd.vim create mode 100644 ale_linters/objcpp/clang.vim create mode 100644 ale_linters/objcpp/clangd.vim create mode 100644 ale_linters/ocaml/merlin.vim create mode 100644 ale_linters/ocaml/ocamllsp.vim create mode 100644 ale_linters/ocaml/ols.vim create mode 100644 ale_linters/ocamlinterface/merlin.vim create mode 100644 ale_linters/ocamlinterface/ocamllsp.vim create mode 100644 ale_linters/odin/ols.vim create mode 100644 ale_linters/openapi/ibm_validator.vim create mode 100644 ale_linters/openapi/yamllint.vim create mode 100644 ale_linters/openscad/sca2d.vim create mode 100644 ale_linters/perl/perl.vim create mode 100644 ale_linters/perl/perlcritic.vim create mode 100644 ale_linters/perl6/perl6.vim create mode 100644 ale_linters/php/cspell.vim create mode 100755 ale_linters/php/intelephense.vim create mode 100644 ale_linters/php/langserver.vim create mode 100644 ale_linters/php/phan.vim create mode 100644 ale_linters/php/php.vim create mode 100644 ale_linters/php/phpactor.vim create mode 100644 ale_linters/php/phpcs.vim create mode 100644 ale_linters/php/phpmd.vim create mode 100644 ale_linters/php/phpstan.vim create mode 100644 ale_linters/php/psalm.vim create mode 100644 ale_linters/php/tlint.vim create mode 100644 ale_linters/po/alex.vim create mode 100644 ale_linters/po/msgfmt.vim create mode 100644 ale_linters/po/proselint.vim create mode 100644 ale_linters/po/writegood.vim create mode 100644 ale_linters/pod/alex.vim create mode 100644 ale_linters/pod/proselint.vim create mode 100644 ale_linters/pod/writegood.vim create mode 100644 ale_linters/pony/ponyc.vim create mode 100644 ale_linters/powershell/cspell.vim create mode 100644 ale_linters/powershell/powershell.vim create mode 100644 ale_linters/powershell/psscriptanalyzer.vim create mode 100644 ale_linters/prolog/swipl.vim create mode 100644 ale_linters/proto/buf_lint.vim create mode 100644 ale_linters/proto/protoc_gen_lint.vim create mode 100644 ale_linters/proto/protolint.vim create mode 100644 ale_linters/pug/puglint.vim create mode 100644 ale_linters/puppet/languageserver.vim create mode 100644 ale_linters/puppet/puppet.vim create mode 100644 ale_linters/puppet/puppetlint.vim create mode 100644 ale_linters/purescript/ls.vim create mode 100644 ale_linters/pyrex/cython.vim create mode 100644 ale_linters/python/bandit.vim create mode 100644 ale_linters/python/cspell.vim create mode 100644 ale_linters/python/flake8.vim create mode 100644 ale_linters/python/flakehell.vim create mode 100644 ale_linters/python/jedils.vim create mode 100644 ale_linters/python/mypy.vim create mode 100644 ale_linters/python/prospector.vim create mode 100644 ale_linters/python/pycln.vim create mode 100644 ale_linters/python/pycodestyle.vim create mode 100644 ale_linters/python/pydocstyle.vim create mode 100644 ale_linters/python/pyflakes.vim create mode 100644 ale_linters/python/pylama.vim create mode 100644 ale_linters/python/pylint.vim create mode 100644 ale_linters/python/pylsp.vim create mode 100644 ale_linters/python/pyre.vim create mode 100644 ale_linters/python/pyright.vim create mode 100644 ale_linters/python/refurb.vim create mode 100644 ale_linters/python/ruff.vim create mode 100644 ale_linters/python/unimport.vim create mode 100644 ale_linters/python/vulture.vim create mode 100644 ale_linters/qml/qmlfmt.vim create mode 100644 ale_linters/qml/qmllint.vim create mode 100644 ale_linters/r/languageserver.vim create mode 100644 ale_linters/r/lintr.vim create mode 100644 ale_linters/racket/langserver.vim create mode 100644 ale_linters/racket/raco.vim create mode 100644 ale_linters/reason/ls.vim create mode 100644 ale_linters/reason/merlin.vim create mode 100644 ale_linters/reason/ols.vim create mode 100644 ale_linters/rego/cspell.vim create mode 100644 ale_linters/rego/opacheck.vim create mode 100644 ale_linters/review/redpen.vim create mode 100644 ale_linters/robot/rflint.vim create mode 100644 ale_linters/rst/alex.vim create mode 100644 ale_linters/rst/cspell.vim create mode 100644 ale_linters/rst/proselint.vim create mode 100644 ale_linters/rst/redpen.vim create mode 100644 ale_linters/rst/rstcheck.vim create mode 100644 ale_linters/rst/textlint.vim create mode 100644 ale_linters/rst/vale.vim create mode 100644 ale_linters/rst/writegood.vim create mode 100644 ale_linters/ruby/brakeman.vim create mode 100644 ale_linters/ruby/cspell.vim create mode 100644 ale_linters/ruby/debride.vim create mode 100644 ale_linters/ruby/packwerk.vim create mode 100644 ale_linters/ruby/rails_best_practices.vim create mode 100644 ale_linters/ruby/reek.vim create mode 100644 ale_linters/ruby/rubocop.vim create mode 100644 ale_linters/ruby/ruby.vim create mode 100644 ale_linters/ruby/solargraph.vim create mode 100644 ale_linters/ruby/sorbet.vim create mode 100644 ale_linters/ruby/standardrb.vim create mode 100644 ale_linters/ruby/steep.vim create mode 100644 ale_linters/rust/analyzer.vim create mode 100644 ale_linters/rust/cargo.vim create mode 100644 ale_linters/rust/cspell.vim create mode 100644 ale_linters/rust/rls.vim create mode 100644 ale_linters/rust/rustc.vim create mode 100644 ale_linters/salt/salt_lint.vim create mode 100644 ale_linters/sass/sasslint.vim create mode 100644 ale_linters/sass/stylelint.vim create mode 100644 ale_linters/scala/cspell.vim create mode 100644 ale_linters/scala/fsc.vim create mode 100644 ale_linters/scala/metals.vim create mode 100644 ale_linters/scala/sbtserver.vim create mode 100644 ale_linters/scala/scalac.vim create mode 100644 ale_linters/scala/scalastyle.vim create mode 100644 ale_linters/scss/sasslint.vim create mode 100644 ale_linters/scss/scsslint.vim create mode 100644 ale_linters/scss/stylelint.vim create mode 100644 ale_linters/sh/bashate.vim create mode 100644 ale_linters/sh/cspell.vim create mode 100644 ale_linters/sh/language_server.vim create mode 100644 ale_linters/sh/shell.vim create mode 100644 ale_linters/sh/shellcheck.vim create mode 100644 ale_linters/slim/slimlint.vim create mode 100644 ale_linters/sml/smlnj.vim create mode 100644 ale_linters/sml/smlnj_cm.vim create mode 100644 ale_linters/solidity/solc.vim create mode 100644 ale_linters/solidity/solhint.vim create mode 100644 ale_linters/solidity/solium.vim create mode 100644 ale_linters/spec/rpmlint.vim create mode 100644 ale_linters/sql/sqlfluff.vim create mode 100644 ale_linters/sql/sqlint.vim create mode 100644 ale_linters/sql/sqllint.vim create mode 100644 ale_linters/stylus/stylelint.vim create mode 100644 ale_linters/sugarss/stylelint.vim create mode 100644 ale_linters/svelte/svelteserver.vim create mode 100644 ale_linters/swift/appleswiftformat.vim create mode 100644 ale_linters/swift/cspell.vim create mode 100644 ale_linters/swift/sourcekitlsp.vim create mode 100644 ale_linters/swift/swiftlint.vim create mode 100644 ale_linters/systemd/systemd_analyze.vim create mode 100644 ale_linters/tcl/nagelfar.vim create mode 100644 ale_linters/terraform/checkov.vim create mode 100644 ale_linters/terraform/terraform.vim create mode 100644 ale_linters/terraform/terraform_ls.vim create mode 100644 ale_linters/terraform/terraform_lsp.vim create mode 100644 ale_linters/terraform/tflint.vim create mode 100644 ale_linters/terraform/tfsec.vim create mode 100644 ale_linters/testft/testlinter.vim create mode 100644 ale_linters/tex/alex.vim create mode 100644 ale_linters/tex/chktex.vim create mode 100644 ale_linters/tex/cspell.vim create mode 100644 ale_linters/tex/lacheck.vim create mode 100644 ale_linters/tex/proselint.vim create mode 100644 ale_linters/tex/redpen.vim create mode 100644 ale_linters/tex/texlab.vim create mode 100644 ale_linters/tex/textlint.vim create mode 100644 ale_linters/tex/vale.vim create mode 100644 ale_linters/tex/writegood.vim create mode 100644 ale_linters/texinfo/alex.vim create mode 100644 ale_linters/texinfo/cspell.vim create mode 100644 ale_linters/texinfo/proselint.vim create mode 100644 ale_linters/texinfo/writegood.vim create mode 100644 ale_linters/text/alex.vim create mode 100644 ale_linters/text/cspell.vim create mode 100644 ale_linters/text/languagetool.vim create mode 100644 ale_linters/text/proselint.vim create mode 100644 ale_linters/text/redpen.vim create mode 100644 ale_linters/text/textlint.vim create mode 100644 ale_linters/text/vale.vim create mode 100644 ale_linters/text/writegood.vim create mode 100644 ale_linters/thrift/thrift.vim create mode 100644 ale_linters/thrift/thriftcheck.vim create mode 100644 ale_linters/typescript/biome.vim create mode 100644 ale_linters/typescript/cspell.vim create mode 100644 ale_linters/typescript/deno.vim create mode 100644 ale_linters/typescript/eslint.vim create mode 100644 ale_linters/typescript/standard.vim create mode 100644 ale_linters/typescript/tslint.vim create mode 100644 ale_linters/typescript/tsserver.vim create mode 100644 ale_linters/typescript/typecheck.vim create mode 100644 ale_linters/typescript/xo.vim create mode 100644 ale_linters/v/v.vim create mode 100644 ale_linters/vala/vala_lint.vim create mode 100644 ale_linters/verilog/hdl_checker.vim create mode 100644 ale_linters/verilog/iverilog.vim create mode 100644 ale_linters/verilog/slang.vim create mode 100644 ale_linters/verilog/verilator.vim create mode 100644 ale_linters/verilog/vlog.vim create mode 100644 ale_linters/verilog/xvlog.vim create mode 100644 ale_linters/verilog/yosys.vim create mode 100644 ale_linters/vhdl/ghdl.vim create mode 100644 ale_linters/vhdl/hdl_checker.vim create mode 100644 ale_linters/vhdl/vcom.vim create mode 100644 ale_linters/vhdl/xvhdl.vim create mode 100644 ale_linters/vim/ale_custom_linting_rules.vim create mode 100644 ale_linters/vim/vimls.vim create mode 100644 ale_linters/vim/vint.vim create mode 100644 ale_linters/vue/cspell.vim create mode 100644 ale_linters/vue/vls.vim create mode 100644 ale_linters/vue/volar.vim create mode 100644 ale_linters/wgsl/naga.vim create mode 100644 ale_linters/xhtml/alex.vim create mode 100644 ale_linters/xhtml/cspell.vim create mode 100644 ale_linters/xhtml/proselint.vim create mode 100644 ale_linters/xhtml/writegood.vim create mode 100644 ale_linters/xml/xmllint.vim create mode 100644 ale_linters/yaml/actionlint.vim create mode 100644 ale_linters/yaml/circleci.vim create mode 100644 ale_linters/yaml/gitlablint.vim create mode 100644 ale_linters/yaml/ls.vim create mode 100644 ale_linters/yaml/spectral.vim create mode 100644 ale_linters/yaml/swaglint.vim create mode 100644 ale_linters/yaml/yamllint.vim create mode 100644 ale_linters/yaml/yq.vim create mode 100644 ale_linters/yang/yang_lsp.vim create mode 100644 ale_linters/yara/yls.vim create mode 100644 ale_linters/zeek/zeek.vim create mode 100644 ale_linters/zig/zlint.vim create mode 100644 ale_linters/zig/zls.vim create mode 100644 autoload/ale.vim create mode 100644 autoload/ale/ant.vim create mode 100644 autoload/ale/args.vim create mode 100644 autoload/ale/assert.vim create mode 100644 autoload/ale/balloon.vim create mode 100644 autoload/ale/c.vim create mode 100644 autoload/ale/code_action.vim create mode 100644 autoload/ale/codefix.vim create mode 100644 autoload/ale/command.vim create mode 100644 autoload/ale/completion.vim create mode 100644 autoload/ale/completion/python.vim create mode 100644 autoload/ale/cursor.vim create mode 100644 autoload/ale/d.vim create mode 100644 autoload/ale/debugging.vim create mode 100644 autoload/ale/definition.vim create mode 100644 autoload/ale/dhall.vim create mode 100644 autoload/ale/engine.vim create mode 100644 autoload/ale/engine/ignore.vim create mode 100644 autoload/ale/events.vim create mode 100644 autoload/ale/filename_mapping.vim create mode 100644 autoload/ale/filerename.vim create mode 100644 autoload/ale/filetypes.vim create mode 100644 autoload/ale/fix.vim create mode 100644 autoload/ale/fix/registry.vim create mode 100644 autoload/ale/fixers/alejandra.vim create mode 100644 autoload/ale/fixers/apkbuild_fixer.vim create mode 100644 autoload/ale/fixers/appleswiftformat.vim create mode 100644 autoload/ale/fixers/astyle.vim create mode 100644 autoload/ale/fixers/autoflake.vim create mode 100644 autoload/ale/fixers/autoimport.vim create mode 100644 autoload/ale/fixers/autopep8.vim create mode 100644 autoload/ale/fixers/bibclean.vim create mode 100644 autoload/ale/fixers/biome.vim create mode 100644 autoload/ale/fixers/black.vim create mode 100644 autoload/ale/fixers/brittany.vim create mode 100644 autoload/ale/fixers/buf_format.vim create mode 100644 autoload/ale/fixers/buildifier.vim create mode 100644 autoload/ale/fixers/clangformat.vim create mode 100644 autoload/ale/fixers/clangtidy.vim create mode 100644 autoload/ale/fixers/cljfmt.vim create mode 100644 autoload/ale/fixers/cmakeformat.vim create mode 100644 autoload/ale/fixers/crystal.vim create mode 100644 autoload/ale/fixers/css_beautify.vim create mode 100644 autoload/ale/fixers/dart_format.vim create mode 100644 autoload/ale/fixers/dartfmt.vim create mode 100644 autoload/ale/fixers/deno.vim create mode 100644 autoload/ale/fixers/dfmt.vim create mode 100644 autoload/ale/fixers/dhall_format.vim create mode 100644 autoload/ale/fixers/dhall_freeze.vim create mode 100644 autoload/ale/fixers/dhall_lint.vim create mode 100644 autoload/ale/fixers/djlint.vim create mode 100644 autoload/ale/fixers/dotnet_format.vim create mode 100644 autoload/ale/fixers/dprint.vim create mode 100644 autoload/ale/fixers/dune.vim create mode 100644 autoload/ale/fixers/elm_format.vim create mode 100644 autoload/ale/fixers/erbformatter.vim create mode 100644 autoload/ale/fixers/erblint.vim create mode 100644 autoload/ale/fixers/erlang_mode.vim create mode 100644 autoload/ale/fixers/erlfmt.vim create mode 100644 autoload/ale/fixers/eslint.vim create mode 100644 autoload/ale/fixers/fecs.vim create mode 100644 autoload/ale/fixers/fish_indent.vim create mode 100644 autoload/ale/fixers/fixjson.vim create mode 100644 autoload/ale/fixers/floskell.vim create mode 100644 autoload/ale/fixers/forge.vim create mode 100644 autoload/ale/fixers/fourmolu.vim create mode 100644 autoload/ale/fixers/generic.vim create mode 100644 autoload/ale/fixers/generic_python.vim create mode 100644 autoload/ale/fixers/gleam_format.vim create mode 100644 autoload/ale/fixers/gnatpp.vim create mode 100644 autoload/ale/fixers/gofmt.vim create mode 100644 autoload/ale/fixers/gofumpt.vim create mode 100644 autoload/ale/fixers/goimports.vim create mode 100644 autoload/ale/fixers/golangci_lint.vim create mode 100644 autoload/ale/fixers/golines.vim create mode 100644 autoload/ale/fixers/gomod.vim create mode 100644 autoload/ale/fixers/google_java_format.vim create mode 100644 autoload/ale/fixers/gopls.vim create mode 100644 autoload/ale/fixers/hackfmt.vim create mode 100644 autoload/ale/fixers/help.vim create mode 100644 autoload/ale/fixers/hfmt.vim create mode 100644 autoload/ale/fixers/hindent.vim create mode 100644 autoload/ale/fixers/hlint.vim create mode 100644 autoload/ale/fixers/html_beautify.vim create mode 100644 autoload/ale/fixers/htmlbeautifier.vim create mode 100644 autoload/ale/fixers/hurlfmt.vim create mode 100644 autoload/ale/fixers/importjs.vim create mode 100644 autoload/ale/fixers/isort.vim create mode 100644 autoload/ale/fixers/jq.vim create mode 100644 autoload/ale/fixers/json_pytool.vim create mode 100644 autoload/ale/fixers/jsonnetfmt.vim create mode 100644 autoload/ale/fixers/ktlint.vim create mode 100644 autoload/ale/fixers/kulala_fmt.vim create mode 100644 autoload/ale/fixers/latexindent.vim create mode 100644 autoload/ale/fixers/lua_format.vim create mode 100644 autoload/ale/fixers/luafmt.vim create mode 100644 autoload/ale/fixers/mix_format.vim create mode 100644 autoload/ale/fixers/nickel_format.vim create mode 100644 autoload/ale/fixers/nimpretty.vim create mode 100644 autoload/ale/fixers/nixfmt.vim create mode 100644 autoload/ale/fixers/nixpkgsfmt.vim create mode 100644 autoload/ale/fixers/npmgroovylint.vim create mode 100644 autoload/ale/fixers/ocamlformat.vim create mode 100644 autoload/ale/fixers/ocp_indent.vim create mode 100644 autoload/ale/fixers/opafmt.vim create mode 100644 autoload/ale/fixers/ormolu.vim create mode 100644 autoload/ale/fixers/packer.vim create mode 100644 autoload/ale/fixers/pandoc.vim create mode 100644 autoload/ale/fixers/perltidy.vim create mode 100644 autoload/ale/fixers/pgformatter.vim create mode 100644 autoload/ale/fixers/php_cs_fixer.vim create mode 100644 autoload/ale/fixers/phpcbf.vim create mode 100644 autoload/ale/fixers/pint.vim create mode 100644 autoload/ale/fixers/prettier.vim create mode 100644 autoload/ale/fixers/prettier_eslint.vim create mode 100644 autoload/ale/fixers/prettier_standard.vim create mode 100644 autoload/ale/fixers/protolint.vim create mode 100644 autoload/ale/fixers/ptop.vim create mode 100644 autoload/ale/fixers/puppetlint.vim create mode 100644 autoload/ale/fixers/purs_tidy.vim create mode 100644 autoload/ale/fixers/purty.vim create mode 100644 autoload/ale/fixers/pycln.vim create mode 100644 autoload/ale/fixers/pyflyby.vim create mode 100644 autoload/ale/fixers/qmlfmt.vim create mode 100644 autoload/ale/fixers/raco_fmt.vim create mode 100644 autoload/ale/fixers/refmt.vim create mode 100644 autoload/ale/fixers/remark_lint.vim create mode 100644 autoload/ale/fixers/reorder_python_imports.vim create mode 100644 autoload/ale/fixers/rubocop.vim create mode 100644 autoload/ale/fixers/rubyfmt.vim create mode 100644 autoload/ale/fixers/ruff.vim create mode 100644 autoload/ale/fixers/ruff_format.vim create mode 100644 autoload/ale/fixers/rufo.vim create mode 100644 autoload/ale/fixers/rustfmt.vim create mode 100644 autoload/ale/fixers/rustywind.vim create mode 100644 autoload/ale/fixers/scadformat.vim create mode 100644 autoload/ale/fixers/scalafmt.vim create mode 100644 autoload/ale/fixers/shfmt.vim create mode 100644 autoload/ale/fixers/sorbet.vim create mode 100644 autoload/ale/fixers/sqlfluff.vim create mode 100644 autoload/ale/fixers/sqlfmt.vim create mode 100644 autoload/ale/fixers/sqlformat.vim create mode 100644 autoload/ale/fixers/standard.vim create mode 100644 autoload/ale/fixers/standardrb.vim create mode 100644 autoload/ale/fixers/statix.vim create mode 100644 autoload/ale/fixers/stylelint.vim create mode 100644 autoload/ale/fixers/styler.vim create mode 100644 autoload/ale/fixers/stylish_haskell.vim create mode 100644 autoload/ale/fixers/stylua.vim create mode 100644 autoload/ale/fixers/swiftformat.vim create mode 100644 autoload/ale/fixers/syntax_tree.vim create mode 100644 autoload/ale/fixers/terraform.vim create mode 100644 autoload/ale/fixers/textlint.vim create mode 100644 autoload/ale/fixers/tidy.vim create mode 100644 autoload/ale/fixers/tslint.vim create mode 100644 autoload/ale/fixers/typstyle.vim create mode 100644 autoload/ale/fixers/uncrustify.vim create mode 100644 autoload/ale/fixers/vfmt.vim create mode 100644 autoload/ale/fixers/xmllint.vim create mode 100644 autoload/ale/fixers/xo.vim create mode 100644 autoload/ale/fixers/yamlfix.vim create mode 100644 autoload/ale/fixers/yamlfmt.vim create mode 100644 autoload/ale/fixers/yapf.vim create mode 100644 autoload/ale/fixers/yq.vim create mode 100644 autoload/ale/fixers/zigfmt.vim create mode 100644 autoload/ale/floating_preview.vim create mode 100644 autoload/ale/go.vim create mode 100644 autoload/ale/gradle.vim create mode 100644 autoload/ale/gradle/init.gradle create mode 100644 autoload/ale/handlers/alex.vim create mode 100644 autoload/ale/handlers/atools.vim create mode 100644 autoload/ale/handlers/biome.vim create mode 100644 autoload/ale/handlers/c3lsp.vim create mode 100644 autoload/ale/handlers/cairo.vim create mode 100644 autoload/ale/handlers/ccls.vim create mode 100644 autoload/ale/handlers/cppcheck.vim create mode 100644 autoload/ale/handlers/cpplint.vim create mode 100644 autoload/ale/handlers/cspell.vim create mode 100644 autoload/ale/handlers/css.vim create mode 100644 autoload/ale/handlers/deadnix.vim create mode 100644 autoload/ale/handlers/deno.vim create mode 100644 autoload/ale/handlers/djlint.vim create mode 100644 autoload/ale/handlers/elixir.vim create mode 100644 autoload/ale/handlers/embertemplatelint.vim create mode 100644 autoload/ale/handlers/eslint.vim create mode 100644 autoload/ale/handlers/fecs.vim create mode 100644 autoload/ale/handlers/flawfinder.vim create mode 100644 autoload/ale/handlers/gawk.vim create mode 100644 autoload/ale/handlers/gcc.vim create mode 100644 autoload/ale/handlers/go.vim create mode 100644 autoload/ale/handlers/haskell.vim create mode 100644 autoload/ale/handlers/haskell_stack.vim create mode 100644 autoload/ale/handlers/hdl_checker.vim create mode 100644 autoload/ale/handlers/hlint.vim create mode 100644 autoload/ale/handlers/inko.vim create mode 100644 autoload/ale/handlers/ktlint.vim create mode 100644 autoload/ale/handlers/languagetool.vim create mode 100644 autoload/ale/handlers/markdownlint.vim create mode 100644 autoload/ale/handlers/naga.vim create mode 100644 autoload/ale/handlers/ocamllsp.vim create mode 100644 autoload/ale/handlers/ols.vim create mode 100644 autoload/ale/handlers/openscad.vim create mode 100644 autoload/ale/handlers/pony.vim create mode 100644 autoload/ale/handlers/redpen.vim create mode 100644 autoload/ale/handlers/ruby.vim create mode 100644 autoload/ale/handlers/rust.vim create mode 100644 autoload/ale/handlers/scala.vim create mode 100644 autoload/ale/handlers/sh.vim create mode 100644 autoload/ale/handlers/shellcheck.vim create mode 100644 autoload/ale/handlers/sml.vim create mode 100644 autoload/ale/handlers/spectral.vim create mode 100644 autoload/ale/handlers/statix.vim create mode 100644 autoload/ale/handlers/textlint.vim create mode 100644 autoload/ale/handlers/tslint.vim create mode 100644 autoload/ale/handlers/tsserver.vim create mode 100644 autoload/ale/handlers/unix.vim create mode 100644 autoload/ale/handlers/vale.vim create mode 100644 autoload/ale/handlers/writegood.vim create mode 100644 autoload/ale/handlers/xo.vim create mode 100644 autoload/ale/handlers/yamllint.vim create mode 100644 autoload/ale/highlight.vim create mode 100644 autoload/ale/history.vim create mode 100644 autoload/ale/hover.vim create mode 100644 autoload/ale/java.vim create mode 100644 autoload/ale/job.vim create mode 100644 autoload/ale/julia.vim create mode 100644 autoload/ale/linter.vim create mode 100644 autoload/ale/list.vim create mode 100644 autoload/ale/loclist_jumping.vim create mode 100644 autoload/ale/lsp.vim create mode 100644 autoload/ale/lsp/message.vim create mode 100644 autoload/ale/lsp/reset.vim create mode 100644 autoload/ale/lsp/response.vim create mode 100644 autoload/ale/lsp/tsserver_message.vim create mode 100644 autoload/ale/lsp_linter.vim create mode 100644 autoload/ale/lsp_window.vim create mode 100644 autoload/ale/lua.vim create mode 100644 autoload/ale/maven.vim create mode 100644 autoload/ale/node.vim create mode 100644 autoload/ale/organize_imports.vim create mode 100644 autoload/ale/other_source.vim create mode 100644 autoload/ale/path.vim create mode 100644 autoload/ale/pattern_options.vim create mode 100644 autoload/ale/powershell.vim create mode 100644 autoload/ale/preview.vim create mode 100644 autoload/ale/python.vim create mode 100644 autoload/ale/racket.vim create mode 100644 autoload/ale/references.vim create mode 100644 autoload/ale/rename.vim create mode 100644 autoload/ale/ruby.vim create mode 100644 autoload/ale/semver.vim create mode 100644 autoload/ale/sign.vim create mode 100644 autoload/ale/socket.vim create mode 100644 autoload/ale/statusline.vim create mode 100644 autoload/ale/swift.vim create mode 100644 autoload/ale/symbol.vim create mode 100644 autoload/ale/test.vim create mode 100644 autoload/ale/toggle.vim create mode 100644 autoload/ale/uri.vim create mode 100644 autoload/ale/uri/jdt.vim create mode 100644 autoload/ale/util.vim create mode 100644 autoload/ale/virtualtext.vim create mode 100644 autoload/asyncomplete/sources/ale.vim create mode 100644 doc/ale-ada.txt create mode 100644 doc/ale-ansible.txt create mode 100644 doc/ale-apkbuild.txt create mode 100644 doc/ale-asciidoc.txt create mode 100644 doc/ale-asm.txt create mode 100644 doc/ale-astro.txt create mode 100644 doc/ale-avra.txt create mode 100644 doc/ale-awk.txt create mode 100644 doc/ale-bats.txt create mode 100644 doc/ale-bazel.txt create mode 100644 doc/ale-bib.txt create mode 100644 doc/ale-bicep.txt create mode 100644 doc/ale-bitbake.txt create mode 100644 doc/ale-c.txt create mode 100644 doc/ale-c3.txt create mode 100644 doc/ale-cairo.txt create mode 100644 doc/ale-chef.txt create mode 100644 doc/ale-clojure.txt create mode 100644 doc/ale-cloudformation.txt create mode 100644 doc/ale-cmake.txt create mode 100644 doc/ale-cpp.txt create mode 100644 doc/ale-cs.txt create mode 100644 doc/ale-css.txt create mode 100644 doc/ale-cuda.txt create mode 100644 doc/ale-d.txt create mode 100644 doc/ale-dafny.txt create mode 100644 doc/ale-dart.txt create mode 100644 doc/ale-desktop.txt create mode 100644 doc/ale-development.txt create mode 100644 doc/ale-dhall.txt create mode 100644 doc/ale-dockerfile.txt create mode 100644 doc/ale-elixir.txt create mode 100644 doc/ale-elm.txt create mode 100644 doc/ale-erlang.txt create mode 100644 doc/ale-eruby.txt create mode 100644 doc/ale-fish.txt create mode 100644 doc/ale-fortran.txt create mode 100644 doc/ale-fountain.txt create mode 100644 doc/ale-fuse.txt create mode 100644 doc/ale-gitcommit.txt create mode 100644 doc/ale-gleam.txt create mode 100644 doc/ale-glsl.txt create mode 100644 doc/ale-go.txt create mode 100644 doc/ale-gohtmltmpl.txt create mode 100644 doc/ale-graphql.txt create mode 100644 doc/ale-groovy.txt create mode 100644 doc/ale-hack.txt create mode 100644 doc/ale-handlebars.txt create mode 100644 doc/ale-haskell.txt create mode 100644 doc/ale-hcl.txt create mode 100644 doc/ale-help.txt create mode 100644 doc/ale-html.txt create mode 100644 doc/ale-htmlangular.txt create mode 100644 doc/ale-htmldjango.txt create mode 100644 doc/ale-http.txt create mode 100644 doc/ale-hurl.txt create mode 100644 doc/ale-idris.txt create mode 100644 doc/ale-ink.txt create mode 100644 doc/ale-inko.txt create mode 100644 doc/ale-ispc.txt create mode 100644 doc/ale-java.txt create mode 100644 doc/ale-javascript.txt create mode 100644 doc/ale-jinja.txt create mode 100644 doc/ale-json.txt create mode 100644 doc/ale-json5.txt create mode 100644 doc/ale-jsonc.txt create mode 100644 doc/ale-jsonnet.txt create mode 100644 doc/ale-julia.txt create mode 100644 doc/ale-kotlin.txt create mode 100644 doc/ale-latex.txt create mode 100644 doc/ale-less.txt create mode 100644 doc/ale-llvm.txt create mode 100644 doc/ale-lua.txt create mode 100644 doc/ale-make.txt create mode 100644 doc/ale-markdown.txt create mode 100644 doc/ale-mercury.txt create mode 100644 doc/ale-nasm.txt create mode 100644 doc/ale-nickel.txt create mode 100644 doc/ale-nim.txt create mode 100644 doc/ale-nix.txt create mode 100644 doc/ale-nroff.txt create mode 100644 doc/ale-nunjucks.txt create mode 100644 doc/ale-objc.txt create mode 100644 doc/ale-objcpp.txt create mode 100644 doc/ale-ocaml.txt create mode 100644 doc/ale-odin.txt create mode 100644 doc/ale-openapi.txt create mode 100644 doc/ale-openscad.txt create mode 100644 doc/ale-packer.txt create mode 100644 doc/ale-pascal.txt create mode 100644 doc/ale-pawn.txt create mode 100644 doc/ale-perl.txt create mode 100644 doc/ale-perl6.txt create mode 100644 doc/ale-php.txt create mode 100644 doc/ale-po.txt create mode 100644 doc/ale-pod.txt create mode 100644 doc/ale-pony.txt create mode 100644 doc/ale-powershell.txt create mode 100644 doc/ale-prolog.txt create mode 100644 doc/ale-proto.txt create mode 100644 doc/ale-pug.txt create mode 100644 doc/ale-puppet.txt create mode 100644 doc/ale-purescript.txt create mode 100644 doc/ale-pyrex.txt create mode 100644 doc/ale-python.txt create mode 100644 doc/ale-qml.txt create mode 100644 doc/ale-r.txt create mode 100644 doc/ale-racket.txt create mode 100644 doc/ale-reasonml.txt create mode 100644 doc/ale-rego.txt create mode 100644 doc/ale-rest.txt create mode 100644 doc/ale-restructuredtext.txt create mode 100644 doc/ale-robot.txt create mode 100644 doc/ale-ruby.txt create mode 100644 doc/ale-rust.txt create mode 100644 doc/ale-salt.tmt create mode 100644 doc/ale-sass.txt create mode 100644 doc/ale-scala.txt create mode 100644 doc/ale-scss.txt create mode 100644 doc/ale-sh.txt create mode 100644 doc/ale-sml.txt create mode 100644 doc/ale-solidity.txt create mode 100644 doc/ale-spec.txt create mode 100644 doc/ale-sql.txt create mode 100644 doc/ale-stylus.txt create mode 100644 doc/ale-sugarss.txt create mode 100644 doc/ale-supported-languages-and-tools.txt create mode 100644 doc/ale-svelte.txt create mode 100644 doc/ale-swift.txt create mode 100644 doc/ale-systemd.txt create mode 100644 doc/ale-tcl.txt create mode 100644 doc/ale-terraform.txt create mode 100644 doc/ale-tex.txt create mode 100644 doc/ale-texinfo.txt create mode 100644 doc/ale-text.txt create mode 100644 doc/ale-thrift.txt create mode 100644 doc/ale-toml.txt create mode 100644 doc/ale-typescript.txt create mode 100644 doc/ale-typst.html create mode 100644 doc/ale-v.txt create mode 100644 doc/ale-vala.txt create mode 100644 doc/ale-verilog.txt create mode 100644 doc/ale-vhdl.txt create mode 100644 doc/ale-vim-help.txt create mode 100644 doc/ale-vim.txt create mode 100644 doc/ale-vue.txt create mode 100644 doc/ale-wgsl.txt create mode 100644 doc/ale-xhtml.txt create mode 100644 doc/ale-xml.txt create mode 100644 doc/ale-yaml.txt create mode 100644 doc/ale-yang.txt create mode 100644 doc/ale-yara.txt create mode 100644 doc/ale-zeek.txt create mode 100644 doc/ale-zig.txt create mode 100644 doc/ale.txt create mode 100644 ftplugin/ale-fix-suggest.vim create mode 100644 ftplugin/ale-info.vim create mode 100644 ftplugin/ale-preview-selection.vim create mode 100644 ftplugin/ale-preview.vim create mode 100644 lspconfig.vim create mode 100644 lua/ale/diagnostics.lua create mode 100644 lua/ale/init.lua create mode 100644 lua/ale/lsp.lua create mode 100644 lua/ale/util.lua create mode 100644 plugin/ale.vim create mode 100644 rplugin/python3/deoplete/sources/ale.py create mode 100755 run-tests create mode 100644 run-tests.bat create mode 100644 supported-tools.md create mode 100644 syntax/ale-fix-suggest.vim create mode 100644 syntax/ale-info.vim create mode 100644 syntax/ale-preview-selection.vim create mode 100644 test-files/python/no_uv/whatever.py create mode 120000 test/.config/nvim/init.vim create mode 100644 test/completion/test_ale_import_command.vader create mode 100644 test/completion/test_complete_events.vader create mode 100644 test/completion/test_completion_events.vader create mode 100644 test/completion/test_completion_filtering.vader create mode 100644 test/completion/test_completion_prefixes.vader create mode 100644 test/completion/test_lsp_completion_messages.vader create mode 100644 test/completion/test_lsp_completion_parsing.vader create mode 100644 test/completion/test_omnifunc_completion.vader create mode 100644 test/completion/test_public_completion_api.vader create mode 100644 test/completion/test_tsserver_completion_parsing.vader create mode 100644 test/fix/test_ale_fix.vader create mode 100644 test/fix/test_ale_fix_aliases.vader create mode 100644 test/fix/test_ale_fix_completion.vader create mode 100644 test/fix/test_ale_fix_completion_filter.vader create mode 100644 test/fix/test_ale_fix_ignore.vader create mode 100644 test/fix/test_ale_fix_suggest.vader create mode 100644 test/fixers/test_alejandra_fixer_callback.vader create mode 100644 test/fixers/test_apkbuild_fixer_callback.vader create mode 100644 test/fixers/test_appleswiftformat_fixer_callback.vader create mode 100644 test/fixers/test_astyle_fixer_callback.vader create mode 100644 test/fixers/test_autoflake_fixer_callback.vader create mode 100644 test/fixers/test_autoimport_fixer_callback.vader create mode 100644 test/fixers/test_autopep8_fixer_callback.vader create mode 100644 test/fixers/test_bibclean_fixer_callback.vader create mode 100644 test/fixers/test_biome_fixer_callback.vader create mode 100644 test/fixers/test_black_fixer_callback.vader create mode 100644 test/fixers/test_break_up_long_lines_python_fixer.vader create mode 100644 test/fixers/test_brittany_fixer_callback.vader create mode 100644 test/fixers/test_buf_format_fixer_callback.vader create mode 100644 test/fixers/test_buildifier_fixer_callback.vader create mode 100644 test/fixers/test_clangformat_fixer_callback.vader create mode 100644 test/fixers/test_clangtidy_fixer_callback.vader create mode 100644 test/fixers/test_cljfmt_fixer_callback.vader create mode 100644 test/fixers/test_cmakeformat_fixer_callback.vader create mode 100644 test/fixers/test_crystal_format_fixer_callback.vader create mode 100644 test/fixers/test_css_beautify_fixer_callback.vader create mode 100644 test/fixers/test_dart_format_fixer_callback.vader create mode 100644 test/fixers/test_dartfmt_fixer_callback.vader create mode 100644 test/fixers/test_dfmt_fixer_callback.vader create mode 100644 test/fixers/test_dhall_format_fixer_callback.vader create mode 100644 test/fixers/test_dhall_freeze_fixer_callback.vader create mode 100644 test/fixers/test_dhall_lint_fixer_callback.vader create mode 100644 test/fixers/test_djlint_fixer_callback.vader create mode 100644 test/fixers/test_dotnet_format_fixer_callback.vader create mode 100644 test/fixers/test_dprint_fixer_callback.vader create mode 100644 test/fixers/test_dune_fixer_callback.vader create mode 100644 test/fixers/test_elm_format_fixer_callback.vader create mode 100644 test/fixers/test_erbformatter_fixer_callback.vader create mode 100644 test/fixers/test_erblint_fixer_callback.vader create mode 100644 test/fixers/test_erlang_mode_fixer_callback.vader create mode 100644 test/fixers/test_erlfmt_fixer_callback.vader create mode 100644 test/fixers/test_eslint_fixer_callback.vader create mode 100644 test/fixers/test_fecs_fixer_callback.vader create mode 100644 test/fixers/test_fish_indent_fixer_callback.vader create mode 100644 test/fixers/test_fixjson_fixer_callback.vader create mode 100644 test/fixers/test_floskell_fixer_callback.vader create mode 100644 test/fixers/test_forge_fixer_callback.vader create mode 100644 test/fixers/test_fourmolu_fixer_callback.vader create mode 100644 test/fixers/test_gleam_format_fixer_callback.vader create mode 100644 test/fixers/test_gnatpp_fixer_callback.vader create mode 100644 test/fixers/test_gofmt_fixer_callback.vader create mode 100644 test/fixers/test_gofumpt_fixer.vader create mode 100644 test/fixers/test_goimports_fixer_callback.vader create mode 100644 test/fixers/test_golangci_lint_fixer_callback.vader create mode 100644 test/fixers/test_golines_fixer_callback.vader create mode 100644 test/fixers/test_gomod_fixer_callback.vader create mode 100644 test/fixers/test_goofle_java_format_fixer_callback.vader create mode 100644 test/fixers/test_gopls_fixer_callback.vader create mode 100644 test/fixers/test_hackfmt_fixer_callback.vader create mode 100644 test/fixers/test_hfmt_fixer_callback.vader create mode 100644 test/fixers/test_hindent_fixer_callback.vader create mode 100644 test/fixers/test_hlint_fixer_callback.vader create mode 100644 test/fixers/test_html_beautify_fixer_callback.vader create mode 100644 test/fixers/test_htmlbeautifier_fixer_callback.vader create mode 100644 test/fixers/test_hurlfmt_fixer_callback.vader create mode 100644 test/fixers/test_importjs_fixer_callback.vader create mode 100644 test/fixers/test_isort_fixer_callback.vader create mode 100644 test/fixers/test_jq_fixer_callback.vader create mode 100644 test/fixers/test_jsonnetfmt_fixer_callback.vader create mode 100644 test/fixers/test_ktlint_fixer_callback.vader create mode 100644 test/fixers/test_kulala_fmt_fixer_callback.vader create mode 100644 test/fixers/test_latexindent_fixer_callback.vader create mode 100644 test/fixers/test_lua_format_fixer_callback.vader create mode 100644 test/fixers/test_luafmt_fixer_callback.vader create mode 100644 test/fixers/test_mix_format_fixer_callback.vader create mode 100644 test/fixers/test_nickel_format_fixer_callback.vader create mode 100644 test/fixers/test_nimpretty_fixer_callback.vader create mode 100644 test/fixers/test_nixfmt_fixer_callback.vader create mode 100644 test/fixers/test_nixpkgsfmt_fixer_callback.vader create mode 100644 test/fixers/test_npmgroovylint_fixer_callback.vader create mode 100644 test/fixers/test_ocamlformat_fixer_callback.vader create mode 100644 test/fixers/test_ocp_indent_fixer_callback.vader create mode 100644 test/fixers/test_opa_fmt_fixer_callback.vader create mode 100644 test/fixers/test_ormolu_fixer_callback.vader create mode 100644 test/fixers/test_packer_fmt_fixer_callback.vader create mode 100644 test/fixers/test_pandoc_fixer_callback.vader create mode 100644 test/fixers/test_perltidy_fixer_callback.vader create mode 100644 test/fixers/test_pgformatter_fixer_callback.vader create mode 100644 test/fixers/test_php_cs_fixer.vader create mode 100644 test/fixers/test_phpcbf_fixer_callback.vader create mode 100644 test/fixers/test_pint_fixer.vader create mode 100644 test/fixers/test_prettier_eslint_fixer.callback.vader create mode 100644 test/fixers/test_prettier_fixer_callback.vader create mode 100644 test/fixers/test_prettier_standard_callback.vader create mode 100644 test/fixers/test_protolint_fixer_callback.vader create mode 100644 test/fixers/test_ptop_fixer_callback.vader create mode 100644 test/fixers/test_puppetlint_fixer_callback.vader create mode 100644 test/fixers/test_purs_tidy_fixer_callback.vader create mode 100644 test/fixers/test_purty_fixer_callback.vader create mode 100644 test/fixers/test_pycln_fixer_callback.vader create mode 100644 test/fixers/test_pyflyby_fixer_callback.vader create mode 100644 test/fixers/test_python_add_blank_lines_fixer.vader create mode 100644 test/fixers/test_qmlfmt_fixer_callback.vader create mode 100644 test/fixers/test_raco_fmt_fixer_callback.vader create mode 100644 test/fixers/test_refmt_fixer_callback.vader create mode 100644 test/fixers/test_remark_lint_fixer_callback.vader create mode 100644 test/fixers/test_reorder_python_imports_fixer_callback.vader create mode 100644 test/fixers/test_rubocop_fixer_callback.vader create mode 100644 test/fixers/test_rubyfmt_fixer_callback.vader create mode 100644 test/fixers/test_ruff_fixer_callback.vader create mode 100644 test/fixers/test_ruff_format_fixer_callback.vader create mode 100644 test/fixers/test_rufo_fixer_callback.vader create mode 100644 test/fixers/test_rustfmt_fixer_callback.vader create mode 100644 test/fixers/test_rustywind_fixer_callback.vader create mode 100644 test/fixers/test_scadformat_fixer.vader create mode 100644 test/fixers/test_scalafmt_fixer_callback.vader create mode 100644 test/fixers/test_shfmt_fixer_callback.vader create mode 100644 test/fixers/test_sorbet_fixer_callback.vader create mode 100644 test/fixers/test_sqlfmt_fixer_callback.vader create mode 100644 test/fixers/test_sqlformat_fixer_callback.vader create mode 100644 test/fixers/test_standard_fixer_callback.vader create mode 100644 test/fixers/test_standardrb_fixer_callback.vader create mode 100644 test/fixers/test_statix_fixer.vader create mode 100644 test/fixers/test_stylelint_fixer_callback.vader create mode 100644 test/fixers/test_styler_fixer_callback.vader create mode 100644 test/fixers/test_stylish_haskell_fixer_callback.vader create mode 100644 test/fixers/test_stylua_fixer_callback.vader create mode 100644 test/fixers/test_swiftformat_fixer_callback.vader create mode 100644 test/fixers/test_syntax_tree_fixer_callback.vader create mode 100644 test/fixers/test_terraform_fmt_fixer_callback.vader create mode 100644 test/fixers/test_textlint_fixer_callback.vader create mode 100644 test/fixers/test_tidy_fixer_callback.vader create mode 100644 test/fixers/test_trim_whitespace.vader create mode 100644 test/fixers/test_tslint_fixer_callback.vader create mode 100644 test/fixers/test_typstyle_fixer_callback.vader create mode 100644 test/fixers/test_uncrustify_fixer_callback.vader create mode 100644 test/fixers/test_vfmt_fixer_callback.vader create mode 100644 test/fixers/test_vim_help_tags_alignment_fixer.vader create mode 100644 test/fixers/test_xmllint_fixer_callback.vader create mode 100644 test/fixers/test_xo_fixer_callback.vader create mode 100644 test/fixers/test_xots_fixer_callback.vader create mode 100644 test/fixers/test_yamlfix_fixer_callback.vader create mode 100644 test/fixers/test_yamlfmt_fixer_callback.vader create mode 100644 test/fixers/test_yapf_fixer_callback.vader create mode 100644 test/fixers/test_zigfmt_fixer_callback.vader create mode 100644 test/handler/test_actionlint_handler.vader create mode 100644 test/handler/test_ada_gcc_handler.vader create mode 100644 test/handler/test_alex_handler.vader create mode 100644 test/handler/test_ameba_handler.vader create mode 100644 test/handler/test_ansible_lint_handler.vader create mode 100644 test/handler/test_appleswiftformat_handler.vader create mode 100644 test/handler/test_asm_handler.vader create mode 100644 test/handler/test_atools_handler.vader create mode 100644 test/handler/test_avra_handler.vader create mode 100644 test/handler/test_bandit_handler.vader create mode 100644 test/handler/test_bashate_handler.vader create mode 100644 test/handler/test_bibclean_handler.vader create mode 100644 test/handler/test_bicep_az_bicep_handler.vader create mode 100644 test/handler/test_bicep_bicep_handler.vader create mode 100644 test/handler/test_bitbake_oelint_adv_handler.vader create mode 100644 test/handler/test_brakeman_handler.vader create mode 100644 test/handler/test_buildifier_handler.vader create mode 100644 test/handler/test_cfn_python_lint_handler.vader create mode 100644 test/handler/test_checkmake_handler.vader create mode 100644 test/handler/test_checkov_handler.vader create mode 100644 test/handler/test_checkstyle_handler.vader create mode 100644 test/handler/test_circleci_handler.vader create mode 100644 test/handler/test_clang_handler.vader create mode 100644 test/handler/test_clojure_clj_kondo_handler.vader create mode 100644 test/handler/test_clojure_joker_handler.vader create mode 100644 test/handler/test_cmake_lint_handler.vader create mode 100644 test/handler/test_coffeelint_handler.vader create mode 100644 test/handler/test_common_handlers.vader create mode 100644 test/handler/test_cookstyle_handler.vader create mode 100644 test/handler/test_cppcheck_handler.vader create mode 100644 test/handler/test_cpplint_handler.vader create mode 100644 test/handler/test_credo_handler.vader create mode 100644 test/handler/test_crystal_handler.vader create mode 100644 test/handler/test_csc_handler.vader create mode 100644 test/handler/test_cspell_handler.vader create mode 100644 test/handler/test_cucumber_handler.vader create mode 100644 test/handler/test_cuda_nvcc_handler.vader create mode 100644 test/handler/test_cypher_lint_handler.vader create mode 100644 test/handler/test_dafny_handler.vader create mode 100644 test/handler/test_dart_analyze_handler.vader create mode 100644 test/handler/test_deadnix_handler.vader create mode 100644 test/handler/test_debride_handler.vader create mode 100644 test/handler/test_desktop_file_validate_handler.vader create mode 100644 test/handler/test_djlint_handler.vader create mode 100644 test/handler/test_dmd_handler.vader create mode 100644 test/handler/test_dockerfile_lint_handler.vader create mode 100644 test/handler/test_dockerlinter_handler.vader create mode 100644 test/handler/test_dogma_handler.vader create mode 100644 test/handler/test_drafter_handler.vader create mode 100644 test/handler/test_elmmake_handler.vader create mode 100644 test/handler/test_embertemplatelint_handler.vader create mode 100644 test/handler/test_erblint_handler.vader create mode 100644 test/handler/test_erlang_dialyzer_handler.vader create mode 100644 test/handler/test_erlang_elvis_handler.vader create mode 100644 test/handler/test_eslint_handler.vader create mode 100644 test/handler/test_eslint_json_handler.vader create mode 100644 test/handler/test_fecs_handler.vader create mode 100644 test/handler/test_fish_handler.vader create mode 100644 test/handler/test_flake8_handler.vader create mode 100644 test/handler/test_flakehell_handler.vader create mode 100644 test/handler/test_flawfinder_handler.vader create mode 100644 test/handler/test_flow_handler.vader create mode 100644 test/handler/test_foodcritic_handler.vader create mode 100644 test/handler/test_fortran_handler.vader create mode 100644 test/handler/test_gawk_handler.vader create mode 100644 test/handler/test_gcc_handler.vader create mode 100644 test/handler/test_ghc_handler.vader create mode 100644 test/handler/test_ghc_mod_handler.vader create mode 100644 test/handler/test_ghdl_handler.vader create mode 100644 test/handler/test_gitlablint_handler.vader create mode 100644 test/handler/test_gitlint_handler.vader create mode 100644 test/handler/test_glslang_handler.vader create mode 100644 test/handler/test_go_generic_handler.vader create mode 100644 test/handler/test_gobuild_handler.vader create mode 100644 test/handler/test_golangci_lint_handler.vader create mode 100644 test/handler/test_hadolint.vader create mode 100644 test/handler/test_haskell_stack_handler.vader create mode 100644 test/handler/test_hlint_handler.vader create mode 100644 test/handler/test_hurlfmt_handler.vader create mode 100644 test/handler/test_ibm_openapi_validator_handler.vader create mode 100644 test/handler/test_idris_handler.vader create mode 100644 test/handler/test_inko_handler.vader create mode 100644 test/handler/test_ispc_ispc_handler.vader create mode 100644 test/handler/test_javac_handler.vader create mode 100644 test/handler/test_jq_handler.vader create mode 100644 test/handler/test_jscs_handler.vader create mode 100644 test/handler/test_ktlint_handler.vader create mode 100644 test/handler/test_lacheck_handler.vader create mode 100644 test/handler/test_languagetool_handler.vader create mode 100644 test/handler/test_lessc_handler.vader create mode 100644 test/handler/test_llc_handler.vader create mode 100644 test/handler/test_llvm_mc_handler.vader create mode 100644 test/handler/test_lua_selene_handler.vader create mode 100644 test/handler/test_luac_handler.vader create mode 100644 test/handler/test_luacheck_handler.vader create mode 100644 test/handler/test_markdownlint_handler.vader create mode 100644 test/handler/test_mcs_handler.vader create mode 100644 test/handler/test_mcsc_handler.vader create mode 100644 test/handler/test_mdl_handler.vader create mode 100644 test/handler/test_mercury_mmc_handler.vader create mode 100644 test/handler/test_mix_handler.vader create mode 100644 test/handler/test_msgfmt_hander.vader create mode 100644 test/handler/test_mypy_handler.vader create mode 100644 test/handler/test_naga_handler.vader create mode 100644 test/handler/test_nagelfar_handler.vader create mode 100644 test/handler/test_nasm_handler.vader create mode 100644 test/handler/test_nim_handler.vader create mode 100644 test/handler/test_nix_handler.vader create mode 100644 test/handler/test_npmgroovylint_handler.vader create mode 100644 test/handler/test_openscad_handler.vader create mode 100644 test/handler/test_packwerk_handler.vader create mode 100644 test/handler/test_perl6_handler.vader create mode 100644 test/handler/test_perl_handler.vader create mode 100644 test/handler/test_perlcritic_handler.vader create mode 100644 test/handler/test_php_handler.vader create mode 100644 test/handler/test_php_phan_handler.vader create mode 100644 test/handler/test_php_phpmd_handler.vader create mode 100644 test/handler/test_phpcs_handler.vader create mode 100644 test/handler/test_phpstan_handler.vader create mode 100644 test/handler/test_pmd_handler.vader create mode 100644 test/handler/test_pony_handler.vader create mode 100644 test/handler/test_powershell_handler.vader create mode 100644 test/handler/test_prospector_handler.vader create mode 100644 test/handler/test_psscriptanalyzer_handler.vader create mode 100644 test/handler/test_puglint_handler.vader create mode 100644 test/handler/test_puppet_handler.vader create mode 100644 test/handler/test_pycodestyle_handler.vader create mode 100644 test/handler/test_pydocstyle_handler.vader create mode 100644 test/handler/test_pyflakes_handler.vader create mode 100644 test/handler/test_pylama_handler.vader create mode 100644 test/handler/test_pylint_handler.vader create mode 100644 test/handler/test_pyrex_cython_handler.vader create mode 100644 test/handler/test_qmlfmt_handler.vader create mode 100644 test/handler/test_qmllint_handler.vader create mode 100644 test/handler/test_raco_handler.vader create mode 100644 test/handler/test_rails_best_practices_handler.vader create mode 100644 test/handler/test_redpen_handler.vader create mode 100644 test/handler/test_reek_handler.vader create mode 100644 test/handler/test_remark_lint_handler.vader create mode 100644 test/handler/test_rflint_handler.vader create mode 100644 test/handler/test_rpmlint_handler.vader create mode 100644 test/handler/test_rstcheck_lint_handler.vader create mode 100644 test/handler/test_rubocop_handler.vader create mode 100644 test/handler/test_ruby_handler.vader create mode 100644 test/handler/test_ruff_handler.vader create mode 100644 test/handler/test_rust_handler.vader create mode 100644 test/handler/test_salt_salt_lint.vader create mode 100644 test/handler/test_scala_handler.vader create mode 100644 test/handler/test_scalastyle_handler.vader create mode 100644 test/handler/test_scarb_handler.vader create mode 100644 test/handler/test_shell_handler.vader create mode 100644 test/handler/test_shellcheck_handler.vader create mode 100644 test/handler/test_sierra_handler.vader create mode 100644 test/handler/test_slang_handler.vader create mode 100644 test/handler/test_slim_handler.vader create mode 100644 test/handler/test_sml_handler.vader create mode 100644 test/handler/test_solc_handler.vader create mode 100644 test/handler/test_solhint_handler.vader create mode 100644 test/handler/test_spectral_handler.vader create mode 100644 test/handler/test_sql_sqlfluff_handler.vader create mode 100644 test/handler/test_sqlint_handler.vader create mode 100644 test/handler/test_sqllint_handler.vader create mode 100644 test/handler/test_standard_handler.vader create mode 100644 test/handler/test_starknet_handler.vader create mode 100644 test/handler/test_statix_handler.vader create mode 100644 test/handler/test_steep_handler.vader create mode 100644 test/handler/test_stylelint_handler.vader create mode 100644 test/handler/test_swaglint_handler.vader create mode 100644 test/handler/test_swiftlint_handler.vader create mode 100644 test/handler/test_swipl_handler.vader create mode 100644 test/handler/test_syntaxerl_handler.vader create mode 100644 test/handler/test_systemd_analyze_handler.vader create mode 100644 test/handler/test_terraform_handler.vader create mode 100644 test/handler/test_textlint_handler.vader create mode 100644 test/handler/test_tflint_handler.vader create mode 100644 test/handler/test_tfsec_handler.vader create mode 100644 test/handler/test_thrift_handler.vader create mode 100644 test/handler/test_thriftcheck_handler.vader create mode 100644 test/handler/test_tlint_handler.vader create mode 100644 test/handler/test_tslint_handler.vader create mode 100644 test/handler/test_typecheck_handler.vader create mode 100644 test/handler/test_unimport_handler.vader create mode 100644 test/handler/test_v_handler.vader create mode 100644 test/handler/test_vala_lint_handler.vader create mode 100644 test/handler/test_vale_handler.vader create mode 100644 test/handler/test_vcom_handler.vader create mode 100644 test/handler/test_verilator_handler.vader create mode 100644 test/handler/test_vint_handler.vader create mode 100644 test/handler/test_vlog_handler.vader create mode 100644 test/handler/test_vulture_handler.vader create mode 100644 test/handler/test_write_good_handler.vader create mode 100644 test/handler/test_xmllint_handler.vader create mode 100644 test/handler/test_xvhdl_handler.vader create mode 100644 test/handler/test_xvlog_handler.vader create mode 100644 test/handler/test_yamllint_handler.vader create mode 100644 test/handler/test_yosys_handler.vader create mode 100644 test/handler/test_yq_handler.vader create mode 100644 test/handler/test_zeek_handler.vader create mode 100644 test/handler/test_zlint_handler.vader create mode 100644 test/jsonnet_files/testfile.jsonnet create mode 100644 test/linter/test_ada_gcc.vader create mode 100644 test/linter/test_adals.vader create mode 100644 test/linter/test_alex.vader create mode 100644 test/linter/test_ameba.vader create mode 100644 test/linter/test_angular.vader create mode 100644 test/linter/test_ansible_language_server.vader create mode 100644 test/linter/test_ansible_lint.vader create mode 100644 test/linter/test_asciidoc_textlint.vader create mode 100644 test/linter/test_asm_gcc.vader create mode 100644 test/linter/test_avra_avra.vader create mode 100644 test/linter/test_bandit.vader create mode 100644 test/linter/test_bashate.vader create mode 100644 test/linter/test_bib_bibclean.vader create mode 100644 test/linter/test_bicep_az_bicep.vader create mode 100644 test/linter/test_bicep_bicep.vader create mode 100644 test/linter/test_bingo.vader create mode 100644 test/linter/test_biome.vader create mode 100755 test/linter/test_bitbake.vader create mode 100644 test/linter/test_brakeman.vader create mode 100644 test/linter/test_buf_lint.vader create mode 100644 test/linter/test_bzl_buildifier.vader create mode 100644 test/linter/test_c3_c3lsp.vader create mode 100644 test/linter/test_c_cc.vader create mode 100644 test/linter/test_c_ccls.vader create mode 100644 test/linter/test_c_clang_tidy.vader create mode 100644 test/linter/test_c_clangcheck.vader create mode 100644 test/linter/test_c_clangd.vader create mode 100644 test/linter/test_c_cppcheck.vader create mode 100644 test/linter/test_c_cquery.vader create mode 100644 test/linter/test_c_flawfinder.vader create mode 100644 test/linter/test_c_import_paths.vader create mode 100644 test/linter/test_cargo.vader create mode 100644 test/linter/test_checkmake.vader create mode 100644 test/linter/test_checkov.vader create mode 100644 test/linter/test_checkstyle.vader create mode 100644 test/linter/test_circleci.vader create mode 100644 test/linter/test_clang_tidy.vader create mode 100644 test/linter/test_clj_kondo.vader create mode 100644 test/linter/test_cmake_cmake_lint.vader create mode 100644 test/linter/test_cookstyle.vader create mode 100644 test/linter/test_cpp_cc.vader create mode 100644 test/linter/test_cpp_ccls.vader create mode 100644 test/linter/test_cpp_clangcheck.vader create mode 100644 test/linter/test_cpp_clazy.vader create mode 100644 test/linter/test_cpp_cppcheck.vader create mode 100644 test/linter/test_cpp_cquery.vader create mode 100644 test/linter/test_cpp_flawfinder.vader create mode 100644 test/linter/test_cpplint.vader create mode 100644 test/linter/test_cs_csc.vader create mode 100644 test/linter/test_cs_mcs.vader create mode 100644 test/linter/test_cs_mcsc.vader create mode 100644 test/linter/test_cspell.vader create mode 100644 test/linter/test_css_csslint.vader create mode 100644 test/linter/test_cucumber.vader create mode 100644 test/linter/test_cuda_nvcc.vader create mode 100644 test/linter/test_cypher_lint.vader create mode 100644 test/linter/test_d_dls.vader create mode 100644 test/linter/test_dart_analysis_server.vader create mode 100644 test/linter/test_dart_language_server.vader create mode 100644 test/linter/test_desktop_file_validate.vader create mode 100644 test/linter/test_dialyxir.vader create mode 100644 test/linter/test_djlint.vader create mode 100644 test/linter/test_dmd_commandline.vader create mode 100644 test/linter/test_dockerfile_lint.vader create mode 100644 test/linter/test_dockerlinter.vader create mode 100644 test/linter/test_dogma.vader create mode 100644 test/linter/test_eclipselsp.vader create mode 100644 test/linter/test_elixir_credo.vader create mode 100644 test/linter/test_elixir_ls.vader create mode 100644 test/linter/test_elixir_mix.vader create mode 100644 test/linter/test_elm_ls.vader create mode 100644 test/linter/test_elm_make.vader create mode 100644 test/linter/test_embertemplatelint.vader create mode 100644 test/linter/test_erb.vader create mode 100644 test/linter/test_erblint.vader create mode 100644 test/linter/test_erlang_dialyzer.vader create mode 100644 test/linter/test_erlang_elvis.vader create mode 100644 test/linter/test_erlang_erlang_ls.vader create mode 100644 test/linter/test_erlang_erlc.vader create mode 100644 test/linter/test_erlang_syntaxerl.vader create mode 100644 test/linter/test_erubi.vader create mode 100644 test/linter/test_erubis.vader create mode 100644 test/linter/test_eslint.vader create mode 100644 test/linter/test_fecs.vader create mode 100644 test/linter/test_flake8.vader create mode 100644 test/linter/test_flakehell.vader create mode 100644 test/linter/test_flow.vader create mode 100644 test/linter/test_foodcritic.vader create mode 100644 test/linter/test_fortran_fortls.vader create mode 100644 test/linter/test_fsc.vader create mode 100644 test/linter/test_fusionlint.vader create mode 100644 test/linter/test_gawk.vader create mode 100644 test/linter/test_gfortran.vader create mode 100644 test/linter/test_ghdl.vader create mode 100644 test/linter/test_gitlint.vader create mode 100644 test/linter/test_gleam_gleamlsp.vader create mode 100644 test/linter/test_glslang.vader create mode 100644 test/linter/test_glslls.vader create mode 100644 test/linter/test_gobuild.vader create mode 100644 test/linter/test_gofmt.vader create mode 100644 test/linter/test_golangci_lint.vader create mode 100644 test/linter/test_golangserver.vader create mode 100644 test/linter/test_gopls.vader create mode 100644 test/linter/test_gosimple.vader create mode 100644 test/linter/test_gotype.vader create mode 100644 test/linter/test_govet.vader create mode 100644 test/linter/test_graphql_gqlint.vader create mode 100644 test/linter/test_hadolint.vader create mode 100644 test/linter/test_haml_hamllint.vader create mode 100644 test/linter/test_haskell_cabal_ghc.vader create mode 100644 test/linter/test_haskell_ghc.vader create mode 100644 test/linter/test_haskell_ghc_mod.vader create mode 100644 test/linter/test_haskell_hdevtools.vader create mode 100644 test/linter/test_haskell_hie.vader create mode 100644 test/linter/test_haskell_hlint.vader create mode 100644 test/linter/test_haskell_hls.vader create mode 100644 test/linter/test_haskell_stack_build.vader create mode 100644 test/linter/test_haskell_stack_ghc.vader create mode 100644 test/linter/test_hdl_checker_options.vader create mode 100644 test/linter/test_html_stylelint.vader create mode 100644 test/linter/test_htmlhint.vader create mode 100644 test/linter/test_hurlfmt.vader create mode 100644 test/linter/test_ibm_openapi_validator.vader create mode 100644 test/linter/test_idris.vader create mode 100644 test/linter/test_ink_ls.vader create mode 100644 test/linter/test_inko_inko.vader create mode 100644 test/linter/test_ispc_ispc.vader create mode 100644 test/linter/test_iverilog.vader create mode 100644 test/linter/test_javac.vader create mode 100644 test/linter/test_javalsp.vader create mode 100644 test/linter/test_javascript_deno_lsp.vader create mode 100644 test/linter/test_javascript_tsserver.vader create mode 100644 test/linter/test_jedils.vader create mode 100644 test/linter/test_jq.vader create mode 100644 test/linter/test_jscs.vader create mode 100644 test/linter/test_jshint.vader create mode 100644 test/linter/test_json_vscodejson.vader create mode 100644 test/linter/test_jsonlint.vader create mode 100644 test/linter/test_jsonnet_lint.vader create mode 100644 test/linter/test_jsonnetfmt.vader create mode 100644 test/linter/test_julia_languageserver.vader create mode 100644 test/linter/test_kotlin_languageserver.vader create mode 100644 test/linter/test_kotlinc.vader create mode 100644 test/linter/test_languagetool.vader create mode 100644 test/linter/test_less_stylelint.vader create mode 100644 test/linter/test_lessc.vader create mode 100644 test/linter/test_lexical.vader create mode 100644 test/linter/test_lintr.vader create mode 100644 test/linter/test_llc.vader create mode 100644 test/linter/test_llvm_mc.vader create mode 100644 test/linter/test_lua_language_server.vader create mode 100644 test/linter/test_lua_selene.vader create mode 100644 test/linter/test_luac.vader create mode 100644 test/linter/test_luacheck.vader create mode 100644 test/linter/test_markdown_markdownlint.vader create mode 100644 test/linter/test_markdown_marksman.vader create mode 100644 test/linter/test_markdown_mdl.vader create mode 100644 test/linter/test_markdown_vale.vader create mode 100644 test/linter/test_mercury_mmc.vader create mode 100644 test/linter/test_mypy.vader create mode 100644 test/linter/test_naga.vader create mode 100644 test/linter/test_nagelfar.vader create mode 100644 test/linter/test_nasm_nasm.vader create mode 100644 test/linter/test_nimlsp.vader create mode 100644 test/linter/test_nix_deadnix.vader create mode 100644 test/linter/test_nix_statix.vader create mode 100644 test/linter/test_npmgroovylint.vader create mode 100644 test/linter/test_objc_ccls.vader create mode 100644 test/linter/test_ocaml_ocamllsp.vader create mode 100644 test/linter/test_ocaml_ols.vader create mode 100644 test/linter/test_ocamlinterface_ocamllsp.vader create mode 100644 test/linter/test_odin_ols.vader create mode 100644 test/linter/test_openscad_sca2d.vader create mode 100644 test/linter/test_packwerk.vader create mode 100644 test/linter/test_perl.vader create mode 100644 test/linter/test_perl6.vader create mode 100644 test/linter/test_perlcritic.vader create mode 100644 test/linter/test_php.vader create mode 100644 test/linter/test_php_intelephense.vader create mode 100644 test/linter/test_php_langserver.vader create mode 100644 test/linter/test_phpactor.vader create mode 100644 test/linter/test_phpcs.vader create mode 100644 test/linter/test_phpmd.vader create mode 100644 test/linter/test_phpstan.vader create mode 100644 test/linter/test_pony_ponyc.vader create mode 100644 test/linter/test_prospector.vader create mode 100644 test/linter/test_proto.vader create mode 100644 test/linter/test_protolint.vader create mode 100644 test/linter/test_psalm.vader create mode 100644 test/linter/test_puglint.vader create mode 100644 test/linter/test_puppet_languageserver.vader create mode 100644 test/linter/test_purescript_ls.vader create mode 100644 test/linter/test_pycln.vader create mode 100644 test/linter/test_pycodestyle.vader create mode 100644 test/linter/test_pydocstyle.vader create mode 100644 test/linter/test_pyflakes.vader create mode 100644 test/linter/test_pylama.vader create mode 100644 test/linter/test_pylint.vader create mode 100644 test/linter/test_pylsp.vader create mode 100644 test/linter/test_pymarkdown.vader create mode 100644 test/linter/test_pymarkdown_handler.vader create mode 100644 test/linter/test_pyre.vader create mode 100644 test/linter/test_pyrex_cython.vader create mode 100644 test/linter/test_pyright.vader create mode 100644 test/linter/test_qmlfmt.vader create mode 100644 test/linter/test_r_languageserver.vader create mode 100644 test/linter/test_racket_langserver.vader create mode 100644 test/linter/test_racket_raco.vader create mode 100644 test/linter/test_rails_best_practices.vader create mode 100644 test/linter/test_reason_ls.vader create mode 100644 test/linter/test_reason_ols.vader create mode 100644 test/linter/test_reek.vader create mode 100644 test/linter/test_refurb.vader create mode 100644 test/linter/test_rego_opacheck.vader create mode 100644 test/linter/test_remark_lint.vader create mode 100644 test/linter/test_revive.vader create mode 100644 test/linter/test_rflint.vader create mode 100644 test/linter/test_rnix.vader create mode 100644 test/linter/test_rst_textlint.vader create mode 100644 test/linter/test_rubocop.vader create mode 100644 test/linter/test_ruby.vader create mode 100644 test/linter/test_ruby_debride.vader create mode 100644 test/linter/test_ruby_solargraph.vader create mode 100644 test/linter/test_ruby_steep.vader create mode 100644 test/linter/test_ruff.vader create mode 100644 test/linter/test_rust_analyzer.vader create mode 100644 test/linter/test_rust_rls.vader create mode 100644 test/linter/test_rustc.vader create mode 100644 test/linter/test_ruumba.vader create mode 100644 test/linter/test_sass_sasslint.vader create mode 100644 test/linter/test_scala_metals.vader create mode 100644 test/linter/test_scala_sbtserver.vader create mode 100644 test/linter/test_scalac.vader create mode 100644 test/linter/test_scalastyle.vader create mode 100644 test/linter/test_scss_sasslint.vader create mode 100644 test/linter/test_scss_stylelint.vader create mode 100644 test/linter/test_shellcheck.vader create mode 100644 test/linter/test_slang.vader create mode 100644 test/linter/test_slimlint.vader create mode 100644 test/linter/test_solc.vader create mode 100644 test/linter/test_solc_commit.vader create mode 100644 test/linter/test_solhint.vader create mode 100644 test/linter/test_sorbet.vader create mode 100644 test/linter/test_spectral.vader create mode 100644 test/linter/test_sql_sqlfluff.vader create mode 100644 test/linter/test_sqllint.vader create mode 100644 test/linter/test_standard.vader create mode 100644 test/linter/test_standardrb.vader create mode 100644 test/linter/test_standardts.vader create mode 100644 test/linter/test_starknet.vader create mode 100644 test/linter/test_staticcheck.vader create mode 100644 test/linter/test_sugarss_stylelint.vader create mode 100644 test/linter/test_svelteserver.vader create mode 100644 test/linter/test_swaglint.vader create mode 100644 test/linter/test_swift_appleswiftformat.vader create mode 100644 test/linter/test_swift_sourcekitlsp.vader create mode 100644 test/linter/test_swiftlint.vader create mode 100644 test/linter/test_systemd_analyze.vader create mode 100644 test/linter/test_terraform_ls.vader create mode 100644 test/linter/test_terraform_lsp.vader create mode 100644 test/linter/test_terraform_terraform.vader create mode 100644 test/linter/test_terraform_tflint.vader create mode 100644 test/linter/test_terraform_tfsec.vader create mode 100644 test/linter/test_tex_chktex.vader create mode 100644 test/linter/test_tex_lacheck.vader create mode 100644 test/linter/test_tex_textlint.vader create mode 100644 test/linter/test_texlab.vader create mode 100644 test/linter/test_textlint.vader create mode 100644 test/linter/test_thrift.vader create mode 100644 test/linter/test_thriftcheck.vader create mode 100644 test/linter/test_tslint.vader create mode 100644 test/linter/test_typescript_deno_lsp.vader create mode 100644 test/linter/test_typescript_tsserver.vader create mode 100644 test/linter/test_unimport.vader create mode 100644 test/linter/test_v_command_callback.vader create mode 100644 test/linter/test_vcom.vader create mode 100644 test/linter/test_verilator.vader create mode 100644 test/linter/test_vim_vimls.vader create mode 100644 test/linter/test_vint.vader create mode 100644 test/linter/test_vlog.vader create mode 100644 test/linter/test_volar.vader create mode 100644 test/linter/test_vulture.vader create mode 100644 test/linter/test_write_good.vader create mode 100644 test/linter/test_xmllint.vader create mode 100644 test/linter/test_xo.vader create mode 100644 test/linter/test_xots.vader create mode 100644 test/linter/test_xvhdl.vader create mode 100644 test/linter/test_xvlog.vader create mode 100644 test/linter/test_yaml_actionlint.vader create mode 100644 test/linter/test_yaml_ls.vader create mode 100644 test/linter/test_yang_lsp.vader create mode 100644 test/linter/test_yara_yls.vader create mode 100644 test/linter/test_yq.vader create mode 100644 test/linter/test_zeek.vader create mode 100644 test/linter/test_zig_zlint.vader create mode 100644 test/linter/test_zig_zls.vader create mode 100644 test/lsp/test_closing_documents.vader create mode 100644 test/lsp/test_did_save_event.vader create mode 100644 test/lsp/test_engine_lsp_response_handling.vader create mode 100644 test/lsp/test_handling_window_requests.vader create mode 100644 test/lsp/test_lsp_address_split.vader create mode 100644 test/lsp/test_lsp_client_messages.vader create mode 100644 test/lsp/test_lsp_command_formatting.vader create mode 100644 test/lsp/test_lsp_connections.vader create mode 100644 test/lsp/test_lsp_custom_request.vader create mode 100644 test/lsp/test_lsp_error_parsing.vader create mode 100644 test/lsp/test_lsp_root_detection.vader create mode 100644 test/lsp/test_lsp_startup.vader create mode 100644 test/lsp/test_other_initialize_message_handling.vader create mode 100644 test/lsp/test_read_lsp_diagnostics.vader create mode 100644 test/lsp/test_reset_lsp.vader create mode 100644 test/lsp/test_update_config.vader create mode 100644 test/lua/.luarc.json create mode 100644 test/lua/ale_diagnostics_spec.lua create mode 100644 test/lua/ale_env_spec.lua create mode 100644 test/lua/ale_get_filename_mappings_spec.lua create mode 100644 test/lua/ale_has_spec.lua create mode 100644 test/lua/ale_lsp_spec.lua create mode 100644 test/lua/ale_pad_spec.lua create mode 100644 test/lua/ale_queue_spec.lua create mode 100644 test/lua/ale_var_spec.lua create mode 100644 test/lua/windows_escaping_spec.lua create mode 100644 test/python/test_deoplete_source.py create mode 100755 test/script/block-padding-checker create mode 100755 test/script/check-duplicate-tags create mode 100755 test/script/check-supported-tools-tables create mode 100755 test/script/check-tag-alignment create mode 100755 test/script/check-tag-references create mode 100755 test/script/check-toc create mode 100755 test/script/custom-checks create mode 100755 test/script/custom-linting-rules create mode 100644 test/script/dumb_named_pipe_server.py create mode 100644 test/script/dumb_tcp_client.py create mode 100644 test/script/dumb_tcp_server.py create mode 100755 test/script/run-lua-tests create mode 100755 test/script/run-vader-tests create mode 100755 test/script/run-vint create mode 100644 test/sign/test_linting_sets_signs.vader create mode 100644 test/sign/test_sign_column_highlighting.vader create mode 100644 test/sign/test_sign_limits.vader create mode 100644 test/sign/test_sign_parsing.vader create mode 100644 test/sign/test_sign_placement.vader create mode 100644 test/smoke_test.vader create mode 100644 test/test-files/.circleci/config.yml create mode 100644 test/test-files/.gitignore create mode 100644 test/test-files/ada/testfile.adb create mode 100644 test/test-files/alex/node-modules-2/node_modules/alex/cli.js create mode 100644 test/test-files/alex/node-modules/node_modules/.bin/alex create mode 100644 test/test-files/angular/node_modules/@angular/language-server/bin/ngserver create mode 100644 test/test-files/angular/node_modules/@angular/language-service/dummy create mode 100644 test/test-files/ant/ant-project/build.xml create mode 100755 test/test-files/ant/bin/ant create mode 100755 test/test-files/ant/bin/ant.exe create mode 100644 test/test-files/bazel/BUILD create mode 100644 test/test-files/bazel/WORKSPACE create mode 100644 test/test-files/bazel/defs.bzl create mode 100644 test/test-files/bib/dummy.bib create mode 100644 test/test-files/biome/json/biome.json create mode 100644 test/test-files/biome/json/src/test.ts create mode 100644 test/test-files/biome/jsonc/biome.jsonc create mode 100644 test/test-files/biome/jsonc/src/test.ts create mode 100644 test/test-files/bzl/bazel-package/BUILD.bazel create mode 100644 test/test-files/c/build_compile_commands_project/build/bad_folder_to_test_priority create mode 100644 test/test-files/c/build_compile_commands_project/build/compile_commands.json create mode 100644 test/test-files/c/configure_project/Makefile create mode 100644 test/test-files/c/configure_project/configure create mode 100644 test/test-files/c/configure_project/include/test.h create mode 100644 test/test-files/c/configure_project/subdir/Makefile create mode 100644 test/test-files/c/dummy.c create mode 100644 test/test-files/c/git_and_nested_makefiles/include/test.h create mode 100644 test/test-files/c/git_and_nested_makefiles/src/Makefile create mode 100644 test/test-files/c/gnumakefile_project/GNUmakefile create mode 100644 test/test-files/c/gnumakefile_project/file.c create mode 100644 test/test-files/c/h_file_project/Makefile create mode 100644 test/test-files/c/h_file_project/subdir/dummy create mode 100644 test/test-files/c/h_file_project/test.h create mode 100644 test/test-files/c/hpp_file_project/Makefile create mode 100644 test/test-files/c/hpp_file_project/subdir/dummy create mode 100644 test/test-files/c/hpp_file_project/test.hpp create mode 100644 test/test-files/c/json_project/build/compile_commands.json create mode 100644 test/test-files/c/json_project/include/test.h create mode 100644 test/test-files/c/json_project/subdir/dummy create mode 100644 test/test-files/c/makefile_project/Makefile create mode 100644 test/test-files/c/makefile_project/_astylerc create mode 100644 test/test-files/c/makefile_project/args create mode 100644 test/test-files/c/makefile_project/include/test.h create mode 100644 test/test-files/c/makefile_project/subdir/args create mode 100644 test/test-files/c/makefile_project/subdir/dummy create mode 100644 test/test-files/c/makefile_project/subdir/file.c create mode 100644 test/test-files/cargo/Cargo.toml create mode 100644 test/test-files/cargo/workspace_paths/Cargo.toml create mode 100644 test/test-files/cargo/workspace_paths/subpath/Cargo.toml create mode 100644 test/test-files/ccls/with_build_dir/unusual_build_dir_name/compile_commands.json create mode 100644 test/test-files/ccls/with_ccls-root/.ccls-root create mode 100644 test/test-files/ccls/with_ccls/.ccls create mode 100644 test/test-files/ccls/with_compile_commands_json/compile_commands.json create mode 100644 test/test-files/checkstyle/other_config.xml create mode 100644 test/test-files/clangd/with_build_dir/unusual_build_dir_name/compile_commands.json create mode 100644 test/test-files/clangd/with_compile_commands/compile_commands.json create mode 100644 test/test-files/clangformat/with_clangformat/.clang-format create mode 100644 test/test-files/cpp/.astylerc create mode 100644 test/test-files/cpp/dummy.cpp create mode 100644 test/test-files/cppcheck/one/compile_commands.json create mode 100644 test/test-files/cppcheck/one/two/three/file.c create mode 100644 test/test-files/cppcheck/one/two/three/file.cpp create mode 100644 test/test-files/cppcheck/with_build_dir/build/compile_commands.json create mode 100644 test/test-files/cquery/build/compile_commands.json create mode 100644 test/test-files/cquery/with_cquery/.cquery create mode 100755 test/test-files/cspell/node-modules-2/node_modules/cspell/bin.js create mode 100755 test/test-files/cspell/node-modules/node_modules/.bin/cspell create mode 100644 test/test-files/csslint/other-app/testfile.css create mode 100644 test/test-files/csslint/some-app/.csslintrc create mode 100644 test/test-files/csslint/some-app/subdir/testfile.css create mode 100644 test/test-files/cucumber/features/cuke.feature create mode 100644 test/test-files/cucumber/features/step_definitions/base_steps.rb create mode 100644 test/test-files/d/test.d create mode 100644 test/test-files/dart/.packages create mode 100644 test/test-files/dart/testfile.dart create mode 100644 test/test-files/dprint/blank.ts create mode 100644 test/test-files/dprint/dprint.json create mode 100644 test/test-files/elixir/mix_project/lib/app.ex create mode 100644 test/test-files/elixir/mix_project/mix.exs create mode 100644 test/test-files/elixir/testfile.ex create mode 100644 test/test-files/elixir/umbrella_project/apps/app1/lib/app.ex create mode 100644 test/test-files/elixir/umbrella_project/apps/app1/mix.exs create mode 100644 test/test-files/elixir/umbrella_project/apps/app2/lib/app.ex create mode 100644 test/test-files/elixir/umbrella_project/apps/app2/mix.exs create mode 100644 test/test-files/elixir/umbrella_project/mix.exs create mode 100644 test/test-files/elm/newapp-notests/elm.json create mode 100644 test/test-files/elm/newapp-notests/node_modules/.bin/elm create mode 100644 test/test-files/elm/newapp-notests/tests/TestMain.elm create mode 100644 test/test-files/elm/newapp/elm.json create mode 100644 test/test-files/elm/newapp/node_modules/.bin/elm create mode 100644 test/test-files/elm/newapp/node_modules/.bin/elm-test create mode 100644 test/test-files/elm/newapp/src/Main.elm create mode 100644 test/test-files/elm/newapp/tests/TestSuite.elm create mode 100644 test/test-files/elm/node_modules/.bin/elm-format create mode 100644 test/test-files/elm/oldapp/elm-package.json create mode 100644 test/test-files/elm/oldapp/node_modules/.bin/elm create mode 100644 test/test-files/elm/oldapp/node_modules/.bin/elm-test create mode 100644 test/test-files/elm/oldapp/src/Main.elm create mode 100644 test/test-files/elm/oldapp/tests/TestSuite.elm create mode 100644 test/test-files/elm/src/subdir/testfile.elm create mode 100644 test/test-files/erlang/app_with_elvis_config/elvis.config create mode 100644 test/test-files/erlang/app_with_erlang_ls_config/_build/default/lib/dep/erlang_ls.config create mode 100644 test/test-files/erlang/app_with_erlang_ls_config/deps/dep/erlang_ls.config create mode 100644 test/test-files/erlang/app_with_erlang_ls_config/erlang_ls.config create mode 100755 test/test-files/erlang/app_with_erlfmt/erlfmt create mode 100644 test/test-files/erlang/erlang_mk_app/deps/dep/erlang.mk create mode 100644 test/test-files/erlang/erlang_mk_app/erlang.mk create mode 100644 test/test-files/erlang/kerl_otp_root/.kerl_config create mode 100644 test/test-files/erlang/rebar3_app/_build/default/lib/dep/rebar.lock create mode 100644 test/test-files/erlang/rebar3_app/_checkouts/dep/_build/.gitkeep create mode 100644 test/test-files/erlang/rebar3_app/_checkouts/dep/rebar.lock create mode 100644 test/test-files/erlang/rebar3_app/rebar.lock create mode 100644 test/test-files/eruby/dummy.html.erb create mode 100644 test/test-files/eslint/astro-app/.eslintrc.js create mode 100644 test/test-files/eslint/astro-app/node_modules/eslint/bin/eslint.js create mode 100644 test/test-files/eslint/astro-app/package.json create mode 100644 test/test-files/eslint/astro-app/src/pages/index.astro create mode 100644 test/test-files/eslint/astro-app/tsconfig.json create mode 100644 test/test-files/eslint/node_modules/.bin/eslint create mode 100644 test/test-files/eslint/other-app/subdir/testfile.js create mode 100644 test/test-files/eslint/package.json create mode 100644 test/test-files/eslint/react-app/.eslintrc.js create mode 100644 test/test-files/eslint/react-app/node_modules/eslint/bin/eslint.js create mode 100644 test/test-files/eslint/react-app/node_modules/standard/bin/cmd.js create mode 100644 test/test-files/eslint/react-app/node_modules/stylelint/bin/stylelint.js create mode 100644 test/test-files/eslint/react-app/node_modules/xo/cli.js create mode 100644 test/test-files/eslint/react-app/subdir-with-config/.eslintrc create mode 100644 test/test-files/eslint/react-app/subdir-with-package-json/node_modules/.gitkeep create mode 100644 test/test-files/eslint/react-app/subdir-with-package-json/package.json create mode 100644 test/test-files/eslint/react-app/subdir/testfile.css create mode 100644 test/test-files/eslint/react-app/subdir/testfile.js create mode 100644 test/test-files/eslint/react-app/subdir/testfile.ts create mode 100644 test/test-files/eslint/yarn2-app/.eslintrc.js create mode 100644 test/test-files/eslint/yarn2-app/.yarn/sdks/eslint/bin/eslint.js create mode 100644 test/test-files/eslint/yarn2-app/subdir/testfile.js create mode 100755 test/test-files/fecs/fecs create mode 100755 test/test-files/fecs/fecs.exe create mode 100644 test/test-files/fish/testfile.fish create mode 100644 test/test-files/flow/a/.flowconfig create mode 100644 test/test-files/flow/a/sub/dummy create mode 100644 test/test-files/flow/b/sub/dummy create mode 100644 test/test-files/fortls-project/.fortls create mode 100644 test/test-files/gleam/gleam.toml create mode 100644 test/test-files/gleam/testfile.gleam create mode 100644 test/test-files/go/go.mod create mode 100644 test/test-files/go/go1/prj1/file.go create mode 100644 test/test-files/go/go2/prj2/file.go create mode 100644 test/test-files/go/gopath/bin/gopls create mode 100644 test/test-files/go/gopath/bin/staticcheck create mode 100644 test/test-files/go/testfile.go create mode 100644 test/test-files/go/testfile2.go create mode 100644 test/test-files/gradle/build-gradle-project/build.gradle create mode 100644 test/test-files/gradle/build-gradle-project/src/main/kotlin/dummy.kt create mode 100755 test/test-files/gradle/gradle create mode 100644 test/test-files/gradle/non-gradle-project/src/main/kotlin/dummy.kt create mode 100644 test/test-files/gradle/settings-gradle-project/settings.gradle create mode 100644 test/test-files/gradle/settings-gradle-project/src/main/kotlin/dummy.kt create mode 100644 test/test-files/gradle/unwrapped-project/build.gradle create mode 100644 test/test-files/gradle/unwrapped-project/settings.gradle create mode 100644 test/test-files/gradle/unwrapped-project/src/main/kotlin/dummy.kt create mode 100644 test/test-files/gradle/wrapped-project/build.gradle create mode 100644 test/test-files/gradle/wrapped-project/gradlew create mode 100644 test/test-files/gradle/wrapped-project/settings.gradle create mode 100644 test/test-files/gradle/wrapped-project/src/main/kotlin/dummy.kt create mode 100644 test/test-files/hamllint/haml-lint-and-rubocop/.haml-lint.yml create mode 100644 test/test-files/hamllint/haml-lint-and-rubocop/.rubocop.yml create mode 100644 test/test-files/hamllint/haml-lint-and-rubocop/subdir/file.haml create mode 100644 test/test-files/hamllint/haml-lint-yml/.haml-lint.yml create mode 100644 test/test-files/hamllint/haml-lint-yml/subdir/file.haml create mode 100644 test/test-files/hamllint/rubocop-yml/.rubocop.yml create mode 100644 test/test-files/hamllint/rubocop-yml/subdir/file.haml create mode 100644 test/test-files/haskell/haskell-packages-project/cabal.project create mode 100644 test/test-files/haskell/haskell-packages-project/package-a/package-a.cabal create mode 100644 test/test-files/haskell/haskell-packages-project/package-a/src/folder/dummy.hs create mode 100644 test/test-files/haskell/haskell-simple-package/package-a/package-a.cabal create mode 100644 test/test-files/haskell/haskell-simple-package/package-a/src/folder/dummy.hs create mode 100644 test/test-files/hdl_server/foo.vhd create mode 100644 test/test-files/hdl_server/with_config_file/.hdl_checker.config create mode 100644 test/test-files/hdl_server/with_config_file/_hdl_checker.config create mode 100644 test/test-files/hdl_server/with_config_file/foo.vhd create mode 100644 test/test-files/hdl_server/with_git/files/foo.vhd create mode 100644 test/test-files/hie_paths/file.hs create mode 100755 test/test-files/html_beautify/html-beautify create mode 100644 test/test-files/html_beautify/test.html create mode 100755 test/test-files/htmlhint/node_modules/.bin/htmlhint create mode 100644 test/test-files/htmlhint/with_config/.htmlhintrc create mode 100644 test/test-files/ink/story/main.ink create mode 100644 test/test-files/inko/test.inko create mode 100644 test/test-files/inko/tests/test/test_foo.inko create mode 100644 test/test-files/java/no_main/src/test/java/com/something/dummy create mode 100644 test/test-files/java/with_jaxb/src/main/java/com/something/dummy create mode 100644 test/test-files/java/with_jaxb/src/main/jaxb/com/something/dummy create mode 100644 test/test-files/java/with_main/build/gen/main/java/com/something/dummy create mode 100644 test/test-files/java/with_main/build/gen2/main/java/com/something/dummy create mode 100644 test/test-files/java/with_main/src/main/java/com/something/dummy create mode 100644 test/test-files/java/with_main/src/test/java/com/something/dummy create mode 100644 test/test-files/javascript/test.js create mode 100644 test/test-files/javascript_deno/custom_import_map.json create mode 100644 test/test-files/javascript_deno/import_map.json create mode 100644 test/test-files/javascript_deno/main.js create mode 100644 test/test-files/javascript_deno/tsconfig.json create mode 100644 test/test-files/json/testfile.json create mode 100644 test/test-files/jsonlint/app-without-jsonlint/src/app.json create mode 100644 test/test-files/jsonlint/app/node_modules/.bin/jsonlint create mode 100644 test/test-files/jsonlint/app/src/app.json create mode 100644 test/test-files/jsonlint/node_modules/jsonlint/lib/cli.js create mode 100644 test/test-files/julia/REQUIRE create mode 100644 test/test-files/julia/test.jl create mode 100644 test/test-files/kotlin/testfile.kt create mode 100755 test/test-files/lessc/node_modules/.bin/lessc create mode 100644 test/test-files/long-line/setup.cfg create mode 100644 test/test-files/lua/testfile.lua create mode 100644 test/test-files/markdown/testfile.md create mode 100755 test/test-files/maven/maven-java-project/module1/mvnw create mode 100755 test/test-files/maven/maven-java-project/module1/mvnw.cmd create mode 100644 test/test-files/maven/maven-java-project/module1/pom.xml create mode 100644 test/test-files/maven/maven-java-project/module1/src/main/java/dummy1.java create mode 100644 test/test-files/maven/maven-java-project/module2/pom.xml create mode 100644 test/test-files/maven/maven-java-project/module2/src/main/java/dummy2.java create mode 100644 test/test-files/maven/maven-kotlin-project/pom.xml create mode 100644 test/test-files/maven/maven-kotlin-project/src/main/kotlin/dummy.kt create mode 100755 test/test-files/maven/mvn create mode 100644 test/test-files/maven/non-maven-project/src/main/java/dummy.java create mode 100644 test/test-files/nim/with-git/src/source.nim create mode 100644 test/test-files/ocaml/testfile.ml create mode 100644 test/test-files/ocamllsp/dune-project create mode 100644 test/test-files/odin/main.odin create mode 100644 test/test-files/ols/.merlin create mode 100644 test/test-files/ols/node_modules/.bin/ocaml-language-server create mode 100644 test/test-files/openscad/dummy.scad create mode 100644 test/test-files/pascal/test.pas create mode 100644 test/test-files/php/project-with-php-cs-fixer/test.php create mode 100644 test/test-files/php/project-with-php-cs-fixer/vendor/bin/php-cs-fixer create mode 100644 test/test-files/php/project-with-phpcbf/foo/test.php create mode 100644 test/test-files/php/project-with-phpcbf/vendor/bin/phpcbf create mode 100644 test/test-files/php/project-with-pint/test.php create mode 100644 test/test-files/php/project-with-pint/vendor/bin/pint create mode 100644 test/test-files/php/project-without-php-cs-fixer/test.php create mode 100644 test/test-files/php/project-without-phpcbf/foo/test.php create mode 100644 test/test-files/php/project-without-pint/test.php create mode 100755 test/test-files/php/vendor/bin/php-language-server.php create mode 100644 test/test-files/php/with-composer/composer.json create mode 100755 test/test-files/php/with-composer/vendor/bin/php-language-server.php create mode 100755 test/test-files/php/with-git/vendor/bin/php-language-server.php create mode 100644 test/test-files/phpcs/project-with-phpcs/foo/test.php create mode 100644 test/test-files/phpcs/project-with-phpcs/vendor/bin/phpcs create mode 100644 test/test-files/phpcs/project-without-phpcs/foo/test.php create mode 100644 test/test-files/prettier/testfile create mode 100644 test/test-files/prettier/testfile.css create mode 100644 test/test-files/prettier/testfile.js create mode 100644 test/test-files/prettier/testfile.json create mode 100644 test/test-files/prettier/testfile.scss create mode 100644 test/test-files/prettier/testfile.ts create mode 100644 test/test-files/prettier/with_config/.prettierrc create mode 100644 test/test-files/prettier/with_config/testfile.js create mode 100644 test/test-files/prettier/with_prettierignore/.prettierignore create mode 100644 test/test-files/prettier/with_prettierignore/src/testfile.js create mode 100644 test/test-files/proto/testfile.proto create mode 100755 test/test-files/psalm/vendor/bin/psalm create mode 100644 test/test-files/puglint/node_modules/.bin/pug-lint create mode 100644 test/test-files/puglint/package.json create mode 100644 test/test-files/puglint/puglint_rc_dir/.pug-lintrc create mode 100644 test/test-files/puglint/puglint_rc_js_dir/.pug-lintrc.js create mode 100644 test/test-files/puglint/puglint_rc_json_dir/.pug-lintrc.json create mode 100644 test/test-files/puppet/dummy.pp create mode 100644 test/test-files/puppet/new-style-module/lib/puppet/types/exampletype.rb create mode 100644 test/test-files/puppet/new-style-module/metadata.json create mode 100644 test/test-files/puppet/new-style-module/template/template.epp create mode 100644 test/test-files/puppet/old-style-module/manifests/init.pp create mode 100644 test/test-files/puppet/old-style-module/templates/template.epp create mode 100644 test/test-files/purescript/bower/Foo.purs create mode 100644 test/test-files/purescript/bower/bower.json create mode 100644 test/test-files/purescript/psc-package/Foo.purs create mode 100644 test/test-files/purescript/psc-package/psc-package.json create mode 100644 test/test-files/purescript/spago/Foo.purs create mode 100644 test/test-files/purescript/spago/spago.dhall create mode 100644 test/test-files/python/namespace_package_manifest/MANIFEST.in create mode 100644 test/test-files/python/namespace_package_manifest/namespace/foo/__init__.py create mode 100644 test/test-files/python/namespace_package_manifest/namespace/foo/bar.py create mode 100644 test/test-files/python/namespace_package_pytest/namespace/foo/__init__.py create mode 100644 test/test-files/python/namespace_package_pytest/namespace/foo/bar.py create mode 100644 test/test-files/python/namespace_package_pytest/pytest.ini create mode 100644 test/test-files/python/namespace_package_setup/namespace/foo/__init__.py create mode 100644 test/test-files/python/namespace_package_setup/namespace/foo/bar.py create mode 100644 test/test-files/python/namespace_package_setup/setup.cfg create mode 100644 test/test-files/python/namespace_package_tox/namespace/foo/__init__.py create mode 100644 test/test-files/python/namespace_package_tox/namespace/foo/bar.py create mode 100644 test/test-files/python/namespace_package_tox/tox.ini create mode 100644 test/test-files/python/no_virtualenv/subdir/foo/COMMIT_EDITMSG create mode 100644 test/test-files/python/no_virtualenv/subdir/foo/__init__.py create mode 100644 test/test-files/python/no_virtualenv/subdir/foo/bar.py create mode 100644 test/test-files/python/pipenv/Pipfile.lock create mode 100644 test/test-files/python/poetry/poetry.lock create mode 100644 test/test-files/python/pyre_configuration_dir/.pyre_configuration.local create mode 100644 test/test-files/python/pyre_configuration_dir/foo/__init__.py create mode 100644 test/test-files/python/pyre_configuration_dir/foo/bar.py create mode 100644 test/test-files/python/python-package-project/.flake8 create mode 100644 test/test-files/python/python-package-project/package-name/module.py create mode 100644 test/test-files/python/uv/.gitkeep create mode 100644 test/test-files/python/uv/uv.lock create mode 100644 test/test-files/python/uv/whatever.py create mode 100644 test/test-files/python/with_bandit/.bandit create mode 100644 test/test-files/python/with_bandit/namespace/foo/__init__.py create mode 100644 test/test-files/python/with_bandit/namespace/foo/bar.py create mode 100644 test/test-files/python/with_mypy_ini_and_pytest_ini/mypy.ini create mode 100644 test/test-files/python/with_mypy_ini_and_pytest_ini/tests/pytest.ini create mode 100644 test/test-files/python/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py create mode 100644 test/test-files/python/with_virtualenv/dir_with_yapf_config/.style.yapf create mode 100644 test/test-files/python/with_virtualenv/env/Scripts/activate create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/autoflake.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/autoimport.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/autopep8.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/black.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/flake8.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/flakehell.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/gitlint.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/isort.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/jedi-language-server.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/mypy.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/pycln.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/pyflakes.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/pylama.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/pylint.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/pylsp.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/pyre.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/pyright-langserver.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/refurb.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/reorder-python-imports.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/ruff.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/tidy-imports.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/unimport.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/vulture.exe create mode 100644 test/test-files/python/with_virtualenv/env/Scripts/yamlfix.exe create mode 100755 test/test-files/python/with_virtualenv/env/Scripts/yapf.exe create mode 100644 test/test-files/python/with_virtualenv/env/bin/activate create mode 100755 test/test-files/python/with_virtualenv/env/bin/autoflake create mode 100755 test/test-files/python/with_virtualenv/env/bin/autoimport create mode 100755 test/test-files/python/with_virtualenv/env/bin/autopep8 create mode 100755 test/test-files/python/with_virtualenv/env/bin/black create mode 100755 test/test-files/python/with_virtualenv/env/bin/flake8 create mode 100755 test/test-files/python/with_virtualenv/env/bin/flakehell create mode 100755 test/test-files/python/with_virtualenv/env/bin/gitlint create mode 100755 test/test-files/python/with_virtualenv/env/bin/isort create mode 100755 test/test-files/python/with_virtualenv/env/bin/jedi-language-server create mode 100755 test/test-files/python/with_virtualenv/env/bin/mypy create mode 100755 test/test-files/python/with_virtualenv/env/bin/pycln create mode 100755 test/test-files/python/with_virtualenv/env/bin/pyflakes create mode 100755 test/test-files/python/with_virtualenv/env/bin/pylama create mode 100755 test/test-files/python/with_virtualenv/env/bin/pylint create mode 100755 test/test-files/python/with_virtualenv/env/bin/pylsp create mode 100755 test/test-files/python/with_virtualenv/env/bin/pyre create mode 100755 test/test-files/python/with_virtualenv/env/bin/pyright-langserver create mode 100755 test/test-files/python/with_virtualenv/env/bin/refurb create mode 100755 test/test-files/python/with_virtualenv/env/bin/reorder-python-imports create mode 100755 test/test-files/python/with_virtualenv/env/bin/ruff create mode 100755 test/test-files/python/with_virtualenv/env/bin/tidy-imports create mode 100755 test/test-files/python/with_virtualenv/env/bin/unimport create mode 100755 test/test-files/python/with_virtualenv/env/bin/vulture create mode 100755 test/test-files/python/with_virtualenv/env/bin/yamlfix create mode 100755 test/test-files/python/with_virtualenv/env/bin/yapf create mode 100644 test/test-files/python/with_virtualenv/subdir/foo/COMMIT_EDITMSG create mode 100644 test/test-files/python/with_virtualenv/subdir/foo/__init__.py create mode 100644 test/test-files/python/with_virtualenv/subdir/foo/bar.py create mode 100644 test/test-files/python/with_virtualenv/subdir/foo/bar.pyi create mode 100644 test/test-files/r/.Rprofile create mode 100644 test/test-files/racket/many-inits/a/b/c/foo.rkt create mode 100644 test/test-files/racket/many-inits/a/b/c/init.rkt create mode 100644 test/test-files/racket/many-inits/a/b/foo.rkt create mode 100644 test/test-files/racket/many-inits/a/b/init.rkt create mode 100644 test/test-files/racket/many-inits/a/foo.rkt create mode 100644 test/test-files/racket/many-inits/a/init.rkt create mode 100644 test/test-files/racket/many-inits/foo.rkt create mode 100644 test/test-files/racket/many-inits/init.rkt create mode 100644 test/test-files/racket/simple-script/foo.rkt create mode 100644 test/test-files/reasonml/bsconfig.json create mode 100644 test/test-files/reasonml/testfile.re create mode 100755 test/test-files/remark_lint/with_bin_path/node_modules/.bin/remark create mode 100644 test/test-files/ruby/dummy.rb create mode 100644 test/test-files/ruby/nested/dummy.rb create mode 100644 test/test-files/ruby/nested/foo/Steepfile create mode 100644 test/test-files/ruby/nested/foo/dummy.rb create mode 100644 test/test-files/ruby/nested/foo/one/dummy.rb create mode 100644 test/test-files/ruby/nested/foo/two/Steepfile create mode 100644 test/test-files/ruby/nested/foo/two/dummmy.rb create mode 100644 test/test-files/ruby/nested/foo/two/three/dummy.rb create mode 100644 test/test-files/ruby/not_a_rails_app/file.rb create mode 100644 test/test-files/ruby/valid_rails_app/app/dummy.rb create mode 100644 test/test-files/ruby/valid_rails_app/app/models/thing.rb create mode 100644 test/test-files/ruby/valid_rails_app/app/views/my_great_view.html.erb create mode 100644 test/test-files/ruby/valid_rails_app/config/dummy.rb create mode 100644 test/test-files/ruby/valid_rails_app/db/dummy.rb create mode 100644 test/test-files/ruby/valid_ruby_app1/Rakefile create mode 100644 test/test-files/ruby/valid_ruby_app1/lib/file.rb create mode 100644 test/test-files/ruby/valid_ruby_app2/Gemfile create mode 100644 test/test-files/ruby/valid_ruby_app2/lib/file.rb create mode 100644 test/test-files/ruby/valid_ruby_app3/.solargraph.yml create mode 100644 test/test-files/ruby/valid_ruby_app3/lib/file.rb create mode 100644 test/test-files/ruby/with_config/.rubocop.yml create mode 100644 test/test-files/ruby/with_config/.standard.yml create mode 100644 test/test-files/rust/cargo/Cargo.toml create mode 100644 test/test-files/rust/cargo/testfile.rs create mode 100644 test/test-files/rust/rust-project/rust-project.json create mode 100644 test/test-files/rust/rust-project/testfile.rs create mode 100644 test/test-files/rustywind/test.html create mode 100755 test/test-files/sasslint/with-bin/node_modules/.bin/sass-lint create mode 100755 test/test-files/sasslint/with-source/node_modules/sass-lint/bin/sass-lint.js create mode 100644 test/test-files/scala/dummy.scala create mode 100644 test/test-files/scala/invalid_sbt_project/Main.scala create mode 100644 test/test-files/scala/valid_sbt_project/Main.scala create mode 100644 test/test-files/scala/valid_sbt_project/build.sbt create mode 100644 test/test-files/slimlint/.rubocop.yml create mode 100644 test/test-files/slimlint/subdir/file.slim create mode 100644 test/test-files/smlnj/cm/foo.sml create mode 100644 test/test-files/smlnj/cm/path/to/bar.sml create mode 100644 test/test-files/smlnj/cm/sources.cm create mode 100644 test/test-files/smlnj/file/qux.sml create mode 100644 test/test-files/solhint/Contract.sol create mode 100644 test/test-files/solhint/node_modules/.bin/solhint create mode 100644 test/test-files/solhint/node_modules/solhint/index.js create mode 100644 test/test-files/solhint/package.json create mode 100644 test/test-files/spectral/node_modules/.bin/spectral create mode 100644 test/test-files/spectral/openapi.yaml create mode 100644 test/test-files/stack/stack.yaml create mode 100755 test/test-files/standard/with-bin/node_modules/.bin/standard create mode 100755 test/test-files/standard/with-cmd/node_modules/standard/bin/cmd.js create mode 100755 test/test-files/stylelint/node_modules/.bin/stylelint create mode 100644 test/test-files/stylua/stylua_config_dir/stylua.toml create mode 100644 test/test-files/stylua/stylua_dot_config_dir/.stylua.toml create mode 100644 test/test-files/swaglint/docs/swagger.yaml create mode 100644 test/test-files/swaglint/node_modules/.bin/swaglint create mode 100644 test/test-files/swift/dummy.swift create mode 100644 test/test-files/swift/non-swift-package-project/src/folder/dummy.swift create mode 100644 test/test-files/swift/swift-package-project-with-config/.swift-format create mode 100644 test/test-files/swift/swift-package-project-with-config/Package.swift create mode 100644 test/test-files/swift/swift-package-project-with-config/src/folder/dummy.swift create mode 100644 test/test-files/swift/swift-package-project/Package.swift create mode 100644 test/test-files/swift/swift-package-project/src/folder/dummy.swift create mode 100644 test/test-files/swiftlint/cocoapods-and-react-native/Pods/SwiftLint/swiftlint create mode 100644 test/test-files/swiftlint/cocoapods-and-react-native/ios/Pods/SwiftLint/swiftlint create mode 100644 test/test-files/swiftlint/cocoapods/Pods/SwiftLint/swiftlint create mode 100644 test/test-files/swiftlint/react-native/ios/Pods/SwiftLint/swiftlint create mode 100644 test/test-files/terraform/.terraform/dummy create mode 100644 test/test-files/terraform/main.tf create mode 100644 test/test-files/tex/sample1.tex create mode 100644 test/test-files/tex/sample2.tex create mode 100644 test/test-files/tex/testfile.tex create mode 100755 test/test-files/textlint/with_bin_path/node_modules/.bin/textlint create mode 100755 test/test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js create mode 100644 test/test-files/tflint/foo/.tflint.hcl create mode 100644 test/test-files/tflint/foo/bar.tf create mode 100644 test/test-files/tfsec/json/.tfsec/config.json create mode 100644 test/test-files/tfsec/json/main.tf create mode 100644 test/test-files/tfsec/yml/.tfsec/config.yml create mode 100644 test/test-files/tfsec/yml/main.tf create mode 100644 test/test-files/tidy/.tidyrc create mode 100644 test/test-files/tidy/test.html create mode 100755 test/test-files/tidy/tidy create mode 100755 test/test-files/tidy/tidy.exe create mode 100644 test/test-files/top/ale-special-directory-name-dont-use-this-please/empty-file create mode 100644 test/test-files/top/example.ini create mode 100644 test/test-files/top/middle/bottom/dummy.txt create mode 100644 test/test-files/tsserver/src/file1.ts create mode 100644 test/test-files/tsserver/src/level-1/file2.ts create mode 100644 test/test-files/tsserver/src/level-1/level-2/file3.ts create mode 100644 test/test-files/tsserver/src/level-1/tsconfig.json create mode 100644 test/test-files/tsserver/tsconfig.json create mode 100644 test/test-files/typescript/custom_import_map.json create mode 100644 test/test-files/typescript/import_map.json create mode 100644 test/test-files/typescript/test.ts create mode 100644 test/test-files/typescript/tsconfig.json create mode 100644 test/test-files/vim/invalid_vim_project/test.vim create mode 100644 test/test-files/vim/node_modules/.bin/vim-language-server create mode 100644 test/test-files/vim/path_with_autoload/autoload/test.vim create mode 100644 test/test-files/vim/path_with_autoload/test.vim create mode 100644 test/test-files/vim/path_with_initvim/init.vim create mode 100644 test/test-files/vim/path_with_plugin/plugin/test.vim create mode 100644 test/test-files/vim/path_with_plugin/test.vim create mode 100644 test/test-files/vim/path_with_vimrc/.vimrc create mode 100755 test/test-files/volar/node_modules/.bin/vue-language-server create mode 100644 test/test-files/volar/node_modules/typescript/lib/tsserverlibrary.js create mode 100644 test/test-files/volar/package.json create mode 100644 test/test-files/volar/src/App.vue create mode 100644 test/test-files/write-good/node-modules-2/node_modules/write-good/bin/write-good.js create mode 100644 test/test-files/write-good/node-modules/node_modules/.bin/write-good create mode 100644 test/test-files/xml/dummy.xml create mode 100644 test/test-files/xo/monorepo/node_modules/xo/cli.js create mode 100644 test/test-files/xo/monorepo/package.json create mode 100644 test/test-files/xo/monorepo/packages/a/index.js create mode 100644 test/test-files/xo/monorepo/packages/a/index.ts create mode 100644 test/test-files/xo/monorepo/packages/a/package.json create mode 100644 test/test-files/yaml/test.yaml create mode 100644 test/test-files/yara/dummy.yar create mode 100644 test/test-files/zig/build.zig create mode 100644 test/test_ale_has.vader create mode 100644 test/test_ale_info.vader create mode 100644 test/test_ale_info_to_clipboard.vader create mode 100644 test/test_ale_lint_command.vader create mode 100644 test/test_ale_lint_stop_command.vader create mode 100644 test/test_ale_populate_command.vader create mode 100644 test/test_ale_toggle.vader create mode 100644 test/test_ale_var.vader create mode 100644 test/test_alejobstarted_autocmd.vader create mode 100644 test/test_alelint_autocmd.vader create mode 100644 test/test_ant_build_classpath_command.vader create mode 100644 test/test_ant_find_project_root.vader create mode 100644 test/test_autocmd_commands.vader create mode 100644 test/test_balloon_messages.vader create mode 100644 test/test_c_flag_parsing.vader create mode 100644 test/test_cleanup.vader create mode 100644 test/test_code_action.vader create mode 100644 test/test_code_action_corner_cases.vader create mode 100644 test/test_codefix.vader create mode 100644 test/test_computed_lint_file_values.vader create mode 100644 test/test_cursor_warnings.vader create mode 100644 test/test_deferred_command_string.vader create mode 100644 test/test_deferred_executable_string.vader create mode 100644 test/test_deno_executable_detection.vader create mode 100644 test/test_disabling_ale.vader create mode 100644 test/test_env_function.vader create mode 100644 test/test_errors_removed_after_filetype_changed.vader create mode 100644 test/test_filename_mapping.vader create mode 100644 test/test_filerename.vader create mode 100644 test/test_filetype_guessing.vader create mode 100644 test/test_filetype_linter_defaults.vader create mode 100644 test/test_find_nearest_directory.vader create mode 100644 test/test_find_references.vader create mode 100644 test/test_floating_preview.vader create mode 100644 test/test_format_command.vader create mode 100644 test/test_format_temporary_file_creation.vader create mode 100644 test/test_function_arg_count.vader create mode 100644 test/test_fuzzy_json_decode.vader create mode 100644 test/test_get_abspath.vader create mode 100644 test/test_get_loclist.vader create mode 100644 test/test_getmatches.vader create mode 100644 test/test_go_to_definition.vader create mode 100644 test/test_gradle_build_classpath_command.vader create mode 100644 test/test_gradle_find_executable.vader create mode 100644 test/test_gradle_find_project_root.vader create mode 100644 test/test_helptags.vader create mode 100644 test/test_highlight_placement.vader create mode 100644 test/test_highlight_position_chunking.vader create mode 100644 test/test_history_saving.vader create mode 100644 test/test_hover.vader create mode 100644 test/test_hover_parsing.vader create mode 100644 test/test_ignoring_linters.vader create mode 100644 test/test_line_join.vader create mode 100644 test/test_lint_file_linters.vader create mode 100644 test/test_lint_on_enter_when_file_changed.vader create mode 100644 test/test_lint_on_filetype_changed.vader create mode 100644 test/test_linter_defintion_processing.vader create mode 100644 test/test_linter_retrieval.vader create mode 100644 test/test_linter_type_mapping.vader create mode 100644 test/test_linting_blacklist.vader create mode 100644 test/test_linting_updates_loclist.vader create mode 100644 test/test_list_formatting.vader create mode 100644 test/test_list_opening.vader create mode 100644 test/test_list_titles.vader create mode 100644 test/test_load_all_linters.vader create mode 100644 test/test_loclist_binary_search.vader create mode 100644 test/test_loclist_corrections.vader create mode 100644 test/test_loclist_jumping.vader create mode 100644 test/test_loclist_sorting.vader create mode 100644 test/test_maven_build_classpath_command.vader create mode 100644 test/test_maven_find_executable.vader create mode 100644 test/test_maven_find_project_root.vader create mode 100644 test/test_nearest_file_search.vader create mode 100644 test/test_neovim_diagnostics.vader create mode 100644 test/test_no_linting_on_write_quit.vader create mode 100644 test/test_organize_imports.vader create mode 100644 test/test_other_sources.vader create mode 100644 test/test_parse_command_args.vader create mode 100644 test/test_path_dirname.vader create mode 100644 test/test_path_equality.vader create mode 100644 test/test_path_upwards.vader create mode 100644 test/test_path_uri.vader create mode 100644 test/test_pattern_options.vader create mode 100644 test/test_prepare_command.vader create mode 100644 test/test_python_pipenv.vader create mode 100644 test/test_python_poetry.vader create mode 100644 test/test_python_traceback.vader create mode 100644 test/test_python_uv.vader create mode 100644 test/test_python_virtualenv.vader create mode 100644 test/test_quickfix_deduplication.vader create mode 100644 test/test_quitting_variable.vader create mode 100644 test/test_redundant_tsserver_rendering_avoided.vader create mode 100644 test/test_regex_escaping.vader create mode 100644 test/test_rename.vader create mode 100644 test/test_resolve_local_path.vader create mode 100644 test/test_results_not_cleared_when_opening_loclist.vader create mode 100644 test/test_sandbox_execution.vader create mode 100644 test/test_semver_utils.vader create mode 100644 test/test_set_list_timers.vader create mode 100644 test/test_setting_loclist_from_another_buffer.vader create mode 100644 test/test_setting_problems_found_in_previous_buffers.vader create mode 100644 test/test_shell_detection.vader create mode 100644 test/test_should_do_nothing_conditions.vader create mode 100644 test/test_sml_command.vader create mode 100644 test/test_socket_connections.vader create mode 100644 test/test_statusline.vader create mode 100644 test/test_swift_find_project_root.vader create mode 100644 test/test_symbol_search.vader create mode 100644 test/test_temporary_file_management.vader create mode 100644 test/test_tmpdir_wrapper.vader create mode 100644 test/test_vim8_processid_parsing.vader create mode 100644 test/test_virtualtext.vader create mode 100644 test/test_windows_escaping.vader create mode 100644 test/test_wrap_comand.vader create mode 100644 test/test_writefile_function.vader create mode 100644 test/util/test_cd_string_commands.vader create mode 100644 test/v_files/testfile.v create mode 100644 test/vimrc diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..e602b4f0 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,62 @@ +--- +# Disabling building for AppVeyor. We are just testing things. +build: false +clone_depth: 10 +# Use the directory C:\testplugin so test directories will mostly work. +clone_folder: C:\testplugin + +branches: + only: + - master + - /v\d+\.\d+\.(x|\d+)/ + +# Skip running Windows tests if we only change files that can't impact +# Windows tests. +skip_commits: + files: + - '.github/**/*' + - .gitattributes + - Dockerfile + - README.md + - doc/* + - lua/* + - run-tests + - supported-tools.md + - syntax/* + +# Cache the vim and vader directories between builds. +cache: + - C:\vim -> .appveyor.yml + - C:\vader -> .appveyor.yml + +init: + # Stop git from changing newlines + - git config --global core.autocrlf input + +# NOTE: If you change the Vim or Vader versions here, please also update the +# instructions for running tests on Windows in ale-development.txt + +install: + # Download and unpack Vim + - ps: >- + if (!(Test-Path -Path C:\vim)){ + Add-Type -A System.IO.Compression.FileSystem + Invoke-WebRequest ftp://ftp.vim.org/pub/vim/pc/vim80-586w32.zip ` + -OutFile C:\vim.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\vim.zip', 'C:\vim') + Invoke-WebRequest ftp://ftp.vim.org/pub/vim/pc/vim80-586rt.zip ` + -OutFile C:\rt.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\rt.zip', 'C:\vim') + } + # Clone Vader and check out the commit we want + - ps: >- + if (!(Test-Path -Path C:\vader)){ + git clone https://github.com/junegunn/vader.vim C:\vader 2> $null + cd C:\vader + git checkout -qf c6243dd81c98350df4dec608fa972df98fa2a3af 2> $null + } + +test_script: + - cd C:\testplugin + - 'C:\vim\vim\vim80\vim.exe -u test\vimrc "+Vader! + test/*.vader test/*/*.vader test/*/*/*.vader test/*/*/*.vader"' diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7923f1ab --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# Top-most EditorConfig file +root = true + +# Match and apply these rules for all file +# types you open in your code editor +[*] +# Unix-style newlines +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.vader] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b1235f82 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +.* export-ignore +/CODE_OF_CONDUCT.md export-ignore +/CONTRIBUTING.md export-ignore +/Dockerfile export-ignore +/ISSUE_TEMPLATE export-ignore +/ISSUE_TEMPLATE.md export-ignore +/Makefile export-ignore +/PULL_REQUEST_TEMPLATE.md export-ignore +/README.md export-ignore +/img export-ignore +/run-tests export-ignore +/run-tests.bat export-ignore +/test export-ignore diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..587bb372 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +Codes of conduct are totally unnecessary and dumb. + +Just don't be a jerk and have fun. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..f2e7474c --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,25 @@ +## Guidelines + +Have fun, and work on whatever floats your boat. Take It Easy :tm:. + +For help with contributing to ALE, see `:help ale-development` in Vim, or view +the help file online [here](/doc/ale-development.txt). + +## Creating Issues + +Before creating any issues, please look through the current list of issues and +pull requests, and ensure that the issue hasn't already been reported. If an +issue has already been reported, but you have some new insight, please add +a comment to the existing issue. + +Please read the FAQ in the README before creating any issues. A feature +you desire may already exist and be documented, or the FAQ might explain +how to solve a problem you have already. + +Please try and describe any issues reported with as much detail as you can +provide about your Vim version, the linter you were trying to run, your +operating system, or any other information you think might be helpful. + +Please describe your issue in clear, grammatically correct, and easy to +understand English. You are more likely to see an issue resolved if others +can understand you. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..fdffb5fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +--- +blank_issues_enabled: false +contact_links: + - name: Ask for Help + url: https://github.com/dense-analysis/ale/discussions/new?category=q-a-ask-for-help-with-problems + about: Ask for Help in ALE Discussions diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md new file mode 100644 index 00000000..c8fa5792 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report-a-bug.md @@ -0,0 +1,48 @@ +--- +name: Report a bug +labels: bug +about: Report a bug with ALE. + +--- + + + +## Information + +**VIM version** + + + +Operating System: + +## What went wrong + + + +Something went wrong in specifically this place, and I also searched through both open and closed issues for the same problem before reporting a bug here. + +Are you having trouble configuring ALE? Try asking for help on [Stack Exchange](https://vi.stackexchange.com/) or perhaps on [Reddit](https://www.reddit.com/r/vim/) instead. The GitHub issue tracker should be used for reporting bugs or asking for new features. + +## Reproducing the bug + + + +1. I did this. +2. Then this happened. + +### :ALEInfo +
+ Expand + + + +
diff --git a/.github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md b/.github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md new file mode 100644 index 00000000..ad235e57 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md @@ -0,0 +1,21 @@ +--- +name: Suggest a new linter or fixer +labels: new tool +about: Suggest a new tool ALE can officially integrate with. + +--- + + + +**Name:** foobar +**URL:** https://foo.bar.com + + diff --git a/.github/ISSUE_TEMPLATE/suggest-an-improvement.md b/.github/ISSUE_TEMPLATE/suggest-an-improvement.md new file mode 100644 index 00000000..d39d0ac8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/suggest-an-improvement.md @@ -0,0 +1,8 @@ +--- +name: Suggest an improvement +labels: enhancement +about: Suggest some way to improve ALE, or add a new feature. + +--- + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..785da9b8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ + + +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-dev` and write tests. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..e20c0923 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +--- +# This configuration closes stale PRs after 56 + 7 days. +# That's 8 weeks until stale bot complains, and a week until it closes a PR. +# Issues in ALE are never, ever stale. They are either resolved or not. +only: pulls +daysUntilStale: 56 +daysUntilClose: 7 +exemptLabels: [] +staleLabel: stale +markComment: > + This pull request has been automatically marked as stale because it has not + been updated recently. Make sure to write tests and document your changes. + See `:help ale-dev` for information on writing tests. + + If your pull request is good to merge, bother w0rp or another maintainer + again, and get them to merge it. +closeComment: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..b2a14d09 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,38 @@ +--- +name: CI +on: # yamllint disable-line rule:truthy + push: + branches: [ master ] # yamllint disable-line rule:brackets + tags: + - v[0-9]+.[0-9]+.x + - v[0-9]+.[0-9]+.[0-9]+ + pull_request: + branches: [ master ] # yamllint disable-line rule:brackets + +jobs: + build_image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build docker run image + shell: bash + env: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + DOCKER_HUB_PASS: ${{ secrets.DOCKER_HUB_PASS }} + run: ./run-tests --build-image + test_ale: + needs: build_image + runs-on: ubuntu-latest + strategy: + matrix: + vim-version: + - '--vim-80-only' + - '--vim-90-only' + - '--neovim-07-only' + - '--neovim-08-only' + - '--lua-only' + - '--linters-only' + steps: + - uses: actions/checkout@v4 + - name: Run tests + run: ./run-tests -v ${{ matrix.vim-version }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7711fb99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +!.editorconfig +*.obj +*.pyc +# Ignore all hidden files everywhere. +# Use `git add -f` to add hidden files. +.* +/doc/tags +/init.vim +/test/ale-info-test-file +/vader_output +__pycache__ +tags diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 00000000..9d88d41e --- /dev/null +++ b/.luarc.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "diagnostics.globals": [ + "vim" + ], + "workspace.ignoreDir": [ + "test" + ], + "workspace.library": [ + "/usr/share/nvim/runtime/lua", + "/opt/homebrew/share/nvim/runtime/lua" + ], + "runtime.pathStrict": true, + "runtime.path": [ + "lua/?.lua", + "lua/?/init.lua" + ], + "runtime.version": "LuaJIT", + "hint.enable": false +} diff --git a/.vintrc.yaml b/.vintrc.yaml new file mode 100644 index 00000000..ce04d476 --- /dev/null +++ b/.vintrc.yaml @@ -0,0 +1,5 @@ +policies: + # Disable a violation that is thrown randomly for reasons I still + # do not understand. + ProhibitMissingScriptEncoding: + enabled: false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b47fa9dd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +ARG TESTBED_VIM_VERSION=24 + +FROM testbed/vim:${TESTBED_VIM_VERSION} + +ENV PACKAGES="\ + lua5.1 \ + lua5.1-dev \ + lua5.1-busted \ + bash \ + git \ + python2 \ + python3 \ + py3-pip \ + grep \ + sed \ +" +RUN apk --update add $PACKAGES && \ + rm -rf /var/cache/apk/* /tmp/* /var/tmp/* + +RUN install_vim -tag v8.0.0027 -build \ + -tag v9.0.0297 -build \ + -tag neovim:v0.7.0 -build \ + -tag neovim:v0.8.0 -build + +RUN pip install vim-vint==0.3.21 + +RUN git clone https://github.com/junegunn/vader.vim vader && \ + cd vader && git checkout c6243dd81c98350df4dec608fa972df98fa2a3af + +ARG GIT_VERSION +LABEL Version=${GIT_VERSION} +LABEL Name=denseanalysis/ale diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..584cc5b5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2016-2023, Dense Analysis +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..810b3d91 --- /dev/null +++ b/README.md @@ -0,0 +1,903 @@ +# Asynchronous Lint Engine + +[![Vim](https://img.shields.io/badge/VIM-%2311AB00.svg?style=for-the-badge&logo=vim&logoColor=white)](https://www.vim.org/) [![Neovim](https://img.shields.io/badge/NeoVim-%2357A143.svg?&style=for-the-badge&logo=neovim&logoColor=white)](https://neovim.io/) [![CI](https://img.shields.io/github/actions/workflow/status/dense-analysis/ale/main.yml?branch=master&label=CI&logo=github&style=for-the-badge)](https://github.com/dense-analysis/ale/actions?query=event%3Apush+workflow%3ACI+branch%3Amaster++) [![AppVeyor Build Status](https://img.shields.io/appveyor/build/dense-analysis/ale?label=Windows&style=for-the-badge)](https://ci.appveyor.com/project/dense-analysis/ale) [![Join the Dense Analysis Discord server](https://img.shields.io/badge/chat-Discord-5865F2?style=for-the-badge&logo=appveyor)](https://discord.gg/5zFD6pQxDk) + +![ALE Logo by Mark Grealish - https://www.bhalash.com/](https://user-images.githubusercontent.com/3518142/59195920-2c339500-8b85-11e9-9c22-f6b7f69637b8.jpg) + +ALE (Asynchronous Lint Engine) is a plugin providing linting (syntax checking +and semantic errors) in Neovim 0.7.0+ and Vim 8.0+ while you edit your text files, +and acts as a Vim [Language Server Protocol](https://langserver.org/) client. + + + +ALE makes use of Neovim and Vim 8 job control functions and timers to +run linters on the contents of text buffers and return errors as +text is changed in Vim. This allows for displaying warnings and +errors in files being edited in Vim before files have been saved +back to a filesystem. + +In other words, this plugin allows you to lint while you type. + +ALE offers support for fixing code with command line tools in a non-blocking +manner with the `:ALEFix` feature, supporting tools in many languages, like +`prettier`, `eslint`, `autopep8`, and more. + +ALE acts as a "language client" to support a variety of Language Server Protocol +features, including: + +* Diagnostics (via Language Server Protocol linters) +* Go To Definition (`:ALEGoToDefinition`) +* Completion (Built in completion support, or with Deoplete) +* Finding references (`:ALEFindReferences`) +* Hover information (`:ALEHover`) +* Symbol search (`:ALESymbolSearch`) + +If you don't care about Language Server Protocol, ALE won't load any of the code +for working with it unless needed. One of ALE's general missions is that you +won't pay for the features that you don't use. + +**Help Wanted:** If you would like to help maintain this plugin by managing the +many issues and pull requests that are submitted, please send the author an +email at [dev@w0rp.com](mailto:dev@w0rp.com?subject=Helping%20with%20ALE). + +If you enjoy this plugin, feel free to contribute or check out the author's +other content at [w0rp.com](https://w0rp.com). + +## Why ALE? + +ALE has been around for many years, and there are many ways to run asynchronous +linting and fixing of code in Vim. ALE offers the following. + +* No dependencies for ALE itself +* Lightweight plugin architecture (No JavaScript or Python required) +* Low memory footprint +* Runs virtually everywhere, including remote shells, and in `git commit` +* Out of the box support for running particular linters and language servers +* Near-zero configuration with custom code for better defaults +* Highly customizable and well-documented (`:help ale-options`) +* Breaking changes for the plugin are extremely rare +* Integrates with Neovim's LSP client (0.8+) and diagnostics (0.7+) +* Support for older Vim and Neovim versions +* Windows support +* Well-integrated with other plugins + +## Supported Languages and Tools + +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. + +## Usage + + + +### Linting + +Once this plugin is installed, while editing your files in supported +languages and tools which have been correctly installed, +this plugin will send the contents of your text buffers to a variety of +programs for checking the syntax and semantics of your programs. By default, +linters will be re-run in the background to check your syntax when you open +new buffers or as you make edits to your files. + +The behavior 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-integration-options` for options specified to particular linters. + + + +### Fixing + +ALE can fix files with the `ALEFix` command. Functions need to be configured +either in each buffer with a `b:ale_fixers`, or globally with `g:ale_fixers`. + +The recommended way to configure fixers is to define a List in an ftplugin file. + +```vim +" In ~/.vim/ftplugin/javascript.vim, or somewhere similar. + +" Fix files with prettier, and then ESLint. +let b:ale_fixers = ['prettier', 'eslint'] +" Equivalent to the above. +let b:ale_fixers = {'javascript': ['prettier', 'eslint']} +``` + +You can also configure your fixers from vimrc using `g:ale_fixers`, before or +after ALE has been loaded. + +A `*` in place of the filetype will apply a List of fixers to all files which +do not match some filetype in the Dictionary. + +Note that using a plain List for `g:ale_fixers` is not supported. + +```vim +" In ~/.vim/vimrc, or somewhere similar. +let g:ale_fixers = { +\ '*': ['remove_trailing_lines', 'trim_whitespace'], +\ 'javascript': ['eslint'], +\} +``` + +If you want to automatically fix files when you save them, you need to turn +a setting on in vimrc. + +```vim +" Set this variable to 1 to fix files when you save them. +let g:ale_fix_on_save = 1 +``` + +The `:ALEFixSuggest` command will suggest some supported tools for fixing code. +Both `g:ale_fixers` and `b:ale_fixers` can also accept functions, including +lambda functions, as fixers, for fixing files with custom tools. + +See `:help ale-fix` for complete information on how to fix files with ALE. + + + +### Completion + +ALE offers some support for completion via hijacking of omnicompletion while you +type. All of ALE's completion information must come from Language Server +Protocol linters, or from `tsserver` for TypeScript. + +When running ALE in Neovim 0.8+, ALE will integrate with Neovim's LSP client by +default, and any auto-completion plugin that uses the native LSP client will +work when ALE runs language servers. +[nvim-cmp](https://github.com/hrsh7th/nvim-cmp) is recommended as a +completion plugin worth trying in Neovim. + +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. +call deoplete#custom#option('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. +" +" You should not turn this setting on if you wish to use ALE as a completion +" source for other completion plugins, like Deoplete. +let g:ale_completion_enabled = 1 +``` + +ALE provides an omni-completion function you can use for triggering +completion manually with ``. + +```vim +set omnifunc=ale#completion#OmniFunc +``` + +ALE supports automatic imports from external modules. This behavior is enabled +by default and can be disabled by setting: + +```vim +let g:ale_completion_autoimport = 0 +``` + +Note that disabling auto import can result in missing completion items from some +LSP servers (e.g. eclipselsp). See `:help ale-completion` for more information. + + + +### Go To Definition + +ALE supports jumping to the definition of words under your cursor with the +`:ALEGoToDefinition` command using any enabled Language Server Protocol linters +and `tsserver`. In Neovim 0.8+, you can also use Neovim's built in `gd` keybind +and more. + +See `:help ale-go-to-definition` for more information. + + + +### Find References + +ALE supports finding references for words under your cursor with the +`:ALEFindReferences` command using any enabled Language Server Protocol linters +and `tsserver`. + +See `:help ale-find-references` for more information. + + + +### Hovering + +ALE supports "hover" information for printing brief information about symbols at +the cursor taken from Language Server Protocol linters and `tsserver` with the +`ALEHover` command. + +Truncated information will be displayed when the cursor rests on a symbol by +default, as long as there are no problems on the same line. + +The information can be displayed in a `balloon` tooltip in Vim or GVim by +hovering your mouse over symbols. Mouse hovering is enabled by default in GVim, +and needs to be configured for Vim 8.1+ in terminals. + +See `:help ale-hover` for more information. + + + +### Symbol Search + +ALE supports searching for workspace symbols via Language Server Protocol +linters with the `ALESymbolSearch` command. + +Search queries can be performed to find functions, types, and more which are +similar to a given query string. + +See `:help ale-symbol-search` for more information. + + + +### Refactoring: Rename, Actions + +ALE supports renaming symbols in code such as variables or class +names with the `ALERename` command. + +`ALEFileRename` will rename file and fix import paths (tsserver +only). + +`ALECodeAction` will execute actions on the cursor or applied to a visual +range selection, such as automatically fixing errors. + +See `:help ale-refactor` for more information. + +## Installation + +Add ALE to your runtime path in the usual ways. + +If you have trouble reading `:help ale`, try the following. + +```vim +packloadall | silent! helptags ALL +``` + +#### Vim `packload`: + +```bash +mkdir -p ~/.vim/pack/git-plugins/start +git clone --depth 1 https://github.com/dense-analysis/ale.git ~/.vim/pack/git-plugins/start/ale +``` + +#### Neovim `packload`: + +```bash +mkdir -p ~/.local/share/nvim/site/pack/git-plugins/start +git clone --depth 1 https://github.com/dense-analysis/ale.git ~/.local/share/nvim/site/pack/git-plugins/start/ale +``` + +#### Windows `packload`: + +```bash +# Run these commands in the "Git for Windows" Bash terminal +mkdir -p ~/vimfiles/pack/git-plugins/start +git clone --depth 1 https://github.com/dense-analysis/ale.git ~/vimfiles/pack/git-plugins/start/ale +``` + +#### [vim-plug](https://github.com/junegunn/vim-plug) + +```vim +Plug 'dense-analysis/ale' +``` + +#### [Vundle](https://github.com/VundleVim/Vundle.vim) + +```vim +Plugin 'dense-analysis/ale' +``` + +#### [Pathogen](https://github.com/tpope/vim-pathogen) +```vim +git clone https://github.com/dense-analysis/ale ~/.vim/bundle/ale +``` + +#### [lazy.nvim](https://github.com/folke/lazy.nvim) +```lua +{ + 'dense-analysis/ale', + config = function() + -- Configuration goes here. + local g = vim.g + + g.ale_ruby_rubocop_auto_correct_all = 1 + + g.ale_linters = { + ruby = {'rubocop', 'ruby'}, + lua = {'lua_language_server'} + } + end +} +``` + +## Contributing + +If you would like to see support for more languages and tools, please +[create an issue](https://github.com/dense-analysis/ale/issues) +or [create a pull request](https://github.com/dense-analysis/ale/pulls). +If your tool can read from stdin or you have code to suggest which is good, +support can be happily added for it. + +If you are interested in the general direction of the project, check out the +[wiki home page](https://github.com/dense-analysis/ale/wiki). The wiki includes +a Roadmap for the future, and more. + +If you'd liked to discuss ALE and more check out the Dense Analysis Discord +server here: https://discord.gg/5zFD6pQxDk + +## FAQ + + + +### How do I disable particular linters? + +By default, all available tools for all supported languages will be run. If you +want to only select a subset of the tools, you can define `b:ale_linters` for a +single buffer, or `g:ale_linters` globally. + +The recommended way to configure linters is to define a List in an ftplugin +file. + +```vim +" In ~/.vim/ftplugin/javascript.vim, or somewhere similar. + +" Enable ESLint only for JavaScript. +let b:ale_linters = ['eslint'] + +" Equivalent to the above. +let b:ale_linters = {'javascript': ['eslint']} +``` + +You can also declare which linters you want to run in your vimrc file, before or +after ALE has been loaded. + +```vim +" In ~/.vim/vimrc, or somewhere similar. +let g:ale_linters = { +\ 'javascript': ['eslint'], +\} +``` + +For all languages unspecified in the dictionary, all possible linters will +be run for those languages, just as when the dictionary is not defined. +Running many linters should not typically obstruct editing in Vim, +as they will all be executed in separate processes simultaneously. + +If you don't want ALE to run anything other than what you've explicitly asked +for, you can set `g:ale_linters_explicit` to `1`. + +```vim +" Only run linters named in ale_linters settings. +let g:ale_linters_explicit = 1 +``` + +This plugin will look for linters in the [`ale_linters`](ale_linters) directory. +Each directory within corresponds to a particular filetype in Vim, and each file +in each directory corresponds to the name of a particular linter. + +### How do I disable a particular warning or error? + +Warnings and errors should be configured in project configuration files for the +relevant tools. ALE supports disabling only warnings relating to trailing +whitespace, which Vim users often fix automatically. + +```vim +" Disable whitespace warnings +let g:ale_warn_about_trailing_whitespace = 0 +``` + +Users generally should not ignore warnings or errors in projects by changing +settings in their own editor. Instead, configure tools appropriately so any +other user of the same project will see the same problems. + + + +### How can I see what ALE has configured for the current file? + +Run the following to see what is currently configured: + +```vim +:ALEInfo +``` + +### How can I disable virtual text appearing at ends of lines? + +By default, ALE displays errors and warnings with virtual text. The problems ALE +shows appear with comment-like syntax after every problem found. You can set ALE +to only show problems where the cursor currently lies like so. + +```vim +let g:ale_virtualtext_cursor = 'current' +``` + +If you want to disable virtual text completely, apply the following. + +```vim +let g:ale_virtualtext_cursor = 'disabled' +``` + + + + +### How can I customise signs? + +Use these options to specify what text should be used for signs: + +```vim +let g:ale_sign_error = '>>' +let g:ale_sign_warning = '--' +``` + +ALE sets some background colors automatically for warnings and errors +in the sign gutter, with the names `ALEErrorSign` and `ALEWarningSign`. +These colors can be customised, or even removed completely: + +```vim +highlight clear ALEErrorSign +highlight clear ALEWarningSign +``` + +You can configure the sign gutter open at all times, if you wish. + +```vim +let g:ale_sign_column_always = 1 +``` + + + +### How can I change or disable the highlights ALE uses? + +ALE's highlights problems with highlight groups which link to `SpellBad`, +`SpellCap`, `error`, and `todo` groups by default. The characters that are +highlighted depend on the linters being used, and the information provided to +ALE. + +Highlighting can be disabled completely by setting `g:ale_set_highlights` to +`0`. + +```vim +" Set this in your vimrc file to disabling highlighting +let g:ale_set_highlights = 0 +``` + +You can control all of the highlights ALE uses, say if you are using a different +color scheme which produces ugly highlights. For example: + +```vim +highlight ALEWarning ctermbg=DarkMagenta +``` + +See `:help ale-highlights` for more information. + + + +### How can I change the format for echo messages? + +There are 3 global options that allow customizing the echoed message. + +- `g:ale_echo_msg_format` where: + * `%s` is the error message itself + * `%...code...%` is an optional error code, and most characters can be + written between the `%` characters. + * `%linter%` is the linter name + * `%severity%` is the severity type +- `g:ale_echo_msg_error_str` is the string used for error severity. +- `g:ale_echo_msg_warning_str` is the string used for warning severity. + +So for example this: + +```vim +let g:ale_echo_msg_error_str = 'E' +let g:ale_echo_msg_warning_str = 'W' +let g:ale_echo_msg_format = '[%linter%] %s [%severity%]' +``` + +Will give you: + +![Echoed message](https://user-images.githubusercontent.com/3518142/59195927-348bd000-8b85-11e9-88b6-508a094f1548.png) + +See `:help g:ale_echo_msg_format` for more information. + + + + +### How can I customise the statusline? + +#### lightline + +[lightline](https://github.com/itchyny/lightline.vim) does not have built-in +support for ALE, nevertheless there is a plugin that adds this functionality: +[maximbaz/lightline-ale](https://github.com/maximbaz/lightline-ale). + +For more information, check out the sources of that plugin, +`:help ale#statusline#Count()` and +[lightline documentation](https://github.com/itchyny/lightline.vim#advanced-configuration). + +#### vim-airline + +[vim-airline](https://github.com/vim-airline/vim-airline) integrates with ALE +for displaying error information in the status bar. If you want to see the +status for ALE in a nice format, it is recommended to use vim-airline with ALE. +The airline extension can be enabled by adding the following to your vimrc: + +```vim +" Set this. Airline will handle the rest. +let g:airline#extensions#ale#enabled = 1 +``` + +#### Custom statusline + +You can implement your own statusline 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: + +```vim +function! LinterStatus() abort + let l:counts = ale#statusline#Count(bufnr('')) + + let l:all_errors = l:counts.error + l:counts.style_error + let l:all_non_errors = l:counts.total - l:all_errors + + return l:counts.total == 0 ? 'OK' : printf( + \ '%dW %dE', + \ all_non_errors, + \ all_errors + \) +endfunction + +set statusline=%{LinterStatus()} +``` + +See `:help ale#statusline#Count()` or `:help ale#statusline#FirstProblem()` +for more information. + + + +### How can I change the borders for floating preview windows? + +Borders for floating preview windows are enabled by default. You can use the +`g:ale_floating_window_border` setting to configure them. + +You could disable the border with an empty list. + +```vim +let g:ale_floating_window_border = [] +``` + +If the terminal supports Unicode, you might try setting the value like below, to +make it look nicer. + +```vim +let g:ale_floating_window_border = ['│', '─', '╭', '╮', '╯', '╰', '│', '─'] +``` + +Since vim's default uses nice Unicode characters when possible, you can trick +ale into using that default with + +```vim +let g:ale_floating_window_border = repeat([''], 8) +``` + + + +### Will this plugin eat all of my laptop battery power? + +ALE takes advantage of the power of various tools to check your code. This of +course means that CPU time will be used to continuously check your code. If you +are concerned about the CPU time ALE will spend, which will of course imply +some cost to battery life, you can adjust your settings to make your CPU do +less work. + +First, consider increasing the delay before which ALE will run any linters +while you type. ALE uses a timeout which is cancelled and reset every time you +type, and this delay can be increased so linters are run less often. See +`:help g:ale_lint_delay` for more information. + +If you don't wish to run linters while you type, you can disable that behavior. +Set `g:ale_lint_on_text_changed` to `never`. You won't get as frequent error +checking, but ALE shouldn't block your ability to edit a document after you save +a file, so the asynchronous nature of the plugin will still be an advantage. + +If you are still concerned, you can turn the automatic linting off altogether, +including the option `g:ale_lint_on_enter`, and you can run ALE manually with +`:ALELint`. + + + + +### How can I use ALE with other LSP clients? + +ALE offers an API for letting any other plugin integrate with ALE. If you are +interested in writing an integration, see `:help ale-lint-other-sources`. + +If you're running ALE in Neovim with +[nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) for configuring +particular language servers, ALE will automatically disable its LSP +functionality for any language servers configured with nvim-lspconfig by +default. The following setting is applied by default: + +```vim +let g:ale_disable_lsp = 'auto' +``` + +If you are running ALE in combination with another LSP client, you may wish to +disable ALE's LSP functionality entirely. You can change the setting to `1` to +always disable all LSP functionality. + +```vim +let g:ale_disable_lsp = 1 +``` + +You can also use `b:ale_disable_lsp` in your ftplugin files to enable or disable +LSP features in ALE for different filetypes. + +#### Neovim Diagnostics + +If you are running Neovim 0.7 or later, you can make ALE display errors and +warnings via the Neovim diagnostics API. + +```vim +let g:ale_use_neovim_diagnostics_api = 1 +``` + + + +#### coc.nvim + +[coc.nvim](https://github.com/neoclide/coc.nvim) is a popular Vim plugin written +in TypeScript and dependent on the [npm](https://www.npmjs.com/) ecosystem for +providing full IDE features to Vim. Both ALE and coc.nvim implement +[Language Server Protocol](https://microsoft.github.io/language-server-protocol/) +(LSP) clients for supporting diagnostics (linting with a live server), and other +features like auto-completion, and others listed above. + +ALE is primarily focused on integrating with external programs through virtually +any means, provided the plugin remains almost entirely written in Vim script. +coc.nvim is primarily focused on bringing IDE features to Vim. If you want to +run external programs on your files to check for errors, and also use the most +advanced IDE features, you might want to use both plugins at the same time. + +The easiest way to get both plugins to work together is to configure coc.nvim to +send diagnostics to ALE, so ALE controls how all problems are presented to you, +and to disable all LSP features in ALE, so ALE doesn't try to provide LSP +features already provided by coc.nvim, such as auto-completion. + +Open your coc.nvim configuration file with `:CocConfig` and add +`"diagnostic.displayByAle": true` to your settings. + +#### vim-lsp + +[vim-lsp](https://github.com/prabirshrestha/vim-lsp) is a popular plugin as +implementation of Language Server Protocol (LSP) client for Vim. It provides +all the LSP features including auto completion, diagnostics, go to definitions, +etc. + +[vim-lsp-ale](https://github.com/rhysd/vim-lsp-ale) is a bridge plugin to solve +the problem when using both ALE and vim-lsp. With the plugin, diagnostics are +provided by vim-lsp and ALE can handle all the errors. Please read +[vim-lsp-ale's documentation](https://github.com/rhysd/vim-lsp-ale/blob/master/doc/vim-lsp-ale.txt) +for more details. + + + +### How can I execute some code when ALE starts or stops linting? + +ALE runs its own [autocmd](http://vimdoc.sourceforge.net/htmldoc/autocmd.html) +events when a lint or fix cycle are started and stopped. There is also an event +that runs when a linter job has been successfully started. These events can be +used to call arbitrary functions during these respective parts of the ALE's +operation. + +```vim +augroup YourGroup + autocmd! + autocmd User ALELintPre call YourFunction() + autocmd User ALELintPost call YourFunction() + + autocmd User ALEJobStarted call YourFunction() + + autocmd User ALEFixPre call YourFunction() + autocmd User ALEFixPost call YourFunction() +augroup END +``` + + + +### How can I navigate between errors quickly? + +ALE offers some commands with `` keybinds for moving between warnings and +errors quickly. You can map the keys Ctrl+j and Ctrl+k to moving between errors +for example: + +```vim +nmap (ale_previous_wrap) +nmap (ale_next_wrap) +``` + +For more information, consult the online documentation with +`:help ale-navigation-commands`. + + + +### How can I run linters only when I save files? + +ALE offers an option `g:ale_lint_on_save` for enabling running the linters when +files are saved. This option is enabled by default. If you only wish to run +linters when files are saved, you can turn the other options off. + +```vim +" Write this in your vimrc file +let g:ale_lint_on_text_changed = 'never' +let g:ale_lint_on_insert_leave = 0 +" You can disable this option too +" if you don't want linters to run on opening a file +let g:ale_lint_on_enter = 0 +``` + +If for whatever reason you don't wish to run linters again when you save files, +you can set `g:ale_lint_on_save` to `0`. + + + +### How can I use the quickfix list instead of the loclist? + +The quickfix list can be enabled by turning the `g:ale_set_quickfix` option on. +If you wish to also disable the loclist, you can disable the `g:ale_set_loclist` +option. + +```vim +" Write this in your vimrc file +let g:ale_set_loclist = 0 +let g:ale_set_quickfix = 1 +``` + +If you wish to show Vim windows for the loclist or quickfix items when a file +contains warnings or errors, `g:ale_open_list` can be set to `1`. +`g:ale_keep_list_window_open` can be set to `1` if you wish to keep the window +open even after errors disappear. + +```vim +let g:ale_open_list = 1 +" Set this if you want to. +" This can be useful if you are combining ALE with +" some other plugin which sets quickfix errors, etc. +let g:ale_keep_list_window_open = 1 +``` + +You can also set `let g:ale_list_vertical = 1` to open the windows vertically +instead of the default horizontally. + +### Why isn't ALE checking my filetype? + + + +#### stylelint for JSX + +First, install eslint and install stylelint with +[stylelint-processor-styled-components](https://github.com/styled-components/stylelint-processor-styled-components). + +Supposing you have installed both tools correctly, configure your .jsx files so +`jsx` is included in the filetype. You can use an `autocmd` for this. + +```vim +augroup FiletypeGroup + autocmd! + au BufNewFile,BufRead *.jsx set filetype=javascript.jsx +augroup END +``` + +Supposing the filetype has been set correctly, you can set the following +options in a jsx.vim ftplugin file. + +```vim +" In ~/.vim/ftplugin/jsx.vim, or somewhere similar. +let b:ale_linter_aliases = ['css', 'javascript'] +let b:ale_linters = ['stylelint', 'eslint'] +``` + +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_linters = {'jsx': ['stylelint', 'eslint']} +``` + +ALE will alias the `jsx` filetype so it uses the `css` filetype linters, and +use the original Array of selected linters for `jsx` from the `g:ale_linters` +object. All available linters will be used for the filetype `javascript`, and +no linter will be run twice for the same file. + + + +#### Checking Vue with ESLint + +To check Vue files with ESLint, your ESLint project configuration file must be +configured to use the [Vue plugin](https://github.com/vuejs/eslint-plugin-vue). +After that, you need to configure ALE so it will run the JavaScript ESLint +linter on your files. The settings you need are similar to the settings needed +for checking JSX code with both stylelint and ESLint, in the previous section. + +```vim +" In ~/.vim/ftplugin/vue.vim, or somewhere similar. + +" Run both javascript and vue linters for vue files. +let b:ale_linter_aliases = ['javascript', 'vue'] +" Select the eslint and vls linters. +let b:ale_linters = ['eslint', 'vls'] +``` + +Run `:ALEInfo` to see which linters are available after telling ALE to run +JavaScript linters on Vue files. Not all linters support checking Vue files. + +If you don't want to configure your linters in ftplugin files for some reason, +you can configure them from your vimrc file instead. + +```vim +" In ~/.vim/vimrc, or somewhere similar. +let g:ale_linter_aliases = {'vue': ['vue', 'javascript']} +let g:ale_linters = {'vue': ['eslint', 'vls']} +``` + + + +### How can I configure my C or C++ project? + +The structure of C and C++ projects varies wildly from project to project, with +many different build tools being used for building them, and many different +formats for project configuration files. ALE can run compilers easily, but +ALE cannot easily detect which compiler flags to use. + +Some tools and build configurations can generate +[compile_commands.json](https://clang.llvm.org/docs/JSONCompilationDatabase.html) +files. The `cppcheck`, `clangcheck`, `clangtidy` and `cquery` linters can read +these files for automatically determining the appropriate compiler flags to +use. + +For linting with compilers like `gcc` and `clang`, and with other tools, you +will need to tell ALE which compiler flags to use yourself. You can use +different options for different projects with the `g:ale_pattern_options` +setting. Consult the documentation for that setting for more information. +`b:ale_linters` can be used to select which tools you want to run, say if you +want to use only `gcc` for one project, and only `clang` for another. + +ALE will attempt to parse `compile_commands.json` files to discover compiler +flags to use when linting code. See `:help g:ale_c_parse_compile_commands` for +more information. See Clang's documentation for +[compile_commands.json files](https://clang.llvm.org/docs/JSONCompilationDatabase.html). +You should strongly consider generating them in your builds, which is easy to do +with CMake. + +You can also configure ALE to automatically run `make -n` to run dry runs on +`Makefile`s to discover compiler flags. This can execute arbitrary code, so the +option is disabled by default. See `:help g:ale_c_parse_makefile`. + + + +### How can I run linters or fixers via Docker or a VM? + +ALE supports running linters or fixers via Docker, virtual machines, or in +combination with any remote machine with a different file system, so long as the +tools are well-integrated with ALE, and ALE is properly configured to run the +correct commands and map filename paths between different file systems. See +`:help ale-lint-other-machines` for the full documentation on how to configure +ALE to support this. diff --git a/ale_linters/ada/adals.vim b/ale_linters/ada/adals.vim new file mode 100644 index 00000000..9a41e1df --- /dev/null +++ b/ale_linters/ada/adals.vim @@ -0,0 +1,26 @@ +" Author: Bartek Jasicki http://github.com/thindil +" Description: Support for Ada Language Server + +call ale#Set('ada_adals_executable', 'ada_language_server') +call ale#Set('ada_adals_project', 'default.gpr') +call ale#Set('ada_adals_encoding', 'utf-8') + +function! ale_linters#ada#adals#GetAdaLSConfig(buffer) abort + return { + \ 'ada.projectFile': ale#Var(a:buffer, 'ada_adals_project'), + \ 'ada.defaultCharset': ale#Var(a:buffer, 'ada_adals_encoding') + \} +endfunction + +function! ale_linters#ada#adals#GetRootDirectory(buffer) abort + return fnamemodify(bufname(a:buffer), ':p:h') +endfunction + +call ale#linter#Define('ada', { +\ 'name': 'adals', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'ada_adals_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#ada#adals#GetRootDirectory'), +\ 'lsp_config': function('ale_linters#ada#adals#GetAdaLSConfig') +\}) diff --git a/ale_linters/ada/cspell.vim b/ale_linters/ada/cspell.vim new file mode 100644 index 00000000..7342d2b6 --- /dev/null +++ b/ale_linters/ada/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Ada files. + +call ale#handlers#cspell#DefineLinter('ada') diff --git a/ale_linters/ada/gcc.vim b/ale_linters/ada/gcc.vim new file mode 100644 index 00000000..5afc9ae3 --- /dev/null +++ b/ale_linters/ada/gcc.vim @@ -0,0 +1,54 @@ +" Author: Martino Pilia +" Description: Lint Ada files with GCC + +call ale#Set('ada_gcc_executable', 'gcc') + +" -gnatwa: activate most optional warnings +" -gnatq: try semantic analysis even if syntax errors have been found +call ale#Set('ada_gcc_options', '-gnatwa -gnatq') + +function! ale_linters#ada#gcc#GetCommand(buffer) abort + " Build a suitable output file name. The output file is specified because + " 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#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) + return '%e -x ada -c -gnatc' + \ . ' -o ' . ale#Escape(l:out_file) + \ . ' -I %s:h' + \ . ale#Pad(ale#Var(a:buffer, 'ada_gcc_options')) + \ . ' %t' +endfunction + +" For the message format please refer to: +" https://gcc.gnu.org/onlinedocs/gnat_ugn/Output-and-Error-Message-Control.html +" https://gcc.gnu.org/onlinedocs/gnat_ugn/Warning-Message-Control.html +function! ale_linters#ada#gcc#Handle(buffer, lines) abort + " Error format: ::: + " Warning format: ::: warning: + let l:re = '\v(.+):([0-9]+):([0-9]+):\s+(warning:)?\s*(.+)\s*' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:re) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'type': l:match[4] is# 'warning:' ? 'W' : 'E', + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('ada', { +\ 'name': 'gcc', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..e15008c8 --- /dev/null +++ b/ale_linters/ansible/ansible_lint.vim @@ -0,0 +1,138 @@ +" Authors: Bjorn Neergaard , Vytautas Macionis +" Description: ansible-lint for ansible-yaml files + +call ale#Set('ansible_ansible_lint_executable', 'ansible-lint') + +function! ale_linters#ansible#ansible_lint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'ansible_ansible_lint_executable') +endfunction + +function! ale_linters#ansible#ansible_lint#Handle(buffer, version, 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:version_group = ale#semver#GTE(a:version, [6, 0, 0]) ? '>=6.0.0' : + \ ale#semver#GTE(a:version, [5, 0, 0]) ? '>=5.0.0' : + \ '<5.0.0' + let l:output = [] + + if '>=6.0.0' is# l:version_group + let l:error_codes = { 'blocker': 'E', 'critical': 'E', 'major': 'W', 'minor': 'W', 'info': 'I' } + let l:linter_issues = ale#util#FuzzyJSONDecode(a:lines, []) + + for l:issue in l:linter_issues + if ale#path#IsBufferPath(a:buffer, l:issue.location.path) + if exists('l:issue.location.positions') + let l:coord_keyname = 'positions' + else + let l:coord_keyname = 'lines' + endif + + let l:column_member = printf( + \ 'l:issue.location.%s.begin.column', l:coord_keyname + \) + + call add(l:output, { + \ 'lnum': exists(l:column_member) ? l:issue.location[l:coord_keyname].begin.line : + \ l:issue.location[l:coord_keyname].begin, + \ 'col': exists(l:column_member) ? l:issue.location[l:coord_keyname].begin.column : 0, + \ 'text': l:issue.check_name, + \ 'detail': l:issue.description, + \ 'code': l:issue.severity, + \ 'type': l:error_codes[l:issue.severity], + \}) + endif + endfor + endif + + if '>=5.0.0' is# l:version_group + " Matches patterns line the following: + " test.yml:3:148: syntax-check 'var' is not a valid attribute for a Play + " roles/test/tasks/test.yml:8: [package-latest] [VERY_LOW] Package installs should not use latest + " D:\test\tasks\test.yml:8: [package-latest] [VERY_LOW] package installs should not use latest + let l:pattern = '\v^(%([a-zA-Z]:)?[^:]+):(\d+):%((\d+):)? %(\[([-[:alnum:]]+)\]) %(\[([_[:alnum:]]+)\]) (.*)$' + let l:error_codes = { 'VERY_HIGH': 'E', 'HIGH': 'E', 'MEDIUM': 'W', 'LOW': 'W', 'VERY_LOW': 'W', 'INFO': 'I' } + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if ale#path#IsBufferPath(a:buffer, l:match[1]) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[6], + \ 'code': l:match[4], + \ 'type': l:error_codes[l:match[5]], + \}) + endif + endfor + endif + + if '<5.0.0' is# l:version_group + " Matches patterns line the following: + " test.yml:35: [EANSIBLE0002] Trailing whitespace + 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[4] + + if l:code is# 'EANSIBLE0002' + \&& !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + if ale#path#IsBufferPath(a:buffer, l:match[1]) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[5], + \ 'code': l:code, + \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', + \}) + endif + endfor + endif + + return l:output +endfunction + +function! ale_linters#ansible#ansible_lint#GetCommand(buffer, version) abort + let l:commands = { + \ '>=6.0.0': '%e --nocolor -f json -x yaml %s', + \ '>=5.0.0': '%e --nocolor --parseable-severity -x yaml %s', + \ '<5.0.0': '%e --nocolor -p %t' + \} + let l:command = ale#semver#GTE(a:version, [6, 0]) ? l:commands['>=6.0.0'] : + \ ale#semver#GTE(a:version, [5, 0]) ? l:commands['>=5.0.0'] : + \ l:commands['<5.0.0'] + + return l:command +endfunction + +call ale#linter#Define('ansible', { +\ 'name': 'ansible_lint', +\ 'aliases': ['ansible', 'ansible-lint'], +\ 'executable': function('ale_linters#ansible#ansible_lint#GetExecutable'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#ansible#ansible_lint#GetExecutable(buffer), +\ '%e --version', +\ function('ale_linters#ansible#ansible_lint#GetCommand'), +\ )}, +\ 'lint_file': 1, +\ 'callback': {buffer, lines -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#ansible#ansible_lint#GetExecutable(buffer), +\ '%e --version', +\ {buffer, version -> ale_linters#ansible#ansible_lint#Handle( +\ buffer, +\ l:version, +\ lines)}, +\ )}, +\}) diff --git a/ale_linters/ansible/language_server.vim b/ale_linters/ansible/language_server.vim new file mode 100644 index 00000000..0c064353 --- /dev/null +++ b/ale_linters/ansible/language_server.vim @@ -0,0 +1,47 @@ +" Author: Horacio Sanson +" Description: Support ansible language server https://github.com/ansible/ansible-language-server/ + +call ale#Set('ansible_language_server_executable', 'ansible-language-server') +call ale#Set('ansible_language_server_config', {}) + +function! ale_linters#ansible#language_server#Executable(buffer) abort + return ale#Var(a:buffer, 'ansible_language_server_executable') +endfunction + +function! ale_linters#ansible#language_server#GetCommand(buffer) abort + let l:executable = ale_linters#ansible#language_server#Executable(a:buffer) + + return ale#Escape(l:executable) . ' --stdio' +endfunction + +function! ale_linters#ansible#language_server#FindProjectRoot(buffer) abort + let l:dir = fnamemodify( + \ ale#path#FindNearestFile(a:buffer, 'ansible.cfg'), + \ ':h' + \) + + if l:dir isnot# '.' && isdirectory(l:dir) + return l:dir + endif + + let l:dir = fnamemodify( + \ ale#path#FindNearestDirectory(a:buffer, '.git'), + \ ':h:h' + \) + + if l:dir isnot# '.' && isdirectory(l:dir) + return l:dir + endif + + return '' +endfunction + +call ale#linter#Define('ansible', { +\ 'name': 'language_server', +\ 'aliases': ['ansible_language_server', 'ansible-language-server'], +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#ansible#language_server#Executable'), +\ 'command': function('ale_linters#ansible#language_server#GetCommand'), +\ 'project_root': function('ale_linters#ansible#language_server#FindProjectRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'ansible_language_server_config')} +\}) diff --git a/ale_linters/apiblueprint/drafter.vim b/ale_linters/apiblueprint/drafter.vim new file mode 100644 index 00000000..5d40c53a --- /dev/null +++ b/ale_linters/apiblueprint/drafter.vim @@ -0,0 +1,38 @@ +" Author: nametake https://nametake.github.io +" Description: apiblueprint parser + +function! ale_linters#apiblueprint#drafter#HandleErrors(buffer, lines) abort + " Matches patterns line the following: + " + " warning: (3) unable to parse response signature, expected 'response [] [()]'; line 4, column 3k - line 4, column 22 + " warning: (10) message-body asset is expected to be a pre-formatted code block, separate it by a newline and indent every of its line by 12 spaces or 3 tabs; line 30, column 5 - line 30, column 9; line 31, column 9 - line 31, column 14; line 32, column 9 - line 32, column 14 + let l:pattern = '\(^.*\): (\d\+) \(.\{-\}\); line \(\d\+\), column \(\d\+\) - line \d\+, column \d\+\(.*; line \d\+, column \d\+ - line \(\d\+\), column \(\d\+\)\)\{-\}$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines[2:], l:pattern) + let l:item = { + \ 'type': l:match[1] is# 'warning' ? 'W' : 'E', + \ 'text': l:match[2], + \ 'lnum': l:match[3] + 0, + \ 'col': l:match[4] + 0, + \} + + if l:match[5] isnot# '' + let l:item.end_lnum = l:match[6] + 0 + let l:item.end_col = l:match[7] + 0 + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + + +call ale#linter#Define('apiblueprint', { +\ 'name': 'drafter', +\ 'output_stream': 'stderr', +\ 'executable': 'drafter', +\ 'command': 'drafter --use-line-num --validate', +\ 'callback': 'ale_linters#apiblueprint#drafter#HandleErrors', +\}) diff --git a/ale_linters/apkbuild/apkbuild_lint.vim b/ale_linters/apkbuild/apkbuild_lint.vim new file mode 100644 index 00000000..285f5534 --- /dev/null +++ b/ale_linters/apkbuild/apkbuild_lint.vim @@ -0,0 +1,12 @@ +" Author: Leo +" Description: apkbuild-lint from atools linter for APKBUILDs + +call ale#Set('apkbuild_apkbuild_lint_executable', 'apkbuild-lint') + +call ale#linter#Define('apkbuild', { +\ 'name': 'apkbuild_lint', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'apkbuild_apkbuild_lint_executable')}, +\ 'command': '%e %t', +\ 'callback': 'ale#handlers#atools#Handle', +\}) diff --git a/ale_linters/apkbuild/secfixes_check.vim b/ale_linters/apkbuild/secfixes_check.vim new file mode 100644 index 00000000..c65267fd --- /dev/null +++ b/ale_linters/apkbuild/secfixes_check.vim @@ -0,0 +1,12 @@ +" Author: Leo +" Description: secfixes-check from atools linter for APKBUILDs + +call ale#Set('apkbuild_secfixes_check_executable', 'secfixes-check') + +call ale#linter#Define('apkbuild', { +\ 'name': 'secfixes_check', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'apkbuild_secfixes_check_executable')}, +\ 'command': '%e %t', +\ 'callback': 'ale#handlers#atools#Handle', +\}) diff --git a/ale_linters/asciidoc/alex.vim b/ale_linters/asciidoc/alex.vim new file mode 100644 index 00000000..97976b2c --- /dev/null +++ b/ale_linters/asciidoc/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for asciidoc files + +call ale#handlers#alex#DefineLinter('asciidoc', '--text') diff --git a/ale_linters/asciidoc/cspell.vim b/ale_linters/asciidoc/cspell.vim new file mode 100644 index 00000000..b228b2e8 --- /dev/null +++ b/ale_linters/asciidoc/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for ASCIIDoc files. + +call ale#handlers#cspell#DefineLinter('asciidoc') diff --git a/ale_linters/asciidoc/languagetool.vim b/ale_linters/asciidoc/languagetool.vim new file mode 100644 index 00000000..8e8de7f3 --- /dev/null +++ b/ale_linters/asciidoc/languagetool.vim @@ -0,0 +1,5 @@ +" Author: Horacio Sanson (hsanson [ät] gmail.com) +" Description: languagetool for asciidoc files, copied from markdown. + + +call ale#handlers#languagetool#DefineLinter('asciidoc') diff --git a/ale_linters/asciidoc/proselint.vim b/ale_linters/asciidoc/proselint.vim new file mode 100644 index 00000000..b636c067 --- /dev/null +++ b/ale_linters/asciidoc/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for AsciiDoc files + +call ale#linter#Define('asciidoc', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/asciidoc/redpen.vim b/ale_linters/asciidoc/redpen.vim new file mode 100644 index 00000000..819e385f --- /dev/null +++ b/ale_linters/asciidoc/redpen.vim @@ -0,0 +1,9 @@ +" Author: rhysd https://rhysd.github.io +" Description: Redpen, a proofreading tool (http://redpen.cc) + +call ale#linter#Define('asciidoc', { +\ 'name': 'redpen', +\ 'executable': 'redpen', +\ 'command': 'redpen -f asciidoc -r json %t', +\ 'callback': 'ale#handlers#redpen#HandleRedpenOutput', +\}) 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 +" 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/asciidoc/vale.vim b/ale_linters/asciidoc/vale.vim new file mode 100644 index 00000000..b3cf4547 --- /dev/null +++ b/ale_linters/asciidoc/vale.vim @@ -0,0 +1,9 @@ +" Author: Jeff Kreeftmeijer https://github.com/jeffkreeftmeijer +" Description: vale for AsciiDoc files + +call ale#linter#Define('asciidoc', { +\ 'name': 'vale', +\ 'executable': 'vale', +\ 'command': 'vale --output=line %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/asciidoc/writegood.vim b/ale_linters/asciidoc/writegood.vim new file mode 100644 index 00000000..a29b7e9c --- /dev/null +++ b/ale_linters/asciidoc/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for AsciiDoc files + +call ale#handlers#writegood#DefineLinter('asciidoc') diff --git a/ale_linters/asm/gcc.vim b/ale_linters/asm/gcc.vim new file mode 100644 index 00000000..cda38923 --- /dev/null +++ b/ale_linters/asm/gcc.vim @@ -0,0 +1,37 @@ +" Author: Lucas Kolstad +" Description: gcc linter for asm files + +call ale#Set('asm_gcc_executable', 'gcc') +call ale#Set('asm_gcc_options', '-Wall') + +function! ale_linters#asm#gcc#GetCommand(buffer) abort + " `-o /dev/null` or `-o null` is needed to catch all errors, + " -fsyntax-only doesn't catch everything. + return '%e -x assembler' + \ . ' -o ' . g:ale#util#nul_file + \ . '-iquote %s:h' + \ . ' ' . ale#Var(a:buffer, 'asm_gcc_options') . ' -' +endfunction + +function! ale_linters#asm#gcc#Handle(buffer, lines) abort + let l:pattern = '^.\+:\(\d\+\): \([^:]\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'type': l:match[2] =~? 'error' ? 'E' : 'W', + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('asm', { +\ 'name': 'gcc', +\ 'output_stream': 'stderr', +\ '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/asm/llvm_mc.vim b/ale_linters/asm/llvm_mc.vim new file mode 100644 index 00000000..ebfd0064 --- /dev/null +++ b/ale_linters/asm/llvm_mc.vim @@ -0,0 +1,37 @@ +" Author: uidops +" Description: llvm-mc linter for asm files + +call ale#Set('asm_llvm_mc_executable', 'llvm-mc') +call ale#Set('asm_llvm_mc_options', '') + +function! ale_linters#asm#llvm_mc#GetCommand(buffer) abort + return '%e --assemble' + \ . ' --filetype=asm' + \ . ' -o ' . g:ale#util#nul_file + \ . ' ' . ale#Var(a:buffer, 'asm_llvm_mc_options') +endfunction + +function! ale_linters#asm#llvm_mc#Handle(buffer, lines) abort + let l:pattern = '^.\+:\(\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, + \ 'type': l:match[3] =~? 'error' ? 'E' : 'W', + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('asm', { +\ 'name': 'llvm_mc', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'asm_llvm_mc_executable')}, +\ 'command': function('ale_linters#asm#llvm_mc#GetCommand'), +\ 'callback': 'ale_linters#asm#llvm_mc#Handle', +\}) + diff --git a/ale_linters/astro/eslint.vim b/ale_linters/astro/eslint.vim new file mode 100644 index 00000000..9fb51ad2 --- /dev/null +++ b/ale_linters/astro/eslint.vim @@ -0,0 +1,11 @@ +" Author: Hyuksang Kwon +" Description: eslint for astro files + +call ale#linter#Define('astro', { +\ 'name': 'eslint', +\ 'output_stream': 'both', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\}) diff --git a/ale_linters/avra/avra.vim b/ale_linters/avra/avra.vim new file mode 100644 index 00000000..edf15c09 --- /dev/null +++ b/ale_linters/avra/avra.vim @@ -0,0 +1,36 @@ +" Author: Utkarsh Verma +" Description: AVRA linter for avra syntax. + +call ale#Set('avra_avra_executable', 'avra') +call ale#Set('avra_avra_options', '') + +function! ale_linters#avra#avra#GetCommand(buffer) abort + return '%e' + \ . ' %t' + \ . ale#Pad(ale#Var(a:buffer, 'avra_avra_options')) + \ . ' -o ' . g:ale#util#nul_file +endfunction + +function! ale_linters#avra#avra#Handle(buffer, lines) abort + " Note that we treat 'fatal' as errors. + let l:pattern = '^\S\+(\(\d\+\))\s\+:\s\+\(\S\+\)\s\+:\s\+\(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'type': l:match[2] =~? 'Error' ? 'E' : 'W', + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('avra', { +\ 'name': 'avra', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'avra_avra_executable')}, +\ 'command': function('ale_linters#avra#avra#GetCommand'), +\ 'callback': 'ale_linters#avra#avra#Handle', +\}) diff --git a/ale_linters/awk/gawk.vim b/ale_linters/awk/gawk.vim new file mode 100644 index 00000000..0c3212fd --- /dev/null +++ b/ale_linters/awk/gawk.vim @@ -0,0 +1,23 @@ +" Author: kmarc +" Description: This file adds support for using GNU awk with scripts. + +call ale#Set('awk_gawk_executable', 'gawk') +call ale#Set('awk_gawk_options', '') + +function! ale_linters#awk#gawk#GetCommand(buffer) abort + " note the --source 'BEGIN ...' is to prevent + " gawk from attempting to execute the body of the script + " it is linting. + return '%e --source ' . ale#Escape('BEGIN { exit } END { exit 1 }') + \ . ' --lint' + \ . ale#Pad(ale#Var(a:buffer, 'awk_gawk_options')) + \ . ' -f %t /dev/null' +endfunction + +call ale#linter#Define('awk', { +\ 'name': 'gawk', +\ '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/bats/shellcheck.vim b/ale_linters/bats/shellcheck.vim new file mode 100644 index 00000000..5c2a0ea9 --- /dev/null +++ b/ale_linters/bats/shellcheck.vim @@ -0,0 +1,4 @@ +" Author: Ian2020 +" Description: shellcheck linter for bats scripts. + +call ale#handlers#shellcheck#DefineLinter('bats') diff --git a/ale_linters/bib/bibclean.vim b/ale_linters/bib/bibclean.vim new file mode 100644 index 00000000..f1610e00 --- /dev/null +++ b/ale_linters/bib/bibclean.vim @@ -0,0 +1,80 @@ +" Author: Horacio Sanson - https://github.com/hsanson +" Description: Support for bibclean linter for BibTeX files. + +call ale#Set('bib_bibclean_executable', 'bibclean') + +function! ale_linters#bib#bibclean#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'bib_bibclean_executable') + + return ale#Escape(l:executable) . ' -file-position ' +endfunction + +function! ale_linters#bib#bibclean#get_type(str) abort + if a:str is# '??' + return 'E' + else + return 'W' + endif +endfunction + +function! ale_linters#bib#bibclean#match_msg(line) abort + " Legacy message pattern works for bibclean <= v2.11.4. If empty, try + " the new message pattern for bibtex > v2.11.4 + let l:matches_legacy = matchlist(a:line, '^\(.*\) "stdin", line \(\d\+\): \(.*\)$') + + return ! empty(l:matches_legacy) ? l:matches_legacy + \ : matchlist(a:line, '^\(.*\) stdin:\(\d\+\):\(.*\)$') +endfunction + +function! ale_linters#bib#bibclean#match_entry(line) abort + return matchlist(a:line, 'Entry input byte=.* line=\(.*\) column=\(.*\) output .*$') +endfunction + +function! ale_linters#bib#bibclean#match_value(line) abort + return matchlist(a:line, 'Value input byte=.* line=\(.*\) column=\(.*\) output .*$') +endfunction + +function! ale_linters#bib#bibclean#Handle(buffer, lines) abort + let l:output = [] + + let l:type = 'E' + 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: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 + endfor + + return l:output +endfunction + +call ale#linter#Define('bib', { +\ 'name': 'bibclean', +\ '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/bicep/az_bicep.vim b/ale_linters/bicep/az_bicep.vim new file mode 100644 index 00000000..f487ae0f --- /dev/null +++ b/ale_linters/bicep/az_bicep.vim @@ -0,0 +1,69 @@ +" Author: Carl Smedstad +" Description: az_bicep for bicep files + +let g:ale_bicep_az_bicep_executable = +\ get(g:, 'ale_bicep_az_bicep_executable', 'az') + +let g:ale_bicep_az_bicep_options = +\ get(g:, 'ale_bicep_az_bicep_options', '') + +function! ale_linters#bicep#az_bicep#Executable(buffer) abort + return ale#Var(a:buffer, 'bicep_az_bicep_executable') +endfunction + +function! ale_linters#bicep#az_bicep#Command(buffer) abort + let l:executable = ale_linters#bicep#az_bicep#Executable(a:buffer) + let l:options = ale#Var(a:buffer, 'bicep_az_bicep_options') + + if has('win32') + let l:nullfile = 'NUL' + else + let l:nullfile = '/dev/null' + endif + + return ale#Escape(l:executable) + \ . ' bicep build --outfile ' + \ . l:nullfile + \ . ' --file ' + \ . '%s ' + \ . l:options +endfunction + +function! ale_linters#bicep#az_bicep#Handle(buffer, lines) abort + let l:pattern = '\v^([A-Z]+)?(:\s)?(.*)\((\d+),(\d+)\)\s:\s([a-zA-Z]*)\s([-a-zA-Z0-9]*):\s(.*)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if l:match[1] is# 'ERROR' + let l:type = 'E' + elseif l:match[1] is# 'WARNING' + let l:type = 'W' + elseif l:match[6] is# 'Error' + let l:type = 'E' + elseif l:match[6] is# 'Warning' + let l:type = 'W' + else + let l:type = 'I' + endif + + call add(l:output, { + \ 'filename': l:match[3], + \ 'lnum': l:match[4] + 0, + \ 'col': l:match[5] + 0, + \ 'type': l:type, + \ 'code': l:match[7], + \ 'text': l:match[8], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('bicep', { +\ 'name': 'az_bicep', +\ 'executable': function('ale_linters#bicep#az_bicep#Executable'), +\ 'command': function('ale_linters#bicep#az_bicep#Command'), +\ 'callback': 'ale_linters#bicep#az_bicep#Handle', +\ 'output_stream': 'stderr', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/bicep/bicep.vim b/ale_linters/bicep/bicep.vim new file mode 100644 index 00000000..a5f0d7c6 --- /dev/null +++ b/ale_linters/bicep/bicep.vim @@ -0,0 +1,65 @@ +" Author: Carl Smedstad +" Description: bicep for bicep files + +let g:ale_bicep_bicep_executable = +\ get(g:, 'ale_bicep_bicep_executable', 'bicep') + +let g:ale_bicep_bicep_options = +\ get(g:, 'ale_bicep_bicep_options', '') + +function! ale_linters#bicep#bicep#Executable(buffer) abort + return ale#Var(a:buffer, 'bicep_bicep_executable') +endfunction + +function! ale_linters#bicep#bicep#Command(buffer) abort + let l:executable = ale_linters#bicep#bicep#Executable(a:buffer) + let l:options = ale#Var(a:buffer, 'bicep_bicep_options') + + if has('win32') + let l:nullfile = 'NUL' + else + let l:nullfile = '/dev/null' + endif + + return ale#Escape(l:executable) + \ . ' build --outfile ' + \ . l:nullfile + \ . ' ' + \ . l:options + \ . ' %s' +endfunction + +function! ale_linters#bicep#bicep#Handle(buffer, lines) abort + let l:pattern = '\v^(.*)\((\d+),(\d+)\)\s:\s([a-zA-Z]*)\s([-a-zA-Z0-9]*):\s(.*)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if l:match[4] is# 'Error' + let l:type = 'E' + elseif l:match[4] is# 'Warning' + let l:type = 'W' + else + let l:type = 'I' + endif + + call add(l:output, { + \ 'filename': l:match[1], + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:type, + \ 'code': l:match[5], + \ 'text': l:match[6], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('bicep', { +\ 'name': 'bicep', +\ 'executable': function('ale_linters#bicep#bicep#Executable'), +\ 'command': function('ale_linters#bicep#bicep#Command'), +\ 'callback': 'ale_linters#bicep#bicep#Handle', +\ 'output_stream': 'both', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/bitbake/oelint_adv.vim b/ale_linters/bitbake/oelint_adv.vim new file mode 100644 index 00000000..fb85a9b9 --- /dev/null +++ b/ale_linters/bitbake/oelint_adv.vim @@ -0,0 +1,47 @@ +" Author: offa +" Description: oelint-adv for BitBake files + +call ale#Set('bitbake_oelint_adv_executable', 'oelint-adv') +call ale#Set('bitbake_oelint_adv_options', '') +call ale#Set('bitbake_oelint_adv_config', '.oelint.cfg') + +function! ale_linters#bitbake#oelint_adv#Command(buffer) abort + let l:config_file = ale#path#FindNearestFile(a:buffer, + \ ale#Var(a:buffer, 'bitbake_oelint_adv_config')) + + return ((!empty(l:config_file)) + \ ? 'OELINT_CONFIG=' . ale#Escape(l:config_file) . ' ' + \ : '') + \ . '%e --quiet ' + \ . ale#Pad(ale#Var(a:buffer, 'bitbake_oelint_adv_options')) . '%s' +endfunction + +function! ale_linters#bitbake#oelint_adv#Handle(buffer, lines) abort + let l:pattern = '\v^(.+):(.+):(.+):(.+):(.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[2]), + \ 'type': l:match[3] is# 'error' + \ ? 'E' : (l:match[3] is# 'warning' ? 'W' : 'I'), + \ 'text': StripAnsiCodes(l:match[5]), + \ 'code': l:match[4] + \ }) + endfor + + return l:output +endfunction + +function! StripAnsiCodes(line) abort + return substitute(a:line, '\e\[[0-9;]\+[mK]', '', 'g') +endfunction + +call ale#linter#Define('bitbake', { +\ 'name': 'oelint_adv', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#Var(b, 'bitbake_oelint_adv_executable')}, +\ 'cwd': '%s:h', +\ 'command': function('ale_linters#bitbake#oelint_adv#Command'), +\ 'callback': 'ale_linters#bitbake#oelint_adv#Handle', +\ }) diff --git a/ale_linters/bzl/buildifier.vim b/ale_linters/bzl/buildifier.vim new file mode 100644 index 00000000..42cd3cd9 --- /dev/null +++ b/ale_linters/bzl/buildifier.vim @@ -0,0 +1,40 @@ +" Author: Chuck Grindel +" Description: Bazel Starlark lint support using buildifier. + +function! ale_linters#bzl#buildifier#GetCommand(buffer) abort + let l:executable = ale#Escape(ale#fixers#buildifier#GetExecutable(a:buffer)) + let l:options = ale#Var(a:buffer, 'bazel_buildifier_options') + let l:filename = ale#Escape(bufname(a:buffer)) + + let l:command = l:executable . ' -mode check -lint warn -path %s' + + if l:options isnot# '' + let l:command .= ' ' . l:options + endif + + return l:command +endfunction + +function! ale_linters#bzl#buildifier#Handle(buffer, lines) abort + let l:pattern = '\v^[^:]+:(\d+):(\d+)?:?\s+(syntax error near)?(.+)$' + 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] . l:match[4], + \ 'type': l:match[3] is# 'syntax error near' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('bzl', { +\ 'name': 'buildifier', +\ 'output_stream': 'both', +\ 'executable': function('ale#fixers#buildifier#GetExecutable'), +\ 'command': function('ale_linters#bzl#buildifier#GetCommand'), +\ 'callback': function('ale_linters#bzl#buildifier#Handle'), +\}) diff --git a/ale_linters/c/cc.vim b/ale_linters/c/cc.vim new file mode 100644 index 00000000..35125f2f --- /dev/null +++ b/ale_linters/c/cc.vim @@ -0,0 +1,67 @@ +" Author: w0rp +" Description: A C compiler linter for C files with gcc/clang, etc. + +call ale#Set('c_cc_executable', '') +call ale#Set('c_cc_options', '-std=c11 -Wall') +call ale#Set('c_cc_use_header_lang_flag', -1) +call ale#Set('c_cc_header_exts', ['h']) + +function! ale_linters#c#cc#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'c_cc_executable') + + " Default to either clang or gcc. + if l:executable is# '' + if ale#engine#IsExecutable(a:buffer, 'clang') + let l:executable = 'clang' + else + let l:executable = 'gcc' + endif + endif + + return l:executable +endfunction + +function! ale_linters#c#cc#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:ale_flags = ale#Var(a:buffer, 'c_cc_options') + + if l:cflags =~# '-std=' + let l:ale_flags = substitute( + \ l:ale_flags, + \ '-std=\(c\|gnu\)[0-9]\{2\}', + \ '', + \ 'g') + endif + + " Select the correct language flag depending on the executable, options + " and file extension + let l:executable = ale_linters#c#cc#GetExecutable(a:buffer) + let l:use_header_lang_flag = ale#Var(a:buffer, 'c_cc_use_header_lang_flag') + let l:header_exts = ale#Var(a:buffer, 'c_cc_header_exts') + let l:lang_flag = ale#c#GetLanguageFlag( + \ a:buffer, + \ l:executable, + \ l:use_header_lang_flag, + \ l:header_exts, + \ 'c') + + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + " + " `-o /dev/null` or `-o null` is needed to catch all errors, + " -fsyntax-only doesn't catch everything. + return '%e -S -x ' . l:lang_flag + \ . ' -o ' . g:ale#util#nul_file + \ . ' -iquote %s:h' + \ . ale#Pad(l:cflags) + \ . ale#Pad(l:ale_flags) . ' -' +endfunction + +call ale#linter#Define('c', { +\ 'name': 'cc', +\ 'aliases': ['gcc', 'clang'], +\ 'output_stream': 'stderr', +\ 'executable': function('ale_linters#c#cc#GetExecutable'), +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#c#cc#GetCommand'))}, +\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', +\}) diff --git a/ale_linters/c/ccls.vim b/ale_linters/c/ccls.vim new file mode 100644 index 00000000..9f105712 --- /dev/null +++ b/ale_linters/c/ccls.vim @@ -0,0 +1,15 @@ +" Author: Ye Jingchen , Ben Falconer , jtalowell +" Description: A language server for C + +call ale#Set('c_ccls_executable', 'ccls') +call ale#Set('c_ccls_init_options', {}) +call ale#Set('c_build_dir', '') + +call ale#linter#Define('c', { +\ 'name': 'ccls', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'c_ccls_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), +\ 'initialization_options': {b -> ale#handlers#ccls#GetInitOpts(b, 'c_ccls_init_options')}, +\}) diff --git a/ale_linters/c/clangcheck.vim b/ale_linters/c/clangcheck.vim new file mode 100644 index 00000000..54dad47b --- /dev/null +++ b/ale_linters/c/clangcheck.vim @@ -0,0 +1,38 @@ +" Author: gagbo +" : luibo +" : Jorengarenar +" Description: clang-check linter for C files +" modified from cpp/clangcheck.vim to match for C + +call ale#Set('c_clangcheck_executable', 'clang-check') +call ale#Set('c_clangcheck_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#c#clangcheck#GetCommand(buffer) abort + let l:user_options = ale#Var(a:buffer, 'c_clangcheck_options') + + " Try to find compilation database to link automatically + let l:build_dir = ale#Var(a:buffer, 'c_build_dir') + + if empty(l:build_dir) + let [l:root, l:json_file] = ale#c#FindCompileCommands(a:buffer) + let l:build_dir = ale#path#Dirname(l:json_file) + endif + + " The extra arguments in the command are used to prevent .plist files from + " being generated. These are only added if no build directory can be + " detected. + return '%e -analyze %s' + \ . (empty(l:build_dir) ? ' --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics': '') + \ . ale#Pad(l:user_options) + \ . (!empty(l:build_dir) ? ' -p ' . ale#Escape(l:build_dir) : '') +endfunction + +call ale#linter#Define('c', { +\ 'name': 'clangcheck', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'c_clangcheck_executable')}, +\ 'command': function('ale_linters#c#clangcheck#GetCommand'), +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/c/clangd.vim b/ale_linters/c/clangd.vim new file mode 100644 index 00000000..c42d4497 --- /dev/null +++ b/ale_linters/c/clangd.vim @@ -0,0 +1,22 @@ +" Author: Andrey Melentyev +" Description: Clangd language server + +call ale#Set('c_clangd_executable', 'clangd') +call ale#Set('c_clangd_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#c#clangd#GetCommand(buffer) abort + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'c_clangd_options')) + \ . (!empty(l:build_dir) ? ' -compile-commands-dir=' . ale#Escape(l:build_dir) : '') +endfunction + +call ale#linter#Define('c', { +\ 'name': 'clangd', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'c_clangd_executable')}, +\ 'command': function('ale_linters#c#clangd#GetCommand'), +\ 'project_root': function('ale#c#FindProjectRoot'), +\}) diff --git a/ale_linters/c/clangtidy.vim b/ale_linters/c/clangtidy.vim new file mode 100644 index 00000000..553cc23b --- /dev/null +++ b/ale_linters/c/clangtidy.vim @@ -0,0 +1,52 @@ +" Author: vdeurzen , w0rp , +" gagbo , Andrej Radovic +" Description: clang-tidy linter for c files + +call ale#Set('c_clangtidy_executable', 'clang-tidy') +" Set this option to check the checks clang-tidy will apply. +" The number of checks that can be applied to C files is limited in contrast to +" C++ +" +" Consult the check list in clang-tidy's documentation: +" http://clang.llvm.org/extra/clang-tidy/checks/list.html + +call ale#Set('c_clangtidy_checks', []) +" Set this option to manually set some options for clang-tidy to use as compile +" flags. +" This will disable compile_commands.json detection. +call ale#Set('c_clangtidy_options', '') +" Set this option to manually set options for clang-tidy directly. +call ale#Set('c_clangtidy_extra_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#c#clangtidy#GetCommand(buffer, output) abort + let l:checks = join(ale#Var(a:buffer, 'c_clangtidy_checks'), ',') + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + let l:options = '' + + " Get the extra options if we couldn't find a build directory. + if empty(l:build_dir) + let l:options = ale#Var(a:buffer, 'c_clangtidy_options') + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:options .= !empty(l:options) ? ale#Pad(l:cflags) : l:cflags + endif + + " Get the options to pass directly to clang-tidy + let l:extra_options = ale#Var(a:buffer, 'c_clangtidy_extra_options') + + return '%e' + \ . (!empty(l:checks) ? ' -checks=' . ale#Escape(l:checks) : '') + \ . (!empty(l:extra_options) ? ' ' . ale#Escape(l:extra_options) : '') + \ . ' %s' + \ . (!empty(l:build_dir) ? ' -p ' . ale#Escape(l:build_dir) : '') + \ . (!empty(l:options) ? ' -- ' . l:options : '') +endfunction + +call ale#linter#Define('c', { +\ 'name': 'clangtidy', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'c_clangtidy_executable')}, +\ 'command': {b -> ale#c#RunMakeCommand(b, 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 new file mode 100644 index 00000000..28c2861f --- /dev/null +++ b/ale_linters/c/cppcheck.vim @@ -0,0 +1,29 @@ +" Author: Bart Libert +" Description: cppcheck linter for c files + +call ale#Set('c_cppcheck_executable', 'cppcheck') +call ale#Set('c_cppcheck_options', '--enable=style') + +function! ale_linters#c#cppcheck#GetCommand(buffer) abort + let l:compile_commands_option = ale#handlers#cppcheck#GetCompileCommandsOptions(a:buffer) + let l:buffer_path_include = empty(l:compile_commands_option) + \ ? ale#handlers#cppcheck#GetBufferPathIncludeOptions(a:buffer) + \ : '' + let l:template = ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + + return '%e -q --language=c' + \ . l:template + \ . ale#Pad(l:compile_commands_option) + \ . ale#Pad(ale#Var(a:buffer, 'c_cppcheck_options')) + \ . l:buffer_path_include + \ . ' %t' +endfunction + +call ale#linter#Define('c', { +\ 'name': 'cppcheck', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#Var(b, 'c_cppcheck_executable')}, +\ 'cwd': function('ale#handlers#cppcheck#GetCwd'), +\ 'command': function('ale_linters#c#cppcheck#GetCommand'), +\ 'callback': 'ale#handlers#cppcheck#HandleCppCheckFormat', +\}) diff --git a/ale_linters/c/cpplint.vim b/ale_linters/c/cpplint.vim new file mode 100644 index 00000000..4b997941 --- /dev/null +++ b/ale_linters/c/cpplint.vim @@ -0,0 +1,20 @@ +" Author: Justin Huang +" Description: cpplint for c files + +call ale#Set('c_cpplint_executable', 'cpplint') +call ale#Set('c_cpplint_options', '') + +function! ale_linters#c#cpplint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'c_cpplint_options') + + return '%e' . ale#Pad(l:options) . ' %s' +endfunction + +call ale#linter#Define('c', { +\ 'name': 'cpplint', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'c_cpplint_executable')}, +\ 'command': function('ale_linters#c#cpplint#GetCommand'), +\ 'callback': 'ale#handlers#cpplint#HandleCppLintFormat', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/c/cquery.vim b/ale_linters/c/cquery.vim new file mode 100644 index 00000000..ff0f34af --- /dev/null +++ b/ale_linters/c/cquery.vim @@ -0,0 +1,30 @@ +" Author: Ben Falconer , jtalowell +" Description: A language server for C + +call ale#Set('c_cquery_executable', 'cquery') +call ale#Set('c_cquery_cache_directory', expand('~/.cache/cquery')) + +function! ale_linters#c#cquery#GetProjectRoot(buffer) abort + " Try to find cquery configuration files first. + let l:config = ale#path#FindNearestFile(a:buffer, '.cquery') + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + " Fall back on default project root detection. + return ale#c#FindProjectRoot(a:buffer) +endfunction + +function! ale_linters#c#cquery#GetInitializationOptions(buffer) abort + return {'cacheDirectory': ale#Var(a:buffer, 'c_cquery_cache_directory')} +endfunction + +call ale#linter#Define('c', { +\ 'name': 'cquery', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'c_cquery_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#c#cquery#GetProjectRoot'), +\ 'initialization_options': function('ale_linters#c#cquery#GetInitializationOptions'), +\}) diff --git a/ale_linters/c/cspell.vim b/ale_linters/c/cspell.vim new file mode 100644 index 00000000..5f016548 --- /dev/null +++ b/ale_linters/c/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for C files. + +call ale#handlers#cspell#DefineLinter('c') diff --git a/ale_linters/c/flawfinder.vim b/ale_linters/c/flawfinder.vim new file mode 100644 index 00000000..53c36716 --- /dev/null +++ b/ale_linters/c/flawfinder.vim @@ -0,0 +1,25 @@ +" Author: Christian Gibbons +" Description: flawfinder linter for c files + +call ale#Set('c_flawfinder_executable', 'flawfinder') +call ale#Set('c_flawfinder_options', '') +call ale#Set('c_flawfinder_minlevel', 1) +call ale#Set('c_flawfinder_error_severity', 6) + +function! ale_linters#c#flawfinder#GetCommand(buffer) abort + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'c_flawfinder_minlevel') + + return '%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': {b -> ale#Var(b, 'c_flawfinder_executable')}, +\ 'command': function('ale_linters#c#flawfinder#GetCommand'), +\ 'callback': 'ale#handlers#flawfinder#HandleFlawfinderFormat', +\}) diff --git a/ale_linters/c3/c3lsp.vim b/ale_linters/c3/c3lsp.vim new file mode 100644 index 00000000..43cd89d8 --- /dev/null +++ b/ale_linters/c3/c3lsp.vim @@ -0,0 +1,22 @@ +" Author: Koni Marti +" Description: A Language Server implementation for C3 + +call ale#Set('c3_c3lsp_executable', 'c3lsp') +call ale#Set('c3_c3lsp_options', '') +call ale#Set('c3_c3lsp_init_options', {}) + +function! ale_linters#c3#c3lsp#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'c3_c3lsp_executable') + + return ale#Escape(l:executable) . ale#Pad(ale#Var(a:buffer, 'c3_c3lsp_options')) +endfunction + + +call ale#linter#Define('c3', { +\ 'name': 'c3lsp', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'c3_c3lsp_executable')}, +\ 'command': function('ale_linters#c3#c3lsp#GetCommand'), +\ 'project_root': function('ale#handlers#c3lsp#GetProjectRoot'), +\ 'lsp_config': {b -> ale#handlers#c3lsp#GetInitOpts(b, 'c3_c3lsp_init_options')}, +\}) diff --git a/ale_linters/cairo/scarb.vim b/ale_linters/cairo/scarb.vim new file mode 100644 index 00000000..48212f0b --- /dev/null +++ b/ale_linters/cairo/scarb.vim @@ -0,0 +1,31 @@ +" Author: 0xhyoga <0xhyoga@gmx.com>, +" Description: scarb for cairo files + +function! ale_linters#cairo#scarb#GetScarbExecutable(bufnr) abort + if ale#path#FindNearestFile(a:bufnr, 'Scarb.toml') isnot# '' + return 'scarb' + else + " if there is no Scarb.toml file, we don't use scarb even if it exists, + " so we return '', because executable('') apparently always fails + return '' + endif +endfunction + +function! ale_linters#cairo#scarb#GetCommand(buffer, version) abort + return 'scarb build' +endfunction + +call ale#linter#Define('cairo', { +\ 'name': 'scarb', +\ 'executable': function('ale_linters#cairo#scarb#GetScarbExecutable'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#cairo#scarb#GetScarbExecutable(buffer), +\ '%e --version', +\ function('ale_linters#cairo#scarb#GetCommand'), +\ )}, +\ 'callback': 'ale#handlers#cairo#HandleCairoErrors', +\ 'output_stream': 'both', +\ 'lint_file': 1, +\}) + diff --git a/ale_linters/cairo/sierra.vim b/ale_linters/cairo/sierra.vim new file mode 100644 index 00000000..06eb87f4 --- /dev/null +++ b/ale_linters/cairo/sierra.vim @@ -0,0 +1,54 @@ +" Author: 0xHyoga <0xHyoga@gmx.com> +" Description: Report Starknet compile to sierra errors in cairo 1.0 code + +call ale#Set('cairo_sierra_executable', 'starknet-compile') +call ale#Set('cairo_sierra_options', '') + +function! ale_linters#cairo#sierra#Handle(buffer, lines) abort + " Matches patterns like the following: + " Error: Expected ';' but got '(' + " --> /path/to/file/file.cairo:1:10:) + let l:pattern = '\v(error|warning): (.*)$' + let l:line_and_column_pattern = '\v\.cairo:(\d+):(\d+)' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + let l:match = matchlist(l:line, l:line_and_column_pattern) + + if len(l:match) > 0 + let l:index = len(l:output) - 1 + let l:output[l:index]['lnum'] = l:match[1] + 0 + let l:output[l:index]['col'] = l:match[2] + 0 + endif + else + let l:isError = l:match[1] is? 'Error' + + call add(l:output, { + \ 'lnum': 0, + \ 'col': 0, + \ 'text': l:match[2], + \ 'type': l:isError ? 'E' : 'W', + \}) + endif + endfor + + return l:output +endfunction + +function! ale_linters#cairo#sierra#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'cairo_sierra_executable') + + return l:executable . ale#Pad(ale#Var(a:buffer, 'cairo_sierra_options')) . ' %s' +endfunction + +call ale#linter#Define('cairo', { +\ 'name': 'sierra', +\ 'executable': {b -> ale#Var(b, 'cairo_sierra_executable')}, +\ 'command': function('ale_linters#cairo#sierra#GetCommand'), +\ 'callback': 'ale_linters#cairo#sierra#Handle', +\ 'output_stream': 'stderr', +\}) + diff --git a/ale_linters/cairo/starknet.vim b/ale_linters/cairo/starknet.vim new file mode 100644 index 00000000..d471cc89 --- /dev/null +++ b/ale_linters/cairo/starknet.vim @@ -0,0 +1,39 @@ +" Author: 0xHyoga <0xHyoga@gmx.com> +" Description: Report starknet-compile errors in cairo code (pre-starknet +" 1.0). This is deprecated but kept for backwards compatability. + +call ale#Set('cairo_starknet_executable', 'starknet-compile') +call ale#Set('cairo_starknet_options', '') + +function! ale_linters#cairo#starknet#Handle(buffer, lines) abort + " Error always on the first line + " e.g ex01.cairo:20:6: Could not find module 'contracts.utils.ex00_base'. Searched in the following paths: + let l:pattern = '\v\.cairo:(\d+):(\d+):+ (.*)' + let l:output = [] + + 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]), + \ 'type': 'E', + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +function! ale_linters#cairo#starknet#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'cairo_starknet_executable') + + return l:executable . ale#Pad(ale#Var(a:buffer, 'cairo_starknet_options')) . ' %s' +endfunction + +call ale#linter#Define('cairo', { +\ 'name': 'starknet', +\ 'executable': {b -> ale#Var(b, 'cairo_starknet_executable')}, +\ 'command': function('ale_linters#cairo#starknet#GetCommand'), +\ 'callback': 'ale_linters#cairo#starknet#Handle', +\ 'output_stream': 'stderr', +\}) + 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 new file mode 100644 index 00000000..48beba75 --- /dev/null +++ b/ale_linters/chef/foodcritic.vim @@ -0,0 +1,41 @@ +" Author: Edward Larkey +" Author: Jose Junior +" Author: w0rp +" Description: This file adds the foodcritic linter for Chef files. + +call ale#Set('chef_foodcritic_executable', 'foodcritic') +call ale#Set('chef_foodcritic_options', '') + +function! ale_linters#chef#foodcritic#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'chef_foodcritic_options') + + return '%e' . ale#Pad(escape(l:options, '~')) . ' %s' +endfunction + +function! ale_linters#chef#foodcritic#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " FC002: Avoid string interpolation where not required: httpd.rb:13 + let l:pattern = '\v([^:]+): (.+): ([a-zA-Z]?:?[^:]+):(\d+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'code': l:match[1], + \ 'text': l:match[2], + \ 'filename': l:match[3], + \ 'lnum': l:match[4] + 0, + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('chef', { +\ 'name': 'foodcritic', +\ '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..b470cf0c --- /dev/null +++ b/ale_linters/clojure/clj_kondo.vim @@ -0,0 +1,47 @@ +" Author: Masashi Iizuka +" Description: linter for clojure using clj-kondo https://github.com/borkdude/clj-kondo + +call ale#Set('clojure_clj_kondo_options', '--cache') + +function! ale_linters#clojure#clj_kondo#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'clojure_clj_kondo_options') + + let l:command = 'clj-kondo' + \ . ale#Pad(l:options) + \ . ' --lint -' + \ . ' --filename %s' + + return l:command +endfunction + +function! ale_linters#clojure#clj_kondo#HandleCljKondoFormat(buffer, lines) abort + " output format + " ::: : + 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': function('ale_linters#clojure#clj_kondo#GetCommand'), +\ 'callback': 'ale_linters#clojure#clj_kondo#HandleCljKondoFormat', +\}) diff --git a/ale_linters/clojure/joker.vim b/ale_linters/clojure/joker.vim new file mode 100644 index 00000000..1f17cd31 --- /dev/null +++ b/ale_linters/clojure/joker.vim @@ -0,0 +1,34 @@ +" Author: Nic West +" Description: linter for clojure using joker https://github.com/candid82/joker + +function! ale_linters#clojure#joker#HandleJokerFormat(buffer, lines) abort + " output format + " ::: : + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):(\d+):? ((Read error|Parse error|Parse warning|Exception): ?(.+))$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = 'E' + + if l:match[4] is? 'Parse 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': 'joker', +\ 'output_stream': 'stderr', +\ 'executable': 'joker', +\ 'command': 'joker --working-dir %s --lint %t', +\ 'callback': 'ale_linters#clojure#joker#HandleJokerFormat', +\}) diff --git a/ale_linters/cloudformation/cfn_python_lint.vim b/ale_linters/cloudformation/cfn_python_lint.vim new file mode 100644 index 00000000..16841431 --- /dev/null +++ b/ale_linters/cloudformation/cfn_python_lint.vim @@ -0,0 +1,36 @@ +" Author: Yasuhiro Kiyota +" Description: Support cfn-python-lint for AWS Cloudformation template file + +function! ale_linters#cloudformation#cfn_python_lint#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " sample.template.yaml:96:7:96:15:E3012:Property Resources/Sample/Properties/FromPort should be of type Integer + let l:pattern = '\v^(.*):(\d+):(\d+):(\d+):(\d+):([[:alnum:]]+):(.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[6] + + if ale#path#IsBufferPath(a:buffer, l:match[1]) + call add(l:output, { + \ 'lnum': l:match[2], + \ 'col': l:match[3], + \ 'end_lnum': l:match[4], + \ 'end_col': l:match[5], + \ 'code': l:code, + \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', + \ 'text': l:match[7] + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('cloudformation', { +\ 'name': 'cloudformation', +\ 'aliases': ['cfn-lint'], +\ 'executable': 'cfn-lint', +\ 'command': 'cfn-lint --template %t --format parseable', +\ 'callback': 'ale_linters#cloudformation#cfn_python_lint#Handle', +\}) diff --git a/ale_linters/cmake/cmake_lint.vim b/ale_linters/cmake/cmake_lint.vim new file mode 100644 index 00000000..5942e08d --- /dev/null +++ b/ale_linters/cmake/cmake_lint.vim @@ -0,0 +1,43 @@ +" Author: Carl Smedstad +" Description: cmake-lint for cmake files + +let g:ale_cmake_cmake_lint_executable = +\ get(g:, 'ale_cmake_cmake_lint_executable', 'cmake-lint') + +let g:ale_cmake_cmake_lint_options = +\ get(g:, 'ale_cmake_cmake_lint_options', '') + +function! ale_linters#cmake#cmake_lint#Executable(buffer) abort + return ale#Var(a:buffer, 'cmake_cmake_lint_executable') +endfunction + +function! ale_linters#cmake#cmake_lint#Command(buffer) abort + let l:executable = ale_linters#cmake#cmake_lint#Executable(a:buffer) + let l:options = ale#Var(a:buffer, 'cmake_cmake_lint_options') + + return ale#Escape(l:executable) . ' ' . l:options . ' %s' +endfunction + +function! ale_linters#cmake#cmake_lint#Handle(buffer, lines) abort + let l:pattern = '\v^[^:]+:(\d+),?(\d+)?:\s\[([A-Z]\d+)\]\s(.+)$' + 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, + \ 'type': 'W', + \ 'code': l:match[3], + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('cmake', { +\ 'name': 'cmake_lint', +\ 'executable': function('ale_linters#cmake#cmake_lint#Executable'), +\ 'command': function('ale_linters#cmake#cmake_lint#Command'), +\ 'callback': 'ale_linters#cmake#cmake_lint#Handle', +\}) diff --git a/ale_linters/cmake/cmakelint.vim b/ale_linters/cmake/cmakelint.vim new file mode 100644 index 00000000..d955a265 --- /dev/null +++ b/ale_linters/cmake/cmakelint.vim @@ -0,0 +1,24 @@ +" Author: Kenneth Benzie +" Description: cmakelint for cmake files + +let g:ale_cmake_cmakelint_executable = +\ get(g:, 'ale_cmake_cmakelint_executable', 'cmakelint') + +let g:ale_cmake_cmakelint_options = +\ get(g:, 'ale_cmake_cmakelint_options', '') + +function! ale_linters#cmake#cmakelint#Executable(buffer) abort + return ale#Var(a:buffer, 'cmake_cmakelint_executable') +endfunction + +function! ale_linters#cmake#cmakelint#Command(buffer) abort + return ale_linters#cmake#cmakelint#Executable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'cmake_cmakelint_options') . ' %t' +endfunction + +call ale#linter#Define('cmake', { +\ 'name': 'cmakelint', +\ '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 new file mode 100644 index 00000000..8e891639 --- /dev/null +++ b/ale_linters/coffee/coffee.vim @@ -0,0 +1,23 @@ +" Author: KabbAmine - https://github.com/KabbAmine +" Description: Coffee for checking coffee files + +function! ale_linters#coffee#coffee#GetExecutable(buffer) abort + return ale#path#ResolveLocalPath( + \ a:buffer, + \ 'node_modules/.bin/coffee', + \ 'coffee' + \) +endfunction + +function! ale_linters#coffee#coffee#GetCommand(buffer) abort + return ale_linters#coffee#coffee#GetExecutable(a:buffer) + \ . ' -cp -s' +endfunction + +call ale#linter#Define('coffee', { +\ 'name': 'coffee', +\ 'executable': 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 new file mode 100644 index 00000000..b7c85fa7 --- /dev/null +++ b/ale_linters/coffee/coffeelint.vim @@ -0,0 +1,43 @@ +" Author: Prashanth Chandra https://github.com/prashcr +" Description: coffeelint linter for coffeescript files + +function! ale_linters#coffee#coffeelint#GetExecutable(buffer) abort + return ale#path#ResolveLocalPath( + \ a:buffer, + \ 'node_modules/.bin/coffeelint', + \ 'coffeelint' + \) +endfunction + +function! ale_linters#coffee#coffeelint#GetCommand(buffer) abort + return ale_linters#coffee#coffeelint#GetExecutable(a:buffer) + \ . ' --stdin --reporter csv' +endfunction + +function! ale_linters#coffee#coffeelint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " path,lineNumber,lineNumberEnd,level,message + " stdin,14,,error,Throwing strings is forbidden + " + " Note that we currently ignore lineNumberEnd for multiline errors + let l:pattern = 'stdin,\(\d\+\),\(\d*\),\(.\{-1,}\),\(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[1]), + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('coffee', { +\ 'name': 'coffeelint', +\ '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/cc.vim b/ale_linters/cpp/cc.vim new file mode 100644 index 00000000..1d35bb67 --- /dev/null +++ b/ale_linters/cpp/cc.vim @@ -0,0 +1,67 @@ +" Author: w0rp +" Description: A C++ compiler linter for C++ files with gcc/clang, etc. + +call ale#Set('cpp_cc_executable', '') +call ale#Set('cpp_cc_options', '-std=c++14 -Wall') +call ale#Set('cpp_cc_use_header_lang_flag', -1) +call ale#Set('cpp_cc_header_exts', ['h', 'hpp']) + +function! ale_linters#cpp#cc#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'cpp_cc_executable') + + " Default to either clang++ or gcc. + if l:executable is# '' + if ale#engine#IsExecutable(a:buffer, 'clang++') + let l:executable = 'clang++' + else + let l:executable = 'gcc' + endif + endif + + return l:executable +endfunction + +function! ale_linters#cpp#cc#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:ale_flags = ale#Var(a:buffer, 'cpp_cc_options') + + if l:cflags =~# '-std=' + let l:ale_flags = substitute( + \ l:ale_flags, + \ '-std=\(c\|gnu\)++[0-9]\{2\}', + \ '', + \ 'g') + endif + + " Select the correct language flag depending on the executable, options + " and file extension + let l:executable = ale_linters#cpp#cc#GetExecutable(a:buffer) + let l:use_header_lang_flag = ale#Var(a:buffer, 'cpp_cc_use_header_lang_flag') + let l:header_exts = ale#Var(a:buffer, 'cpp_cc_header_exts') + let l:lang_flag = ale#c#GetLanguageFlag( + \ a:buffer, + \ l:executable, + \ l:use_header_lang_flag, + \ l:header_exts, + \ 'c++') + + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + " + " `-o /dev/null` or `-o null` is needed to catch all errors, + " -fsyntax-only doesn't catch everything. + return '%e -S -x ' . l:lang_flag + \ . ' -o ' . g:ale#util#nul_file + \ . ' -iquote %s:h' + \ . ale#Pad(l:cflags) + \ . ale#Pad(l:ale_flags) . ' -' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cc', +\ 'aliases': ['gcc', 'clang', 'g++', 'clang++'], +\ 'output_stream': 'stderr', +\ 'executable': function('ale_linters#cpp#cc#GetExecutable'), +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#cpp#cc#GetCommand'))}, +\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', +\}) diff --git a/ale_linters/cpp/ccls.vim b/ale_linters/cpp/ccls.vim new file mode 100644 index 00000000..38f8df9c --- /dev/null +++ b/ale_linters/cpp/ccls.vim @@ -0,0 +1,15 @@ +" Author: Ye Jingchen , Ben Falconer , jtalowell +" Description: A language server for C++ + +call ale#Set('cpp_ccls_executable', 'ccls') +call ale#Set('cpp_ccls_init_options', {}) +call ale#Set('c_build_dir', '') + +call ale#linter#Define('cpp', { +\ 'name': 'ccls', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'cpp_ccls_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), +\ 'initialization_options': {b -> ale#handlers#ccls#GetInitOpts(b, 'cpp_ccls_init_options')}, +\}) diff --git a/ale_linters/cpp/clangcheck.vim b/ale_linters/cpp/clangcheck.vim new file mode 100644 index 00000000..4cb04864 --- /dev/null +++ b/ale_linters/cpp/clangcheck.vim @@ -0,0 +1,35 @@ +" Author: gagbo +" Description: clang-check linter for cpp files + +call ale#Set('cpp_clangcheck_executable', 'clang-check') +call ale#Set('cpp_clangcheck_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#cpp#clangcheck#GetCommand(buffer) abort + let l:user_options = ale#Var(a:buffer, 'cpp_clangcheck_options') + + " Try to find compilation database to link automatically + let l:build_dir = ale#Var(a:buffer, 'c_build_dir') + + if empty(l:build_dir) + let [l:root, l:json_file] = ale#c#FindCompileCommands(a:buffer) + let l:build_dir = ale#path#Dirname(l:json_file) + endif + + " The extra arguments in the command are used to prevent .plist files from + " being generated. These are only added if no build directory can be + " detected. + return '%e -analyze %s' + \ . (empty(l:build_dir) ? ' --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics': '') + \ . ale#Pad(l:user_options) + \ . (!empty(l:build_dir) ? ' -p ' . ale#Escape(l:build_dir) : '') +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'clangcheck', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..14f3fe55 --- /dev/null +++ b/ale_linters/cpp/clangd.vim @@ -0,0 +1,22 @@ +" Author: Andrey Melentyev +" Description: Clangd language server + +call ale#Set('cpp_clangd_executable', 'clangd') +call ale#Set('cpp_clangd_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#cpp#clangd#GetCommand(buffer) abort + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'cpp_clangd_options')) + \ . (!empty(l:build_dir) ? ' -compile-commands-dir=' . ale#Escape(l:build_dir) : '') +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'clangd', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'cpp_clangd_executable')}, +\ 'command': function('ale_linters#cpp#clangd#GetCommand'), +\ 'project_root': function('ale#c#FindProjectRoot'), +\}) diff --git a/ale_linters/cpp/clangtidy.vim b/ale_linters/cpp/clangtidy.vim new file mode 100644 index 00000000..fa9f8e31 --- /dev/null +++ b/ale_linters/cpp/clangtidy.vim @@ -0,0 +1,53 @@ +" Author: vdeurzen , w0rp , +" gagbo +" Description: clang-tidy linter for cpp files + +call ale#Set('cpp_clangtidy_executable', 'clang-tidy') +" Set this option to check the checks clang-tidy will apply. +call ale#Set('cpp_clangtidy_checks', []) +" Set this option to manually set some options for clang-tidy to use as compile +" flags. +" This will disable compile_commands.json detection. +call ale#Set('cpp_clangtidy_options', '') +" Set this option to manually set options for clang-tidy directly. +call ale#Set('cpp_clangtidy_extra_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#cpp#clangtidy#GetCommand(buffer, output) abort + let l:checks = join(ale#Var(a:buffer, 'cpp_clangtidy_checks'), ',') + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + let l:options = '' + + " Get the extra options if we couldn't find a build directory. + if empty(l:build_dir) + let l:options = ale#Var(a:buffer, 'cpp_clangtidy_options') + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:options .= !empty(l:options) ? ale#Pad(l:cflags) : l:cflags + + " Tell clang-tidy a .h header with a C++ filetype in Vim is a C++ file + " only when compile-commands.json file is not there. Adding these + " flags makes clang-tidy completely ignore compile commands. + if expand('#' . a:buffer) =~# '\.h$' + let l:options .= !empty(l:options) ? ' -x c++' : '-x c++' + endif + endif + + " Get the options to pass directly to clang-tidy + let l:extra_options = ale#Var(a:buffer, 'cpp_clangtidy_extra_options') + + return '%e' + \ . (!empty(l:checks) ? ' -checks=' . ale#Escape(l:checks) : '') + \ . (!empty(l:extra_options) ? ' ' . ale#Escape(l:extra_options) : '') + \ . ' %s' + \ . (!empty(l:build_dir) ? ' -p ' . ale#Escape(l:build_dir) : '') + \ . (!empty(l:options) ? ' -- ' . l:options : '') +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'clangtidy', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'cpp_clangtidy_executable')}, +\ 'command': {b -> ale#c#RunMakeCommand(b, 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 new file mode 100644 index 00000000..9b29ac9a --- /dev/null +++ b/ale_linters/cpp/clazy.vim @@ -0,0 +1,32 @@ +" Description: clazy linter for cpp files (clang-based and Qt-oriented) + +call ale#Set('cpp_clazy_executable', 'clazy-standalone') +" Set this option to check the checks clazy will apply. +call ale#Set('cpp_clazy_checks', ['level1']) +" Set this option to manually set some options for clazy. +" This will disable compile_commands.json detection. +call ale#Set('cpp_clazy_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#cpp#clazy#GetCommand(buffer) abort + let l:checks = join(ale#Var(a:buffer, 'cpp_clazy_checks'), ',') + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + + " Get the extra options if we couldn't find a build directory. + let l:options = ale#Var(a:buffer, 'cpp_clazy_options') + + return '%e' + \ . (!empty(l:checks) ? ' -checks=' . ale#Escape(l:checks) : '') + \ . (!empty(l:build_dir) ? ' -p ' . ale#Escape(l:build_dir) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %s' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'clazy', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..eb86adf4 --- /dev/null +++ b/ale_linters/cpp/cppcheck.vim @@ -0,0 +1,29 @@ +" Author: Bart Libert +" Description: cppcheck linter for cpp files + +call ale#Set('cpp_cppcheck_executable', 'cppcheck') +call ale#Set('cpp_cppcheck_options', '--enable=style') + +function! ale_linters#cpp#cppcheck#GetCommand(buffer) abort + let l:compile_commands_option = ale#handlers#cppcheck#GetCompileCommandsOptions(a:buffer) + let l:buffer_path_include = empty(l:compile_commands_option) + \ ? ale#handlers#cppcheck#GetBufferPathIncludeOptions(a:buffer) + \ : '' + let l:template = ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + + return '%e -q --language=c++' + \ . l:template + \ . ale#Pad(l:compile_commands_option) + \ . ale#Pad(ale#Var(a:buffer, 'cpp_cppcheck_options')) + \ . l:buffer_path_include + \ . ' %t' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cppcheck', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#Var(b, 'cpp_cppcheck_executable')}, +\ 'cwd': function('ale#handlers#cppcheck#GetCwd'), +\ '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 new file mode 100644 index 00000000..f1f6ce7f --- /dev/null +++ b/ale_linters/cpp/cpplint.vim @@ -0,0 +1,20 @@ +" Author: Dawid Kurek https://github.com/dawikur +" Description: cpplint for cpp files + +call ale#Set('cpp_cpplint_executable', 'cpplint') +call ale#Set('cpp_cpplint_options', '') + +function! ale_linters#cpp#cpplint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'cpp_cpplint_options') + + return '%e' . ale#Pad(l:options) . ' %s' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cpplint', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..2971cdcb --- /dev/null +++ b/ale_linters/cpp/cquery.vim @@ -0,0 +1,30 @@ +" Author: Ben Falconer +" Description: A language server for C++ + +call ale#Set('cpp_cquery_executable', 'cquery') +call ale#Set('cpp_cquery_cache_directory', expand('~/.cache/cquery')) + +function! ale_linters#cpp#cquery#GetProjectRoot(buffer) abort + " Try to find cquery configuration files first. + let l:config = ale#path#FindNearestFile(a:buffer, '.cquery') + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + " Fall back on default project root detection. + return ale#c#FindProjectRoot(a:buffer) +endfunction + +function! ale_linters#cpp#cquery#GetInitializationOptions(buffer) abort + return {'cacheDirectory': ale#Var(a:buffer, 'cpp_cquery_cache_directory')} +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cquery', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'cpp_cquery_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#cpp#cquery#GetProjectRoot'), +\ 'initialization_options': function('ale_linters#cpp#cquery#GetInitializationOptions'), +\}) diff --git a/ale_linters/cpp/cspell.vim b/ale_linters/cpp/cspell.vim new file mode 100644 index 00000000..ace4c3b2 --- /dev/null +++ b/ale_linters/cpp/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for C++ files. + +call ale#handlers#cspell#DefineLinter('cpp') diff --git a/ale_linters/cpp/flawfinder.vim b/ale_linters/cpp/flawfinder.vim new file mode 100644 index 00000000..5bfdea22 --- /dev/null +++ b/ale_linters/cpp/flawfinder.vim @@ -0,0 +1,25 @@ +" Author: Christian Gibbons +" Description: flawfinder linter for c++ files + +call ale#Set('cpp_flawfinder_executable', 'flawfinder') +call ale#Set('cpp_flawfinder_options', '') +call ale#Set('cpp_flawfinder_minlevel', 1) +call ale#Set('c_flawfinder_error_severity', 6) + +function! ale_linters#cpp#flawfinder#GetCommand(buffer) abort + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'cpp_flawfinder_minlevel') + + return '%e -CDQS' + \ . ale#Var(a:buffer, 'cpp_flawfinder_options') + \ . l:minlevel + \ . ' %t' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'flawfinder', +\ 'output_stream': 'stdout', +\ '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/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 new file mode 100644 index 00000000..8a905b12 --- /dev/null +++ b/ale_linters/crystal/crystal.vim @@ -0,0 +1,35 @@ +" Author: Jordan Andree , David Alexander +" Description: This file adds support for checking Crystal with crystal build + +function! ale_linters#crystal#crystal#Handle(buffer, lines) abort + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + if !has_key(l:error, 'file') + continue + endif + + call add(l:output, { + \ 'lnum': l:error.line + 0, + \ 'col': l:error.column + 0, + \ 'text': l:error.message, + \}) + endfor + + return l:output +endfunction + +function! ale_linters#crystal#crystal#GetCommand(buffer) abort + return 'crystal build -f json --no-codegen --no-color -o ' + \ . ale#Escape(g:ale#util#nul_file) + \ . ' %s' +endfunction + +call ale#linter#Define('crystal', { +\ 'name': 'crystal', +\ 'executable': 'crystal', +\ 'output_stream': 'both', +\ 'lint_file': 1, +\ 'command': function('ale_linters#crystal#crystal#GetCommand'), +\ 'callback': 'ale_linters#crystal#crystal#Handle', +\}) diff --git a/ale_linters/cs/csc.vim b/ale_linters/cs/csc.vim new file mode 100644 index 00000000..5ee3de29 --- /dev/null +++ b/ale_linters/cs/csc.vim @@ -0,0 +1,90 @@ +call ale#Set('cs_csc_options', '') +call ale#Set('cs_csc_source', '') +call ale#Set('cs_csc_assembly_path', []) +call ale#Set('cs_csc_assemblies', []) + +function! ale_linters#cs#csc#GetCwd(buffer) abort + let l:cwd = ale#Var(a:buffer, 'cs_csc_source') + + return !empty(l:cwd) ? l:cwd : expand('#' . a:buffer . ':p:h') +endfunction + +function! ale_linters#cs#csc#GetCommand(buffer) abort + " Pass assembly paths via the -lib: parameter. + let l:path_list = ale#Var(a:buffer, 'cs_csc_assembly_path') + + let l:lib_option = !empty(l:path_list) + \ ? '/lib:' . join(map(copy(l:path_list), 'ale#Escape(v:val)'), ',') + \ : '' + + " Pass paths to DLL files via the -r: parameter. + let l:assembly_list = ale#Var(a:buffer, 'cs_csc_assemblies') + + let l:r_option = !empty(l:assembly_list) + \ ? '/r:' . join(map(copy(l:assembly_list), 'ale#Escape(v:val)'), ',') + \ : '' + + " register temporary module target file with ale + " register temporary module target file with ALE. + let l:out = ale#command#CreateFile(a:buffer) + + " The code is compiled as a module and the output is redirected to a + " temporary file. + return 'csc /unsafe' + \ . ale#Pad(ale#Var(a:buffer, 'cs_csc_options')) + \ . ale#Pad(l:lib_option) + \ . ale#Pad(l:r_option) + \ . ' /out:' . l:out + \ . ' /t:module' + \ . ' /recurse:' . ale#Escape('*.cs') +endfunction + +function! ale_linters#cs#csc#Handle(buffer, lines) abort + " Look for lines like the following. + " + " Tests.cs(12,29): error CSXXXX: ; expected + " + " NOTE: pattern also captures file name as linter compiles all + " files within the source tree rooted at the specified source + " path and not just the file loaded in the buffer + let l:patterns = [ + \ '^\v(.+\.cs)\((\d+),(\d+)\)\:\s+([^ ]+)\s+([cC][sS][^ ]+):\s(.+)$', + \ '^\v([^ ]+)\s+([Cc][sS][^ ]+):\s+(.+)$', + \] + let l:output = [] + let l:dir = ale_linters#cs#csc#GetCwd(a:buffer) + + for l:match in ale#util#GetMatches(a:lines, l:patterns) + if len(l:match) > 6 && strlen(l:match[5]) > 2 && l:match[5][:1] is? 'CS' + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'code': l:match[5], + \ 'text': l:match[6] , + \}) + elseif strlen(l:match[2]) > 2 && l:match[2][:1] is? 'CS' + call add(l:output, { + \ 'filename':'', + \ 'lnum': -1, + \ 'col': -1, + \ 'type': l:match[1] is# 'error' ? 'E' : 'W', + \ 'code': l:match[2], + \ 'text': l:match[3], + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('cs',{ +\ 'name': 'csc', +\ 'output_stream': 'stdout', +\ 'executable': 'csc', +\ 'cwd': function('ale_linters#cs#csc#GetCwd'), +\ 'command': function('ale_linters#cs#csc#GetCommand'), +\ 'callback': 'ale_linters#cs#csc#Handle', +\ 'lint_file': 1 +\}) diff --git a/ale_linters/cs/cspell.vim b/ale_linters/cs/cspell.vim new file mode 100644 index 00000000..c62dd11b --- /dev/null +++ b/ale_linters/cs/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for C# files. + +call ale#handlers#cspell#DefineLinter('cs') diff --git a/ale_linters/cs/mcs.vim b/ale_linters/cs/mcs.vim new file mode 100644 index 00000000..1b373e73 --- /dev/null +++ b/ale_linters/cs/mcs.vim @@ -0,0 +1,37 @@ +let g:ale_cs_mcs_options = get(g:, 'ale_cs_mcs_options', '') + +function! ale_linters#cs#mcs#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'cs_mcs_options') + + return 'mcs -unsafe --parse' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + +function! ale_linters#cs#mcs#Handle(buffer, lines) abort + " Look for lines like the following. + " + " Tests.cs(12,29): error CSXXXX: ; expected + let l:pattern = '^\v(.+\.cs)\((\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, + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'code': l:match[5], + \ 'text': l:match[6], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('cs',{ +\ 'name': 'mcs', +\ 'output_stream': 'stderr', +\ 'executable': 'mcs', +\ '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 new file mode 100644 index 00000000..2dd46661 --- /dev/null +++ b/ale_linters/cs/mcsc.vim @@ -0,0 +1,91 @@ +call ale#Set('cs_mcsc_options', '') +call ale#Set('cs_mcsc_source', '') +call ale#Set('cs_mcsc_assembly_path', []) +call ale#Set('cs_mcsc_assemblies', []) + +function! ale_linters#cs#mcsc#GetCwd(buffer) abort + let l:cwd = ale#Var(a:buffer, 'cs_mcsc_source') + + return !empty(l:cwd) ? l:cwd : expand('#' . a:buffer . ':p:h') +endfunction + +function! ale_linters#cs#mcsc#GetCommand(buffer) abort + " Pass assembly paths via the -lib: parameter. + let l:path_list = ale#Var(a:buffer, 'cs_mcsc_assembly_path') + + let l:lib_option = !empty(l:path_list) + \ ? '-lib:' . join(map(copy(l:path_list), 'ale#Escape(v:val)'), ',') + \ : '' + + " Pass paths to DLL files via the -r: parameter. + let l:assembly_list = ale#Var(a:buffer, 'cs_mcsc_assemblies') + + let l:r_option = !empty(l:assembly_list) + \ ? '-r:' . join(map(copy(l:assembly_list), 'ale#Escape(v:val)'), ',') + \ : '' + + " register temporary module target file with ale + " register temporary module target file with ALE. + let l:out = ale#command#CreateFile(a:buffer) + + " The code is compiled as a module and the output is redirected to a + " temporary file. + return 'mcs -unsafe' + \ . ale#Pad(ale#Var(a:buffer, 'cs_mcsc_options')) + \ . ale#Pad(l:lib_option) + \ . ale#Pad(l:r_option) + \ . ' -out:' . l:out + \ . ' -t:module' + \ . ' -recurse:' . ale#Escape('*.cs') +endfunction + +function! ale_linters#cs#mcsc#Handle(buffer, lines) abort + " Look for lines like the following. + " + " Tests.cs(12,29): error CSXXXX: ; expected + " + " NOTE: pattern also captures file name as linter compiles all + " files within the source tree rooted at the specified source + " path and not just the file loaded in the buffer + let l:patterns = [ + \ '^\v(.+\.cs)\((\d+),(\d+)\)\:\s+([^ ]+)\s+([cC][sS][^ ]+):\s(.+)$', + \ '^\v([^ ]+)\s+([Cc][sS][^ ]+):\s+(.+)$', + \] + let l:output = [] + + let l:dir = ale_linters#cs#mcsc#GetCwd(a:buffer) + + for l:match in ale#util#GetMatches(a:lines, l:patterns) + if len(l:match) > 6 && strlen(l:match[5]) > 2 && l:match[5][:1] is? 'CS' + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'code': l:match[5], + \ 'text': l:match[6] , + \}) + elseif strlen(l:match[2]) > 2 && l:match[2][:1] is? 'CS' + call add(l:output, { + \ 'filename':'', + \ 'lnum': -1, + \ 'col': -1, + \ 'type': l:match[1] is# 'error' ? 'E' : 'W', + \ 'code': l:match[2], + \ 'text': l:match[3], + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('cs',{ +\ 'name': 'mcsc', +\ 'output_stream': 'stderr', +\ 'executable': 'mcs', +\ 'cwd': function('ale_linters#cs#mcsc#GetCwd'), +\ 'command': function('ale_linters#cs#mcsc#GetCommand'), +\ 'callback': 'ale_linters#cs#mcsc#Handle', +\ 'lint_file': 1 +\}) diff --git a/ale_linters/css/cspell.vim b/ale_linters/css/cspell.vim new file mode 100644 index 00000000..d42375b4 --- /dev/null +++ b/ale_linters/css/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for CSS files. + +call ale#handlers#cspell#DefineLinter('css') diff --git a/ale_linters/css/csslint.vim b/ale_linters/css/csslint.vim new file mode 100644 index 00000000..50c21ce2 --- /dev/null +++ b/ale_linters/css/csslint.vim @@ -0,0 +1,18 @@ +" Author: w0rp +" Description: This file adds support for checking CSS code with csslint. + +function! ale_linters#css#csslint#GetCommand(buffer) abort + let l:csslintrc = ale#path#FindNearestFile(a:buffer, '.csslintrc') + let l:config_option = !empty(l:csslintrc) + \ ? '--config=' . ale#Escape(l:csslintrc) + \ : '' + + return 'csslint --format=compact ' . l:config_option . ' %t' +endfunction + +call ale#linter#Define('css', { +\ 'name': 'csslint', +\ 'executable': 'csslint', +\ '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 +" 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 new file mode 100644 index 00000000..ceb42461 --- /dev/null +++ b/ale_linters/css/stylelint.vim @@ -0,0 +1,20 @@ +" Author: diartyz + +call ale#Set('css_stylelint_executable', 'stylelint') +call ale#Set('css_stylelint_options', '') +call ale#Set('css_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#css#stylelint#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'css_stylelint_options')) + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('css', { +\ 'name': 'stylelint', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#path#FindExecutable(b, 'css_stylelint', [ +\ 'node_modules/.bin/stylelint', +\ ])}, +\ 'command': function('ale_linters#css#stylelint#GetCommand'), +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/css/vscodecss.vim b/ale_linters/css/vscodecss.vim new file mode 100644 index 00000000..2d59adf0 --- /dev/null +++ b/ale_linters/css/vscodecss.vim @@ -0,0 +1,16 @@ +" Author: Dalius Dobravolskas +" Description: VSCode css language server + +function! ale_linters#css#vscodecss#GetProjectRoot(buffer) abort + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' +endfunction + +call ale#linter#Define('css', { +\ 'name': 'vscodecss', +\ 'lsp': 'stdio', +\ 'executable': 'vscode-css-language-server', +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#css#vscodecss#GetProjectRoot'), +\}) diff --git a/ale_linters/cucumber/cucumber.vim b/ale_linters/cucumber/cucumber.vim new file mode 100644 index 00000000..0cfd815e --- /dev/null +++ b/ale_linters/cucumber/cucumber.vim @@ -0,0 +1,46 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Cucumber, a BDD test tool + +function! ale_linters#cucumber#cucumber#GetCommand(buffer) abort + let l:features_dir = ale#path#FindNearestDirectory(a:buffer, 'features') + + if !empty(l:features_dir) + let l:features_arg = '-r ' . ale#Escape(l:features_dir) + else + let l:features_arg = '' + endif + + return 'cucumber --dry-run --quiet --strict --format=json ' + \ . l:features_arg . ' %t' +endfunction + +function! ale_linters#cucumber#cucumber#Handle(buffer, lines) abort + try + let l:json = ale#util#FuzzyJSONDecode(a:lines, {})[0] + catch + return [] + endtry + + let l:output = [] + + for l:element in get(l:json, 'elements', []) + for l:step in l:element['steps'] + if l:step['result']['status'] is# 'undefined' + call add(l:output, { + \ 'lnum': l:step['line'], + \ 'code': 'E', + \ 'text': 'Undefined step' + \}) + endif + endfor + endfor + + return l:output +endfunction + +call ale#linter#Define('cucumber', { +\ 'name': 'cucumber', +\ 'executable': 'cucumber', +\ 'command': function('ale_linters#cucumber#cucumber#GetCommand'), +\ 'callback': 'ale_linters#cucumber#cucumber#Handle' +\}) diff --git a/ale_linters/cuda/clangd.vim b/ale_linters/cuda/clangd.vim new file mode 100644 index 00000000..bfda821b --- /dev/null +++ b/ale_linters/cuda/clangd.vim @@ -0,0 +1,23 @@ +" Author: Tommy Chiang +" Description: Clangd language server for CUDA (modified from Andrey +" Melentyev's implementation for C++) + +call ale#Set('cuda_clangd_executable', 'clangd') +call ale#Set('cuda_clangd_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#cuda#clangd#GetCommand(buffer) abort + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'cuda_clangd_options')) + \ . (!empty(l:build_dir) ? ' -compile-commands-dir=' . ale#Escape(l:build_dir) : '') +endfunction + +call ale#linter#Define('cuda', { +\ 'name': 'clangd', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'cuda_clangd_executable')}, +\ 'command': function('ale_linters#cuda#clangd#GetCommand'), +\ 'project_root': function('ale#c#FindProjectRoot'), +\}) diff --git a/ale_linters/cuda/nvcc.vim b/ale_linters/cuda/nvcc.vim new file mode 100644 index 00000000..2734f6ec --- /dev/null +++ b/ale_linters/cuda/nvcc.vim @@ -0,0 +1,46 @@ +" Author: blahgeek +" Description: NVCC linter for cuda files + +call ale#Set('cuda_nvcc_executable', 'nvcc') +call ale#Set('cuda_nvcc_options', '-std=c++11') + +function! ale_linters#cuda#nvcc#GetCommand(buffer) abort + return '%e -cuda' + \ . ale#Pad(ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer))) + \ . ale#Pad(ale#Var(a:buffer, 'cuda_nvcc_options')) + \ . ' %s -o ' . g:ale#util#nul_file +endfunction + +function! ale_linters#cuda#nvcc#HandleNVCCFormat(buffer, lines) abort + " Look for lines like the following. + " + " test.cu(8): error: argument of type "void *" is incompatible with parameter of type "int *" + let l:pattern = '\v^([^:\(\)]+):?\(?(\d+)\)?:(\d+)?:?\s*\w*\s*(error|warning): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'type': l:match[4] =~# 'error' ? 'E' : 'W', + \ 'text': l:match[5], + \ 'filename': fnamemodify(l:match[1], ':p'), + \} + + if !empty(l:match[3]) + let l:item.col = str2nr(l:match[3]) + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('cuda', { +\ 'name': 'nvcc', +\ 'output_stream': 'stderr', +\ '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 +" 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 new file mode 100644 index 00000000..78d1c152 --- /dev/null +++ b/ale_linters/d/dls.vim @@ -0,0 +1,22 @@ +" Author: aurieh +" Description: A Language Server implementation for D + +call ale#Set('d_dls_executable', 'dls') + +function! ale_linters#d#dls#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'd_dls_executable') +endfunction + +function! ale_linters#d#dls#FindProjectRoot(buffer) abort + " Note: this will return . if dub config is empty + " dls can run outside DUB projects just fine + return fnamemodify(ale#d#FindDUBConfig(a:buffer), ':h') +endfunction + +call ale#linter#Define('d', { +\ 'name': 'dls', +\ 'lsp': 'stdio', +\ '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 new file mode 100644 index 00000000..f38e812c --- /dev/null +++ b/ale_linters/d/dmd.vim @@ -0,0 +1,116 @@ +" Author: w0rp +" Description: "dmd for D files" + +function! s: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. + let l:config = ale#d#FindDUBConfig(a:buffer) + + " To support older dub versions, we just change the directory to the + " directory where we found the dub config, and then run `dub describe` + " from that directory. + if !empty(l:config) + return [fnamemodify(l:config, ':h'), 'dub describe --data-list + \ --data=import-paths + \ --data=string-import-paths + \ --data=versions + \ --data=debug-versions + \'] + endif + endif + + return ['', ''] +endfunction + +function! ale_linters#d#dmd#RunDUBCommand(buffer) abort + let [l:cwd, l:command] = s: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'), + \ {'cwd': l:cwd}, + \) +endfunction + +function! ale_linters#d#dmd#DMDCommand(buffer, dub_output, meta) abort + let l:import_list = [] + let l:str_import_list = [] + let l:versions_list = [] + let l:deb_versions_list = [] + let l:list_ind = 1 + let l:seen_line = 0 + + " Build a list of options generated from DUB, if available. + " DUB output each path or version on a single line. + " Each list is separated by a blank line. + " Empty list are represented by a blank line (followed and/or + " preceded by a separation blank line) + for l:line in a:dub_output + " line still has end of line char on windows + let l:line = substitute(l:line, '[\r\n]*$', '', '') + + if !empty(l:line) + if l:list_ind == 1 + call add(l:import_list, '-I' . ale#Escape(l:line)) + elseif l:list_ind == 2 + call add(l:str_import_list, '-J' . ale#Escape(l:line)) + elseif l:list_ind == 3 + call add(l:versions_list, '-version=' . ale#Escape(l:line)) + elseif l:list_ind == 4 + call add(l:deb_versions_list, '-debug=' . ale#Escape(l:line)) + endif + + let l:seen_line = 1 + elseif !l:seen_line + " if list is empty must skip one empty line + let l:seen_line = 1 + else + let l:seen_line = 0 + let l:list_ind += 1 + endif + endfor + + return 'dmd ' . join(l:import_list) . ' ' . + \ join(l:str_import_list) . ' ' . + \ join(l:versions_list) . ' ' . + \ join(l:deb_versions_list) . ' -o- -wi -vcolumns -c %t' +endfunction + +function! ale_linters#d#dmd#Handle(buffer, lines) abort + " Matches patterns lines like the following: + " /tmp/tmp.qclsa7qLP7/file.d(1): Error: function declaration without return type. (Note that constructors are always named 'this') + " /tmp/tmp.G1L5xIizvB.d(8,8): Error: module weak_reference is in file 'dstruct/weak_reference.d' which cannot be read + let l:pattern = '\v^(\f+)\((\d+)(,(\d+))?\): (\w+): (.+)$' + let l:output = [] + let l:dir = expand('#' . a:buffer . ':p:h') + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " If dmd was invoked with relative path, match[1] is relative, otherwise it is absolute. + " As we invoke dmd with the buffer path (in /tmp), this will generally be absolute already + let l:fname = ale#path#GetAbsPath(l:dir, l:match[1]) + call add(l:output, { + \ 'filename': l:fname, + \ 'lnum': l:match[2], + \ 'col': l:match[4], + \ 'type': l:match[5] is# 'Warning' || l:match[5] is# 'Deprecation' ? 'W' : 'E', + \ 'text': l:match[6], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('d', { +\ 'name': 'dmd', +\ 'executable': 'dmd', +\ 'command': function('ale_linters#d#dmd#RunDUBCommand'), +\ 'callback': 'ale_linters#d#dmd#Handle', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/dafny/dafny.vim b/ale_linters/dafny/dafny.vim new file mode 100644 index 00000000..8a114d22 --- /dev/null +++ b/ale_linters/dafny/dafny.vim @@ -0,0 +1,41 @@ +" Author: Taylor Blau +call ale#Set('dafny_dafny_timelimit', 10) + +function! ale_linters#dafny#dafny#Handle(buffer, lines) abort + let l:pattern = '\v(.*)\((\d+),(\d+)\): (.*): (.*)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'filename': l:match[1], + \ 'col': l:match[3] + 0, + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[5], + \ 'type': l:match[4] =~# '^Error' ? 'E' : 'W' + \ }) + endfor + + for l:match in ale#util#GetMatches(a:lines, '\v(.*)\((\d+),(\d+)\): (Verification of .{-} timed out after \d+ seconds)') + call add(l:output, { + \ 'filename': l:match[1], + \ 'col': l:match[3] + 0, + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \ }) + endfor + + return l:output +endfunction + +function! ale_linters#dafny#dafny#GetCommand(buffer) abort + return printf('dafny %%s /compile:0 /timeLimit:%d', ale#Var(a:buffer, 'dafny_dafny_timelimit')) +endfunction + +call ale#linter#Define('dafny', { +\ 'name': 'dafny', +\ 'executable': 'dafny', +\ 'command': function('ale_linters#dafny#dafny#GetCommand'), +\ 'callback': 'ale_linters#dafny#dafny#Handle', +\ 'lint_file': 1, +\ }) diff --git a/ale_linters/dart/analysis_server.vim b/ale_linters/dart/analysis_server.vim new file mode 100644 index 00000000..12a5d590 --- /dev/null +++ b/ale_linters/dart/analysis_server.vim @@ -0,0 +1,37 @@ +" Author: Nelson Yeung +" Description: Check Dart files with dart analysis server LSP + +call ale#Set('dart_analysis_server_enable_language_server', 1) +call ale#Set('dart_analysis_server_executable', 'dart') + +function! ale_linters#dart#analysis_server#GetProjectRoot(buffer) abort + " Note: pub only looks for pubspec.yaml, there's no point in adding + " support for pubspec.yml + let l:pubspec = ale#path#FindNearestFile(a:buffer, 'pubspec.yaml') + + return !empty(l:pubspec) ? fnamemodify(l:pubspec, ':h:h') : '.' +endfunction + +function! ale_linters#dart#analysis_server#GetCommand(buffer) abort + let l:language_server = ale#Var(a:buffer, 'dart_analysis_server_enable_language_server') + let l:executable = ale#Var(a:buffer, 'dart_analysis_server_executable') + let l:dart = resolve(exepath(l:executable)) + let l:output = '%e ' + \ . fnamemodify(l:dart, ':h') . '/snapshots/analysis_server.dart.snapshot' + \ . ' --lsp' + + " Enable new language-server command + if l:language_server == 1 + let l:output = '%e language-server --protocol=lsp' + endif + + return l:output +endfunction + +call ale#linter#Define('dart', { +\ 'name': 'analysis_server', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'dart_analysis_server_executable')}, +\ 'command': function('ale_linters#dart#analysis_server#GetCommand'), +\ 'project_root': function('ale_linters#dart#analysis_server#GetProjectRoot'), +\}) diff --git a/ale_linters/dart/dart_analyze.vim b/ale_linters/dart/dart_analyze.vim new file mode 100644 index 00000000..7d67c31c --- /dev/null +++ b/ale_linters/dart/dart_analyze.vim @@ -0,0 +1,29 @@ +" Author: ghsang +" Description: Check Dart files with dart analyze + +call ale#Set('dart_analyze_executable', 'dart') + +function! ale_linters#dart#dart_analyze#Handle(buffer, lines) abort + let l:pattern = '\v([a-z]+) - (.+):(\d+):(\d+) - (.+) - (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let [l:type, l:filename, l:lnum, l:col, l:message, l:code] = l:match[1:6] + call add(l:output, { + \ 'type': l:type is# 'error' ? 'E' : l:type is# 'info' ? 'I' : 'W', + \ 'text': l:code . ': ' . l:message, + \ 'lnum': str2nr(l:lnum), + \ 'col': str2nr(l:col), + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('dart', { +\ 'name': 'dart_analyze', +\ 'executable': {b -> ale#Var(b, 'dart_analyze_executable')}, +\ 'command': '%e analyze --fatal-infos %s', +\ 'callback': 'ale_linters#dart#dart_analyze#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/dart/language_server.vim b/ale_linters/dart/language_server.vim new file mode 100644 index 00000000..d0e639c8 --- /dev/null +++ b/ale_linters/dart/language_server.vim @@ -0,0 +1,20 @@ +" Author: aurieh +" Description: A language server for dart + +call ale#Set('dart_language_server_executable', 'dart_language_server') + +function! ale_linters#dart#language_server#GetProjectRoot(buffer) abort + " Note: pub only looks for pubspec.yaml, there's no point in adding + " support for pubspec.yml + let l:pubspec = ale#path#FindNearestFile(a:buffer, 'pubspec.yaml') + + return !empty(l:pubspec) ? fnamemodify(l:pubspec, ':h:h') : '' +endfunction + +call ale#linter#Define('dart', { +\ 'name': 'language_server', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'dart_language_server_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#dart#language_server#GetProjectRoot'), +\}) diff --git a/ale_linters/desktop/desktop_file_validate.vim b/ale_linters/desktop/desktop_file_validate.vim new file mode 100644 index 00000000..5a97d315 --- /dev/null +++ b/ale_linters/desktop/desktop_file_validate.vim @@ -0,0 +1,31 @@ +call ale#Set('desktop_desktop_file_validate_options', '') + +" Example matches for pattern: +" +" foo.desktop: warning: key "TerminalOptions" in group ... +" foo.desktop: error: action "new-private-window" is defined, ... +let s:pattern = '\v^(.+): ([a-z]+): (.+)$' + +function! ale_linters#desktop#desktop_file_validate#Handle(buffer, lines) abort + " The error format doesn't specify lines, so we can just put all of the + " errors on line 1. + return ale#util#MapMatches(a:lines, s:pattern, {match -> { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': match[2] is? 'error' ? 'E' : 'W', + \ 'text': match[3], + \}}) +endfunction + +call ale#linter#Define('desktop', { +\ 'name': 'desktop_file_validate', +\ 'aliases': ['desktop-file-validate'], +\ 'executable': 'desktop-file-validate', +\ 'command': {b -> +\ '%e' +\ . ale#Pad(ale#Var(b, 'desktop_desktop_file_validate_options')) +\ . ' %t' +\ }, +\ 'callback': 'ale_linters#desktop#desktop_file_validate#Handle', +\ 'output_stream': 'both', +\}) diff --git a/ale_linters/dockerfile/dockerfile_lint.vim b/ale_linters/dockerfile/dockerfile_lint.vim new file mode 100644 index 00000000..0c0ad533 --- /dev/null +++ b/ale_linters/dockerfile/dockerfile_lint.vim @@ -0,0 +1,76 @@ +" Author: Alexander Olofsson + +call ale#Set('dockerfile_dockerfile_lint_executable', 'dockerfile_lint') +call ale#Set('dockerfile_dockerfile_lint_options', '') + +function! ale_linters#dockerfile#dockerfile_lint#GetType(type) abort + if a:type is? 'error' + return 'E' + elseif a:type is? 'warn' + return 'W' + endif + + return 'I' +endfunction + +function! ale_linters#dockerfile#dockerfile_lint#Handle(buffer, lines) abort + try + let l:data = json_decode(join(a:lines, '')) + catch + return [] + endtry + + if empty(l:data) + " Should never happen, but it's better to be on the safe side + return [] + endif + + let l:messages = [] + + for l:type in ['error', 'warn', 'info'] + for l:object in l:data[l:type]['data'] + let l:line = get(l:object, 'line', -1) + let l:message = l:object['message'] + + let l:link = get(l:object, 'reference_url', '') + + if type(l:link) == v:t_list + " Somehow, reference_url is returned as two-part list. + " Anchor markers in that list are sometimes duplicated. + " See https://github.com/projectatomic/dockerfile_lint/issues/134 + let l:link = join(l:link, '') + let l:link = substitute(l:link, '##', '#', '') + endif + + let l:detail = l:message + + if get(l:object, 'description', 'None') isnot# 'None' + let l:detail .= "\n\n" . l:object['description'] + endif + + let l:detail .= "\n\n" . l:link + + call add(l:messages, { + \ 'lnum': l:line, + \ 'text': l:message, + \ 'type': ale_linters#dockerfile#dockerfile_lint#GetType(l:type), + \ 'detail': l:detail, + \}) + endfor + endfor + + return l:messages +endfunction + +function! ale_linters#dockerfile#dockerfile_lint#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'dockerfile_dockerfile_lint_options')) + \ . ' -p -j -f' + \ . ' %t' +endfunction + +call ale#linter#Define('dockerfile', { +\ 'name': 'dockerfile_lint', +\ '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/dockerlinter.vim b/ale_linters/dockerfile/dockerlinter.vim new file mode 100644 index 00000000..6b35438f --- /dev/null +++ b/ale_linters/dockerfile/dockerlinter.vim @@ -0,0 +1,69 @@ +" Author: Shad +" Description: dockerlinter linter for dockerfile + +call ale#Set('dockerfile_dockerlinter_executable', 'dockerlinter') +call ale#Set('dockerfile_dockerlinter_options', '') + +function! ale_linters#dockerfile#dockerlinter#GetType(type) abort + if a:type is? 'error' + return 'E' + elseif a:type is? 'warning' + return 'W' + endif + + return 'I' +endfunction + +function! ale_linters#dockerfile#dockerlinter#Handle(buffer, lines) abort + try + let l:data = json_decode(join(a:lines, '')) + catch + return [] + endtry + + if empty(l:data) + " Should never happen, but it's better to be on the safe side + return [] + endif + + let l:messages = [] + + for l:object in l:data + let l:line = get(l:object, 'lineNumber', -1) + let l:message = l:object['message'] + let l:type = l:object['level'] + let l:detail = l:message + let l:code = l:object['code'] + + if l:code =~# '^SC' + let l:link = 'https://www.shellcheck.net/wiki/' . l:code + else + let l:link = 'https://github.com/buddy-works/dockerfile-linter/blob/master/Rules.md#' . l:code + endif + + let l:detail = l:message . "\n\n" . l:link + + call add(l:messages, { + \ 'lnum': l:line, + \ 'code': l:code, + \ 'text': l:message, + \ 'type': ale_linters#dockerfile#dockerlinter#GetType(l:type), + \ 'detail': l:detail, + \}) + endfor + + return l:messages +endfunction + +function! ale_linters#dockerfile#dockerlinter#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'dockerfile_dockerlinter_options')) + \ . ' -j -f' + \ . ' %t' +endfunction + +call ale#linter#Define('dockerfile', { +\ 'name': 'dockerlinter', +\ 'executable': {b -> ale#Var(b, 'dockerfile_dockerlinter_executable')}, +\ 'command': function('ale_linters#dockerfile#dockerlinter#GetCommand'), +\ 'callback': 'ale_linters#dockerfile#dockerlinter#Handle', +\}) diff --git a/ale_linters/dockerfile/hadolint.vim b/ale_linters/dockerfile/hadolint.vim new file mode 100644 index 00000000..38ce0834 --- /dev/null +++ b/ale_linters/dockerfile/hadolint.vim @@ -0,0 +1,122 @@ +" Author: hauleth - https://github.com/hauleth + +" always, yes, never +call ale#Set('dockerfile_hadolint_use_docker', 'never') +call ale#Set('dockerfile_hadolint_docker_image', 'hadolint/hadolint') +call ale#Set('dockerfile_hadolint_options', '') + +function! ale_linters#dockerfile#hadolint#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " -:19 DL3001 warning: Pipe chain should start with a raw value. + " /dev/stdin:19:3 unexpected thing + let l:pattern = '\v^%(/dev/stdin|-):(\d+):?(\d+)? ((DL|SC)(\d+) )?((.+)?: )?(.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:lnum = 0 + let l:colnum = 0 + + if l:match[1] isnot# '' + let l:lnum = l:match[1] + 0 + endif + + if l:match[2] isnot# '' + let l:colnum = l:match[2] + 0 + endif + + " Shellcheck knows a 'style' severity - pin it to info level as well. + if l:match[7] is# 'style' + let l:type = 'I' + elseif l:match[7] is# 'info' + let l:type = 'I' + elseif l:match[7] is# 'warning' + let l:type = 'W' + else + let l:type = 'E' + endif + + let l:text = l:match[8] + let l:detail = l:match[8] + let l:domain = 'https://github.com/hadolint/hadolint/wiki/' + let l:code = '' + let l:link = '' + + if l:match[4] is# 'SC' + let l:domain = 'https://github.com/koalaman/shellcheck/wiki/' + endif + + if l:match[5] isnot# '' + let l:code = l:match[4] . l:match[5] + let l:link = ' ( ' . l:domain . l:code . ' )' + let l:detail = l:code . l:link . "\n\n" . l:detail + else + let l:type = 'E' + let l:detail = 'hadolint could not parse the file because of a syntax error.' + endif + + let l:line_output = { + \ 'lnum': l:lnum, + \ 'col': l:colnum, + \ 'type': l:type, + \ 'text': l:text, + \ 'detail': l:detail + \} + + if l:code isnot# '' + let l:line_output['code'] = l:code + endif + + call add(l:output, l:line_output) + endfor + + return l:output +endfunction + +" This is a little different than the typical 'executable' callback. We want +" to afford the user the chance to say always use docker, never use docker, +" and use docker if the hadolint executable is not present on the system. +" +" In the case of neither docker nor hadolint executables being present, it +" really doesn't matter which we return -- either will have the effect of +" 'nope, can't use this linter!'. + +function! ale_linters#dockerfile#hadolint#GetExecutable(buffer) abort + let l:use_docker = ale#Var(a:buffer, 'dockerfile_hadolint_use_docker') + + " check for mandatory directives + if l:use_docker is# 'never' + return 'hadolint' + elseif l:use_docker is# 'always' + return 'docker' + endif + + " if we reach here, we want to use 'hadolint' if present... + if executable('hadolint') + return 'hadolint' + endif + + "... and 'docker' as a fallback. + return 'docker' +endfunction + +function! ale_linters#dockerfile#hadolint#GetCommand(buffer) abort + let l:command = ale_linters#dockerfile#hadolint#GetExecutable(a:buffer) + let l:opts = ale#Var(a:buffer, 'dockerfile_hadolint_options') . ' --no-color -' + + if l:command is# 'docker' + return printf('docker run --rm -i %s hadolint %s', + \ ale#Var(a:buffer, 'dockerfile_hadolint_docker_image'), + \ l:opts) + endif + + return 'hadolint ' . l:opts +endfunction + + +call ale#linter#Define('dockerfile', { +\ 'name': 'hadolint', +\ '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 new file mode 100644 index 00000000..d6a861f4 --- /dev/null +++ b/ale_linters/elixir/credo.vim @@ -0,0 +1,71 @@ +" Author: hauleth - https://github.com/hauleth + +function! ale_linters#elixir#credo#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " lib/filename.ex:19:7: F: Pipe chain should start with a raw value. + let l:pattern = '\v:(\d+):?(\d+)?: (.): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = l:match[3] + let l:text = l:match[4] + + " Refactoring opportunities + if l:type is# 'F' + let l:type = 'W' + " Consistency + elseif l:type is# 'C' + let l:type = 'W' + " Software Design + elseif l:type is# 'D' + let l:type = 'I' + " Code Readability + elseif l:type is# 'R' + let l:type = 'I' + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + endfor + + 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#GetConfigFile() abort + let l:config_file = get(g:, 'ale_elixir_credo_config_file', '') + + if empty(l:config_file) + return '' + endif + + return ' --config-file ' . l:config_file +endfunction + +function! ale_linters#elixir#credo#GetCommand(buffer) abort + return 'mix help credo && ' + \ . 'mix credo ' . ale_linters#elixir#credo#GetMode() + \ . ale_linters#elixir#credo#GetConfigFile() + \ . ' --format=flycheck --read-from-stdin %s' +endfunction + +call ale#linter#Define('elixir', { +\ 'name': 'credo', +\ 'executable': 'mix', +\ 'cwd': function('ale#handlers#elixir#FindMixUmbrellaRoot'), +\ 'command': function('ale_linters#elixir#credo#GetCommand'), +\ 'callback': 'ale_linters#elixir#credo#Handle', +\}) diff --git a/ale_linters/elixir/cspell.vim b/ale_linters/elixir/cspell.vim new file mode 100644 index 00000000..12dc271f --- /dev/null +++ b/ale_linters/elixir/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Elixir files. + +call ale#handlers#cspell#DefineLinter('elixir') diff --git a/ale_linters/elixir/dialyxir.vim b/ale_linters/elixir/dialyxir.vim new file mode 100644 index 00000000..9b8a5cda --- /dev/null +++ b/ale_linters/elixir/dialyxir.vim @@ -0,0 +1,34 @@ +" Author: Fran C. - https://github.com/franciscoj +" Description: Add dialyzer support for elixir through dialyxir +" https://github.com/jeremyjh/dialyxir + +function! ale_linters#elixir#dialyxir#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " lib/filename.ex:19: Function fname/1 has no local return + let l:pattern = '\v(.+):(\d+): (.+)$' + let l:output = [] + let l:type = 'W' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if bufname(a:buffer) == l:match[1] + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[2] + 0, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:match[3], + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('elixir', { +\ 'name': 'dialyxir', +\ 'executable': 'mix', +\ 'cwd': function('ale#handlers#elixir#FindMixProjectRoot'), +\ 'command': 'mix help dialyzer && mix dialyzer', +\ 'callback': 'ale_linters#elixir#dialyxir#Handle', +\}) diff --git a/ale_linters/elixir/dogma.vim b/ale_linters/elixir/dogma.vim new file mode 100644 index 00000000..28e7f420 --- /dev/null +++ b/ale_linters/elixir/dogma.vim @@ -0,0 +1,39 @@ +" Author: archseer - https://github.com/archSeer + +function! ale_linters#elixir#dogma#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " lib/filename.ex:19:7: F: Pipe chain should start with a raw value. + let l:pattern = '\v:(\d+):?(\d+)?: (.): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = l:match[3] + let l:text = l:match[4] + + if l:type is# 'C' + let l:type = 'E' + elseif l:type is# 'R' + let l:type = 'W' + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('elixir', { +\ 'name': 'dogma', +\ 'executable': 'mix', +\ 'cwd': function('ale#handlers#elixir#FindMixProjectRoot'), +\ 'command': 'mix help dogma && mix dogma %s --format=flycheck', +\ '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 new file mode 100644 index 00000000..c7dda600 --- /dev/null +++ b/ale_linters/elixir/elixir_ls.vim @@ -0,0 +1,22 @@ +" Author: Jon Parise +" Description: ElixirLS integration (https://github.com/elixir-lsp/elixir-ls) + +call ale#Set('elixir_elixir_ls_release', 'elixir-ls') +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 = has('win32') ? '\language_server.bat' : '/language_server.sh' + + return l:dir . l:cmd +endfunction + +call ale#linter#Define('elixir', { +\ 'name': 'elixir_ls', +\ 'aliases': ['elixir-ls', 'elixirls'], +\ 'lsp': 'stdio', +\ '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/lexical.vim b/ale_linters/elixir/lexical.vim new file mode 100644 index 00000000..ea13142e --- /dev/null +++ b/ale_linters/elixir/lexical.vim @@ -0,0 +1,19 @@ +" Author: Axel Clark +" Description: Lexical integration (https://github.com/lexical-lsp/lexical) + +call ale#Set('elixir_lexical_release', 'lexical') + +function! ale_linters#elixir#lexical#GetExecutable(buffer) abort + let l:dir = ale#path#Simplify(ale#Var(a:buffer, 'elixir_lexical_release')) + let l:cmd = has('win32') ? '\start_lexical.bat' : '/start_lexical.sh' + + return l:dir . l:cmd +endfunction + +call ale#linter#Define('elixir', { +\ 'name': 'lexical', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#elixir#lexical#GetExecutable'), +\ 'command': function('ale_linters#elixir#lexical#GetExecutable'), +\ 'project_root': function('ale#handlers#elixir#FindMixUmbrellaRoot'), +\}) diff --git a/ale_linters/elixir/mix.vim b/ale_linters/elixir/mix.vim new file mode 100644 index 00000000..948c6d36 --- /dev/null +++ b/ale_linters/elixir/mix.vim @@ -0,0 +1,45 @@ +" Author: evnu - https://github.com/evnu +" Author: colbydehart - https://github.com/colbydehart +" Description: Mix compile checking for Elixir files + +function! ale_linters#elixir#mix#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " Error format + " ** (CompileError) apps/sim/lib/sim/server.ex:87: undefined function update_in/4 + " + " TODO: Warning format + " warning: variable "foobar" does not exist and is being expanded to "foobar()", please use parentheses to remove the ambiguity or change the variable name + let l:pattern = '\v\(([^\)]+Error)\) ([^:]+):([^:]+): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = 'E' + let l:text = l:match[4] + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[3] + 0, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +function! ale_linters#elixir#mix#GetCommand(buffer) abort + let l:temp_dir = ale#command#CreateDirectory(a:buffer) + + return ale#Env('MIX_BUILD_PATH', l:temp_dir) . 'mix compile %s' +endfunction + +call ale#linter#Define('elixir', { +\ 'name': 'mix', +\ 'executable': 'mix', +\ 'cwd': function('ale#handlers#elixir#FindMixProjectRoot'), +\ 'command': function('ale_linters#elixir#mix#GetCommand'), +\ 'callback': 'ale_linters#elixir#mix#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/elm/ls.vim b/ale_linters/elm/ls.vim new file mode 100644 index 00000000..111f3ac2 --- /dev/null +++ b/ale_linters/elm/ls.vim @@ -0,0 +1,41 @@ +" Author: antew - https://github.com/antew +" Description: elm-language-server integration for elm (diagnostics, formatting, and more) + +call ale#Set('elm_ls_executable', 'elm-language-server') +call ale#Set('elm_ls_use_global', get(g:, 'ale_use_global_executables', 1)) + +" elm-language-server will search for local and global binaries, if empty +call ale#Set('elm_ls_elm_path', '') +call ale#Set('elm_ls_elm_format_path', '') +call ale#Set('elm_ls_elm_test_path', '') +call ale#Set('elm_ls_elm_analyse_trigger', 'change') + +function! ale_linters#elm#ls#GetProjectRoot(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 + +function! ale_linters#elm#ls#GetInitializationOptions(buffer) abort + return { + \ 'elmPath': ale#Var(a:buffer, 'elm_ls_elm_path'), + \ 'elmFormatPath': ale#Var(a:buffer, 'elm_ls_elm_format_path'), + \ 'elmTestPath': ale#Var(a:buffer, 'elm_ls_elm_test_path'), + \ 'elmAnalyseTrigger': ale#Var(a:buffer, 'elm_ls_elm_analyse_trigger'), + \} +endfunction + +call ale#linter#Define('elm', { +\ 'name': 'ls', +\ 'aliases': ['elm_ls'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#path#FindExecutable(b, 'elm_ls', [ +\ 'node_modules/.bin/elm-language-server', +\ 'node_modules/.bin/elm-lsp', +\ 'elm-lsp' +\ ])}, +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#elm#ls#GetProjectRoot'), +\ 'language': 'elm', +\ 'initialization_options': function('ale_linters#elm#ls#GetInitializationOptions') +\}) diff --git a/ale_linters/elm/make.vim b/ale_linters/elm/make.vim new file mode 100644 index 00000000..a7f9ea7b --- /dev/null +++ b/ale_linters/elm/make.vim @@ -0,0 +1,242 @@ +" Author: buffalocoder - https://github.com/buffalocoder, soywod - https://github.com/soywod, hecrj - https://github.com/hecrj +" Description: Elm linting in Ale. Closely follows the Syntastic checker in https://github.com/ElmCast/elm-vim. + +call ale#Set('elm_make_executable', 'elm') +call ale#Set('elm_make_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#elm#make#Handle(buffer, lines) abort + let l:output = [] + let l:unparsed_lines = [] + + for l:line in a:lines + if l:line[0] is# '{' + " Elm 0.19 + call ale_linters#elm#make#HandleElm019Line(l:line, l:output) + elseif l:line[0] is# '[' + " Elm 0.18 + call ale_linters#elm#make#HandleElm018Line(l:line, l:output) + elseif l:line isnot# 'Successfully generated /dev/null' + call add(l:unparsed_lines, l:line) + endif + endfor + + if len(l:unparsed_lines) > 0 + call add(l:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:unparsed_lines[0], + \ 'detail': join(l:unparsed_lines, "\n") + \}) + endif + + return l:output +endfunction + +function! ale_linters#elm#make#HandleElm019Line(line, output) abort + let l:report = json_decode(a:line) + + if l:report.type is? 'error' + " General problem + let l:details = ale_linters#elm#make#ParseMessage(l:report.message) + + if empty(l:report.path) + let l:report.path = 'Elm' + endif + + if ale_linters#elm#make#FileIsBuffer(l:report.path) + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:details, + \}) + else + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:report.path .' - '. l:details, + \ 'detail': l:report.path ." ----------\n\n". l:details, + \}) + endif + else + " Compilation errors + for l:error in l:report.errors + let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.path) + + for l:problem in l:error.problems + let l:details = ale_linters#elm#make#ParseMessage(l:problem.message) + + if l:file_is_buffer + " Buffer module has problems + call add(a:output, { + \ 'lnum': l:problem.region.start.line, + \ 'col': l:problem.region.start.column, + \ 'end_lnum': l:problem.region.end.line, + \ 'end_col': l:problem.region.end.column, + \ 'type': 'E', + \ 'text': l:details, + \}) + else + " Imported module has problems + let l:location = l:error.path .':'. l:problem.region.start.line + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:location .' - '. l:details, + \ 'detail': l:location ." ----------\n\n". l:details, + \}) + endif + endfor + endfor + endif +endfunction + +function! ale_linters#elm#make#HandleElm018Line(line, output) abort + let l:errors = json_decode(a:line) + + for l:error in l:errors + let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.file) + + if l:file_is_buffer + " Current buffer has problems + call add(a:output, { + \ 'lnum': l:error.region.start.line, + \ 'col': l:error.region.start.column, + \ 'end_lnum': l:error.region.end.line, + \ 'end_col': l:error.region.end.column, + \ 'type': (l:error.type is? 'error') ? 'E' : 'W', + \ 'text': l:error.overview, + \ 'detail': l:error.overview . "\n\n" . l:error.details + \}) + elseif l:error.type is? 'error' + " Imported module has errors + let l:location = l:error.file .':'. l:error.region.start.line + + call add(a:output, { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': l:location .' - '. l:error.overview, + \ 'detail': l:location ." ----------\n\n". l:error.overview . "\n\n" . l:error.details + \}) + endif + endfor +endfunction + +function! ale_linters#elm#make#FileIsBuffer(path) abort + return ale#path#IsTempName(a:path) +endfunction + +function! ale_linters#elm#make#ParseMessage(message) abort + return join(map(copy(a:message), 'ale_linters#elm#make#ParseMessageItem(v:val)'), '') +endfunction + +function! ale_linters#elm#make#ParseMessageItem(item) abort + if type(a:item) is v:t_string + return a:item + else + return a:item.string + endif +endfunction + +function! ale_linters#elm#make#GetPackageFile(buffer) abort + let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json') + + if empty(l:elm_json) + " Fallback to Elm 0.18 + 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 + +function! ale_linters#elm#make#GetCwd(buffer) abort + let l:root_dir = ale_linters#elm#make#GetRootDir(a:buffer) + + return !empty(l:root_dir) ? l:root_dir : '' +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:is_v19 = ale_linters#elm#make#IsVersionGte19(a:buffer) + let l:is_using_elm_test = l:executable =~# 'elm-test$' + + " 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#path#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 '%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#path#FindExecutable( + \ a:buffer, + \ 'elm_make', + \ ['node_modules/.bin/elm-test', 'node_modules/.bin/elm'] + \) + else + return ale#path#FindExecutable(a:buffer, 'elm_make', ['node_modules/.bin/elm']) + endif +endfunction + +call ale#linter#Define('elm', { +\ 'name': 'make', +\ 'executable': function('ale_linters#elm#make#GetExecutable'), +\ 'output_stream': 'both', +\ 'cwd': function('ale_linters#elm#make#GetCwd'), +\ 'command': function('ale_linters#elm#make#GetCommand'), +\ 'callback': 'ale_linters#elm#make#Handle' +\}) diff --git a/ale_linters/erlang/dialyzer.vim b/ale_linters/erlang/dialyzer.vim new file mode 100644 index 00000000..a97c9520 --- /dev/null +++ b/ale_linters/erlang/dialyzer.vim @@ -0,0 +1,97 @@ +" Author: Autoine Gagne - https://github.com/AntoineGagne +" Description: Define a checker that runs dialyzer on Erlang files. + +let g:ale_erlang_dialyzer_executable = +\ get(g:, 'ale_erlang_dialyzer_executable', 'dialyzer') +let g:ale_erlang_dialyzer_options = +\ get(g:, 'ale_erlang_dialyzer_options', '-Wunmatched_returns' +\ . ' -Werror_handling' +\ . ' -Wrace_conditions' +\ . ' -Wunderspecs') +let g:ale_erlang_dialyzer_plt_file = +\ get(g:, 'ale_erlang_dialyzer_plt_file', '') +let g:ale_erlang_dialyzer_rebar3_profile = +\ get(g:, 'ale_erlang_dialyzer_rebar3_profile', 'default') + +function! ale_linters#erlang#dialyzer#GetRebar3Profile(buffer) abort + return ale#Var(a:buffer, 'erlang_dialyzer_rebar3_profile') +endfunction + +function! ale_linters#erlang#dialyzer#FindPlt(buffer) abort + let l:plt_file = '' + let l:rebar3_profile = ale_linters#erlang#dialyzer#GetRebar3Profile(a:buffer) + let l:plt_file_directory = ale#path#FindNearestDirectory(a:buffer, '_build/' . l:rebar3_profile) + + if !empty(l:plt_file_directory) + let l:plt_file = globpath(l:plt_file_directory, '*_plt', 0, 1) + endif + + if !empty(l:plt_file) + return l:plt_file[0] + endif + + if !empty($REBAR_PLT_DIR) + return expand('$REBAR_PLT_DIR/dialyzer/plt') + endif + + return expand('$HOME/.dialyzer_plt') +endfunction + +function! ale_linters#erlang#dialyzer#GetPlt(buffer) abort + let l:plt_file = ale#Var(a:buffer, 'erlang_dialyzer_plt_file') + + if !empty(l:plt_file) + return l:plt_file + endif + + return ale_linters#erlang#dialyzer#FindPlt(a:buffer) +endfunction + +function! ale_linters#erlang#dialyzer#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'erlang_dialyzer_executable') +endfunction + +function! ale_linters#erlang#dialyzer#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'erlang_dialyzer_options') + + let l:command = ale#Escape(ale_linters#erlang#dialyzer#GetExecutable(a:buffer)) + \ . ' -n' + \ . ' --plt ' . ale#Escape(ale_linters#erlang#dialyzer#GetPlt(a:buffer)) + \ . ' ' . l:options + \ . ' %s' + + return l:command +endfunction + +function! ale_linters#erlang#dialyzer#Handle(buffer, lines) abort + " Match patterns like the following: + " + " erl_tidy_prv_fmt.erl:3: Callback info about the provider behaviour is not available + let l:pattern = '^\S\+:\(\d\+\): \(.\+\)$' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) != 0 + let l:code = l:match[2] + + call add(l:output, { + \ 'lnum': str2nr(l:match[1]), + \ 'lcol': 0, + \ 'text': l:code, + \ 'type': 'W' + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('erlang', { +\ 'name': 'dialyzer', +\ 'executable': function('ale_linters#erlang#dialyzer#GetExecutable'), +\ 'command': function('ale_linters#erlang#dialyzer#GetCommand'), +\ 'callback': function('ale_linters#erlang#dialyzer#Handle'), +\ 'lint_file': 1 +\}) diff --git a/ale_linters/erlang/elvis.vim b/ale_linters/erlang/elvis.vim new file mode 100644 index 00000000..321a6c72 --- /dev/null +++ b/ale_linters/erlang/elvis.vim @@ -0,0 +1,59 @@ +" Author: Dmitri Vereshchagin +" Description: Elvis linter for Erlang files + +call ale#Set('erlang_elvis_executable', 'elvis') + +function! ale_linters#erlang#elvis#Handle(buffer, lines) abort + let l:pattern = '\v:(\d+):[^:]+:(.+)' + let l:loclist = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:loclist, { + \ 'lnum': str2nr(l:match[1]), + \ 'text': s:AbbreviateMessage(l:match[2]), + \ 'type': 'W', + \ 'sub_type': 'style', + \}) + endfor + + return l:loclist +endfunction + +function! s:AbbreviateMessage(text) abort + let l:pattern = '\v\c^(line \d+ is too long):.*$' + + return substitute(a:text, l:pattern, '\1.', '') +endfunction + +function! s:GetCommand(buffer) abort + let l:cwd = s:GetCwd(a:buffer) + + let l:file = !empty(l:cwd) + \ ? expand('#' . a:buffer . ':p')[len(l:cwd) + 1:] + \ : expand('#' . a:buffer . ':.') + + return '%e rock --output-format=parsable ' . ale#Escape(l:file) +endfunction + +function! s:GetCwd(buffer) abort + let l:markers = ['elvis.config', 'rebar.lock', 'erlang.mk'] + + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + for l:marker in l:markers + if filereadable(l:path . '/' . l:marker) + return l:path + endif + endfor + endfor + + return '' +endfunction + +call ale#linter#Define('erlang', { +\ 'name': 'elvis', +\ 'callback': 'ale_linters#erlang#elvis#Handle', +\ 'executable': {b -> ale#Var(b, 'erlang_elvis_executable')}, +\ 'command': function('s:GetCommand'), +\ 'cwd': function('s:GetCwd'), +\ 'lint_file': 1, +\}) diff --git a/ale_linters/erlang/erlang_ls.vim b/ale_linters/erlang/erlang_ls.vim new file mode 100644 index 00000000..15edd48e --- /dev/null +++ b/ale_linters/erlang/erlang_ls.vim @@ -0,0 +1,57 @@ +" Author: Dmitri Vereshchagin +" Description: LSP linter for Erlang files + +call ale#Set('erlang_erlang_ls_executable', 'erlang_ls') +call ale#Set('erlang_erlang_ls_log_dir', '') +call ale#Set('erlang_erlang_ls_log_level', 'info') + +function! s:GetCommand(buffer) abort + let l:log_dir = ale#Var(a:buffer, 'erlang_erlang_ls_log_dir') + let l:log_level = ale#Var(a:buffer, 'erlang_erlang_ls_log_level') + + let l:command = '%e' + + if !empty(l:log_dir) + let l:command .= ' --log-dir=' . ale#Escape(l:log_dir) + endif + + let l:command .= ' --log-level=' . ale#Escape(l:log_level) + + return l:command +endfunction + +function! s:FindProjectRoot(buffer) abort + let l:markers = [ + \ '_checkouts/', + \ '_build/', + \ 'deps/', + \ 'erlang_ls.config', + \ 'rebar.lock', + \ 'erlang.mk', + \] + + " This is a way to find Erlang/OTP root (the one that is managed + " by kerl or asdf). Useful if :ALEGoToDefinition takes us there. + let l:markers += ['.kerl_config'] + + for l:marker in l:markers + let l:path = l:marker[-1:] is# '/' + \ ? ale#path#FindNearestDirectory(a:buffer, l:marker) + \ : ale#path#FindNearestFile(a:buffer, l:marker) + + if !empty(l:path) + return ale#path#Dirname(l:path) + endif + endfor + + return '' +endfunction + +call ale#linter#Define('erlang', { +\ 'name': 'erlang_ls', +\ 'executable': {b -> ale#Var(b, 'erlang_erlang_ls_executable')}, +\ 'command': function('s:GetCommand'), +\ 'lsp': 'stdio', +\ 'project_root': function('s:FindProjectRoot'), +\ 'aliases': ['erlang-ls'], +\}) diff --git a/ale_linters/erlang/erlc.vim b/ale_linters/erlang/erlc.vim new file mode 100644 index 00000000..0c67a73f --- /dev/null +++ b/ale_linters/erlang/erlc.vim @@ -0,0 +1,104 @@ +" Author: Magnus Ottenklinger - https://github.com/evnu + +let g:ale_erlang_erlc_executable = get(g:, 'ale_erlang_erlc_executable', 'erlc') +let g:ale_erlang_erlc_options = get(g:, 'ale_erlang_erlc_options', '') + +function! ale_linters#erlang#erlc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'erlang_erlc_executable') +endfunction + +function! ale_linters#erlang#erlc#GetCommand(buffer) abort + let l:output_file = ale#util#Tempname() + call ale#command#ManageFile(a:buffer, l:output_file) + + let l:command = ale#Escape(ale_linters#erlang#erlc#GetExecutable(a:buffer)) + \ . ' -o ' . ale#Escape(l:output_file) + \ . ' ' . ale#Var(a:buffer, 'erlang_erlc_options') + \ . ' %t' + + return l:command +endfunction + +function! ale_linters#erlang#erlc#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " error.erl:4: variable 'B' is unbound + " error.erl:3: Warning: function main/0 is unused + " error.erl:4: Warning: variable 'A' is unused + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+:)? (Warning: )?(.+)$' + + " parse_transforms are a special case. The error message does not indicate a location: + " error.erl: undefined parse transform 'some_parse_transform' + let l:pattern_parse_transform = '\v(undefined parse transform .*)$' + let l:output = [] + + let l:pattern_no_module_definition = '\v(no module definition)$' + let l:pattern_unused = '\v(.* is unused)$' + + let l:is_hrl = fnamemodify(bufname(a:buffer), ':e') is# 'hrl' + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + " Determine if the output indicates an error. We distinguish between two cases: + " + " 1) normal errors match l:pattern + " 2) parse_transform errors match l:pattern_parse_transform + " + " If none of the patterns above match, the line can be ignored + if len(l:match) == 0 " not a 'normal' warning or error + let l:match_parse_transform = matchlist(l:line, l:pattern_parse_transform) + + if len(l:match_parse_transform) == 0 " also not a parse_transform error + continue + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'E', + \ 'text': l:match_parse_transform[0], + \}) + + continue + endif + + let l:line = l:match[2] + let l:warning_or_text = l:match[4] + let l:text = l:match[5] + + " If this file is a header .hrl, ignore the following expected messages: + " - 'no module definition' + " - 'X is unused' + if l:is_hrl && ( + \ match(l:text, l:pattern_no_module_definition) != -1 + \ || match(l:text, l:pattern_unused) != -1 + \) + continue + endif + + if !empty(l:warning_or_text) + let l:type = 'W' + else + let l:type = 'E' + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:line, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('erlang', { +\ 'name': 'erlc', +\ 'executable': function('ale_linters#erlang#erlc#GetExecutable'), +\ '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 new file mode 100644 index 00000000..bd984fef --- /dev/null +++ b/ale_linters/erlang/syntaxerl.vim @@ -0,0 +1,44 @@ +" Author: Dmitri Vereshchagin +" Description: SyntaxErl linter for Erlang files + +call ale#Set('erlang_syntaxerl_executable', 'syntaxerl') + +function! ale_linters#erlang#syntaxerl#Handle(buffer, lines) abort + let l:pattern = '\v\C:(\d+):( warning:)? (.+)' + let l:loclist = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:loclist, { + \ 'lnum': str2nr(l:match[1]), + \ 'text': l:match[3], + \ 'type': empty(l:match[2]) ? 'E' : 'W', + \}) + endfor + + return l:loclist +endfunction + +function! s:GetExecutable(buffer) abort + return ale#Var(a:buffer, 'erlang_syntaxerl_executable') +endfunction + +function! s:GetCommand(buffer) abort + let l:Callback = function('s:GetCommandFromHelpOutput') + + return ale#command#Run(a:buffer, '%e -h', l:Callback, { + \ 'executable': s:GetExecutable(a:buffer), + \}) +endfunction + +function! s:GetCommandFromHelpOutput(buffer, output, metadata) abort + let l:has_b_option = match(a:output, '\V\C-b, --base\>') > -1 + + return l:has_b_option ? '%e -b %s %t' : '%e %t' +endfunction + +call ale#linter#Define('erlang', { +\ 'name': 'syntaxerl', +\ 'callback': 'ale_linters#erlang#syntaxerl#Handle', +\ 'executable': function('s:GetExecutable'), +\ 'command': function('s:GetCommand'), +\}) diff --git a/ale_linters/eruby/erb.vim b/ale_linters/eruby/erb.vim new file mode 100644 index 00000000..0ca157aa --- /dev/null +++ b/ale_linters/eruby/erb.vim @@ -0,0 +1,25 @@ +" Author: Matthias Guenther - https://wikimatze.de, Eddie Lebow https://github.com/elebow +" Description: ERB from the Ruby standard library, for eruby/erb files + +function! ale_linters#eruby#erb#GetCommand(buffer) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if empty(l:rails_root) + return 'erb -P -T - -x %t | ruby -c' + endif + + " Rails-flavored eRuby does not comply with the standard as understood by + " ERB, so we'll have to do some substitution. This does not reduce the + " effectiveness of the linter—the translated code is still evaluated. + return 'ruby -r erb -e ' . ale#Escape('puts ERB.new($stdin.read.gsub(%{<%=},%{<%}), trim_mode: %{-}).src') . '< %t | ruby -c' +endfunction + +call ale#linter#Define('eruby', { +\ 'name': 'erb', +\ 'aliases': ['erubylint'], +\ 'executable': 'erb', +\ 'output_stream': 'stderr', +\ 'command': function('ale_linters#eruby#erb#GetCommand'), +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) + diff --git a/ale_linters/eruby/erblint.vim b/ale_linters/eruby/erblint.vim new file mode 100644 index 00000000..19960185 --- /dev/null +++ b/ale_linters/eruby/erblint.vim @@ -0,0 +1,51 @@ +" Author: Roeland Moors - https://github.com/roelandmoors +" based on the ale ruumba and robocop linters +" Description: ERB Lint, support for https://github.com/Shopify/erb-lint + +call ale#Set('eruby_erblint_executable', 'erblint') +call ale#Set('eruby_erblint_options', '') + +function! ale_linters#eruby#erblint#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'eruby_erblint_executable') + + return ale#ruby#EscapeExecutable(l:executable, 'erblint') + \ . ' --format json ' + \ . ale#Var(a:buffer, 'eruby_erblint_options') + \ . ' --stdin %s' +endfunction + +function! ale_linters#eruby#erblint#Handle(buffer, lines) abort + if empty(a:lines) + return [] + endif + + let l:errors = ale#util#FuzzyJSONDecode(a:lines[0], []) + + if !has_key(l:errors, 'summary') + \|| l:errors['summary']['offenses'] == 0 + \|| empty(l:errors['files']) + return [] + endif + + let l:output = [] + + for l:error in l:errors['files'][0]['offenses'] + call add(l:output, { + \ 'lnum': l:error['location']['start_line'] + 0, + \ 'col': l:error['location']['start_column'] + 0, + \ 'end_col': l:error['location']['last_column'] + 0, + \ 'code': l:error['linter'], + \ 'text': l:error['message'], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('eruby', { +\ 'name': 'erblint', +\ 'executable': {b -> ale#Var(b, 'eruby_erblint_executable')}, +\ 'command': function('ale_linters#eruby#erblint#GetCommand'), +\ 'callback': 'ale_linters#eruby#erblint#Handle', +\}) diff --git a/ale_linters/eruby/erubi.vim b/ale_linters/eruby/erubi.vim new file mode 100644 index 00000000..ddca3f61 --- /dev/null +++ b/ale_linters/eruby/erubi.vim @@ -0,0 +1,32 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: eruby checker using `erubi` + +function! ale_linters#eruby#erubi#GetCommand(buffer, output, meta) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if !empty(a:output) + " The empty command in CheckErubi returns nothing if erubi runs and + " emits an error if erubi is not present + return '' + endif + + if empty(l:rails_root) + return 'ruby -r erubi/capture_end -e ' . ale#Escape('puts Erubi::CaptureEndEngine.new($stdin.read).src') . '< %t | ruby -c' + endif + + " Rails-flavored eRuby does not comply with the standard as understood by + " Erubi, so we'll have to do some substitution. This does not reduce the + " effectiveness of the linter---the translated code is still evaluated. + return 'ruby -r erubi/capture_end -e ' . ale#Escape('puts Erubi::CaptureEndEngine.new($stdin.read.gsub(%{<%=},%{<%}), nil, %{-}).src') . '< %t | ruby -c' +endfunction + +call ale#linter#Define('eruby', { +\ 'name': 'erubi', +\ 'executable': 'ruby', +\ '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 new file mode 100644 index 00000000..755c5803 --- /dev/null +++ b/ale_linters/eruby/erubis.vim @@ -0,0 +1,23 @@ +" Author: Jake Zimmerman , Eddie Lebow https://github.com/elebow +" Description: eruby checker using `erubis`, instead of `erb` + +function! ale_linters#eruby#erubis#GetCommand(buffer) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if empty(l:rails_root) + return 'erubis -x %t | ruby -c' + endif + + " Rails-flavored eRuby does not comply with the standard as understood by + " Erubis, so we'll have to do some substitution. This does not reduce the + " effectiveness of the linter - the translated code is still evaluated. + return 'ruby -r erubis -e ' . ale#Escape('puts Erubis::Eruby.new($stdin.read.gsub(%{<%=},%{<%})).src') . '< %t | ruby -c' +endfunction + +call ale#linter#Define('eruby', { +\ 'name': 'erubis', +\ 'executable': 'erubis', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..f415f1ab --- /dev/null +++ b/ale_linters/eruby/ruumba.vim @@ -0,0 +1,62 @@ +" Author: aclemons - https://github.com/aclemons +" based on the ale rubocop linter +" Description: Ruumba, RuboCop linting for ERB templates. + +call ale#Set('eruby_ruumba_executable', 'ruumba') +call ale#Set('eruby_ruumba_options', '') + +function! ale_linters#eruby#ruumba#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'eruby_ruumba_executable') + + return ale#ruby#EscapeExecutable(l:executable, 'ruumba') + \ . ' --format json --force-exclusion ' + \ . ale#Var(a:buffer, 'eruby_ruumba_options') + \ . ' --stdin %s' +endfunction + +function! ale_linters#eruby#ruumba#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#eruby#ruumba#GetType(l:error['severity']), + \}) + endfor + + return l:output +endfunction + +function! ale_linters#eruby#ruumba#GetType(severity) abort + if a:severity is? 'convention' + \|| a:severity is? 'warning' + \|| a:severity is? 'refactor' + return 'W' + endif + + return 'E' +endfunction + +call ale#linter#Define('eruby', { +\ 'name': 'ruumba', +\ '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/fish/fish.vim b/ale_linters/fish/fish.vim new file mode 100644 index 00000000..87ede29a --- /dev/null +++ b/ale_linters/fish/fish.vim @@ -0,0 +1,67 @@ +" Author: Niraj Thapaliya - https://github.com/nthapaliya +" Description: Lints fish files using fish -n + +function! ale_linters#fish#fish#Handle(buffer, lines) abort + " Matches patterns such as: + " + " home/.config/fish/functions/foo.fish (line 1): Missing end to balance this function definition + " function foo + " ^ + " + " OR, patterns such as: + " + " Unsupported use of '||'. In fish, please use 'COMMAND; or COMMAND'. + " /tmp/vLz620o/258/test.fish (line 2): if set -q SSH_CLIENT || set -q SSH_TTY + " ^ + " + " fish -n can return errors in either format. + let l:pattern = '^\(.* (line \(\d\+\)): \)\(.*\)$' + let l:column_pattern = '^ *\^' + let l:output = [] + let l:column_offset = 0 + let l:last_line_with_message = '' + + for l:line in a:lines + " Look for error lines first. + let l:match = matchlist(l:line, l:pattern) + + if !empty(l:match) + if !empty(l:last_line_with_message) + let l:text = l:last_line_with_message + else + let l:text = l:match[3] + endif + + let l:column_offset = len(l:match[1]) + + let l:last_line_with_message = '' + call add(l:output, { + \ 'col': 0, + \ 'lnum': str2nr(l:match[2]), + \ 'text': l:text, + \}) + else + " Look for column markers like ' ^' second. + " The column index will be set according to how long the line is. + let l:column_match = matchstr(l:line, l:column_pattern) + + if !empty(l:column_match) && !empty(l:output) + let l:output[-1].col = len(l:column_match) - l:column_offset + let l:last_line_with_message = '' + else + let l:last_line_with_message = l:line + let l:column_offset = 0 + endif + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('fish', { +\ 'name': 'fish', +\ 'output_stream': 'stderr', +\ 'executable': 'fish', +\ 'command': 'fish -n %t', +\ 'callback': 'ale_linters#fish#fish#Handle', +\}) diff --git a/ale_linters/fortran/gcc.vim b/ale_linters/fortran/gcc.vim new file mode 100644 index 00000000..6e97d6fd --- /dev/null +++ b/ale_linters/fortran/gcc.vim @@ -0,0 +1,72 @@ +" Author: w0rp +" Description: gcc for Fortran files + +" This option can be set to 0 to use -ffixed-form +call ale#Set('fortran_gcc_use_free_form', 1) +call ale#Set('fortran_gcc_executable', 'gcc') +" Set this option to change the GCC options for warnings for Fortran. +call ale#Set('fortran_gcc_options', '-Wall') + +function! ale_linters#fortran#gcc#Handle(buffer, lines) abort + " We have to match a starting line and a later ending line together, + " like so. + " + " :21.34: + " Error: Expected comma in I/O list at (1) + let l:line_marker_pattern = ':\(\d\+\)[.:]\=\(\d\+\)\=:\=$' + let l:message_pattern = '^\(Error\|Warning\): \(.\+\)$' + let l:looking_for_message = 0 + let l:last_loclist_obj = {} + + let l:output = [] + + for l:line in a:lines + if l:looking_for_message + let l:match = matchlist(l:line, l:message_pattern) + else + let l:match = matchlist(l:line, l:line_marker_pattern) + endif + + if len(l:match) == 0 + continue + endif + + if l:looking_for_message + let l:looking_for_message = 0 + + " Now we have the text, we can set it and add the error. + let l:last_loclist_obj.text = l:match[2] + let l:last_loclist_obj.type = l:match[1] is# 'Warning' ? 'W' : 'E' + call add(l:output, l:last_loclist_obj) + else + let l:last_loclist_obj = { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \} + + " Start looking for the message and error type. + let l:looking_for_message = 1 + endif + endfor + + return l:output +endfunction + +function! ale_linters#fortran#gcc#GetCommand(buffer) abort + let l:layout_option = ale#Var(a:buffer, 'fortran_gcc_use_free_form') + \ ? '-ffree-form' + \ : '-ffixed-form' + + return '%e -S -x f95 -fsyntax-only ' . l:layout_option + \ . ale#Pad(ale#Var(a:buffer, 'fortran_gcc_options')) + \ . ' -' +endfunction + +call ale#linter#Define('fortran', { +\ 'name': 'gcc', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..c885b699 --- /dev/null +++ b/ale_linters/fortran/language_server.vim @@ -0,0 +1,20 @@ +" Author: unpairedbracket ben.spiers22@gmail.com +" Description: A language server for fortran + +call ale#Set('fortran_language_server_executable', 'fortls') +call ale#Set('fortran_language_server_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#fortran#language_server#GetProjectRoot(buffer) abort + let l:fortls_file = ale#path#FindNearestFile(a:buffer, '.fortls') + + return !empty(l:fortls_file) ? fnamemodify(l:fortls_file, ':h') : '' +endfunction + +call ale#linter#Define('fortran', { +\ 'name': 'language_server', +\ 'aliases': ['fortls'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'fortran_language_server_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#fortran#language_server#GetProjectRoot'), +\}) diff --git a/ale_linters/fountain/proselint.vim b/ale_linters/fountain/proselint.vim new file mode 100644 index 00000000..353a2e5f --- /dev/null +++ b/ale_linters/fountain/proselint.vim @@ -0,0 +1,9 @@ +" Author: Jansen Mitchell https://github.com/JansenMitchell +" Description: proselint for Fountain files + +call ale#linter#Define('fountain', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/fuse/fusionlint.vim b/ale_linters/fuse/fusionlint.vim new file mode 100644 index 00000000..ffb25d33 --- /dev/null +++ b/ale_linters/fuse/fusionlint.vim @@ -0,0 +1,33 @@ +" Author: RyanSquared +" Description: `fusion-lint` linter for FusionScript files + +call ale#Set('fuse_fusionlint_executable', 'fusion-lint') +call ale#Set('fuse_fusionlint_options', '') + +function! ale_linters#fuse#fusionlint#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'fuse_fusionlint_options')) + \ . ' --filename %s -i' +endfunction + +function! ale_linters#fuse#fusionlint#Handle(buffer, lines) abort + let l:pattern = '^.*:\(\d\+\):\(\d\+\): (\([WE]\)\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[4], + \ 'type': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('fuse', { +\ 'name': 'fusionlint', +\ '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 new file mode 100644 index 00000000..4b9cec63 --- /dev/null +++ b/ale_linters/gitcommit/gitlint.vim @@ -0,0 +1,51 @@ +" Author: Nick Yamane +" Description: gitlint for git commit message files + +call ale#Set('gitcommit_gitlint_executable', 'gitlint') +call ale#Set('gitcommit_gitlint_options', '') +call ale#Set('gitcommit_gitlint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#gitcommit#gitlint#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'gitcommit_gitlint', ['gitlint']) +endfunction + +function! ale_linters#gitcommit#gitlint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'gitcommit_gitlint_options') + + return '%e' . ale#Pad(l:options) . ' lint' +endfunction + +function! ale_linters#gitcommit#gitlint#Handle(buffer, lines) abort + " Matches patterns line the following: + let l:pattern = '\v^(\d+): (\w+) (.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[2] + + if !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + if l:code is# 'T2' || l:code is# 'B2' + continue + endif + endif + + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[3], + \ 'code': l:code, + \ 'type': 'E', + \} + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('gitcommit', { +\ 'name': 'gitlint', +\ 'output_stream': 'stderr', +\ '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/gleam/gleamlsp.vim b/ale_linters/gleam/gleamlsp.vim new file mode 100644 index 00000000..f32d1434 --- /dev/null +++ b/ale_linters/gleam/gleamlsp.vim @@ -0,0 +1,18 @@ +" Author: Jonathan Palardt https://github.com/jpalardy +" Description: Support for Gleam Language Server + +call ale#Set('gleam_gleamlsp_executable', 'gleam') + +function! ale_linters#gleam#gleamlsp#GetProjectRoot(buffer) abort + let l:gleam_toml = ale#path#FindNearestFile(a:buffer, 'gleam.toml') + + return !empty(l:gleam_toml) ? fnamemodify(l:gleam_toml, ':p:h') : '' +endfunction + +call ale#linter#Define('gleam', { +\ 'name': 'gleamlsp', +\ 'lsp': 'stdio', +\ 'executable': {buffer -> ale#Var(buffer, 'gleam_gleamlsp_executable')}, +\ 'command': '%e lsp', +\ 'project_root': function('ale_linters#gleam#gleamlsp#GetProjectRoot'), +\}) diff --git a/ale_linters/glimmer/embertemplatelint.vim b/ale_linters/glimmer/embertemplatelint.vim new file mode 100644 index 00000000..10b54bb4 --- /dev/null +++ b/ale_linters/glimmer/embertemplatelint.vim @@ -0,0 +1,6 @@ +" Author: Sam Saffron +" Description: Ember-template-lint for checking GJS (Glimmer JS) files + +scriptencoding utf-8 + +call ale#handlers#embertemplatelint#DefineLinter('glimmer') diff --git a/ale_linters/glsl/glslang.vim b/ale_linters/glsl/glslang.vim new file mode 100644 index 00000000..54fc0caa --- /dev/null +++ b/ale_linters/glsl/glslang.vim @@ -0,0 +1,42 @@ +" Author: Sven-Hendrik Haase +" Description: glslang-based linter for glsl files +" +" TODO: Once https://github.com/KhronosGroup/glslang/pull/1047 is accepted, +" we can use stdin. + +call ale#Set('glsl_glslang_executable', 'glslangValidator') +call ale#Set('glsl_glslang_options', '') + +function! ale_linters#glsl#glslang#GetCommand(buffer) abort + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'glsl_glslang_options')) + \ . ' -C %t' +endfunction + +function! ale_linters#glsl#glslang#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " ERROR: 0:5: 'foo' : undeclared identifier + " or when using options like -V or -G or --target-env + " ERROR: filename:5: 'foo' : undeclared identifier + let l:pattern = '^\(.\+\): \(.\+\):\(\d\+\): \(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[3]), + \ 'col' : 0, + \ 'text': l:match[4], + \ 'type': l:match[1] is# 'ERROR' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('glsl', { +\ 'name': 'glslang', +\ '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 new file mode 100644 index 00000000..b62844c7 --- /dev/null +++ b/ale_linters/glsl/glslls.vim @@ -0,0 +1,30 @@ +" Author: Sven-Hendrik Haase +" Description: A language server for glsl + +call ale#Set('glsl_glslls_executable', 'glslls') +call ale#Set('glsl_glslls_logfile', '') + +function! ale_linters#glsl#glslls#GetCommand(buffer) abort + let l:logfile = ale#Var(a:buffer, 'glsl_glslls_logfile') + let l:logfile_args = '' + + if l:logfile isnot# '' + let l:logfile_args = ' --verbose -l ' . l:logfile + endif + + return '%e' . l:logfile_args . ' --stdin' +endfunction + +function! ale_linters#glsl#glslls#GetProjectRoot(buffer) abort + let l:project_root = ale#c#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h:h') : '' +endfunction + +call ale#linter#Define('glsl', { +\ 'name': 'glslls', +\ 'lsp': 'stdio', +\ '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..1e43f8e4 --- /dev/null +++ b/ale_linters/go/bingo.vim @@ -0,0 +1,31 @@ +" Author: Jerko Steiner +" 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 ale#go#EnvString(a:buffer) . '%e' . ale#Pad(ale#Var(a:buffer, 'go_bingo_options')) +endfunction + +function! ale_linters#go#bingo#FindProjectRoot(buffer) abort + let l:go_modules_off = ale#Var(a:buffer, 'go_go111module') is# 'off' + let l:project_root = l:go_modules_off ? + \ '' : 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/cspell.vim b/ale_linters/go/cspell.vim new file mode 100644 index 00000000..f986a31a --- /dev/null +++ b/ale_linters/go/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Go files. + +call ale#handlers#cspell#DefineLinter('go') diff --git a/ale_linters/go/gobuild.vim b/ale_linters/go/gobuild.vim new file mode 100644 index 00000000..0342a230 --- /dev/null +++ b/ale_linters/go/gobuild.vim @@ -0,0 +1,52 @@ +" Author: Joshua Rubin , Ben Reedy , +" Jeff Willette +" Description: go build for Go files +" inspired by work from dzhou121 + +call ale#Set('go_go_executable', 'go') +call ale#Set('go_gobuild_options', '') + +function! ale_linters#go#gobuild#GetMatches(lines) abort + " Matches patterns like the following: + " + " file.go:27: missing argument for Printf("%s"): format reads arg 2, have only 1 args + " file.go:53:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) + " file.go:5:2: expected declaration, found 'STRING' "log" + " go test returns relative paths so use tail of filename as part of pattern matcher + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? (.+)$' + + return ale#util#GetMatches(a:lines, l:pattern) +endfunction + +function! ale_linters#go#gobuild#Handler(buffer, lines) abort + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] + + for l:match in ale_linters#go#gobuild#GetMatches(a:lines) + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('go', { +\ 'name': 'gobuild', +\ 'aliases': ['go build'], +\ 'executable': {b -> ale#Var(b, 'go_go_executable')}, +\ 'cwd': '%s:h', +\ 'command': {b -> +\ ale#go#EnvString(b) +\ . ale#Escape(ale#Var(b, 'go_go_executable')) . ' test' +\ . ale#Pad(ale#Var(b, 'go_gobuild_options')) +\ . ' -c -o /dev/null ./' +\ }, +\ 'output_stream': 'stderr', +\ 'callback': 'ale_linters#go#gobuild#Handler', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/go/gofmt.vim b/ale_linters/go/gofmt.vim new file mode 100644 index 00000000..b313f9ca --- /dev/null +++ b/ale_linters/go/gofmt.vim @@ -0,0 +1,15 @@ +" Author: neersighted +" Description: gofmt for Go files + +function! ale_linters#go#gofmt#GetCommand(buffer) abort + return ale#go#EnvString(a:buffer) + \ . '%e -e %t' +endfunction + +call ale#linter#Define('go', { +\ 'name': 'gofmt', +\ 'output_stream': 'stderr', +\ 'executable': 'gofmt', +\ 'command': function('ale_linters#go#gofmt#GetCommand'), +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) diff --git a/ale_linters/go/golangci_lint.vim b/ale_linters/go/golangci_lint.vim new file mode 100644 index 00000000..d65f8e91 --- /dev/null +++ b/ale_linters/go/golangci_lint.vim @@ -0,0 +1,83 @@ +" Author: Sascha Grunert +" Description: Adds support of golangci-lint + +call ale#Set('go_golangci_lint_options', '') +call ale#Set('go_golangci_lint_executable', 'golangci-lint') +call ale#Set('go_golangci_lint_package', 1) + +function! ale_linters#go#golangci_lint#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'go_golangci_lint_executable') + + return l:executable +endfunction + +function! ale_linters#go#golangci_lint#GetCommand(buffer, version) abort + let l:filename = expand('#' . a:buffer . ':t') + let l:options = ale#Var(a:buffer, 'go_golangci_lint_options') + let l:lint_package = ale#Var(a:buffer, 'go_golangci_lint_package') + + if ale#semver#GTE(a:version, [2, 0, 0]) + let l:options = l:options + \ . ' --output.json.path stdout' + \ . ' --output.text.path stderr' + \ . ' --show-stats=0' + else + let l:options = l:options + \ . ' --out-format=json' + \ . ' --show-stats=0' + endif + + if l:lint_package + return ale#go#EnvString(a:buffer) + \ . '%e run ' + \ . l:options + endif + + return ale#go#EnvString(a:buffer) + \ . '%e run ' + \ . ale#Escape(l:filename) + \ . ' ' . l:options +endfunction + +function! ale_linters#go#golangci_lint#Handler(buffer, lines) abort + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] + + let l:matches = ale#util#FuzzyJSONDecode(a:lines, []) + + if empty(l:matches) + return [] + endif + + for l:match in l:matches['Issues'] + if l:match['FromLinter'] is# 'typecheck' + let l:msg_type = 'E' + else + let l:msg_type = 'W' + endif + + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, fnamemodify(l:match['Pos']['Filename'], ':t')), + \ 'lnum': l:match['Pos']['Line'] + 0, + \ 'col': l:match['Pos']['Column'] + 0, + \ 'type': l:msg_type, + \ 'text': match['FromLinter'] . ' - ' . l:match['Text'], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('go', { +\ 'name': 'golangci-lint', +\ 'executable': function('ale_linters#go#golangci_lint#GetExecutable'), +\ 'cwd': '%s:h', +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#go#golangci_lint#GetExecutable(buffer), +\ '%e --version', +\ function('ale_linters#go#golangci_lint#GetCommand'), +\ )}, +\ 'callback': 'ale_linters#go#golangci_lint#Handler', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/go/gopls.vim b/ale_linters/go/gopls.vim new file mode 100644 index 00000000..80909830 --- /dev/null +++ b/ale_linters/go/gopls.vim @@ -0,0 +1,39 @@ +" Author: w0rp +" Author: Jerko Steiner +" Description: https://github.com/saibing/gopls + +call ale#Set('go_gopls_executable', 'gopls') +call ale#Set('go_gopls_options', '--mode stdio') +call ale#Set('go_gopls_init_options', {}) +call ale#Set('go_gopls_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#go#gopls#GetCommand(buffer) abort + return ale#go#EnvString(a:buffer) + \ . '%e' + \ . ale#Pad(ale#Var(a:buffer, 'go_gopls_options')) +endfunction + +function! ale_linters#go#gopls#FindProjectRoot(buffer) abort + let l:go_modules_off = ale#Var(a:buffer, 'go_go111module') is# 'off' + let l:project_root = l:go_modules_off ? + \ '' : 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#path#FindExecutable(b, 'go_gopls', [ +\ ale#go#GetGoPathExecutable('bin/gopls'), +\ ])}, +\ 'command': function('ale_linters#go#gopls#GetCommand'), +\ 'project_root': function('ale_linters#go#gopls#FindProjectRoot'), +\ 'initialization_options': {b -> ale#Var(b, 'go_gopls_init_options')}, +\}) diff --git a/ale_linters/go/gosimple.vim b/ale_linters/go/gosimple.vim new file mode 100644 index 00000000..490d15a9 --- /dev/null +++ b/ale_linters/go/gosimple.vim @@ -0,0 +1,12 @@ +" Author: Ben Reedy +" Description: gosimple for Go files + +call ale#linter#Define('go', { +\ 'name': 'gosimple', +\ 'executable': 'gosimple', +\ 'cwd': '%s:h', +\ 'command': {b -> ale#go#EnvString(b) . 'gosimple .'}, +\ '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 new file mode 100644 index 00000000..8fd6df27 --- /dev/null +++ b/ale_linters/go/gotype.vim @@ -0,0 +1,24 @@ +" Author: Jelte Fennema +" Description: gotype for Go files + +function! ale_linters#go#gotype#GetExecutable(buffer) abort + if expand('#' . a:buffer . ':p') =~# '_test\.go$' + return '' + endif + + return 'gotype' +endfunction + +function! ale_linters#go#gotype#GetCommand(buffer) abort + return ale#go#EnvString(a:buffer) . 'gotype -e .' +endfunction + +call ale#linter#Define('go', { +\ 'name': 'gotype', +\ 'output_stream': 'stderr', +\ 'executable': function('ale_linters#go#gotype#GetExecutable'), +\ 'cwd': '%s:h', +\ '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 new file mode 100644 index 00000000..414c8b11 --- /dev/null +++ b/ale_linters/go/govet.vim @@ -0,0 +1,21 @@ +" Author: neersighted , John Eikenberry +" Description: go vet for Go files + +call ale#Set('go_go_executable', 'go') +call ale#Set('go_govet_options', '') + +call ale#linter#Define('go', { +\ 'name': 'govet', +\ 'aliases': ['go vet'], +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'go_go_executable')}, +\ 'cwd': '%s:h', +\ 'command': {b -> +\ ale#go#EnvString(b) +\ . '%e vet' +\ . ale#Pad(ale#Var(b, 'go_govet_options')) +\ . ' .' +\ }, +\ 'callback': 'ale#handlers#go#Handler', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/go/langserver.vim b/ale_linters/go/langserver.vim new file mode 100644 index 00000000..7130db40 --- /dev/null +++ b/ale_linters/go/langserver.vim @@ -0,0 +1,29 @@ +" Author: Horacio Sanson +" Description: Support for go-langserver https://github.com/sourcegraph/go-langserver + +call ale#Set('go_langserver_executable', 'go-langserver') +call ale#Set('go_langserver_options', '') + +function! ale_linters#go#langserver#GetCommand(buffer) abort + let l:executable = [ale#Escape(ale#Var(a:buffer, 'go_langserver_executable'))] + let l:options = ale#Var(a:buffer, 'go_langserver_options') + 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') + call add(l:options, '-gocodecompletion') + endif + + let l:options = uniq(sort(l:options)) + let l:env = ale#go#EnvString(a:buffer) + + return l:env . join(extend(l:executable, l:options), ' ') +endfunction + +call ale#linter#Define('go', { +\ 'name': 'golangserver', +\ 'lsp': 'stdio', +\ '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/revive.vim b/ale_linters/go/revive.vim new file mode 100644 index 00000000..b14b5ab9 --- /dev/null +++ b/ale_linters/go/revive.vim @@ -0,0 +1,21 @@ +" Author: Penghui Liao +" Description: Adds support for revive + +call ale#Set('go_revive_executable', 'revive') +call ale#Set('go_revive_options', '') + +function! ale_linters#go#revive#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'go_revive_options') + + return ale#go#EnvString(a:buffer) . '%e' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + +call ale#linter#Define('go', { +\ 'name': 'revive', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#Var(b, 'go_revive_executable')}, +\ 'command': function('ale_linters#go#revive#GetCommand'), +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/go/staticcheck.vim b/ale_linters/go/staticcheck.vim new file mode 100644 index 00000000..36622440 --- /dev/null +++ b/ale_linters/go/staticcheck.vim @@ -0,0 +1,34 @@ +" Author: Ben Reedy +" Description: staticcheck for Go files + +call ale#Set('go_staticcheck_executable', 'staticcheck') +call ale#Set('go_staticcheck_options', '') +call ale#Set('go_staticcheck_lint_package', 1) +call ale#Set('go_staticcheck_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#go#staticcheck#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'go_staticcheck_options') + let l:lint_package = ale#Var(a:buffer, 'go_staticcheck_lint_package') + let l:env = ale#go#EnvString(a:buffer) + + if l:lint_package + return l:env . '%e' + \ . (!empty(l:options) ? ' ' . l:options : '') . ' .' + endif + + return l:env . '%e' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %s:t' +endfunction + +call ale#linter#Define('go', { +\ 'name': 'staticcheck', +\ 'executable': {b -> ale#path#FindExecutable(b, 'go_staticcheck', [ +\ ale#go#GetGoPathExecutable('bin/staticcheck'), +\ ])}, +\ 'cwd': '%s:h', +\ 'command': function('ale_linters#go#staticcheck#GetCommand'), +\ 'callback': 'ale#handlers#go#Handler', +\ 'output_stream': 'both', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/gohtmltmpl/djlint.vim b/ale_linters/gohtmltmpl/djlint.vim new file mode 100644 index 00000000..79f1f8f4 --- /dev/null +++ b/ale_linters/gohtmltmpl/djlint.vim @@ -0,0 +1,12 @@ +" Author: Adrian Vollmer +" Description: djlint for Django HTML template files + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_options', '') + +call ale#linter#Define('gohtmltmpl', { +\ 'name': 'djlint', +\ 'executable': function('ale#handlers#djlint#GetExecutable'), +\ 'command': function('ale#handlers#djlint#GetCommand'), +\ 'callback': 'ale#handlers#djlint#Handle', +\}) diff --git a/ale_linters/graphql/eslint.vim b/ale_linters/graphql/eslint.vim new file mode 100644 index 00000000..a98233e9 --- /dev/null +++ b/ale_linters/graphql/eslint.vim @@ -0,0 +1,10 @@ +" Author: Benjie Gillam +" Description: eslint for GraphQL files + +call ale#linter#Define('graphql', { +\ 'name': 'eslint', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\}) diff --git a/ale_linters/graphql/gqlint.vim b/ale_linters/graphql/gqlint.vim new file mode 100644 index 00000000..6f1ca54a --- /dev/null +++ b/ale_linters/graphql/gqlint.vim @@ -0,0 +1,10 @@ +" Author: Michiel Westerbeek +" Description: Linter for GraphQL Schemas + +call ale#linter#Define('graphql', { +\ 'name': 'gqlint', +\ 'executable': 'gqlint', +\ 'cwd': '%s:h', +\ 'command': 'gqlint --reporter=simple %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/groovy/npmgroovylint.vim b/ale_linters/groovy/npmgroovylint.vim new file mode 100644 index 00000000..4141bd25 --- /dev/null +++ b/ale_linters/groovy/npmgroovylint.vim @@ -0,0 +1,46 @@ +" Author: lucas-str +" Description: Integration of npm-groovy-lint for Groovy files. + +call ale#Set('groovy_npmgroovylint_executable', 'npm-groovy-lint') +call ale#Set('groovy_npmgroovylint_options', '--loglevel warning') + +function! ale_linters#groovy#npmgroovylint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'groovy_npmgroovylint_options') + + return '%e --failon none --output json' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + +function! ale_linters#groovy#npmgroovylint#Handle(buffer, lines) abort + let l:output = [] + let l:json = ale#util#FuzzyJSONDecode(a:lines, {}) + + for [l:filename, l:file] in items(get(l:json, 'files', {})) + for l:error in get(l:file, 'errors', []) + let l:output_line = { + \ 'filename': l:filename, + \ 'lnum': l:error.line, + \ 'text': l:error.msg, + \ 'type': toupper(l:error.severity[0]), + \} + + if has_key(l:error, 'range') + let l:output_line.col = l:error.range.start.character + let l:output_line.end_col = l:error.range.end.character + let l:output_line.end_lnum = l:error.range.end.line + endif + + call add(l:output, l:output_line) + endfor + endfor + + return l:output +endfunction + +call ale#linter#Define('groovy', { +\ 'name': 'npm-groovy-lint', +\ 'executable': {b -> ale#Var(b, 'groovy_npmgroovylint_executable')}, +\ 'command': function('ale_linters#groovy#npmgroovylint#GetCommand'), +\ 'callback': 'ale_linters#groovy#npmgroovylint#Handle', +\}) diff --git a/ale_linters/hack/hack.vim b/ale_linters/hack/hack.vim new file mode 100644 index 00000000..822b5c87 --- /dev/null +++ b/ale_linters/hack/hack.vim @@ -0,0 +1,22 @@ +" Author: Fred Emmott +" Description: Hack support via `hack lsp` + +call ale#Set('hack_hack_executable', 'hh_client') + +function! ale_linters#hack#hack#GetProjectRoot(buffer) abort + let l:hhconfig = ale#path#FindNearestFile(a:buffer, '.hhconfig') + + return !empty(l:hhconfig) ? fnamemodify(l:hhconfig, ':h') : '' +endfunction + +function! ale_linters#hack#hack#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'hack_hack_executable') +endfunction + +call ale#linter#Define('hack', { +\ 'name': 'hack', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#hack#hack#GetExecutable'), +\ 'command': '%e lsp --from vim-ale', +\ 'project_root': function('ale_linters#hack#hack#GetProjectRoot'), +\}) diff --git a/ale_linters/hack/hhast.vim b/ale_linters/hack/hhast.vim new file mode 100644 index 00000000..5e6d4dec --- /dev/null +++ b/ale_linters/hack/hhast.vim @@ -0,0 +1,40 @@ +" Author: Fred Emmott +" Description: Hack support via `hhast lsp` + +call ale#Set('hack_hhast_executable', 'vendor/bin/hhast-lint') + +function! ale_linters#hack#hhast#GetProjectRoot(buffer) abort + " Find the hack root, then figure out if it's also an HHAST root. + " Don't try to use lint configurations from vendor/foo/bar/hhast-lint.json + let l:hhconfig = ale#path#FindNearestFile(a:buffer, '.hhconfig') + + if empty(l:hhconfig) + return '' + endif + + let l:root = fnamemodify(l:hhconfig, ':h') + let l:hhast_config = findfile('hhast-lint.json', l:root) + + return !empty(l:hhast_config) ? l:root : '' +endfunction + +function! ale_linters#hack#hhast#GetExecutable(buffer) abort + let l:root = ale_linters#hack#hhast#GetProjectRoot(a:buffer) + let l:relative = ale#Var(a:buffer, 'hack_hhast_executable') + let l:absolute = findfile(l:relative, l:root) + + return !empty(l:absolute) ? l:absolute : '' +endfunction + +function! ale_linters#hack#hhast#GetInitializationOptions(buffer) abort + return {'lintMode': 'open-files'} +endfunction + +call ale#linter#Define('hack', { +\ 'name': 'hhast', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#hack#hhast#GetExecutable'), +\ 'command': '%e --mode lsp --from vim-ale', +\ '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 new file mode 100644 index 00000000..9fcd999f --- /dev/null +++ b/ale_linters/haml/hamllint.vim @@ -0,0 +1,57 @@ +" Author: Patrick Lewis - https://github.com/patricklewis, thenoseman - https://github.com/thenoseman +" Description: haml-lint for Haml files + +call ale#Set('haml_hamllint_executable', 'haml-lint') + +function! ale_linters#haml#hamllint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'haml_hamllint_executable') +endfunction + +function! ale_linters#haml#hamllint#GetCommand(buffer) abort + let l:prefix = '' + + let l:rubocop_config_file_path = ale#path#FindNearestFile(a:buffer, '.rubocop.yml') + let l:hamllint_config_file_path = ale#path#FindNearestFile(a:buffer, '.haml-lint.yml') + + " Set HAML_LINT_RUBOCOP_CONF variable as it is needed for haml-lint to + " pick up the rubocop config. + " + " 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 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) + endif + endif + + return (!empty(l:prefix) ? l:prefix . ' ' : '') + \ . ale_linters#haml#hamllint#GetExecutable(a:buffer) + \ . (!empty(l:hamllint_config_file_path) ? ' --config ' . ale#Escape(l:hamllint_config_file_path) : '') + \ . ' %t' +endfunction + +function! ale_linters#haml#hamllint#Handle(buffer, lines) abort + " Matches patterns like the following: + " :51 [W] RuboCop: Use the new Ruby 1.9 hash syntax. + let l:pattern = '\v^.*:(\d+) \[([EW])\] (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'type': l:match[2], + \ 'text': l:match[3] + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('haml', { +\ 'name': 'hamllint', +\ '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/djlint.vim b/ale_linters/handlebars/djlint.vim new file mode 100644 index 00000000..b192901f --- /dev/null +++ b/ale_linters/handlebars/djlint.vim @@ -0,0 +1,12 @@ +" Author: Adrian Vollmer +" Description: djlint for Django HTML template files + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_options', '') + +call ale#linter#Define('handlebars', { +\ 'name': 'djlint', +\ 'executable': function('ale#handlers#djlint#GetExecutable'), +\ 'command': function('ale#handlers#djlint#GetCommand'), +\ 'callback': 'ale#handlers#djlint#Handle', +\}) diff --git a/ale_linters/handlebars/embertemplatelint.vim b/ale_linters/handlebars/embertemplatelint.vim new file mode 100644 index 00000000..62b5db03 --- /dev/null +++ b/ale_linters/handlebars/embertemplatelint.vim @@ -0,0 +1,6 @@ +" Author: Adrian Zalewski +" Description: Ember-template-lint for checking Handlebars files + +scriptencoding utf-8 + +call ale#handlers#embertemplatelint#DefineLinter('handlebars') diff --git a/ale_linters/haskell/cabal_ghc.vim b/ale_linters/haskell/cabal_ghc.vim new file mode 100644 index 00000000..1bb31ebb --- /dev/null +++ b/ale_linters/haskell/cabal_ghc.vim @@ -0,0 +1,20 @@ +" Author: Eric Wolf +" Description: ghc for Haskell files called with cabal exec + +call ale#Set('haskell_cabal_ghc_options', '-fno-code -v0') + +function! ale_linters#haskell#cabal_ghc#GetCommand(buffer) abort + return 'cabal exec -- ghc ' + \ . ale#Var(a:buffer, 'haskell_cabal_ghc_options') + \ . ' %t' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'cabal_ghc', +\ 'aliases': ['cabal-ghc'], +\ 'output_stream': 'stderr', +\ 'executable': 'cabal', +\ 'cwd': '%s:h', +\ 'command': function('ale_linters#haskell#cabal_ghc#GetCommand'), +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) diff --git a/ale_linters/haskell/cspell.vim b/ale_linters/haskell/cspell.vim new file mode 100644 index 00000000..b0971a9e --- /dev/null +++ b/ale_linters/haskell/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Haskell files. + +call ale#handlers#cspell#DefineLinter('haskell') diff --git a/ale_linters/haskell/ghc.vim b/ale_linters/haskell/ghc.vim new file mode 100644 index 00000000..9c3906b2 --- /dev/null +++ b/ale_linters/haskell/ghc.vim @@ -0,0 +1,18 @@ +" Author: w0rp +" Description: ghc for Haskell files + +call ale#Set('haskell_ghc_options', '-fno-code -v0') + +function! ale_linters#haskell#ghc#GetCommand(buffer) abort + return 'ghc ' + \ . ale#Var(a:buffer, 'haskell_ghc_options') + \ . ' %t' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'ghc', +\ 'output_stream': 'stderr', +\ 'executable': 'ghc', +\ '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 new file mode 100644 index 00000000..30e96b40 --- /dev/null +++ b/ale_linters/haskell/ghc_mod.vim @@ -0,0 +1,19 @@ +" Author: wizzup +" Description: ghc-mod for Haskell files + +call ale#Set('haskell_ghc_mod_executable', 'ghc-mod') + +function! ale_linters#haskell#ghc_mod#GetCommand (buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_ghc_mod_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'ghc-mod') + \ . ' --map-file %s=%t check %s' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'ghc_mod', +\ 'aliases': ['ghc-mod'], +\ '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 new file mode 100644 index 00000000..3e55e4f0 --- /dev/null +++ b/ale_linters/haskell/hdevtools.vim @@ -0,0 +1,20 @@ +" Author: rob-b, Takano Akio +" Description: hdevtools for Haskell files + +call ale#Set('haskell_hdevtools_executable', 'hdevtools') +call ale#Set('haskell_hdevtools_options', get(g:, 'hdevtools_options', '-g -Wall')) + +function! ale_linters#haskell#hdevtools#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_hdevtools_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hdevtools') + \ . ' check' . ale#Pad(ale#Var(a:buffer, 'haskell_hdevtools_options')) + \ . ' -p %s %t' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'hdevtools', +\ '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 new file mode 100644 index 00000000..c4b5f1df --- /dev/null +++ b/ale_linters/haskell/hie.vim @@ -0,0 +1,41 @@ +" Author: Luxed +" Description: A language server for Haskell + +call ale#Set('haskell_hie_executable', 'hie') + +function! ale_linters#haskell#hie#GetProjectRoot(buffer) abort + " Search for the stack file first + let l:project_file = ale#path#FindNearestFile(a:buffer, 'stack.yaml') + + " If it's empty, search for the cabal file + if empty(l:project_file) + " 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 + + " 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 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' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'hie', +\ 'lsp': 'stdio', +\ '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 new file mode 100644 index 00000000..1425251a --- /dev/null +++ b/ale_linters/haskell/hlint.vim @@ -0,0 +1,46 @@ +" Author: jparoz +" Description: hlint for Haskell files + +call ale#Set('haskell_hlint_executable', 'hlint') +call ale#Set('haskell_hlint_options', get(g:, 'hlint_options', '')) + +function! ale_linters#haskell#hlint#Handle(buffer, lines) abort + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + if l:error.severity is# 'Error' + let l:type = 'E' + elseif l:error.severity is# 'Suggestion' + let l:type = 'I' + else + let l:type = 'W' + endif + + call add(l:output, { + \ 'lnum': str2nr(l:error.startLine), + \ 'col': str2nr(l:error.startColumn), + \ 'end_lnum': str2nr(l:error.endLine), + \ 'end_col': str2nr(l:error.endColumn), + \ 'text': l:error.severity . ': ' . l:error.hint . '. Found: ' . l:error.from . ' Why not: ' . l:error.to, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +function! ale_linters#haskell#hlint#GetCommand(buffer) abort + let l:hlintopts = '--color=never --json' + + return ale#handlers#hlint#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'haskell_hlint_options') + \ . ' ' . l:hlintopts + \ . ' -' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'hlint', +\ '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/hls.vim b/ale_linters/haskell/hls.vim new file mode 100644 index 00000000..7f7f42e1 --- /dev/null +++ b/ale_linters/haskell/hls.vim @@ -0,0 +1,66 @@ +" Author: Yen3 +" Description: A language server for haskell +" The file is based on hie.vim (author: Luxed +" ). It search more project root files. +" +call ale#Set('haskell_hls_executable', 'haskell-language-server-wrapper') +call ale#Set('haskell_hls_config', {}) + +function! ale_linters#haskell#hls#FindRootFile(buffer) abort + let l:serach_root_files = [ + \ 'stack.yaml', + \ 'cabal.project', + \ 'package.yaml', + \ 'hie.yaml' + \ ] + + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + for l:root_file in l:serach_root_files + if filereadable(l:path . '/' . l:root_file) + " Add on / so fnamemodify(..., ':h') below keeps the path. + return l:path . '/' + endif + endfor + endfor + + return '' +endfunction + +function! ale_linters#haskell#hls#GetProjectRoot(buffer) abort + " Search for the project file first + let l:project_file = ale_linters#haskell#hls#FindRootFile(a:buffer) + + " If it's empty, search for the cabal file + if empty(l:project_file) + " 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 + + " 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 fnamemodify(l:project_file, ':h') +endfunction + +function! ale_linters#haskell#hls#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_hls_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, + \ 'haskell-language-server-wrapper') + \ . ' --lsp' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'hls', +\ 'lsp': 'stdio', +\ 'command': function('ale_linters#haskell#hls#GetCommand'), +\ 'executable': {b -> ale#Var(b, 'haskell_hls_executable')}, +\ 'project_root': function('ale_linters#haskell#hls#GetProjectRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'haskell_hls_config')}, +\}) diff --git a/ale_linters/haskell/stack_build.vim b/ale_linters/haskell/stack_build.vim new file mode 100644 index 00000000..8f2d9fd9 --- /dev/null +++ b/ale_linters/haskell/stack_build.vim @@ -0,0 +1,23 @@ +" Author: Jake Zimmerman +" Description: Like stack-ghc, but for entire projects +" +" Note: Ideally, this would *only* typecheck. Right now, it also does codegen. +" See . + +call ale#Set('haskell_stack_build_options', '--fast') + +function! ale_linters#haskell#stack_build#GetCommand(buffer) abort + let l:flags = ale#Var(a:buffer, 'haskell_stack_build_options') + + return 'stack build ' . l:flags +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'stack_build', +\ 'aliases': ['stack-build'], +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..51ecc744 --- /dev/null +++ b/ale_linters/haskell/stack_ghc.vim @@ -0,0 +1,21 @@ +" Author: w0rp +" 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#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': function('ale#handlers#haskell#GetStackExecutable'), +\ 'cwd': '%s:h', +\ '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 new file mode 100644 index 00000000..9be00a82 --- /dev/null +++ b/ale_linters/help/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for help files + +call ale#handlers#alex#DefineLinter('help', '--text') diff --git a/ale_linters/help/cspell.vim b/ale_linters/help/cspell.vim new file mode 100644 index 00000000..92eb9501 --- /dev/null +++ b/ale_linters/help/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for help files. + +call ale#handlers#cspell#DefineLinter('help') diff --git a/ale_linters/help/proselint.vim b/ale_linters/help/proselint.vim new file mode 100644 index 00000000..62124502 --- /dev/null +++ b/ale_linters/help/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for Vim help files + +call ale#linter#Define('help', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/help/writegood.vim b/ale_linters/help/writegood.vim new file mode 100644 index 00000000..eeb21a73 --- /dev/null +++ b/ale_linters/help/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for vim Help files + +call ale#handlers#writegood#DefineLinter('help') diff --git a/ale_linters/html/alex.vim b/ale_linters/html/alex.vim new file mode 100644 index 00000000..97756753 --- /dev/null +++ b/ale_linters/html/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for HTML files + +call ale#handlers#alex#DefineLinter('html', '--html') diff --git a/ale_linters/html/angular.vim b/ale_linters/html/angular.vim new file mode 100644 index 00000000..05caf59d --- /dev/null +++ b/ale_linters/html/angular.vim @@ -0,0 +1,56 @@ +" Author: w0rp +" Description: tsserver integration for ALE + +call ale#Set('html_angular_executable', 'ngserver') +call ale#Set('html_angular_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#html#angular#GetProjectRoot(buffer) abort + return ale#path#Dirname( + \ ale#path#FindNearestDirectory(a:buffer, 'node_modules') + \) +endfunction + +function! ale_linters#html#angular#GetExecutable(buffer) abort + return 'node' +endfunction + +function! ale_linters#html#angular#GetCommand(buffer) abort + let l:language_service_dir = ale#path#Simplify( + \ ale#path#FindNearestDirectory( + \ a:buffer, + \ 'node_modules/@angular/language-service' + \ ) + \) + + if empty(l:language_service_dir) + return '' + endif + + let l:language_service_dir = fnamemodify(l:language_service_dir, ':h') + let l:typescript_dir = ale#path#Simplify( + \ fnamemodify(l:language_service_dir, ':h:h') + \ . '/typescript' + \) + let l:script = ale#path#FindExecutable(a:buffer, 'html_angular', [ + \ 'node_modules/@angular/language-server/bin/ngserver', + \ 'node_modules/@angular/language-server/index.js', + \]) + + if !filereadable(l:script) + return '' + endif + + return ale#Escape('node') . ' ' . ale#Escape(l:script) + \ . ' --ngProbeLocations ' . ale#Escape(l:language_service_dir) + \ . ' --tsProbeLocations ' . ale#Escape(l:typescript_dir) + \ . ' --stdio' +endfunction + +call ale#linter#Define('html', { +\ 'name': 'angular', +\ 'aliases': ['angular-language-server', 'angularls'], +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#html#angular#GetExecutable'), +\ 'command': function('ale_linters#html#angular#GetCommand'), +\ 'project_root': function('ale_linters#html#angular#GetProjectRoot'), +\}) diff --git a/ale_linters/html/cspell.vim b/ale_linters/html/cspell.vim new file mode 100644 index 00000000..743350ea --- /dev/null +++ b/ale_linters/html/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for HTML files. + +call ale#handlers#cspell#DefineLinter('html') diff --git a/ale_linters/html/djlint.vim b/ale_linters/html/djlint.vim new file mode 100644 index 00000000..df75fb28 --- /dev/null +++ b/ale_linters/html/djlint.vim @@ -0,0 +1,14 @@ +" Author: Vivian De Smedt +" Description: Adds support for djlint + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_options', '') + +call ale#linter#Define('html', { +\ 'name': 'djlint', +\ 'executable': function('ale#handlers#djlint#GetExecutable'), +\ 'command': function('ale#handlers#djlint#GetCommand'), +\ 'callback': 'ale#handlers#djlint#Handle', +\}) + +" vim:ts=4:sw=4:et: diff --git a/ale_linters/html/eslint.vim b/ale_linters/html/eslint.vim new file mode 100644 index 00000000..699f5301 --- /dev/null +++ b/ale_linters/html/eslint.vim @@ -0,0 +1,12 @@ +" Author: Victor Ananyev +" Description: eslint for js snippets in HTML files + + +call ale#linter#Define('html', { +\ 'name': 'eslint', +\ 'output_stream': 'both', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\ }) 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 +" 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 new file mode 100644 index 00000000..25bf5137 --- /dev/null +++ b/ale_linters/html/htmlhint.vim @@ -0,0 +1,32 @@ +" Author: KabbAmine , deathmaz <00maz1987@gmail.com>, diartyz +" Description: HTMLHint for checking html files + +call ale#Set('html_htmlhint_options', '') +call ale#Set('html_htmlhint_executable', 'htmlhint') +call ale#Set('html_htmlhint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#html#htmlhint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'html_htmlhint_options') + let l:config = l:options !~# '--config' + \ ? ale#path#FindNearestFile(a:buffer, '.htmlhintrc') + \ : '' + + if !empty(l:config) + let l:options .= ' --config ' . ale#Escape(l:config) + endif + + if !empty(l:options) + let l:options = substitute(l:options, '--format=unix', '', '') + endif + + return '%e' . ale#Pad(l:options) . ' --format=unix %t' +endfunction + +call ale#linter#Define('html', { +\ 'name': 'htmlhint', +\ 'executable': {b -> ale#path#FindExecutable(b, 'html_htmlhint', [ +\ 'node_modules/.bin/htmlhint', +\ ])}, +\ 'command': function('ale_linters#html#htmlhint#GetCommand'), +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) diff --git a/ale_linters/html/proselint.vim b/ale_linters/html/proselint.vim new file mode 100644 index 00000000..9fd7d671 --- /dev/null +++ b/ale_linters/html/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for HTML files + +call ale#linter#Define('html', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/html/stylelint.vim b/ale_linters/html/stylelint.vim new file mode 100644 index 00000000..c191c468 --- /dev/null +++ b/ale_linters/html/stylelint.vim @@ -0,0 +1,28 @@ +" Author: Filipe Kiss http://github.com/filipekiss + +call ale#Set('html_stylelint_executable', 'stylelint') +call ale#Set('html_stylelint_options', '') +call ale#Set('html_stylelint_use_global', 0) + +function! ale_linters#html#stylelint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'html_stylelint', [ + \ 'node_modules/.bin/stylelint', + \]) +endfunction + +function! ale_linters#html#stylelint#GetCommand(buffer) abort + let l:executable = ale_linters#html#stylelint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'html_stylelint_options') + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('html', { +\ 'name': 'stylelint', +\ 'output_stream': 'both', +\ '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 new file mode 100644 index 00000000..1e476d40 --- /dev/null +++ b/ale_linters/html/tidy.vim @@ -0,0 +1,70 @@ +" Author: KabbAmine +" Description: This file adds support for checking HTML code with tidy. + +let g:ale_html_tidy_executable = get(g:, 'ale_html_tidy_executable', 'tidy') +let g:ale_html_tidy_options = get(g:, 'ale_html_tidy_options', '-q -e -language en') + +function! ale_linters#html#tidy#GetCommand(buffer) abort + " Specify file encoding in options + " (Idea taken from https://github.com/scrooloose/syntastic/blob/master/syntax_checkers/html/tidy.vim) + let l:file_encoding = get({ + \ 'ascii': '-ascii', + \ 'big5': '-big5', + \ 'cp1252': '-win1252', + \ 'cp850': '-ibm858', + \ 'cp932': '-shiftjis', + \ 'iso-2022-jp': '-iso-2022', + \ 'latin1': '-latin1', + \ 'macroman': '-mac', + \ 'sjis': '-shiftjis', + \ 'utf-16le': '-utf16le', + \ 'utf-16': '-utf16', + \ 'utf-8': '-utf8', + \ }, &fileencoding, '-utf8') + + " On macOS, old tidy (released on 31 Oct 2006) is installed. It does not + " consider HTML5 so we should avoid it. + let l:executable = ale#Var(a:buffer, 'html_tidy_executable') + + if has('mac') && l:executable is# 'tidy' && exists('*exepath') + \ && exepath(l:executable) is# '/usr/bin/tidy' + return '' + endif + + return printf('%s %s %s -', + \ l:executable, + \ ale#Var(a:buffer, 'html_tidy_options'), + \ l:file_encoding + \) +endfunction + +function! ale_linters#html#tidy#Handle(buffer, lines) abort + " Matches patterns lines like the following: + " line 7 column 5 - Warning: missing before + let l:pattern = '^line \(\d\+\) column \(\d\+\) - \(Warning\|Error\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[1] + 0 + let l:col = l:match[2] + 0 + let l:type = l:match[3] is# 'Error' ? 'E' : 'W' + let l:text = l:match[4] + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:col, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('html', { +\ 'name': 'tidy', +\ 'executable': {b -> ale#Var(b, 'html_tidy_executable')}, +\ 'output_stream': 'stderr', +\ 'command': function('ale_linters#html#tidy#GetCommand'), +\ 'callback': 'ale_linters#html#tidy#Handle', +\ }) diff --git a/ale_linters/html/vscodehtml.vim b/ale_linters/html/vscodehtml.vim new file mode 100644 index 00000000..46814a0b --- /dev/null +++ b/ale_linters/html/vscodehtml.vim @@ -0,0 +1,16 @@ +" Author: Dalius Dobravolskas +" Description: VSCode html language server + +function! ale_linters#html#vscodehtml#GetProjectRoot(buffer) abort + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' +endfunction + +call ale#linter#Define('html', { +\ 'name': 'vscodehtml', +\ 'lsp': 'stdio', +\ 'executable': 'vscode-html-language-server', +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#html#vscodehtml#GetProjectRoot'), +\}) diff --git a/ale_linters/html/writegood.vim b/ale_linters/html/writegood.vim new file mode 100644 index 00000000..6a2bd8e5 --- /dev/null +++ b/ale_linters/html/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for html files + +call ale#handlers#writegood#DefineLinter('html') diff --git a/ale_linters/htmlangular/djlint.vim b/ale_linters/htmlangular/djlint.vim new file mode 100644 index 00000000..b353d741 --- /dev/null +++ b/ale_linters/htmlangular/djlint.vim @@ -0,0 +1,12 @@ +" Author: Adrian Vollmer +" Description: djlint for Django HTML template files + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_options', '') + +call ale#linter#Define('htmlangular', { +\ 'name': 'djlint', +\ 'executable': function('ale#handlers#djlint#GetExecutable'), +\ 'command': function('ale#handlers#djlint#GetCommand'), +\ 'callback': 'ale#handlers#djlint#Handle', +\}) diff --git a/ale_linters/htmldjango/djlint.vim b/ale_linters/htmldjango/djlint.vim new file mode 100644 index 00000000..9327979f --- /dev/null +++ b/ale_linters/htmldjango/djlint.vim @@ -0,0 +1,12 @@ +" Author: Adrian Vollmer +" Description: djlint for Django HTML template files + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_options', '') + +call ale#linter#Define('htmldjango', { +\ 'name': 'djlint', +\ 'executable': function('ale#handlers#djlint#GetExecutable'), +\ 'command': function('ale#handlers#djlint#GetCommand'), +\ 'callback': 'ale#handlers#djlint#Handle', +\}) diff --git a/ale_linters/hurl/hurlfmt.vim b/ale_linters/hurl/hurlfmt.vim new file mode 100644 index 00000000..fa4252da --- /dev/null +++ b/ale_linters/hurl/hurlfmt.vim @@ -0,0 +1,69 @@ +" Description: Hurl linter using hurlfmt --check. +" https://hurl.dev/ + +call ale#Set('hurl_hurlfmt_executable', 'hurlfmt') + +function! ale_linters#hurl#hurlfmt#GetCommand(buffer) abort + return '%e' + \ . ' --check --no-color ' +endfunction + +function! ale_linters#hurl#hurlfmt#HandleOutput(buffer, lines) abort + " Matches patterns: + " + " error: Parsing space + " --> test.hurl:11:48 + " | + " 8 | header "Content-Type"= "application/json; charset=utf-8" + " | ^ expecting a space + " | + " + " error: Parsing URL + " --> test.hurl:11:48 + " | + " 11 | PUT https://jsonplaceholder.typicode.com/posts/{post_id}} + " | ^ illegal character <{> + " | + " + " Note: hurlfmt seems to report always the first error only so we assume + " there is only one error to make parsing easier. + let l:output = [] + + if empty(a:lines) + return l:output + endif + + let l:pattern = '\v(error|warning): (.+) --\> (.+):(\d+):(\d+) .+ \^ (.+) |' + let l:lines = join(a:lines, ' ') + + for l:match in ale#util#GetMatches(l:lines, l:pattern) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': match[4] + 0, + \ 'col': match[5] + 0, + \ 'end_col': match[5] + 0, + \ 'text': match[2] . ' : ' . match[6], + \ 'type': (match[1] is# 'error') ? 'E' : 'W' + \}) + endfor + + return l:output +endfunction + +function! ale_linters#hurl#hurlfmt#GetType(severity) abort + if a:severity is? 'convention' + \|| a:severity is? 'warning' + \|| a:severity is? 'refactor' + return 'W' + endif + + return 'E' +endfunction + +call ale#linter#Define('hurl', { +\ 'name': 'hurlfmt', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'hurl_hurlfmt_executable')}, +\ 'command': function('ale_linters#hurl#hurlfmt#GetCommand'), +\ 'callback': 'ale_linters#hurl#hurlfmt#HandleOutput', +\}) diff --git a/ale_linters/idris/idris.vim b/ale_linters/idris/idris.vim new file mode 100644 index 00000000..879e92f2 --- /dev/null +++ b/ale_linters/idris/idris.vim @@ -0,0 +1,81 @@ +" Author: Scott Bonds +" Description: default Idris compiler + +call ale#Set('idris_idris_executable', 'idris') +call ale#Set('idris_idris_options', '--total --warnpartial --warnreach --warnipkg') + +function! ale_linters#idris#idris#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'idris_idris_options') + + return '%e' . ale#Pad(l:options) . ' --check %s' +endfunction + +function! ale_linters#idris#idris#Handle(buffer, lines) abort + " This was copied almost verbatim from ale#handlers#haskell#HandleGHCFormat + " + " Look for lines like the following: + " foo.idr:2:6:When checking right hand side of main with expected type + " bar.idr:11:11-13: + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)(-\d+)?:(.*)?$' + let l:output = [] + + let l:corrected_lines = [] + + for l:line in a:lines + if len(matchlist(l:line, l:pattern)) > 0 + call add(l:corrected_lines, l:line) + elseif len(l:corrected_lines) > 0 + if l:line is# '' + let l:corrected_lines[-1] .= ' ' " turn a blank line into a space + else + let l:corrected_lines[-1] .= l:line + endif + + let l:corrected_lines[-1] = substitute(l:corrected_lines[-1], '\s\+', ' ', 'g') + endif + endfor + + for l:line in l:corrected_lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + if !ale#path#IsBufferPath(a:buffer, l:match[1]) + continue + endif + + let l:errors = matchlist(l:match[5], '\v([wW]arning|[eE]rror) - ?(.*)') + + if len(l:errors) > 0 + let l:ghc_type = l:errors[1] + let l:text = l:errors[2] + else + let l:ghc_type = '' + let l:text = l:match[5][:0] is# ' ' ? l:match[5][1:] : l:match[5] + endif + + if l:ghc_type is? 'Warning' + let l:type = 'W' + else + let l:type = 'E' + endif + + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('idris', { +\ 'name': 'idris', +\ 'executable': {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/ink/ls.vim b/ale_linters/ink/ls.vim new file mode 100644 index 00000000..00b2f323 --- /dev/null +++ b/ale_linters/ink/ls.vim @@ -0,0 +1,35 @@ +" Author: Andreww Hayworth +" Description: Integrate ALE with ink-language-server + +call ale#Set('ink_ls_executable', 'ink-language-server') +call ale#Set('ink_ls_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('ink_ls_initialization_options', {}) + +function! ale_linters#ink#ls#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'ink_ls', [ + \ 'ink-language-server', + \ 'node_modules/.bin/ink-language-server', + \]) +endfunction + +function! ale_linters#ink#ls#GetCommand(buffer) abort + let l:executable = ale_linters#ink#ls#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' --stdio' +endfunction + +function! ale_linters#ink#ls#FindProjectRoot(buffer) abort + let l:main_file = get(ale#Var(a:buffer, 'ink_ls_initialization_options'), 'mainStoryPath', 'main.ink') + let l:config = ale#path#ResolveLocalPath(a:buffer, l:main_file, expand('#' . a:buffer . ':p')) + + return ale#path#Dirname(l:config) +endfunction + +call ale#linter#Define('ink', { +\ 'name': 'ink-language-server', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#ink#ls#GetExecutable'), +\ 'command': function('ale_linters#ink#ls#GetCommand'), +\ 'project_root': function('ale_linters#ink#ls#FindProjectRoot'), +\ 'initialization_options': {b -> ale#Var(b, 'ink_ls_initialization_options')}, +\}) diff --git a/ale_linters/inko/inko.vim b/ale_linters/inko/inko.vim new file mode 100644 index 00000000..11558897 --- /dev/null +++ b/ale_linters/inko/inko.vim @@ -0,0 +1,33 @@ +" Author: Yorick Peterse +" Description: linting of Inko source code using the Inko compiler + +call ale#Set('inko_inko_executable', 'inko') + +function! ale_linters#inko#inko#GetCommand(buffer) abort + let l:include = '' + + " Include the tests source directory, but only for test files. + if expand('#' . a:buffer . ':p') =~? '\vtests[/\\]test[/\\]' + let l:test_dir = ale#path#FindNearestDirectory(a:buffer, 'tests') + + if isdirectory(l:test_dir) + let l:include = '--include ' . ale#Escape(l:test_dir) + endif + endif + + " We use %s instead of %t so the compiler determines the correct module + " names for the file being edited. Not doing so may lead to errors in + " certain cases. + return '%e build --check --format=json' + \ . ale#Pad(l:include) + \ . ' %s' +endfunction + +call ale#linter#Define('inko', { +\ 'name': 'inko', +\ 'executable': {b -> ale#Var(b, 'inko_inko_executable')}, +\ 'command': function('ale_linters#inko#inko#GetCommand'), +\ 'callback': 'ale#handlers#inko#Handle', +\ 'output_stream': 'stderr', +\ 'lint_file': 1 +\}) diff --git a/ale_linters/ispc/ispc.vim b/ale_linters/ispc/ispc.vim new file mode 100644 index 00000000..eb365117 --- /dev/null +++ b/ale_linters/ispc/ispc.vim @@ -0,0 +1,45 @@ +" Author: Martino Pilia +" Description: Lint ispc files with the Intel(R) SPMD Program Compiler + +call ale#Set('ispc_ispc_executable', 'ispc') +call ale#Set('ispc_ispc_options', '') + +function! ale_linters#ispc#ispc#GetCommand(buffer) abort + " --nowrap: do not wrap message lines + return '%e --nowrap' + \ . ale#Pad(ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer))) + \ . ale#Pad(ale#Var(a:buffer, 'ispc_ispc_options')) + \ . ' %s' +endfunction + +" Note that we ignore the two warnings in the beginning of the compiler output +" ('no output file specified' and 'no --target specified'), since they have +" nothing to do with linting. +function! ale_linters#ispc#ispc#Handle(buffer, lines) abort + " Message format: :: : + " As far as I know, can be any of: + " 'error', 'Error', 'fatal error', 'Warning', 'Performance Warning' + let l:re = '\v.+:([0-9]+):([0-9]+):\s+([^:]+):\s+(.+)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:re) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': str2nr(l:match[1]), + \ 'col': str2nr(l:match[2]), + \ 'type': l:match[3] =~? 'error' ? 'E' : 'W', + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('ispc', { +\ 'name': 'ispc', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..1ccbc505 --- /dev/null +++ b/ale_linters/java/checkstyle.vim @@ -0,0 +1,73 @@ +" Author: Devon Meunier +" Description: checkstyle for Java files + +call ale#Set('java_checkstyle_executable', 'checkstyle') +call ale#Set('java_checkstyle_config', '/google_checks.xml') +call ale#Set('java_checkstyle_options', '') + +function! ale_linters#java#checkstyle#Handle(buffer, lines) abort + let l:output = [] + + " modern checkstyle versions + let l:pattern = '\v\[(WARN|ERROR)\] [a-zA-Z]?:?[^:]+:(\d+):(\d+)?:? (.*) \[(.+)\]' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'type': l:match[1] is? 'WARN' ? 'W' : 'E', + \ 'sub_type': 'style', + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'code': l:match[5], + \}) + endfor + + if !empty(l:output) + return l:output + endif + + " old checkstyle versions + let l:pattern = '\v(.+):(\d+): ([^:]+): (.+)$' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'type': l:match[3] is? 'warning' ? 'W' : 'E', + \ 'sub_type': 'style', + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +function! s:GetConfig(buffer, config) abort + if ale#path#IsAbsolute(a:config) + return a:config + endif + + let s:file = ale#path#FindNearestFile(a:buffer, a:config) + + return !empty(s:file) ? s:file : a:config +endfunction + +function! ale_linters#java#checkstyle#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'java_checkstyle_options') + let l:config_option = ale#Var(a:buffer, 'java_checkstyle_config') + let l:config = l:options !~# '\v(^| )-c ' && !empty(l:config_option) + \ ? s:GetConfig(a:buffer, l:config_option) + \ : '' + + return '%e' + \ . ale#Pad(l:options) + \ . (!empty(l:config) ? ' -c ' . ale#Escape(l:config) : '') + \ . ' %s' +endfunction + +call ale#linter#Define('java', { +\ 'name': 'checkstyle', +\ 'executable': {b -> ale#Var(b, 'java_checkstyle_executable')}, +\ 'command': function('ale_linters#java#checkstyle#GetCommand'), +\ 'callback': 'ale_linters#java#checkstyle#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/java/cspell.vim b/ale_linters/java/cspell.vim new file mode 100644 index 00000000..a6eecc0b --- /dev/null +++ b/ale_linters/java/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Java files. + +call ale#handlers#cspell#DefineLinter('java') diff --git a/ale_linters/java/eclipselsp.vim b/ale_linters/java/eclipselsp.vim new file mode 100644 index 00000000..ad7cbeb4 --- /dev/null +++ b/ale_linters/java/eclipselsp.vim @@ -0,0 +1,200 @@ +" Author: Horacio Sanson +" 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_config_path', '') +call ale#Set('java_eclipselsp_workspace_path', '') +call ale#Set('java_eclipselsp_executable', 'java') +call ale#Set('java_eclipselsp_javaagent', '') + +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) + + if has('win32') + let l:platform = 'win32' + elseif has('macunix') + let l:platform = 'macosx' + else + let l:platform = 'linux' + endif + + " Search jar file within repository path when manually built using mvn + let l:files = globpath(l:path, '**/'.l:platform.'/**/plugins/org.eclipse.equinox.launcher_*\.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, '**/'.l:platform.'/plugins/org.eclipse.equinox.launcher_*\.jar', 1, 1) + + if len(l:files) >= 1 + return l:files[0] + endif + + " Search jar file within unzipped tar.gz file + let l:files = globpath(l:path, 'plugins/org.eclipse.equinox.launcher_*\.jar', 1, 1) + + if len(l:files) >= 1 + return l:files[0] + endif + + " Search jar file within system package path + let l:files = globpath('/usr/share/java/jdtls/plugins', 'org.eclipse.equinox.launcher_*\.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') + let l:config_path = ale#Var(a:buffer, 'java_eclipselsp_config_path') + + if !empty(l:config_path) + return ale#path#Simplify(l:config_path) + endif + + 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#WorkspacePath(buffer) abort + let l:wspath = ale#Var(a:buffer, 'java_eclipselsp_workspace_path') + + if !empty(l:wspath) + return l:wspath + endif + + return ale#path#Dirname(ale#java#FindProjectRoot(a:buffer)) +endfunction + +function! ale_linters#java#eclipselsp#Javaagent(buffer) abort + let l:rets = [] + let l:raw = ale#Var(a:buffer, 'java_eclipselsp_javaagent') + + if empty(l:raw) + return '' + endif + + let l:jars = split(l:raw) + + for l:jar in l:jars + call add(l:rets, ale#Escape('-javaagent:' . l:jar)) + endfor + + return join(l:rets, ' ') +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), + \ ale_linters#java#eclipselsp#Javaagent(a:buffer), + \ '-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#Escape(ale_linters#java#eclipselsp#JarPath(a:buffer)), + \ '-configuration', + \ ale#Escape(ale_linters#java#eclipselsp#ConfigurationPath(a:buffer)), + \ '-data', + \ ale#Escape(ale_linters#java#eclipselsp#WorkspacePath(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'), + \ { 'output_stream': 'both' } + \) +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'), +\ 'initialization_options': { +\ 'extendedClientCapabilities': { +\ 'classFileContentsSupport': v:true +\ } +\ } +\}) diff --git a/ale_linters/java/javac.vim b/ale_linters/java/javac.vim new file mode 100644 index 00000000..971e8de0 --- /dev/null +++ b/ale_linters/java/javac.vim @@ -0,0 +1,163 @@ +" Author: farenjihn , w0rp +" Description: Lints java files using javac + +let s:classpath_sep = has('unix') ? ':' : ';' + +call ale#Set('java_javac_executable', 'javac') +call ale#Set('java_javac_options', '') +call ale#Set('java_javac_classpath', '') +call ale#Set('java_javac_sourcepath', '') + +function! ale_linters#java#javac#RunWithImportPaths(buffer) abort + let [l:cwd, l:command] = ale#maven#BuildClasspathCommand(a:buffer) + + " Try to use Gradle if Maven isn't available. + if empty(l:command) + let [l:cwd, l:command] = ale#gradle#BuildClasspathCommand(a:buffer) + endif + + " Try to use Ant if Gradle and Maven aren't available + if empty(l:command) + let [l:cwd, l:command] = ale#ant#BuildClasspathCommand(a:buffer) + endif + + if empty(l:command) + return ale_linters#java#javac#GetCommand(a:buffer, [], {}) + endif + + return ale#command#Run( + \ a:buffer, + \ l:command, + \ function('ale_linters#java#javac#GetCommand'), + \ {'cwd': l:cwd}, + \) +endfunction + +function! s:BuildClassPathOption(buffer, import_paths) abort + " Filter out lines like [INFO], etc. + let l:class_paths = filter(a:import_paths[:], 'v:val !~# ''[''') + let l:cls_path = ale#Var(a:buffer, 'java_javac_classpath') + + if !empty(l:cls_path) && type(l:cls_path) is v:t_string + call extend(l:class_paths, split(l:cls_path, s:classpath_sep)) + endif + + if !empty(l:cls_path) && type(l:cls_path) is v:t_list + call extend(l:class_paths, l:cls_path) + endif + + return !empty(l:class_paths) + \ ? '-cp ' . ale#Escape(join(l:class_paths, s:classpath_sep)) + \ : '' +endfunction + +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 = '' + + " Find the src directory, for files in this project. + let l:src_dir = ale#path#FindNearestDirectory(a:buffer, 'src/main/java') + let l:sp_dirs = [] + + if !empty(l:src_dir) + call add(l:sp_dirs, l:src_dir) + + " Automatically include the jaxb directory too, if it's there. + let l:jaxb_dir = fnamemodify(l:src_dir, ':h:h') + \ . (has('win32') ? '\jaxb\' : '/jaxb/') + + 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 = ale#path#FindNearestDirectory(a:buffer, 'src/test/java') + + if isdirectory(l:test_dir) + call add(l:sp_dirs, l:test_dir) + endif + endif + + let l:source_paths = [] + let l:source_path = ale#Var(a:buffer, 'java_javac_sourcepath') + + if !empty(l:source_path) && type(l:source_path) is v:t_string + let l:source_paths = split(l:source_path, s:classpath_sep) + endif + + if !empty(l:source_path) && type(l:source_path) is v:t_list + let l:source_paths = l:source_path + endif + + if !empty(l:source_paths) + for l:path in l:source_paths + let l:sp_path = ale#path#FindNearestDirectory(a:buffer, l:path) + + if !empty(l:sp_path) + call add(l:sp_dirs, l:sp_path) + endif + endfor + endif + + if !empty(l:sp_dirs) + let l:sp_option = '-sourcepath ' + \ . ale#Escape(join(l:sp_dirs, s:classpath_sep)) + endif + + " Create .class files in a temporary directory, which we will delete later. + 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. + return '%e -Xlint' + \ . ale#Pad(l:cp_option) + \ . ale#Pad(l:sp_option) + \ . ' -d ' . ale#Escape(l:class_file_directory) + \ . ale#Pad(ale#Var(a:buffer, 'java_javac_options')) + \ . ' %t' +endfunction + +function! ale_linters#java#javac#Handle(buffer, lines) abort + " Look for lines like the following. + " + " Main.java:13: warning: [deprecation] donaught() in Testclass has been deprecated + " Main.java:16: error: ';' expected + let l:directory = expand('#' . a:buffer . ':p:h') + let l:pattern = '\v^(.*):(\d+): (.{-1,}):(.+)$' + let l:col_pattern = '\v^(\s*\^)$' + let l:symbol_pattern = '\v^ +symbol: *(class|method) +([^ ]+)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, [l:pattern, l:col_pattern, l:symbol_pattern]) + if empty(l:match[2]) && empty(l:match[3]) + if !empty(l:match[1]) && !empty(l:output) + let l:output[-1].col = len(l:match[1]) + endif + elseif empty(l:match[3]) + " Add symbols to 'cannot find symbol' errors. + if l:output[-1].text is# 'error: cannot find symbol' + let l:output[-1].text .= ': ' . l:match[2] + endif + else + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:directory, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[3] . ':' . l:match[4], + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('java', { +\ 'name': 'javac', +\ 'executable': {b -> ale#Var(b, 'java_javac_executable')}, +\ 'cwd': '%s:h', +\ '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 new file mode 100644 index 00000000..fa3b0e2c --- /dev/null +++ b/ale_linters/java/javalsp.vim @@ -0,0 +1,56 @@ +" Author: Horacio Sanson +" Description: Support for the Java language server https://github.com/georgewfraser/vscode-javac + +call ale#Set('java_javalsp_executable', '') +call ale#Set('java_javalsp_config', {}) + +function! ale_linters#java#javalsp#Executable(buffer) abort + return ale#Var(a:buffer, 'java_javalsp_executable') +endfunction + +function! ale_linters#java#javalsp#Config(buffer) abort + let l:defaults = { 'java': { 'classPath': [], 'externalDependencies': [] } } + let l:config = ale#Var(a:buffer, 'java_javalsp_config') + + " Ensure the config dictionary contains both classPath and + " externalDependencies keys to avoid a NPE crash on Java Language Server. + call extend(l:config, l:defaults, 'keep') + call extend(l:config['java'], l:defaults['java'], 'keep') + + return l:config +endfunction + +function! ale_linters#java#javalsp#Command(buffer) abort + let l:executable = ale_linters#java#javalsp#Executable(a:buffer) + + if fnamemodify(l:executable, ':t') is# 'java' + " For backward compatibility. + let l:cmd = [ + \ ale#Escape(l:executable), + \ '--add-exports jdk.compiler/com.sun.tools.javac.api=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.code=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.comp=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.main=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.tree=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.model=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.util=javacs', + \ '--add-opens jdk.compiler/com.sun.tools.javac.api=javacs', + \ '-m javacs/org.javacs.Main', + \] + + return join(l:cmd, ' ') + else + return ale#Escape(l:executable) + endif +endfunction + +call ale#linter#Define('java', { +\ 'name': 'javalsp', +\ 'aliases': ['java_language_server'], +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#java#javalsp#Executable'), +\ 'command': function('ale_linters#java#javalsp#Command'), +\ 'language': 'java', +\ 'project_root': function('ale#java#FindProjectRoot'), +\ 'lsp_config': function('ale_linters#java#javalsp#Config') +\}) diff --git a/ale_linters/java/pmd.vim b/ale_linters/java/pmd.vim new file mode 100644 index 00000000..a1f4c93c --- /dev/null +++ b/ale_linters/java/pmd.vim @@ -0,0 +1,36 @@ +" Author: Johannes Wienke +" Description: PMD for Java files + +function! ale_linters#java#pmd#Handle(buffer, lines) abort + let l:pattern = '"\(\d\+\)",".*","\(.\+\)","\(\d\+\)","\(\d\+\)","\(.\+\)","\(.\+\)","\(.\+\)"$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'type': 'W', + \ 'lnum': l:match[4] + 0, + \ 'text': l:match[5], + \ 'code': l:match[6] . ' - ' . l:match[7], + \}) + endfor + + return l:output +endfunction + +function! ale_linters#java#pmd#GetCommand(buffer) abort + return 'pmd ' + \ . ale#Var(a:buffer, 'java_pmd_options') + \ . ' -f csv' + \ . ' -d %t' +endfunction + +if !exists('g:ale_java_pmd_options') + let g:ale_java_pmd_options = '-R category/java/bestpractices.xml' +endif + +call ale#linter#Define('java', { +\ 'name': 'pmd', +\ 'executable': 'pmd', +\ 'command': function('ale_linters#java#pmd#GetCommand'), +\ 'callback': 'ale_linters#java#pmd#Handle', +\}) diff --git a/ale_linters/javascript/biome.vim b/ale_linters/javascript/biome.vim new file mode 100644 index 00000000..17714de1 --- /dev/null +++ b/ale_linters/javascript/biome.vim @@ -0,0 +1,11 @@ +" Author: Filip Gospodinov +" Description: biome for JavaScript files + +call ale#linter#Define('javascript', { +\ 'name': 'biome', +\ 'lsp': 'stdio', +\ 'language': function('ale#handlers#biome#GetLanguage'), +\ 'executable': function('ale#handlers#biome#GetExecutable'), +\ 'command': '%e lsp-proxy', +\ 'project_root': function('ale#handlers#biome#GetProjectRoot'), +\}) diff --git a/ale_linters/javascript/cspell.vim b/ale_linters/javascript/cspell.vim new file mode 100644 index 00000000..5a496779 --- /dev/null +++ b/ale_linters/javascript/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for JavaScript files. + +call ale#handlers#cspell#DefineLinter('javascript') diff --git a/ale_linters/javascript/deno.vim b/ale_linters/javascript/deno.vim new file mode 100644 index 00000000..659eb855 --- /dev/null +++ b/ale_linters/javascript/deno.vim @@ -0,0 +1,11 @@ +" Author: Arnold Chand +" Description: Deno lsp linter for JavaScript files. + +call ale#linter#Define('javascript', { +\ 'name': 'deno', +\ 'lsp': 'stdio', +\ 'executable': function('ale#handlers#deno#GetExecutable'), +\ 'command': '%e lsp', +\ 'project_root': function('ale#handlers#deno#GetProjectRoot'), +\ 'initialization_options': function('ale#handlers#deno#GetInitializationOptions'), +\}) diff --git a/ale_linters/javascript/eslint.vim b/ale_linters/javascript/eslint.vim new file mode 100644 index 00000000..cf4de6ec --- /dev/null +++ b/ale_linters/javascript/eslint.vim @@ -0,0 +1,11 @@ +" Author: w0rp +" Description: eslint for JavaScript files + +call ale#linter#Define('javascript', { +\ 'name': 'eslint', +\ 'output_stream': 'both', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\}) 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 +" 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 new file mode 100644 index 00000000..601bac33 --- /dev/null +++ b/ale_linters/javascript/flow.vim @@ -0,0 +1,160 @@ +" Author: Zach Perrault -- @zperrault +" Author: Florian Beeres +" Description: FlowType checking for JavaScript files + +call ale#Set('javascript_flow_executable', 'flow') +call ale#Set('javascript_flow_use_home_config', 0) +call ale#Set('javascript_flow_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_flow_use_respect_pragma', 1) + +function! ale_linters#javascript#flow#GetExecutable(buffer) abort + let l:flow_config = ale#path#FindNearestFile(a:buffer, '.flowconfig') + + if empty(l:flow_config) + " Don't run Flow if we can't find a .flowconfig file. + return '' + endif + + " Don't run Flow with a configuration file from the home directory by + " default, which can eat all of your RAM. + if fnamemodify(l:flow_config, ':h') is? $HOME + \&& !ale#Var(a:buffer, 'javascript_flow_use_home_config') + return '' + endif + + return ale#path#FindExecutable(a:buffer, 'javascript_flow', [ + \ 'node_modules/.bin/flow', + \]) +endfunction + +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(a:version) || ale#semver#GTE(a:version, [0, 36])) + + return '%e check-contents' + \ . (l:use_respect_pragma ? ' --respect-pragma': '') + \ . ' --json --from ale %s < %t' + \ . (!has('win32') ? '; echo' : '') +endfunction + +" Filter lines of flow output until we find the first line where the JSON +" output starts. +function! s:GetJSONLines(lines) abort + let l:start_index = 0 + + for l:line in a:lines + if l:line[:0] is# '{' + break + endif + + let l:start_index += 1 + endfor + + return a:lines[l:start_index :] +endfunction + +function! s:ExtraErrorMsg(current, new) abort + let l:newMsg = '' + + if a:current is# '' + " extra messages appear to already have a : + let l:newMsg = a:new + else + let l:newMsg = a:current . ' ' . a:new + endif + + return l:newMsg +endfunction + +function! s:GetDetails(error) abort + let l:detail = '' + + for l:extra_error in a:error.extra + if has_key(l:extra_error, 'message') + for l:extra_message in l:extra_error.message + let l:detail = s:ExtraErrorMsg(l:detail, l:extra_message.descr) + endfor + endif + + if has_key(l:extra_error, 'children') + for l:child in l:extra_error.children + for l:child_message in l:child.message + let l:detail = l:detail . ' ' . l:child_message.descr + endfor + endfor + endif + endfor + + return l:detail +endfunction + +function! ale_linters#javascript#flow#Handle(buffer, lines) abort + let l:str = join(s:GetJSONLines(a:lines), '') + + if empty(l:str) + return [] + endif + + let l:flow_output = json_decode(l:str) + let l:output = [] + + for l:error in get(l:flow_output, 'errors', []) + " Each error is broken up into parts + let l:text = '' + let l:line = 0 + let l:col = 0 + + for l:message in l:error.message + " Comments have no line of column information, so we skip them. + " In certain cases, `l:message.loc.source` points to a different path + " than the buffer one, thus we skip this loc information too. + if has_key(l:message, 'loc') + \&& l:line is# 0 + \&& ale#path#IsBufferPath(a:buffer, l:message.loc.source) + let l:line = l:message.loc.start.line + 0 + let l:col = l:message.loc.start.column + 0 + endif + + if l:text is# '' + let l:text = l:message.descr . ':' + else + let l:text = l:text . ' ' . l:message.descr + endif + endfor + + if has_key(l:error, 'operation') + let l:text = l:text . ' See also: ' . l:error.operation.descr + endif + + let l:errorToAdd = { + \ 'lnum': l:line, + \ 'col': l:col, + \ 'text': l:text, + \ 'type': has_key(l:error, 'level') && l:error.level is# 'error' ? 'E' : 'W', + \} + + if has_key(l:error, 'extra') + let l:errorToAdd.detail = l:errorToAdd.text + \ . "\n" . s:GetDetails(l:error) + endif + + call add(l:output, l:errorToAdd) + endfor + + return l:output +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'flow', +\ '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', +\ 'read_buffer': 0, +\}) diff --git a/ale_linters/javascript/flow_ls.vim b/ale_linters/javascript/flow_ls.vim new file mode 100644 index 00000000..9755ca4e --- /dev/null +++ b/ale_linters/javascript/flow_ls.vim @@ -0,0 +1,29 @@ +" Author: t_t +" Description: Integrate ALE with flow-language-server. + +call ale#Set('javascript_flow_ls_executable', 'flow') +call ale#Set('javascript_flow_ls_use_global', +\ get(g:, 'ale_use_global_executables', 0) +\) + +function! ale_linters#javascript#flow_ls#FindProjectRoot(buffer) abort + let l:flow_config = ale#path#FindNearestFile(a:buffer, '.flowconfig') + + if !empty(l:flow_config) + return fnamemodify(l:flow_config, ':h') + endif + + return '' +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'flow_ls', +\ 'aliaes': ['flow-language-server'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#path#FindExecutable(b, 'javascript_flow_ls', [ +\ 'node_modules/.bin/flow', +\ ])}, +\ 'command': '%e lsp --from ale-lsp', +\ '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 new file mode 100644 index 00000000..ae3be68c --- /dev/null +++ b/ale_linters/javascript/jscs.vim @@ -0,0 +1,61 @@ +" Author: Chris Kyrouac - https://github.com/fijshion +" Description: jscs for JavaScript files + +call ale#Set('javascript_jscs_executable', 'jscs') +call ale#Set('javascript_jscs_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#javascript#jscs#GetCommand(buffer) abort + " Search for a local JShint config locaation, and default to a global one. + let l:jscs_config = ale#path#ResolveLocalPath( + \ a:buffer, + \ '.jscsrc', + \ get(g:, 'ale_jscs_config_loc', '') + \) + + let l:command = '%e --reporter inline --no-colors' + + if !empty(l:jscs_config) + let l:command .= ' --config ' . ale#Escape(l:jscs_config) + endif + + let l:command .= ' -' + + return l:command +endfunction + +function! ale_linters#javascript#jscs#Handle(buffer, lines) abort + " Matches patterns looking like the following + " + " foobar.js: line 2, col 1, Expected indentation of 1 characters + " + let l:pattern = '\v^.*:\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[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3] + \} + + let l:code_match = matchlist(l:match[3], '\v([^ :]+): (.+)$') + + if !empty(l:code_match) + let l:obj.code = l:code_match[1] + let l:obj.text = l:code_match[2] + endif + + call add(l:output, l:obj) + endfor + + return l:output +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'jscs', +\ 'executable': {b -> ale#path#FindExecutable(b, 'javascript_jscs', [ +\ 'node_modules/.bin/jscs', +\ ])}, +\ '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 new file mode 100644 index 00000000..26d4fda2 --- /dev/null +++ b/ale_linters/javascript/jshint.vim @@ -0,0 +1,33 @@ +" Author: Chris Kyrouac - https://github.com/fijshion +" Description: JSHint for Javascript files + +call ale#Set('javascript_jshint_executable', 'jshint') +call ale#Set('javascript_jshint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#javascript#jshint#GetCommand(buffer) abort + " Search for a local JShint config locaation, and default to a global one. + let l:jshint_config = ale#path#ResolveLocalPath( + \ a:buffer, + \ '.jshintrc', + \ get(g:, 'ale_jshint_config_loc', '') + \) + + let l:command = '%e --reporter unix --extract auto' + + if !empty(l:jshint_config) + let l:command .= ' --config ' . ale#Escape(l:jshint_config) + endif + + let l:command .= ' --filename %s -' + + return l:command +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'jshint', +\ 'executable': {b -> ale#path#FindExecutable(b, 'javascript_jshint', [ +\ 'node_modules/.bin/jshint', +\ ])}, +\ '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 new file mode 100644 index 00000000..addf41dd --- /dev/null +++ b/ale_linters/javascript/standard.vim @@ -0,0 +1,32 @@ +" Author: Ahmed El Gabri <@ahmedelgabri> +" Description: standardjs for JavaScript files + +call ale#Set('javascript_standard_executable', 'standard') +call ale#Set('javascript_standard_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_standard_options', '') + +function! ale_linters#javascript#standard#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'javascript_standard', [ + \ 'node_modules/standardx/bin/cmd.js', + \ 'node_modules/standard/bin/cmd.js', + \ 'node_modules/semistandard/bin/cmd.js', + \ 'node_modules/.bin/standard', + \]) +endfunction + +function! ale_linters#javascript#standard#GetCommand(buffer) abort + let l:executable = ale_linters#javascript#standard#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'javascript_standard_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin %s' +endfunction + +" standard uses eslint and the output format is the same +call ale#linter#Define('javascript', { +\ 'name': 'standard', +\ '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 new file mode 100644 index 00000000..caf6972b --- /dev/null +++ b/ale_linters/javascript/tsserver.vim @@ -0,0 +1,17 @@ +" Author: Chaucerbao, w0rp +" Description: tsserver integration for ALE + +call ale#Set('javascript_tsserver_executable', 'tsserver') +call ale#Set('javascript_tsserver_config_path', '') +call ale#Set('javascript_tsserver_use_global', get(g:, 'ale_use_global_executables', 0)) + +call ale#linter#Define('javascript', { +\ 'name': 'tsserver', +\ 'lsp': 'tsserver', +\ 'executable': {b -> ale#path#FindExecutable(b, 'javascript_tsserver', [ +\ 'node_modules/.bin/tsserver', +\ ])}, +\ 'command': '%e', +\ 'project_root': function('ale#handlers#tsserver#GetProjectRoot'), +\ 'language': '', +\}) diff --git a/ale_linters/javascript/xo.vim b/ale_linters/javascript/xo.vim new file mode 100644 index 00000000..5e04ad5c --- /dev/null +++ b/ale_linters/javascript/xo.vim @@ -0,0 +1,9 @@ +" Author: Daniel Lupu +" Description: xo for JavaScript files + +call ale#linter#Define('javascript', { +\ 'name': 'xo', +\ 'executable': function('ale#handlers#xo#GetExecutable'), +\ 'command': function('ale#handlers#xo#GetLintCommand'), +\ 'callback': 'ale#handlers#xo#HandleJSON', +\}) diff --git a/ale_linters/jinja/djlint.vim b/ale_linters/jinja/djlint.vim new file mode 100644 index 00000000..fd52c954 --- /dev/null +++ b/ale_linters/jinja/djlint.vim @@ -0,0 +1,12 @@ +" Author: Adrian Vollmer +" Description: djlint for Django HTML template files + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_options', '') + +call ale#linter#Define('jinja', { +\ 'name': 'djlint', +\ 'executable': function('ale#handlers#djlint#GetExecutable'), +\ 'command': function('ale#handlers#djlint#GetCommand'), +\ 'callback': 'ale#handlers#djlint#Handle', +\}) diff --git a/ale_linters/json/biome.vim b/ale_linters/json/biome.vim new file mode 100644 index 00000000..086ee44a --- /dev/null +++ b/ale_linters/json/biome.vim @@ -0,0 +1,10 @@ +" Description: biome for json files + +call ale#linter#Define('json', { +\ 'name': 'biome', +\ 'lsp': 'stdio', +\ 'language': function('ale#handlers#biome#GetLanguage'), +\ 'executable': function('ale#handlers#biome#GetExecutable'), +\ 'command': '%e lsp-proxy', +\ 'project_root': function('ale#handlers#biome#GetProjectRoot'), +\}) diff --git a/ale_linters/json/cspell.vim b/ale_linters/json/cspell.vim new file mode 100644 index 00000000..0d7314a4 --- /dev/null +++ b/ale_linters/json/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for JSON files. + +call ale#handlers#cspell#DefineLinter('json') diff --git a/ale_linters/json/eslint.vim b/ale_linters/json/eslint.vim new file mode 100644 index 00000000..bdabb9fa --- /dev/null +++ b/ale_linters/json/eslint.vim @@ -0,0 +1,16 @@ +" Author: João Pesce +" Description: eslint for JSON files. +" +" Requires eslint-plugin-jsonc or a similar plugin to work +" +" Uses the same funtcions as ale_linters/javascript/eslint.vim by w0rp +" + +call ale#linter#Define('json', { +\ 'name': 'eslint', +\ 'output_stream': 'both', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\}) diff --git a/ale_linters/json/jq.vim b/ale_linters/json/jq.vim new file mode 100644 index 00000000..ad1da269 --- /dev/null +++ b/ale_linters/json/jq.vim @@ -0,0 +1,24 @@ +" Author: jD91mZM2 +call ale#Set('json_jq_executable', 'jq') +call ale#Set('json_jq_options', '') +call ale#Set('json_jq_filters', '.') + +" Matches patterns like the following: +" parse error: Expected another key-value pair at line 4, column 3 +let s:pattern = 'parse error: \(.\+\) at line \(\d\+\), column \(\d\+\)$' + +function! ale_linters#json#jq#Handle(buffer, lines) abort + return ale#util#MapMatches(a:lines, s:pattern, {match -> { + \ 'text': match[1], + \ 'lnum': match[2] + 0, + \ 'col': match[3] + 0, + \}}) +endfunction + +call ale#linter#Define('json', { +\ 'name': 'jq', +\ 'executable': {b -> ale#Var(b, 'json_jq_executable')}, +\ 'output_stream': 'stderr', +\ 'command': '%e', +\ 'callback': 'ale_linters#json#jq#Handle', +\}) diff --git a/ale_linters/json/jsonlint.vim b/ale_linters/json/jsonlint.vim new file mode 100644 index 00000000..812540af --- /dev/null +++ b/ale_linters/json/jsonlint.vim @@ -0,0 +1,43 @@ +" Author: KabbAmine , David Sierra + +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#path#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: + " line 2, col 15, found: 'STRING' - expected: 'EOF', '}', ',', ']'. + let l:pattern = '^line \(\d\+\), col \(\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], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('json', { +\ 'name': 'jsonlint', +\ 'executable': function('ale_linters#json#jsonlint#GetExecutable'), +\ 'output_stream': 'stderr', +\ 'command': function('ale_linters#json#jsonlint#GetCommand'), +\ 'callback': 'ale_linters#json#jsonlint#Handle', +\}) diff --git a/ale_linters/json/spectral.vim b/ale_linters/json/spectral.vim new file mode 100644 index 00000000..14129c56 --- /dev/null +++ b/ale_linters/json/spectral.vim @@ -0,0 +1,14 @@ +" Author: t2h5 +" Description: Integration of Stoplight Spectral CLI with ALE. + +call ale#Set('json_spectral_executable', 'spectral') +call ale#Set('json_spectral_use_global', get(g:, 'ale_use_global_executables', 0)) + +call ale#linter#Define('json', { +\ 'name': 'spectral', +\ 'executable': {b -> ale#path#FindExecutable(b, 'json_spectral', [ +\ 'node_modules/.bin/spectral', +\ ])}, +\ 'command': '%e lint --ignore-unknown-format -q -f text %t', +\ 'callback': 'ale#handlers#spectral#HandleSpectralOutput' +\}) diff --git a/ale_linters/json/vscodejson.vim b/ale_linters/json/vscodejson.vim new file mode 100644 index 00000000..be9eaf53 --- /dev/null +++ b/ale_linters/json/vscodejson.vim @@ -0,0 +1,32 @@ +" Author: Dalius Dobravolskas +" Description: VSCode json language server + +call ale#Set('json_vscodejson_executable', '') + +function! ale_linters#json#vscodejson#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'json_vscodejson_executable') + + if l:executable is# '' + if ale#engine#IsExecutable(a:buffer, 'vscode-json-languageserver') + let l:executable = 'vscode-json-languageserver' + else + let l:executable = 'vscode-json-language-server' + endif + endif + + return l:executable +endfunction + +function! ale_linters#json#vscodejson#GetProjectRoot(buffer) abort + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' +endfunction + +call ale#linter#Define('json', { +\ 'name': 'vscodejson', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#json#vscodejson#GetExecutable'), +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#json#vscodejson#GetProjectRoot'), +\}) diff --git a/ale_linters/json5/eslint.vim b/ale_linters/json5/eslint.vim new file mode 100644 index 00000000..6207f2d7 --- /dev/null +++ b/ale_linters/json5/eslint.vim @@ -0,0 +1,16 @@ +" Author: João Pesce +" Description: eslint for JSON5 files. +" +" Requires eslint-plugin-jsonc or a similar plugin to work +" +" Uses the same funtcions as ale_linters/javascript/eslint.vim by w0rp +" + +call ale#linter#Define('json5', { +\ 'name': 'eslint', +\ 'output_stream': 'both', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\}) diff --git a/ale_linters/jsonc/biome.vim b/ale_linters/jsonc/biome.vim new file mode 100644 index 00000000..5a691093 --- /dev/null +++ b/ale_linters/jsonc/biome.vim @@ -0,0 +1,10 @@ +" Description: biome for jsonc files + +call ale#linter#Define('jsonc', { +\ 'name': 'biome', +\ 'lsp': 'stdio', +\ 'language': function('ale#handlers#biome#GetLanguage'), +\ 'executable': function('ale#handlers#biome#GetExecutable'), +\ 'command': '%e lsp-proxy', +\ 'project_root': function('ale#handlers#biome#GetProjectRoot'), +\}) diff --git a/ale_linters/jsonc/eslint.vim b/ale_linters/jsonc/eslint.vim new file mode 100644 index 00000000..1a5cc528 --- /dev/null +++ b/ale_linters/jsonc/eslint.vim @@ -0,0 +1,16 @@ +" Author: João Pesce +" Description: eslint for JSONC files. +" +" Requires eslint-plugin-jsonc or a similar plugin to work +" +" Uses the same funtcions as ale_linters/javascript/eslint.vim by w0rp +" + +call ale#linter#Define('jsonc', { +\ 'name': 'eslint', +\ 'output_stream': 'both', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\}) diff --git a/ale_linters/jsonnet/jsonnet_lint.vim b/ale_linters/jsonnet/jsonnet_lint.vim new file mode 100644 index 00000000..a5ebdc39 --- /dev/null +++ b/ale_linters/jsonnet/jsonnet_lint.vim @@ -0,0 +1,59 @@ +" Author: Trevor Whitney +" Description: jsonnet-lint for jsonnet files + +call ale#Set('jsonnet_jsonnet_lint_executable', 'jsonnet-lint') +call ale#Set('jsonnet_jsonnet_lint_options', '') + +function! ale_linters#jsonnet#jsonnet_lint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'jsonnet_jsonnet_lint_options') + + return '%e' + \ . ale#Pad(l:options) + \ . ' %t' +endfunction + + +function! ale_linters#jsonnet#jsonnet_lint#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " ERROR: foo.jsonnet:22:3-12 expected token OPERATOR but got (IDENTIFIER, "bar") + " ERROR: hoge.jsonnet:20:3 unexpected: "}" while parsing terminal + " ERROR: main.jsonnet:212:1-14 Expected , or ; but got (IDENTIFIER, "older_cluster") + let l:pattern = '^ERROR: [^:]*:\(\d\+\):\(\d\+\)\(-\d\+\)* \(.*\)' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + let line_number = l:match[1] + 0 + let column = l:match[2] + 0 + " l:match[3] has optional -14, when linter is showing a range + let text = l:match[4] + + + " vcol is Needed to indicate that the column is a character. + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': line_number, + \ 'vcol': 0, + \ 'col': column, + \ 'text': text, + \ 'type': 'E', + \ 'nr': -1, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('jsonnet', { +\ 'name': 'jsonnet_lint', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'jsonnet_jsonnet_lint_executable')}, +\ 'command': function('ale_linters#jsonnet#jsonnet_lint#GetCommand'), +\ 'callback': 'ale_linters#jsonnet#jsonnet_lint#Handle', +\}) diff --git a/ale_linters/jsonnet/jsonnetfmt.vim b/ale_linters/jsonnet/jsonnetfmt.vim new file mode 100644 index 00000000..8904019e --- /dev/null +++ b/ale_linters/jsonnet/jsonnetfmt.vim @@ -0,0 +1,52 @@ +" Authors: Trevor Whitney and Takuya Kosugiyama +" Description: jsonnetfmt for jsonnet files + +call ale#Set('jsonnet_jsonnetfmt_executable', 'jsonnetfmt') +call ale#Set('jsonnet_jsonnetfmt_options', '') + +function! ale_linters#jsonnet#jsonnetfmt#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'jsonnet_jsonnetfmt_options') + + return '%e' + \ . ale#Pad(l:options) + \ . ' %t' +endfunction + + +function! ale_linters#jsonnet#jsonnetfmt#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " STATIC ERROR: foo.jsonnet:22:3-12: expected token OPERATOR but got (IDENTIFIER, "bar") + " STATIC ERROR: hoge.jsonnet:20:3: unexpected: "}" while parsing terminal + let l:pattern = '^STATIC ERROR:[^:]*:\(\d\+\):\(\d\+\):*\(-\d\+\)* \(.*\)' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + " vcol is Needed to indicate that the column is a character. + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'vcol': 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \ 'nr': -1, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('jsonnet', { +\ 'name': 'jsonnetfmt', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'jsonnet_jsonnetfmt_executable')}, +\ 'command': function('ale_linters#jsonnet#jsonnetfmt#GetCommand'), +\ 'callback': 'ale_linters#jsonnet#jsonnetfmt#Handle', +\}) diff --git a/ale_linters/julia/languageserver.vim b/ale_linters/julia/languageserver.vim new file mode 100644 index 00000000..fbfab517 --- /dev/null +++ b/ale_linters/julia/languageserver.vim @@ -0,0 +1,22 @@ +" Author: Bartolomeo Stellato +" Description: A language server for Julia + +" Set julia executable variable +call ale#Set('julia_executable', 'julia') + +function! ale_linters#julia#languageserver#GetCommand(buffer) abort + let l:julia_executable = ale#Var(a:buffer, 'julia_executable') + let l:cmd_string = 'using LanguageServer; using Pkg; import StaticLint; import SymbolServer; server = LanguageServer.LanguageServerInstance(isdefined(Base, :stdin) ? stdin : STDIN, isdefined(Base, :stdout) ? stdout : STDOUT, dirname(Pkg.Types.Context().env.project_file)); server.runlinter = true; run(server);' + + return ale#Escape(l:julia_executable) . ' --project=@. --startup-file=no --history-file=no -e ' . ale#Escape(l:cmd_string) +endfunction + +call ale#linter#Define('julia', { +\ 'name': 'languageserver', +\ 'aliases': ['julials'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'julia_executable')}, +\ 'command': function('ale_linters#julia#languageserver#GetCommand'), +\ 'language': 'julia', +\ 'project_root': function('ale#julia#FindProjectRoot'), +\}) diff --git a/ale_linters/kotlin/kotlinc.vim b/ale_linters/kotlin/kotlinc.vim new file mode 100644 index 00000000..e8bc924d --- /dev/null +++ b/ale_linters/kotlin/kotlinc.vim @@ -0,0 +1,177 @@ +" Author: Francis Agyapong +" Description: A linter for the Kotlin programming language that uses kotlinc + +let g:ale_kotlin_kotlinc_options = get(g:, 'ale_kotlin_kotlinc_options', '') +let g:ale_kotlin_kotlinc_enable_config = get(g:, 'ale_kotlin_kotlinc_enable_config', 0) +let g:ale_kotlin_kotlinc_config_file = get(g:, 'ale_kotlin_kotlinc_config_file', '.ale_kotlinc_config') +let g:ale_kotlin_kotlinc_classpath = get(g:, 'ale_kotlin_kotlinc_classpath', '') +let g:ale_kotlin_kotlinc_sourcepath = get(g:, 'ale_kotlin_kotlinc_sourcepath', '') +let g:ale_kotlin_kotlinc_use_module_file = get(g:, 'ale_kotlin_kotlinc_use_module_file', 0) +let g:ale_kotlin_kotlinc_module_filename = get(g:, 'ale_kotlin_kotlinc_module_filename', 'module.xml') + +let s:classpath_sep = has('unix') ? ':' : ';' + +function! ale_linters#kotlin#kotlinc#RunWithImportPaths(buffer) abort + let l:command = '' + + " exec maven/gradle only if classpath is not set + if !empty(ale#Var(a:buffer, 'kotlin_kotlinc_classpath')) + return ale_linters#kotlin#kotlinc#GetCommand(a:buffer, [], {}) + endif + + let [l:cwd, l:command] = ale#maven#BuildClasspathCommand(a:buffer) + + " Try to use Gradle if Maven isn't available. + if empty(l:command) + let [l:cwd, l:command] = ale#gradle#BuildClasspathCommand(a:buffer) + endif + + 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'), + \ {'cwd': l:cwd}, + \) +endfunction + +function! s:BuildClassPathOption(buffer, import_paths) abort + " Filter out lines like [INFO], etc. + let l:class_paths = filter(a:import_paths[:], 'v:val !~# ''[''') + call extend( + \ l:class_paths, + \ split(ale#Var(a:buffer, 'kotlin_kotlinc_classpath'), s:classpath_sep), + \) + + return !empty(l:class_paths) + \ ? ' -cp ' . ale#Escape(join(l:class_paths, s:classpath_sep)) + \ : '' +endfunction + +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 ' + + " If the config file is enabled and readable, source it + if ale#Var(a:buffer, 'kotlin_kotlinc_enable_config') + let l:conf = expand(ale#Var(a:buffer, 'kotlin_kotlinc_config_file'), 1) + + if filereadable(l:conf) + execute 'source ' . fnameescape(l:conf) + endif + endif + + " If use module and module file is readable use that and return + if ale#Var(a:buffer, 'kotlin_kotlinc_use_module_file') + let l:module_filename = ale#Escape(expand(ale#Var(a:buffer, 'kotlin_kotlinc_module_filename'), 1)) + + if filereadable(l:module_filename) + let l:kotlinc_opts .= ' -module ' . l:module_filename + let l:command .= 'kotlinc ' . l:kotlinc_opts + + return l:command + endif + endif + + " We only get here if not using module or the module file not readable + if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') isnot# '' + let l:kotlinc_opts .= ' -cp ' . ale#Var(a:buffer, 'kotlin_kotlinc_classpath') + else + " get classpath from maven/gradle + let l:kotlinc_opts .= s:BuildClassPathOption(a:buffer, a:import_paths) + endif + + let l:fname = '' + + if ale#Var(a:buffer, 'kotlin_kotlinc_sourcepath') isnot# '' + let l:fname .= expand(ale#Var(a:buffer, 'kotlin_kotlinc_sourcepath'), 1) . ' ' + else + " Find the src directory for files in this project. + let l:project_root = ale#gradle#FindProjectRoot(a:buffer) + + if !empty(l:project_root) + let l:src_dir = l:project_root + else + let l:src_dir = ale#path#FindNearestDirectory(a:buffer, 'src/main/java') + \ . ' ' . ale#path#FindNearestDirectory(a:buffer, 'src/main/kotlin') + endif + + let l:fname .= expand(l:src_dir, 1) . ' ' + endif + + let l:fname .= ale#Escape(expand('#' . a:buffer . ':p')) + let l:command .= l:kotlinc_opts . ' ' . l:fname + + return l:command +endfunction + +function! ale_linters#kotlin#kotlinc#Handle(buffer, lines) abort + let l:code_pattern = '^\(.*\):\([0-9]\+\):\([0-9]\+\):\s\+\(error\|warning\):\s\+\(.*\)' + let l:general_pattern = '^\(warning\|error\|info\):\s*\(.*\)' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:code_pattern) + + if len(l:match) == 0 + continue + endif + + let l:file = l:match[1] + let l:line = l:match[2] + 0 + let l:column = l:match[3] + 0 + let l:type = l:match[4] + let l:text = l:match[5] + + let l:buf_abspath = fnamemodify(l:file, ':p') + let l:curbuf_abspath = expand('#' . a:buffer . ':p') + + " Skip if file is not loaded + if l:buf_abspath isnot# l:curbuf_abspath + continue + endif + + let l:type_marker_str = l:type is# 'warning' ? 'W' : 'E' + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:column, + \ 'text': l:text, + \ 'type': l:type_marker_str, + \}) + endfor + + " Non-code related messages + for l:line in a:lines + let l:match = matchlist(l:line, l:general_pattern) + + if len(l:match) == 0 + continue + endif + + let l:type = l:match[1] + let l:text = l:match[2] + + let l:type_marker_str = l:type is# 'warning' || l:type is# 'info' ? 'W' : 'E' + + call add(l:output, { + \ 'lnum': 1, + \ 'text': l:text, + \ 'type': l:type_marker_str, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('kotlin', { +\ 'name': 'kotlinc', +\ 'executable': 'kotlinc', +\ '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 new file mode 100644 index 00000000..0bb64b19 --- /dev/null +++ b/ale_linters/kotlin/ktlint.vim @@ -0,0 +1,10 @@ +" Author: Francis Agyapong +" Description: Lint kotlin files using ktlint + +call ale#linter#Define('kotlin', { +\ 'name': 'ktlint', +\ 'executable': 'ktlint', +\ 'command': function('ale#handlers#ktlint#GetCommand'), +\ 'callback': 'ale#handlers#ktlint#Handle', +\ 'output_stream': 'stderr' +\}) diff --git a/ale_linters/kotlin/languageserver.vim b/ale_linters/kotlin/languageserver.vim new file mode 100644 index 00000000..18b153ae --- /dev/null +++ b/ale_linters/kotlin/languageserver.vim @@ -0,0 +1,30 @@ +" Author: MTDL9 +" Description: Support for the Kotlin language server https://github.com/fwcd/KotlinLanguageServer + +call ale#Set('kotlin_languageserver_executable', 'kotlin-language-server') + +function! ale_linters#kotlin#languageserver#GetProjectRoot(buffer) abort + let l:gradle_root = ale#gradle#FindProjectRoot(a:buffer) + + if !empty(l:gradle_root) + return l:gradle_root + endif + + let l:maven_pom_file = ale#path#FindNearestFile(a:buffer, 'pom.xml') + + if !empty(l:maven_pom_file) + return fnamemodify(l:maven_pom_file, ':h') + endif + + return '' +endfunction + +call ale#linter#Define('kotlin', { +\ 'name': 'languageserver', +\ 'aliaes': ['kotlin_language_server'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'kotlin_languageserver_executable')}, +\ 'command': '%e', +\ 'language': 'kotlin', +\ 'project_root': function('ale_linters#kotlin#languageserver#GetProjectRoot'), +\}) diff --git a/ale_linters/less/lessc.vim b/ale_linters/less/lessc.vim new file mode 100644 index 00000000..8e21f5b4 --- /dev/null +++ b/ale_linters/less/lessc.vim @@ -0,0 +1,47 @@ +" Author: zanona , w0rp +" Description: This file adds support for checking Less code with lessc. + +call ale#Set('less_lessc_executable', 'lessc') +call ale#Set('less_lessc_options', '') +call ale#Set('less_lessc_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#less#lessc#GetCommand(buffer) abort + return '%e --no-color --lint' + \ . ' --include-path=' . ale#Escape(expand('#' . a:buffer . ':p:h')) + \ . ale#Pad(ale#Var(a:buffer, 'less_lessc_options')) + \ . ' -' +endfunction + +function! ale_linters#less#lessc#Handle(buffer, lines) abort + let l:dir = expand('#' . a:buffer . ':p:h') + " Matches patterns like the following: + let l:pattern = '^\(\w\+\): \(.\{-}\) in \(.\{-}\) on line \(\d\+\), column \(\d\+\):$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[4] + 0, + \ 'col': l:match[5] + 0, + \ 'text': l:match[2], + \ 'type': 'E', + \} + + if l:match[3] isnot# '-' + let l:item.filename = ale#path#GetAbsPath(l:dir, l:match[3]) + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('less', { +\ 'name': 'lessc', +\ 'executable': {b -> ale#path#FindExecutable(b, 'less_lessc', [ +\ 'node_modules/.bin/lessc', +\ ])}, +\ '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 new file mode 100644 index 00000000..9430fe9b --- /dev/null +++ b/ale_linters/less/stylelint.vim @@ -0,0 +1,21 @@ +" Author: diartyz , w0rp + +call ale#Set('less_stylelint_executable', 'stylelint') +call ale#Set('less_stylelint_options', '') +call ale#Set('less_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#less#stylelint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'less_stylelint_options') + + return '%e' . ale#Pad(l:options) . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('less', { +\ 'name': 'stylelint', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#path#FindExecutable(b, 'less_stylelint', [ +\ 'node_modules/.bin/stylelint', +\ ])}, +\ '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 new file mode 100644 index 00000000..594be063 --- /dev/null +++ b/ale_linters/llvm/llc.vim @@ -0,0 +1,24 @@ +" Author: rhysd +" Description: Support for checking LLVM IR with llc + +call ale#Set('llvm_llc_executable', 'llc') + +function! ale_linters#llvm#llc#HandleErrors(buffer, lines) abort + " Handle '{path}: {file}:{line}:{col}: error: {message}' format + let l:pattern = '\v^[a-zA-Z]?:?[^:]+: [^:]+:(\d+):(\d+): (.+)$' + + return map(ale#util#GetMatches(a:lines, l:pattern), "{ + \ 'lnum': str2nr(v:val[1]), + \ 'col': str2nr(v:val[2]), + \ 'text': v:val[3], + \ 'type': 'E', + \}") +endfunction + +call ale#linter#Define('llvm', { +\ 'name': 'llc', +\ 'executable': {b -> ale#Var(b, 'llvm_llc_executable')}, +\ 'output_stream': 'stderr', +\ 'command': {-> '%e -filetype=null -o=' . g:ale#util#nul_file}, +\ 'callback': 'ale_linters#llvm#llc#HandleErrors', +\}) diff --git a/ale_linters/lua/cspell.vim b/ale_linters/lua/cspell.vim new file mode 100644 index 00000000..215b82f8 --- /dev/null +++ b/ale_linters/lua/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Lua files. + +call ale#handlers#cspell#DefineLinter('lua') diff --git a/ale_linters/lua/lua_language_server.vim b/ale_linters/lua/lua_language_server.vim new file mode 100644 index 00000000..0892ad3b --- /dev/null +++ b/ale_linters/lua/lua_language_server.vim @@ -0,0 +1,15 @@ +" Author: w0rp +" Description: lua-language-server integration (https://github.com/LuaLS/lua-language-server) + +call ale#Set('lua_language_server_executable', 'lua-language-server') +call ale#Set('lua_language_server_config', {}) + +call ale#linter#Define('lua', { +\ 'name': 'lua_language_server', +\ 'aliases': ['lua-language-server', 'lua_ls'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'lua_language_server_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale#lua#FindProjectRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'lua_language_server_config')}, +\}) diff --git a/ale_linters/lua/luac.vim b/ale_linters/lua/luac.vim new file mode 100644 index 00000000..41674a43 --- /dev/null +++ b/ale_linters/lua/luac.vim @@ -0,0 +1,31 @@ +" Author: Jon Xie https://github.com/xiejiangzhi +" Description: luac linter for lua files + +call ale#Set('lua_luac_executable', 'luac') + +function! ale_linters#lua#luac#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " luac: stdin:5: '=' expected near ')' + " luac: stdin:8: ')' expected (to close '(' at line 6) near '123' + let l:pattern = '\v^.*:(\d+): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'type': 'E', + \ 'text': l:match[2], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('lua', { +\ 'name': 'luac', +\ '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 new file mode 100644 index 00000000..16dc08a5 --- /dev/null +++ b/ale_linters/lua/luacheck.vim @@ -0,0 +1,79 @@ +" Author: Sol Bekic https://github.com/s-ol +" Description: luacheck linter for lua files + +call ale#Set('lua_luacheck_executable', 'luacheck') +call ale#Set('lua_luacheck_options', '') + +function! s:IsInRuntimepath(buffer) abort + let l:runtimepath_dirs = split(&runtimepath, ',') + + for l:dir in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + for l:runtime_dir in l:runtimepath_dirs + if l:dir is# l:runtime_dir + return 1 + endif + endfor + endfor + + return 0 +endfunction + +function! ale_linters#lua#luacheck#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'lua_luacheck_options') + + " Add `--globals vim` by default if the file is in runtimepath. + if l:options !~# '--globals' + let l:in_runtime = getbufvar(a:buffer, 'ale_in_runtimepath', v:null) + + if l:in_runtime is v:null + let l:in_runtime = s:IsInRuntimepath(a:buffer) + " Save the result of check this buffer so we only check once. + call setbufvar(a:buffer, 'ale_in_runtimepath', l:in_runtime) + endif + + if l:in_runtime + if !empty(l:options) + let l:options .= ' ' + endif + + let l:options .= '--globals vim' + endif + endif + + return '%e' . ale#Pad(l:options) + \ . ' --formatter plain --codes --filename %s -' +endfunction + +function! ale_linters#lua#luacheck#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " artal.lua:159:17: (W111) shadowing definition of loop variable 'i' on line 106 + " artal.lua:182:7: (W213) unused loop variable 'i' + let l:pattern = '^.*:\(\d\+\):\(\d\+\): (\([WE]\)\(\d\+\)) \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + \ && l:match[3] is# 'W' + \ && index(range(611, 614), str2nr(l:match[4])) >= 0 + continue + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:match[3], + \ 'code': l:match[3] . l:match[4], + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('lua', { +\ 'name': 'luacheck', +\ '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/lua/selene.vim b/ale_linters/lua/selene.vim new file mode 100644 index 00000000..6b33cbfd --- /dev/null +++ b/ale_linters/lua/selene.vim @@ -0,0 +1,46 @@ +call ale#Set('lua_selene_executable', 'selene') +call ale#Set('lua_selene_options', '') + +function! ale_linters#lua#selene#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'lua_selene_options')) + \ . ' --display-style=json -' +endfunction + +function! ale_linters#lua#selene#Handle(buffer, lines) abort + let l:output = [] + + for l:line in a:lines + " as of version 0.17.0, selene has no way to suppress summary + " information when outputting json, so stop processing when we hit it + " (PR for this here: https://github.com/Kampfkarren/selene/pull/356) + if l:line is# 'Results:' + break + endif + + let l:json = json_decode(l:line) + let l:lint = { + \ 'lnum': l:json.primary_label.span.start_line + 1, + \ 'end_lnum': l:json.primary_label.span.end_line + 1, + \ 'col': l:json.primary_label.span.start_column + 1, + \ 'end_col': l:json.primary_label.span.end_column, + \ 'text': l:json.message, + \ 'code': l:json.code, + \ 'type': l:json.severity is# 'Warning' ? 'W' : 'E', + \} + + if has_key(l:json, 'notes') && len(l:json.notes) > 0 + let l:lint.detail = l:lint.text . "\n\n" . join(l:json.notes, "\n") + endif + + call add(l:output, l:lint) + endfor + + return l:output +endfunction + +call ale#linter#Define('lua', { +\ 'name': 'selene', +\ 'executable': {b -> ale#Var(b, 'lua_selene_executable')}, +\ 'command': function('ale_linters#lua#selene#GetCommand'), +\ 'callback': 'ale_linters#lua#selene#Handle', +\}) diff --git a/ale_linters/mail/alex.vim b/ale_linters/mail/alex.vim new file mode 100644 index 00000000..0fceea7b --- /dev/null +++ b/ale_linters/mail/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for mail files + +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..f68dab7a --- /dev/null +++ b/ale_linters/mail/languagetool.vim @@ -0,0 +1,5 @@ +" Author: Vincent (wahrwolf [at] wolfpit.net) +" Description: languagetool for mails + + +call ale#handlers#languagetool#DefineLinter('mail') diff --git a/ale_linters/mail/proselint.vim b/ale_linters/mail/proselint.vim new file mode 100644 index 00000000..82c8d1f8 --- /dev/null +++ b/ale_linters/mail/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for mail files + +call ale#linter#Define('mail', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/mail/vale.vim b/ale_linters/mail/vale.vim new file mode 100644 index 00000000..e6dfd2ec --- /dev/null +++ b/ale_linters/mail/vale.vim @@ -0,0 +1,9 @@ +" Author: chew-z https://github.com/chew-z +" Description: vale for Markdown files + +call ale#linter#Define('mail', { +\ 'name': 'vale', +\ 'executable': 'vale', +\ 'command': 'vale --output=JSON %t', +\ 'callback': 'ale#handlers#vale#Handle', +\}) diff --git a/ale_linters/make/checkmake.vim b/ale_linters/make/checkmake.vim new file mode 100644 index 00000000..fed01b5f --- /dev/null +++ b/ale_linters/make/checkmake.vim @@ -0,0 +1,37 @@ +" Author: aurieh - https://github.com/aurieh + +call ale#Set('make_checkmake_config', '') + +function! ale_linters#make#checkmake#Handle(buffer, lines) abort + let l:pattern = '\v^(\d+):(.+):(.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'type': 'E', + \ 'code': l:match[2], + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +function! ale_linters#make#checkmake#GetCommand(buffer) abort + let l:config = ale#Var(a:buffer, 'make_checkmake_config') + let l:cmd = 'checkmake' + \ . ' --format="{{.LineNumber}}:{{.Rule}}:{{.Violation}}{{\"\r\n\"}}"' + \ . (!empty(l:config) ? ' --config="' . l:config . '"' : '') + \ . ' %s' + + return l:cmd +endfunction + +call ale#linter#Define('make', { +\ 'name': 'checkmake', +\ 'executable': 'checkmake', +\ 'command': function('ale_linters#make#checkmake#GetCommand'), +\ 'callback': 'ale_linters#make#checkmake#Handle', +\}) diff --git a/ale_linters/markdown/alex.vim b/ale_linters/markdown/alex.vim new file mode 100644 index 00000000..63769b5e --- /dev/null +++ b/ale_linters/markdown/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for markdown files + +call ale#handlers#alex#DefineLinter('markdown', '') diff --git a/ale_linters/markdown/cspell.vim b/ale_linters/markdown/cspell.vim new file mode 100644 index 00000000..45c586cc --- /dev/null +++ b/ale_linters/markdown/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Markdown files. + +call ale#handlers#cspell#DefineLinter('markdown') diff --git a/ale_linters/markdown/languagetool.vim b/ale_linters/markdown/languagetool.vim new file mode 100644 index 00000000..422a38c3 --- /dev/null +++ b/ale_linters/markdown/languagetool.vim @@ -0,0 +1,5 @@ +" Author: Vincent (wahrwolf [at] 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 new file mode 100644 index 00000000..424c9f24 --- /dev/null +++ b/ale_linters/markdown/markdownlint.vim @@ -0,0 +1,27 @@ +" Author: Ty-Lucas Kelley +" Description: Adds support for markdownlint + +call ale#Set('markdown_markdownlint_executable', 'markdownlint') +call ale#Set('markdown_markdownlint_options', '') + +function! ale_linters#markdown#markdownlint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'markdown_markdownlint_executable') +endfunction + +function! ale_linters#markdown#markdownlint#GetCommand(buffer) abort + let l:executable = ale_linters#markdown#markdownlint#GetExecutable(a:buffer) + + let l:options = ale#Var(a:buffer, 'markdown_markdownlint_options') + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') . ' %s' +endfunction + +call ale#linter#Define('markdown', { +\ 'name': 'markdownlint', +\ 'executable': function('ale_linters#markdown#markdownlint#GetExecutable'), +\ 'lint_file': 1, +\ 'output_stream': 'both', +\ 'command': function('ale_linters#markdown#markdownlint#GetCommand'), +\ 'callback': 'ale#handlers#markdownlint#Handle' +\}) diff --git a/ale_linters/markdown/marksman.vim b/ale_linters/markdown/marksman.vim new file mode 100644 index 00000000..b23f0e05 --- /dev/null +++ b/ale_linters/markdown/marksman.vim @@ -0,0 +1,35 @@ +" Author: Peter Benjamin +" Description: Write Markdown with code assist and intelligence in the comfort of your favourite editor. + +call ale#Set('markdown_marksman_executable', 'marksman') + +function! ale_linters#markdown#marksman#GetCommand(buffer) abort + return '%e server' +endfunction + +function! ale_linters#markdown#marksman#GetProjectRoot(buffer) abort + " Find nearest .marksman.toml + let l:marksman_toml = ale#path#FindNearestFile(a:buffer, '.marksman.toml') + + if !empty(l:marksman_toml) + return fnamemodify(l:marksman_toml, ':h') + endif + + " Find nearest .git/ directory + let l:project_root = finddir('.git/..', expand('#' . a:buffer . '...').';') + + if !empty(l:project_root) + return l:project_root + endif + + return '' +endfunction + +call ale#linter#Define('markdown', { +\ 'name': 'marksman', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'markdown_marksman_executable')}, +\ 'command': function('ale_linters#markdown#marksman#GetCommand'), +\ 'project_root': function('ale_linters#markdown#marksman#GetProjectRoot'), +\ 'initialization_options': {}, +\}) diff --git a/ale_linters/markdown/mdl.vim b/ale_linters/markdown/mdl.vim new file mode 100644 index 00000000..fd44de6e --- /dev/null +++ b/ale_linters/markdown/mdl.vim @@ -0,0 +1,43 @@ +" Author: Steve Dignam , Josh Leeb-du Toit +" Description: Support for mdl, a markdown linter. + +call ale#Set('markdown_mdl_executable', 'mdl') +call ale#Set('markdown_mdl_options', '') + +function! ale_linters#markdown#mdl#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'markdown_mdl_executable') +endfunction + +function! ale_linters#markdown#mdl#GetCommand(buffer) abort + let l:executable = ale_linters#markdown#mdl#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? 'bundle$' + \ ? ' exec mdl' + \ : '' + + let l:options = ale#Var(a:buffer, 'markdown_mdl_options') + + return ale#Escape(l:executable) . l:exec_args + \ . ' -j' . (!empty(l:options) ? ' ' . l:options : '') +endfunction + +function! ale_linters#markdown#mdl#Handle(buffer, lines) abort + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + call add(l:output, { + \ 'lnum': l:error['line'], + \ 'code': l:error['rule'] . '/' . join(l:error['aliases'], '/'), + \ 'text': l:error['description'], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('markdown', { +\ 'name': 'mdl', +\ '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/proselint.vim b/ale_linters/markdown/proselint.vim new file mode 100644 index 00000000..289d8819 --- /dev/null +++ b/ale_linters/markdown/proselint.vim @@ -0,0 +1,9 @@ +" Author: poohzrn https://github.com/poohzrn +" Description: proselint for Markdown files + +call ale#linter#Define('markdown', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/markdown/pymarkdown.vim b/ale_linters/markdown/pymarkdown.vim new file mode 100644 index 00000000..7700974b --- /dev/null +++ b/ale_linters/markdown/pymarkdown.vim @@ -0,0 +1,73 @@ + +call ale#Set('markdown_pymarkdown_executable', 'pymarkdown') +call ale#Set('markdown_pymarkdown_options', '') +call ale#Set('markdown_pymarkdown_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('markdown_pymarkdown_auto_pipenv', 0) +call ale#Set('markdown_pymarkdown_auto_poetry', 0) +call ale#Set('markdown_pymarkdown_auto_uv', 0) + +function! ale_linters#markdown#pymarkdown#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'markdown_pymarkdown_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'markdown_pymarkdown_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'markdown_pymarkdown_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'markdown_pymarkdown', ['pymarkdown']) +endfunction + +function! ale_linters#markdown#pymarkdown#GetCommand(buffer) abort + let l:executable = ale_linters#markdown#pymarkdown#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv\|poetry\|uv$' + \ ? ' run pymarkdown' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . ' ' + \ . ale#Var(a:buffer, 'markdown_pymarkdown_options') + \ . 'scan-stdin' +endfunction + +function! ale_linters#markdown#pymarkdown#Handle(buffer, lines) abort + let l:pattern = '\v^(\S*):(\d+):(\d+): ([A-Z]+\d+): (.*)$' + let l:output = [] + " lines are formatted as follows: + " sample.md:1:1: MD022: Headings should be surrounded by blank lines. [Expected: 1; Actual: 0; Below] (blanks-around-headings,blanks-around-headers) + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if(l:match[4] is# 'MD009') + \&& !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:match[4][0], + \ 'text': l:match[5], + \ 'code': l:match[4], + \} + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('markdown', { +\ 'name': 'pymarkdown', +\ 'executable': function('ale_linters#markdown#pymarkdown#GetExecutable'), +\ 'command': function('ale_linters#markdown#pymarkdown#GetCommand'), +\ 'callback': 'ale_linters#markdown#pymarkdown#Handle', +\}) diff --git a/ale_linters/markdown/redpen.vim b/ale_linters/markdown/redpen.vim new file mode 100644 index 00000000..ff2cbaf8 --- /dev/null +++ b/ale_linters/markdown/redpen.vim @@ -0,0 +1,9 @@ +" Author: rhysd https://rhysd.github.io +" Description: Redpen, a proofreading tool (http://redpen.cc) + +call ale#linter#Define('markdown', { +\ 'name': 'redpen', +\ 'executable': 'redpen', +\ 'command': 'redpen -f markdown -r json %t', +\ 'callback': 'ale#handlers#redpen#HandleRedpenOutput', +\}) diff --git a/ale_linters/markdown/remark_lint.vim b/ale_linters/markdown/remark_lint.vim new file mode 100644 index 00000000..6085e7ef --- /dev/null +++ b/ale_linters/markdown/remark_lint.vim @@ -0,0 +1,48 @@ +scriptencoding utf-8 +" Author rhysd https://rhysd.github.io/, Dirk Roorda (dirkroorda), Adrián González Rus (@adrigzr) +" Description: remark-lint for Markdown files +call ale#Set('markdown_remark_lint_executable', 'remark') +call ale#Set('markdown_remark_lint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('markdown_remark_lint_options', '') + +function! ale_linters#markdown#remark_lint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'markdown_remark_lint_options') + + return '%e' . ale#Pad(l:options) . ' --no-stdout --no-color' +endfunction + +function! ale_linters#markdown#remark_lint#Handle(buffer, lines) abort + " matches: ' 1:4 warning Incorrect list-item indent: add 1 space list-item-indent remark-lint' + " matches: ' 18:71-19:1 error Missing new line after list item list-item-spacing remark-lint', + let l:pattern = '^ \+\(\d\+\):\(\d\+\)\(-\(\d\+\):\(\d\+\)\)\? \(warning\|error\) \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:match[6] is# 'error' ? 'E' : 'W', + \ 'text': l:match[7], + \} + + if l:match[3] isnot# '' + let l:item.end_lnum = l:match[4] + 0 + let l:item.end_col = l:match[5] + 0 + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('markdown', { +\ 'name': 'remark_lint', +\ 'aliases': ['remark-lint'], +\ 'executable': {b -> ale#path#FindExecutable(b, 'markdown_remark_lint', [ +\ 'node_modules/.bin/remark', +\ ])}, +\ '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 new file mode 100644 index 00000000..613c8411 --- /dev/null +++ b/ale_linters/markdown/textlint.vim @@ -0,0 +1,9 @@ +" Author: tokida https://rouger.info, Yasuhiro Kiyota +" Description: textlint, a proofreading tool (https://textlint.github.io/) + +call ale#linter#Define('markdown', { +\ 'name': 'textlint', +\ 'executable': function('ale#handlers#textlint#GetExecutable'), +\ 'command': function('ale#handlers#textlint#GetCommand'), +\ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', +\}) diff --git a/ale_linters/markdown/vale.vim b/ale_linters/markdown/vale.vim new file mode 100644 index 00000000..06a64416 --- /dev/null +++ b/ale_linters/markdown/vale.vim @@ -0,0 +1,24 @@ +" Author: chew-z https://github.com/chew-z +" Description: vale for Markdown files + +call ale#Set('markdown_vale_executable', 'vale') +call ale#Set('markdown_vale_input_file', '%t') +call ale#Set('markdown_vale_options', '') + +function! ale_linters#markdown#vale#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'markdown_vale_executable') + let l:input_file = ale#Var(a:buffer, 'markdown_vale_input_file') + + " Defaults to `vale --output=JSON %t` + return ale#Escape(l:executable) + \ . ' --output=JSON ' + \ . ale#Var(a:buffer, 'markdown_vale_options') + \ . ' ' . l:input_file +endfunction + +call ale#linter#Define('markdown', { +\ 'name': 'vale', +\ 'executable': {b -> ale#Var(b, 'markdown_vale_executable')}, +\ 'command': function('ale_linters#markdown#vale#GetCommand'), +\ 'callback': 'ale#handlers#vale#Handle', +\}) diff --git a/ale_linters/markdown/writegood.vim b/ale_linters/markdown/writegood.vim new file mode 100644 index 00000000..7108e7ac --- /dev/null +++ b/ale_linters/markdown/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for Markdown files + +call ale#handlers#writegood#DefineLinter('markdown') diff --git a/ale_linters/matlab/mlint.vim b/ale_linters/matlab/mlint.vim new file mode 100644 index 00000000..e7daf37e --- /dev/null +++ b/ale_linters/matlab/mlint.vim @@ -0,0 +1,44 @@ +" Author: awlayton +" Description: mlint for MATLAB files + +call ale#Set('matlab_mlint_executable', 'mlint') + +function! ale_linters#matlab#mlint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " L 27 (C 1): FNDEF: Terminate statement with semicolon to suppress output. + " L 30 (C 13-15): FNDEF: A quoted string is unterminated. + let l:pattern = '^L \(\d\+\) (C \([0-9-]\+\)): \([A-Z]\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:lnum = l:match[1] + 0 + let l:col = l:match[2] + 0 + let l:code = l:match[3] + let l:text = l:match[4] + + " Suppress erroneous warning about filename + " TODO: Enable this error when copying filename is supported + if l:code is# 'FNDEF' + continue + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:lnum, + \ 'col': l:col, + \ 'text': l:text, + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('matlab', { +\ 'name': 'mlint', +\ '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 new file mode 100644 index 00000000..85969e10 --- /dev/null +++ b/ale_linters/mercury/mmc.vim @@ -0,0 +1,38 @@ +" Author: stewy33 +" Description: Lints mercury files using mmc + +call ale#Set('mercury_mmc_executable', 'mmc') +call ale#Set('mercury_mmc_options', '--make --output-compile-error-lines 100') + +function! ale_linters#mercury#mmc#GetCommand(buffer) abort + return '%e --errorcheck-only ' + \ . ale#Var(a:buffer, 'mercury_mmc_options') + \ . ' %s:t:r' +endfunction + +function! ale_linters#mercury#mmc#Handle(buffer, lines) abort + " output format + " :: : + let l:pattern = '\v^\w+\.m:(\d+):\s+([W|w]arning|.*[E|e]rror.*): (.*)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': substitute(l:match[1], '\v^0*', '', '') + 0, + \ 'type': l:match[2][0] =~? 'W' ? 'W' : 'E', + \ 'text': l:match[2] . ': ' . l:match[3] + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('mercury', { +\ 'name': 'mmc', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'mercury_mmc_executable')}, +\ 'cwd': '%s:h', +\ '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 new file mode 100644 index 00000000..c4f53629 --- /dev/null +++ b/ale_linters/nasm/nasm.vim @@ -0,0 +1,41 @@ +" Author: Oyvind Ingvaldsen +" Description: NASM linter for asmsyntax nasm. + +call ale#Set('nasm_nasm_executable', 'nasm') +call ale#Set('nasm_nasm_options', '') + +function! ale_linters#nasm#nasm#GetCommand(buffer) abort + " Note that NASM requires a trailing slash for the -I option. + let l:separator = has('win32') ? '\' : '/' + let l:output_null = has('win32') ? 'NUL' : '/dev/null' + + return '%e -X gnu -I %s:h' . l:separator + \ . ale#Pad(ale#Var(a:buffer, 'nasm_nasm_options')) + \ . ' %s' + \ . ' -o ' . l:output_null +endfunction + +function! ale_linters#nasm#nasm#Handle(buffer, lines) abort + " Note that we treat 'fatal' as errors. + let l:pattern = '^.\+:\(\d\+\): \([^:]\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'type': l:match[2] =~? 'error\|fatal' ? 'E' : 'W', + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('nasm', { +\ 'name': 'nasm', +\ 'output_stream': 'stderr', +\ 'lint_file': 1, +\ '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 new file mode 100644 index 00000000..b739ca04 --- /dev/null +++ b/ale_linters/nim/nimcheck.vim @@ -0,0 +1,79 @@ +" Author: Baabelfish +" Description: Typechecking for nim files + +let s:end_col_patterns = [ +\ '\v''([^'']+)'' is declared but not used.*', +\ '\videntifier expected, but found ''([^'']+)''', +\ '\vimported and not used: ''([^'']+)''.*', +\ '\vundeclared identifier: ''([^'']+)''', +\ '\v''([^'']+)'' cannot be assigned to', +\ '\vredefinition of ''([^'']+)'';', +\] + +function! ale_linters#nim#nimcheck#Handle(buffer, lines) abort + let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p:t') + let l:pattern = '^\(.\+\.nim\)(\(\d\+\), \(\d\+\)) \(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " Only show errors of the current buffer + " NOTE: Checking filename only is OK because nim enforces unique + " module names. + let l:temp_buffer_filename = fnamemodify(l:match[1], ':p:t') + + if l:buffer_filename isnot# '' && l:temp_buffer_filename isnot# l:buffer_filename + continue + endif + + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'W', + \} + + " Extract error type from message of type 'Error: Some error message' + let l:error_match = matchlist(l:item.text, '^\(.\{-}\): \(.\+\)$') + + if !empty(l:error_match) + if l:error_match[1] is# 'Error' + let l:item.type = 'E' + let l:item.text = l:error_match[2] + elseif l:error_match[1] is# 'Warning' + \|| l:error_match[1] is# 'Hint' + let l:item.text = l:error_match[2] + endif + endif + + let l:code_match = matchlist(l:item.text, '\v^(.+) \[([^ \[]+)\]$') + + if !empty(l:code_match) + let l:item.text = l:code_match[1] + let l:item.code = l:code_match[2] + endif + + " Find position end_col. + for l:col_match in ale#util#GetMatches(l:item.text, s:end_col_patterns) + let l:item.end_col = l:item.col + len(l:col_match[1]) - 1 + endfor + + call add(l:output, l:item) + endfor + + return l:output +endfunction + + +function! ale_linters#nim#nimcheck#GetCommand(buffer) abort + return 'nim check --verbosity:0 --colors:off --listFullPaths %s' +endfunction + + +call ale#linter#Define('nim', { +\ 'name': 'nimcheck', +\ 'executable': 'nim', +\ 'output_stream': 'both', +\ 'command': function('ale_linters#nim#nimcheck#GetCommand'), +\ 'callback': 'ale_linters#nim#nimcheck#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/nim/nimlsp.vim b/ale_linters/nim/nimlsp.vim new file mode 100644 index 00000000..5d041043 --- /dev/null +++ b/ale_linters/nim/nimlsp.vim @@ -0,0 +1,33 @@ +" Author: jeremija +" Description: Support for nimlsp (language server for nim) + +call ale#Set('nim_nimlsp_nim_sources', '') + +function! ale_linters#nim#nimlsp#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git') + + if !empty(l:project_root) + return fnamemodify(l:project_root, ':h:h') + endif + + return '' +endfunction + +function! ale_linters#nim#nimlsp#GetCommand(buffer) abort + let l:nim_sources = ale#Var(a:buffer, 'nim_nimlsp_nim_sources') + + if !empty(l:nim_sources) + let l:nim_sources = ale#Escape(l:nim_sources) + endif + + return '%e' . ale#Pad(l:nim_sources) +endfunction + +call ale#linter#Define('nim', { +\ 'name': 'nimlsp', +\ 'lsp': 'stdio', +\ 'executable': 'nimlsp', +\ 'command': function('ale_linters#nim#nimlsp#GetCommand'), +\ 'language': 'nim', +\ 'project_root': function('ale_linters#nim#nimlsp#GetProjectRoot'), +\}) diff --git a/ale_linters/nix/deadnix.vim b/ale_linters/nix/deadnix.vim new file mode 100644 index 00000000..3e8aec66 --- /dev/null +++ b/ale_linters/nix/deadnix.vim @@ -0,0 +1,13 @@ +call ale#Set('nix_deadnix_executable', 'deadnix') +call ale#Set('nix_deadnix_options', '') + +function! ale_linters#nix#deadnix#GetCommand(buffer) abort + return '%e -o json' . ale#Pad(ale#Var(a:buffer, 'nix_deadnix_options')) . ' -- %t' +endfunction + +call ale#linter#Define('nix', { +\ 'name': 'deadnix', +\ 'executable': {b -> ale#Var(b, 'nix_deadnix_executable')}, +\ 'command': function('ale_linters#nix#deadnix#GetCommand'), +\ 'callback': 'ale#handlers#deadnix#Handle', +\}) diff --git a/ale_linters/nix/nix.vim b/ale_linters/nix/nix.vim new file mode 100644 index 00000000..8d41cb7b --- /dev/null +++ b/ale_linters/nix/nix.vim @@ -0,0 +1,63 @@ +" Author: Alistair Bill <@alibabzo> +" Author: Maximilian Bosch +" Description: nix-instantiate linter for nix files + +function! ale_linters#nix#nix#Command(buffer, output, meta) abort + let l:version = a:output[0][22:] + + if l:version =~# '^\(1\|2.[0-3]\.\).*' + return 'nix-instantiate --parse -' + else + return 'nix-instantiate --log-format internal-json --parse -' + endif +endfunction + +function! ale_linters#nix#nix#Handle(buffer, lines) abort + let l:output = [] + + if empty(a:lines) + return l:output + endif + + if a:lines[0] =~# '^@nix .*' + for l:line in a:lines + if l:line =~# '^@nix .*' + let l:result = json_decode(strpart(l:line, 4)) + + if has_key(l:result, 'column') + call add(l:output, { + \ 'type': 'E', + \ 'lnum': l:result.line, + \ 'col': l:result.column, + \ 'text': substitute(l:result.raw_msg, '\e\[[0-9;]*m', '', 'g'), + \}) + endif + endif + endfor + else + let l:pattern = '^\(.\+\): \(.\+\) at .*:\(\d\+\):\(\d\+\)$' + + 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, + \ 'text': l:match[1] . ': ' . substitute(l:match[2], ',$', '', ''), + \ 'type': l:match[1] =~# '^error' ? 'E' : 'W', + \}) + endfor + endif + + return l:output +endfunction + +call ale#linter#Define('nix', { +\ 'name': 'nix', +\ 'output_stream': 'stderr', +\ 'executable': 'nix-instantiate', +\ 'command': {buffer -> ale#command#Run( +\ buffer, +\ 'nix-instantiate --version', +\ function('ale_linters#nix#nix#Command') +\ )}, +\ 'callback': 'ale_linters#nix#nix#Handle', +\}) diff --git a/ale_linters/nix/rnix_lsp.vim b/ale_linters/nix/rnix_lsp.vim new file mode 100644 index 00000000..99c0fbfa --- /dev/null +++ b/ale_linters/nix/rnix_lsp.vim @@ -0,0 +1,17 @@ +" Author: jD91mZM2 +" Description: rnix-lsp language client + +function! ale_linters#nix#rnix_lsp#GetProjectRoot(buffer) abort + " rnix-lsp does not yet use the project root, so getting it right is not + " important + return fnamemodify(a:buffer, ':h') +endfunction + +call ale#linter#Define('nix', { +\ 'name': 'rnix_lsp', +\ 'aliases': ['rnix'], +\ 'lsp': 'stdio', +\ 'executable': 'rnix-lsp', +\ 'command': '%e', +\ 'project_root': function('ale_linters#nix#rnix_lsp#GetProjectRoot'), +\}) diff --git a/ale_linters/nix/statix.vim b/ale_linters/nix/statix.vim new file mode 100644 index 00000000..a90a68a6 --- /dev/null +++ b/ale_linters/nix/statix.vim @@ -0,0 +1,18 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: statix analysis and suggestions for Nix files + +call ale#Set('nix_statix_check_executable', 'statix') +call ale#Set('nix_statix_check_options', '') + +function! ale_linters#nix#statix#GetCommand(buffer) abort + return '%e check -o errfmt --stdin' + \ . ale#Pad(ale#Var(a:buffer, 'nix_statix_check_options')) +endfunction + +call ale#linter#Define('nix', { +\ 'name': 'statix', +\ 'executable': {b -> ale#Var(b, 'nix_statix_check_executable')}, +\ 'command': function('ale_linters#nix#statix#GetCommand'), +\ 'callback': 'ale#handlers#statix#Handle', +\}) diff --git a/ale_linters/nroff/alex.vim b/ale_linters/nroff/alex.vim new file mode 100644 index 00000000..3f06af26 --- /dev/null +++ b/ale_linters/nroff/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for nroff files + +call ale#handlers#alex#DefineLinter('nroff', '--text') diff --git a/ale_linters/nroff/proselint.vim b/ale_linters/nroff/proselint.vim new file mode 100644 index 00000000..a23e56b1 --- /dev/null +++ b/ale_linters/nroff/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for nroff files + +call ale#linter#Define('nroff', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/nroff/writegood.vim b/ale_linters/nroff/writegood.vim new file mode 100644 index 00000000..bcf344f6 --- /dev/null +++ b/ale_linters/nroff/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for nroff files + +call ale#handlers#writegood#DefineLinter('nroff') diff --git a/ale_linters/nunjucks/djlint.vim b/ale_linters/nunjucks/djlint.vim new file mode 100644 index 00000000..f71eac6e --- /dev/null +++ b/ale_linters/nunjucks/djlint.vim @@ -0,0 +1,12 @@ +" Author: Adrian Vollmer +" Description: djlint for Django HTML template files + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_options', '') + +call ale#linter#Define('nunjucks', { +\ 'name': 'djlint', +\ 'executable': function('ale#handlers#djlint#GetExecutable'), +\ 'command': function('ale#handlers#djlint#GetCommand'), +\ 'callback': 'ale#handlers#djlint#Handle', +\}) diff --git a/ale_linters/objc/ccls.vim b/ale_linters/objc/ccls.vim new file mode 100644 index 00000000..7aef5325 --- /dev/null +++ b/ale_linters/objc/ccls.vim @@ -0,0 +1,15 @@ +" Author: Ye Jingchen , Ben Falconer , jtalowell +" Description: A language server for Objective-C + +call ale#Set('objc_ccls_executable', 'ccls') +call ale#Set('objc_ccls_init_options', {}) +call ale#Set('c_build_dir', '') + +call ale#linter#Define('objc', { +\ 'name': 'ccls', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'objc_ccls_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), +\ 'initialization_options': {b -> ale#handlers#ccls#GetInitOpts(b, 'objc_ccls_init_options')}, +\}) diff --git a/ale_linters/objc/clang.vim b/ale_linters/objc/clang.vim new file mode 100644 index 00000000..cafb97db --- /dev/null +++ b/ale_linters/objc/clang.vim @@ -0,0 +1,23 @@ +" Author: Bang Lee +" Description: clang linter for objc files + +" Set this option to change the Clang options for warnings for ObjC. +if !exists('g:ale_objc_clang_options') + let g:ale_objc_clang_options = '-std=c11 -Wall' +endif + +function! ale_linters#objc#clang#GetCommand(buffer) abort + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + return 'clang -S -x objective-c -fsyntax-only ' + \ . '-iquote %s:h' + \ . ' ' . ale#Var(a:buffer, 'objc_clang_options') . ' -' +endfunction + +call ale#linter#Define('objc', { +\ 'name': 'clang', +\ 'output_stream': 'stderr', +\ 'executable': 'clang', +\ '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 new file mode 100644 index 00000000..318d85b5 --- /dev/null +++ b/ale_linters/objc/clangd.vim @@ -0,0 +1,17 @@ +" Author: Andrey Melentyev +" Description: Clangd language server + +call ale#Set('objc_clangd_executable', 'clangd') +call ale#Set('objc_clangd_options', '') + +function! ale_linters#objc#clangd#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'objc_clangd_options')) +endfunction + +call ale#linter#Define('objc', { +\ 'name': 'clangd', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'objc_clangd_executable')}, +\ 'command': function('ale_linters#objc#clangd#GetCommand'), +\ 'project_root': function('ale#c#FindProjectRoot'), +\}) diff --git a/ale_linters/objcpp/clang.vim b/ale_linters/objcpp/clang.vim new file mode 100644 index 00000000..35a40c6f --- /dev/null +++ b/ale_linters/objcpp/clang.vim @@ -0,0 +1,23 @@ +" Author: Bang Lee +" Description: clang linter for objcpp files + +" Set this option to change the Clang options for warnings for ObjCPP. +if !exists('g:ale_objcpp_clang_options') + let g:ale_objcpp_clang_options = '-std=c++14 -Wall' +endif + +function! ale_linters#objcpp#clang#GetCommand(buffer) abort + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + return 'clang++ -S -x objective-c++ -fsyntax-only ' + \ . '-iquote %s:h' + \ . ' ' . ale#Var(a:buffer, 'objcpp_clang_options') . ' -' +endfunction + +call ale#linter#Define('objcpp', { +\ 'name': 'clang', +\ 'output_stream': 'stderr', +\ 'executable': 'clang++', +\ '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 new file mode 100644 index 00000000..29455325 --- /dev/null +++ b/ale_linters/objcpp/clangd.vim @@ -0,0 +1,17 @@ +" Author: Andrey Melentyev +" Description: Clangd language server + +call ale#Set('objcpp_clangd_executable', 'clangd') +call ale#Set('objcpp_clangd_options', '') + +function! ale_linters#objcpp#clangd#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'objcpp_clangd_options')) +endfunction + +call ale#linter#Define('objcpp', { +\ 'name': 'clangd', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'objcpp_clangd_executable')}, +\ 'command': function('ale_linters#objcpp#clangd#GetCommand'), +\ 'project_root': function('ale#c#FindProjectRoot'), +\}) diff --git a/ale_linters/ocaml/merlin.vim b/ale_linters/ocaml/merlin.vim new file mode 100644 index 00000000..cfec9966 --- /dev/null +++ b/ale_linters/ocaml/merlin.vim @@ -0,0 +1,17 @@ +" Author: Andrey Popp -- @andreypopp +" Description: Report errors in OCaml code with Merlin + +if !exists('g:merlin') + finish +endif + +function! ale_linters#ocaml#merlin#Handle(buffer, lines) abort + return merlin#ErrorLocList() +endfunction + +call ale#linter#Define('ocaml', { +\ 'name': 'merlin', +\ 'executable': 'ocamlmerlin', +\ 'command': 'true', +\ 'callback': 'ale_linters#ocaml#merlin#Handle', +\}) diff --git a/ale_linters/ocaml/ocamllsp.vim b/ale_linters/ocaml/ocamllsp.vim new file mode 100644 index 00000000..4ff7419c --- /dev/null +++ b/ale_linters/ocaml/ocamllsp.vim @@ -0,0 +1,13 @@ +" Author: Risto Stevcev +" Description: The official language server for OCaml + +call ale#Set('ocaml_ocamllsp_use_opam', 1) + +call ale#linter#Define('ocaml', { +\ 'name': 'ocamllsp', +\ 'lsp': 'stdio', +\ 'executable': function('ale#handlers#ocamllsp#GetExecutable'), +\ 'command': function('ale#handlers#ocamllsp#GetCommand'), +\ 'language': function('ale#handlers#ocamllsp#GetLanguage'), +\ 'project_root': function('ale#handlers#ocamllsp#GetProjectRoot'), +\}) diff --git a/ale_linters/ocaml/ols.vim b/ale_linters/ocaml/ols.vim new file mode 100644 index 00000000..b26c7826 --- /dev/null +++ b/ale_linters/ocaml/ols.vim @@ -0,0 +1,15 @@ +" Author: Michael Jungo +" Description: A language server for OCaml + +call ale#Set('ocaml_ols_executable', 'ocaml-language-server') +call ale#Set('ocaml_ols_use_global', get(g:, 'ale_use_global_executables', 0)) + +call ale#linter#Define('ocaml', { +\ 'name': 'ols', +\ 'aliases': ['ocaml-language-server'], +\ 'lsp': 'stdio', +\ 'executable': function('ale#handlers#ols#GetExecutable'), +\ 'command': function('ale#handlers#ols#GetCommand'), +\ 'language': function('ale#handlers#ols#GetLanguage'), +\ 'project_root': function('ale#handlers#ols#GetProjectRoot'), +\}) diff --git a/ale_linters/ocamlinterface/merlin.vim b/ale_linters/ocamlinterface/merlin.vim new file mode 100644 index 00000000..799490f7 --- /dev/null +++ b/ale_linters/ocamlinterface/merlin.vim @@ -0,0 +1,17 @@ +" Author: Andrey Popp -- @andreypopp +" Description: Report errors in OCaml code with Merlin + +if !exists('g:merlin') + finish +endif + +function! ale_linters#ocamlinterface#merlin#Handle(buffer, lines) abort + return merlin#ErrorLocList() +endfunction + +call ale#linter#Define('ocamlinterface', { +\ 'name': 'merlin', +\ 'executable': 'ocamlmerlin', +\ 'command': 'true', +\ 'callback': 'ale_linters#ocamlinterface#merlin#Handle', +\}) diff --git a/ale_linters/ocamlinterface/ocamllsp.vim b/ale_linters/ocamlinterface/ocamllsp.vim new file mode 100644 index 00000000..cd4bea80 --- /dev/null +++ b/ale_linters/ocamlinterface/ocamllsp.vim @@ -0,0 +1,13 @@ +" Author: Risto Stevcev +" Description: The official language server for OCaml + +call ale#Set('ocaml_ocamllsp_use_opam', 1) + +call ale#linter#Define('ocamlinterface', { +\ 'name': 'ocamllsp', +\ 'lsp': 'stdio', +\ 'executable': function('ale#handlers#ocamllsp#GetExecutable'), +\ 'command': function('ale#handlers#ocamllsp#GetCommand'), +\ 'language': function('ale#handlers#ocamllsp#GetLanguage'), +\ 'project_root': function('ale#handlers#ocamllsp#GetProjectRoot'), +\}) diff --git a/ale_linters/odin/ols.vim b/ale_linters/odin/ols.vim new file mode 100644 index 00000000..242dea0e --- /dev/null +++ b/ale_linters/odin/ols.vim @@ -0,0 +1,19 @@ +" Author: Benjamin Block +" Description: A language server for Odin. + +function! ale_linters#odin#ols#GetProjectRoot(buffer) abort + return fnamemodify('', ':h') +endfunction + +call ale#Set('odin_ols_executable', 'ols') +call ale#Set('odin_ols_config', {}) + +call ale#linter#Define('odin', { +\ 'name': 'ols', +\ 'lsp': 'stdio', +\ 'language': 'odin', +\ 'lsp_config': {b -> ale#Var(b, 'odin_ols_config')}, +\ 'executable': {b -> ale#Var(b, 'odin_ols_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#odin#ols#GetProjectRoot'), +\}) diff --git a/ale_linters/openapi/ibm_validator.vim b/ale_linters/openapi/ibm_validator.vim new file mode 100644 index 00000000..446931a2 --- /dev/null +++ b/ale_linters/openapi/ibm_validator.vim @@ -0,0 +1,58 @@ +" Author: Horacio Sanson + +call ale#Set('openapi_ibm_validator_executable', 'lint-openapi') +call ale#Set('openapi_ibm_validator_options', '') + +function! ale_linters#openapi#ibm_validator#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'openapi_ibm_validator_options')) + \ . ' %t' +endfunction + +function! ale_linters#openapi#ibm_validator#Handle(buffer, lines) abort + let l:output = [] + let l:type = 'E' + let l:message = '' + let l:nr = -1 + + for l:line in a:lines + let l:match = matchlist(l:line, '^errors$') + + if !empty(l:match) + let l:type = 'E' + endif + + let l:match = matchlist(l:line, '^warnings$') + + if !empty(l:match) + let l:type = 'W' + endif + + let l:match = matchlist(l:line, '^ *Message : *\(.\+\)$') + + if !empty(l:match) + let l:message = l:match[1] + endif + + let l:match = matchlist(l:line, '^ *Line *: *\(\d\+\)$') + + if !empty(l:match) + let l:nr = l:match[1] + + call add(l:output, { + \ 'lnum': l:nr + 0, + \ 'col': 0, + \ 'text': l:message, + \ 'type': l:type, + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('openapi', { +\ 'name': 'ibm_validator', +\ 'executable': {b -> ale#Var(b, 'openapi_ibm_validator_executable')}, +\ 'command': function('ale_linters#openapi#ibm_validator#GetCommand'), +\ 'callback': 'ale_linters#openapi#ibm_validator#Handle', +\}) diff --git a/ale_linters/openapi/yamllint.vim b/ale_linters/openapi/yamllint.vim new file mode 100644 index 00000000..2b8952cc --- /dev/null +++ b/ale_linters/openapi/yamllint.vim @@ -0,0 +1,9 @@ +call ale#Set('yaml_yamllint_executable', 'yamllint') +call ale#Set('yaml_yamllint_options', '') + +call ale#linter#Define('openapi', { +\ 'name': 'yamllint', +\ 'executable': {b -> ale#Var(b, 'yaml_yamllint_executable')}, +\ 'command': function('ale#handlers#yamllint#GetCommand'), +\ 'callback': 'ale#handlers#yamllint#Handle', +\}) diff --git a/ale_linters/openscad/sca2d.vim b/ale_linters/openscad/sca2d.vim new file mode 100644 index 00000000..60804a13 --- /dev/null +++ b/ale_linters/openscad/sca2d.vim @@ -0,0 +1,24 @@ +" Description: SCA2D linter for OpenSCAD files + +call ale#Set('openscad_sca2d_executable', 'sca2d') +call ale#Set('openscad_sca2d_options', '') + +function! ale_linters#openscad#sca2d#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'openscad_sca2d_executable') +endfunction + +function! ale_linters#openscad#sca2d#GetCommand(buffer) abort + let l:executable = ale_linters#openscad#sca2d#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'openscad_sca2d_options') + + return ale#Escape(l:executable) . ale#Pad(l:options) . ' %s' +endfunction + +call ale#linter#Define('openscad', { +\ 'name': 'SCA2D', +\ 'aliases': ['sca2d'], +\ 'executable': function('ale_linters#openscad#sca2d#GetExecutable'), +\ 'command': function('ale_linters#openscad#sca2d#GetCommand'), +\ 'callback': 'ale#handlers#openscad#SCA2D_callback', +\ 'lint_file': 1, +\ }) diff --git a/ale_linters/perl/perl.vim b/ale_linters/perl/perl.vim new file mode 100644 index 00000000..0f06528a --- /dev/null +++ b/ale_linters/perl/perl.vim @@ -0,0 +1,64 @@ +" Author: Vincent Lequertier +" Description: This file adds support for checking perl syntax + +call ale#Set('perl_perl_executable', 'perl') +call ale#Set('perl_perl_options', '-c -Mwarnings -Ilib') + +function! ale_linters#perl#perl#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'perl_perl_options')) . ' %t' +endfunction + +let s:begin_failed_skip_pattern = '\v' . join([ +\ '^Compilation failed in require', +\ '^Can''t locate', +\], '|') + +function! ale_linters#perl#perl#Handle(buffer, lines) abort + if empty(a:lines) + return [] + endif + + let l:pattern = '\(..\{-}\) at \(..\{-}\) line \(\d\+\)' + let l:output = [] + let l:basename = expand('#' . a:buffer . ':t') + + let l:type = 'E' + + if a:lines[-1] =~# 'syntax OK' + let l:type = 'W' + endif + + let l:seen = {} + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[3] + let l:file = l:match[2] + let l:text = l:match[1] + + if ale#path#IsBufferPath(a:buffer, l:file) + \ && !has_key(l:seen,l:line) + \ && ( + \ l:text isnot# 'BEGIN failed--compilation aborted' + \ || empty(l:output) + \ || match(l:output[-1].text, s:begin_failed_skip_pattern) < 0 + \ ) + call add(l:output, { + \ 'lnum': l:line, + \ 'text': l:text, + \ 'type': l:type, + \}) + + let l:seen[l:line] = 1 + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('perl', { +\ 'name': 'perl', +\ 'executable': {b -> ale#Var(b, 'perl_perl_executable')}, +\ 'output_stream': 'both', +\ '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 new file mode 100644 index 00000000..f3154c09 --- /dev/null +++ b/ale_linters/perl/perlcritic.vim @@ -0,0 +1,61 @@ +" Author: Vincent Lequertier , Chris Weyl +" Description: This file adds support for checking perl with perl critic + +call ale#Set('perl_perlcritic_executable', 'perlcritic') +call ale#Set('perl_perlcritic_profile', '.perlcriticrc') +call ale#Set('perl_perlcritic_options', '') +call ale#Set('perl_perlcritic_showrules', 0) + +function! ale_linters#perl#perlcritic#GetProfile(buffer) abort + " first see if we've been overridden + let l:profile = ale#Var(a:buffer, 'perl_perlcritic_profile') + + if l:profile is? '' + return '' + endif + + " otherwise, iterate upwards to find it + return ale#path#FindNearestFile(a:buffer, l:profile) +endfunction + +function! ale_linters#perl#perlcritic#GetCommand(buffer) abort + let l:critic_verbosity = '%l:%c %m\n' + + if ale#Var(a:buffer, 'perl_perlcritic_showrules') + let l:critic_verbosity = '%l:%c %m [%p]\n' + endif + + let l:profile = ale_linters#perl#perlcritic#GetProfile(a:buffer) + let l:options = ale#Var(a:buffer, 'perl_perlcritic_options') + + return '%e' + \ . ' --verbose ' . ale#Escape(l:critic_verbosity) + \ . ' --nocolor' + \ . (!empty(l:profile) ? ' --profile ' . ale#Escape(l:profile) : '') + \ . ale#Pad(l:options) +endfunction + + +function! ale_linters#perl#perlcritic#Handle(buffer, lines) abort + let l:pattern = '\(\d\+\):\(\d\+\) \(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1], + \ 'col': l:match[2], + \ 'text': l:match[3], + \ 'type': 'W' + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('perl', { +\ 'name': 'perlcritic', +\ 'output_stream': 'stdout', +\ '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 new file mode 100644 index 00000000..444ae4d7 --- /dev/null +++ b/ale_linters/perl6/perl6.vim @@ -0,0 +1,166 @@ +" Author:Travis Gibson +" Description: This file adds support for checking perl6 syntax + +let g:ale_perl6_perl6_executable = +\ get(g:, 'ale_perl6_perl6_executable', 'perl6') + +let g:ale_perl6_perl6_options = +\ get(g:, 'ale_perl6_perl6_options', '-c -Ilib') + +let $PERL6_EXCEPTIONS_HANDLER = 'JSON' + +let $RAKUDO_ERROR_COLOR = 0 + +function! ale_linters#perl6#perl6#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'perl6_perl6_executable') +endfunction + +function! ale_linters#perl6#perl6#GetCommand(buffer) abort + return ale_linters#perl6#perl6#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'perl6_perl6_options') + \ . ' %t' +endfunction + +function! ale_linters#perl6#perl6#ExtractError(dict, item, type, buffer) abort + let l:file = '' + let l:line = 1 + let l:column = '' + let l:text = '' + let l:pre = '' + let l:counter = 2 + let l:end_line = '' + let l:linepatternmessage = 'at\s\+line\s\+\(\d\+\)' + + if has_key(a:dict[a:item], 'filename') && !empty(a:dict[a:item]['filename']) + let l:file = a:dict[a:item]['filename'] + 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 + endif + + if has_key(a:dict[a:item], 'column') && !empty(a:dict[a:item]['column']) + let l:column = a:dict[a:item]['column'] + endif + + if has_key(a:dict[a:item], 'message') && !empty(a:dict[a:item]['message']) + let l:text = substitute(a:dict[a:item]['message'], '\s*\n\s*', ' ', 'g') + let l:counter -= 1 + endif + + if has_key(a:dict[a:item], 'line-real') && !empty(a:dict[a:item]['line-real']) + let l:end_line = l:line + let l:line = a:dict[a:item]['line-real'] + endif + + for l:match in ale#util#GetMatches(l:text, l:linepatternmessage) + let l:line = l:match[1] + let l:counter -= 1 + endfor + +" 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# '' ) + return { + \ 'lnum': '' . l:line, + \ 'text': l:text, + \ 'type': a:type, + \ 'col': l:column, + \ 'end_lnum': l:end_line, + \ 'code': a:item, + \} + endif + + return '' +endfunction + +function! ale_linters#perl6#perl6#Handle(buffer, lines) abort + let l:output = [] + + if empty(a:lines) + return l:output + endif + + if a:lines[0] is# 'Syntax OK' + return l:output + endif + + try + let l:json = json_decode(join(a:lines, '')) + catch /E474\|E491/ + call add(l:output, { + \ 'lnum': '1', + \ 'text': 'Received output in the default Perl6 error format. See :ALEDetail for details', + \ 'detail': join(a:lines, "\n"), + \ 'type': 'W', + \ }) + + return l:output + endtry + + 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 !empty(l:json[l:key]['sorrows']) + for l:dictionary in get(l:json[l:key], 'sorrows') + for l:item in keys(l:dictionary) + let l:result = + \ ale_linters#perl6#perl6#ExtractError( + \ l:dictionary, + \ l:item, + \ 'E', + \ a:buffer, + \ ) + + if l:result isnot# '' + call add(l:output, l:result) + endif + endfor + endfor + endif + + if !empty(l:json[l:key]['worries']) + for l:dictionary in get(l:json[l:key], 'worries') + for l:item in keys(l:dictionary) + let l:result = + \ ale_linters#perl6#perl6#ExtractError( + \ l:dictionary, + \ l:item, + \ 'W', + \ a:buffer, + \ ) + + if l:result isnot# '' + call add(l:output, l:result) + endif + endfor + endfor + endif + else + let l:result = ale_linters#perl6#perl6#ExtractError( + \ l:json, + \ l:key, + \ 'E', + \ a:buffer, + \ ) + + if l:result isnot# '' + call add(l:output, l:result) + endif + endif + endfor + endif + + return l:output +endfunction + +call ale#linter#Define('perl6', { +\ 'name': 'perl6', +\ 'executable': function('ale_linters#perl6#perl6#GetExecutable'), +\ 'output_stream': 'both', +\ 'command': function('ale_linters#perl6#perl6#GetCommand'), +\ 'callback': 'ale_linters#perl6#perl6#Handle', +\}) + diff --git a/ale_linters/php/cspell.vim b/ale_linters/php/cspell.vim new file mode 100644 index 00000000..574df575 --- /dev/null +++ b/ale_linters/php/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for PHP files. + +call ale#handlers#cspell#DefineLinter('php') diff --git a/ale_linters/php/intelephense.vim b/ale_linters/php/intelephense.vim new file mode 100755 index 00000000..0fdcc93e --- /dev/null +++ b/ale_linters/php/intelephense.vim @@ -0,0 +1,32 @@ +" Author: Eric Stern , +" Arnold Chand +" Description: Intelephense language server integration for ALE + +call ale#Set('php_intelephense_executable', 'intelephense') +call ale#Set('php_intelephense_use_global', 1) +call ale#Set('php_intelephense_config', {}) + +function! ale_linters#php#intelephense#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') : '' +endfunction + +function! ale_linters#php#intelephense#GetInitializationOptions(buffer) abort + return ale#Var(a:buffer, 'php_intelephense_config') +endfunction + +call ale#linter#Define('php', { +\ 'name': 'intelephense', +\ 'lsp': 'stdio', +\ 'initialization_options': function('ale_linters#php#intelephense#GetInitializationOptions'), +\ 'executable': {b -> ale#path#FindExecutable(b, 'php_intelephense', [])}, +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#php#intelephense#GetProjectRoot'), +\}) diff --git a/ale_linters/php/langserver.vim b/ale_linters/php/langserver.vim new file mode 100644 index 00000000..c3d89a00 --- /dev/null +++ b/ale_linters/php/langserver.vim @@ -0,0 +1,27 @@ +" Author: Eric Stern +" Description: PHP Language server integration for ALE + +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') : '' +endfunction + +call ale#linter#Define('php', { +\ 'name': 'langserver', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#path#FindExecutable(b, 'php_langserver', [ +\ 'vendor/bin/php-language-server.php', +\ ])}, +\ 'command': 'php %e', +\ 'project_root': function('ale_linters#php#langserver#GetProjectRoot'), +\}) diff --git a/ale_linters/php/phan.vim b/ale_linters/php/phan.vim new file mode 100644 index 00000000..50c6d6e6 --- /dev/null +++ b/ale_linters/php/phan.vim @@ -0,0 +1,75 @@ +" Author: diegoholiveira , haginaga +" Description: static analyzer for PHP + +" Define the minimum severity +let g:ale_php_phan_minimum_severity = get(g:, 'ale_php_phan_minimum_severity', 0) + +let g:ale_php_phan_executable = get(g:, 'ale_php_phan_executable', 'phan') +let g:ale_php_phan_use_client = get(g:, 'ale_php_phan_use_client', 0) + +function! ale_linters#php#phan#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'php_phan_executable') + + if ale#Var(a:buffer, 'php_phan_use_client') == 1 && l:executable is# 'phan' + let l:executable = 'phan_client' + endif + + return l:executable +endfunction + +function! ale_linters#php#phan#GetCommand(buffer) abort + if ale#Var(a:buffer, 'php_phan_use_client') == 1 + let l:args = '-l ' + \ . ' %s' + else + let l:args = '-y ' + \ . ale#Var(a:buffer, 'php_phan_minimum_severity') + \ . ' %s' + endif + + let l:executable = ale_linters#php#phan#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' ' . l:args +endfunction + +function! ale_linters#php#phan#Handle(buffer, lines) abort + " Matches against lines like the following: + if ale#Var(a:buffer, 'php_phan_use_client') == 1 + " Phan error: ERRORTYPE: message in /path/to/some-filename.php on line nnn + let l:pattern = '^Phan error: \(\w\+\): \(.\+\) in \(.\+\) on line \(\d\+\)$' + else + " /path/to/some-filename.php:18 ERRORTYPE message + let l:pattern = '^\(.*\):\(\d\+\)\s\(\w\+\)\s\(.\+\)$' + endif + + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if ale#Var(a:buffer, 'php_phan_use_client') == 1 + let l:dict = { + \ 'lnum': l:match[4] + 0, + \ 'text': l:match[2], + \ 'filename': l:match[3], + \ 'type': 'W', + \} + else + let l:dict = { + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[4], + \ 'type': 'W', + \ 'filename': l:match[1], + \} + endif + + call add(l:output, l:dict) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phan', +\ '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 new file mode 100644 index 00000000..51a109b9 --- /dev/null +++ b/ale_linters/php/php.vim @@ -0,0 +1,39 @@ +" Author: Spencer Wood , Adriaan Zonnenberg +" Description: This file adds support for checking PHP with php-cli + +call ale#Set('php_php_executable', 'php') + +function! ale_linters#php#php#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " PHP 7.1<= - Parse error: syntax error, unexpected ';', expecting ']' in - on line 15 + " PHP 7.2>= - Parse error: syntax error, unexpected ';', expecting ']' in Standard input code on line 15 + let l:pattern = '\v^%(Fatal|Parse) error:\s+(.+unexpected ''(.+)%(expecting.+)@ 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/phpactor.vim b/ale_linters/php/phpactor.vim new file mode 100644 index 00000000..b137eaf1 --- /dev/null +++ b/ale_linters/php/phpactor.vim @@ -0,0 +1,23 @@ +" Author: Arizard +" Description: PHPactor integration for ALE + +" Copied from langserver.vim +function! ale_linters#php#phpactor#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') : '' +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phpactor', +\ 'lsp': 'stdio', +\ 'executable': 'phpactor', +\ 'command': '%e language-server', +\ 'project_root': function('ale_linters#php#phpactor#GetProjectRoot'), +\}) diff --git a/ale_linters/php/phpcs.vim b/ale_linters/php/phpcs.vim new file mode 100644 index 00000000..ce47a13b --- /dev/null +++ b/ale_linters/php/phpcs.vim @@ -0,0 +1,54 @@ +" Author: jwilliams108 , Eric Stern +" Description: phpcs for PHP files + +let g:ale_php_phpcs_standard = get(g:, 'ale_php_phpcs_standard', '') + +call ale#Set('php_phpcs_options', '') +call ale#Set('php_phpcs_executable', 'phpcs') +call ale#Set('php_phpcs_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#php#phpcs#GetCommand(buffer) abort + let l:standard = ale#Var(a:buffer, 'php_phpcs_standard') + let l:standard_option = !empty(l:standard) + \ ? '--standard=' . ale#Escape(l:standard) + \ : '' + + return '%e -s --report=emacs --stdin-path=%s' + \ . ale#Pad(l:standard_option) + \ . ale#Pad(ale#Var(a:buffer, 'php_phpcs_options')) +endfunction + +function! ale_linters#php#phpcs#Handle(buffer, lines) abort + " Matches against lines like the following: + " + " /path/to/some-filename.php:18:3: error - Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact) + let l:pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\) - \(.\+\) (\(.\+\)).*$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[5] + let l:text = l:match[4] . ' (' . l:code . ')' + let l:type = l:match[3] + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:text, + \ 'type': l:type is# 'error' ? 'E' : 'W', + \ 'sub_type': 'style', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phpcs', +\ 'executable': {b -> ale#path#FindExecutable(b, 'php_phpcs', [ +\ 'vendor/bin/phpcs', +\ 'phpcs' +\ ])}, +\ 'cwd': '%s:h', +\ '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 new file mode 100644 index 00000000..368a66c3 --- /dev/null +++ b/ale_linters/php/phpmd.vim @@ -0,0 +1,38 @@ +" Author: medains , David Sierra +" Description: phpmd for PHP files + +let g:ale_php_phpmd_executable = get(g:, 'ale_php_phpmd_executable', 'phpmd') + +" Set to change the ruleset +let g:ale_php_phpmd_ruleset = get(g:, 'ale_php_phpmd_ruleset', 'cleancode,codesize,controversial,design,naming,unusedcode') + +function! ale_linters#php#phpmd#GetCommand(buffer) abort + return '%e %t text' + \ . ale#Pad(ale#Var(a:buffer, 'php_phpmd_ruleset')) + \ . ' --ignore-violations-on-exit' +endfunction + +function! ale_linters#php#phpmd#Handle(buffer, lines) abort + " Matches against lines like the following: + " + " /path/to/some-filename.php:18 message + let l:pattern = '^.*:\(\d\+\)\s\+\(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[2], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phpmd', +\ '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 new file mode 100644 index 00000000..d3c80393 --- /dev/null +++ b/ale_linters/php/phpstan.vim @@ -0,0 +1,106 @@ +" Author: medains , ardis , Arizard +" Description: phpstan for PHP files + +" 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', '') +let g:ale_php_phpstan_configuration = get(g:, 'ale_php_phpstan_configuration', '') +let g:ale_php_phpstan_autoload = get(g:, 'ale_php_phpstan_autoload', '') +let g:ale_php_phpstan_memory_limit = get(g:, 'ale_php_phpstan_memory_limit', '') +call ale#Set('php_phpstan_use_global', get(g:, 'ale_use_global_executables', 0)) + +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) + \ : '' + + let l:autoload = ale#Var(a:buffer, 'php_phpstan_autoload') + let l:autoload_option = !empty(l:autoload) + \ ? ' -a ' . ale#Escape(l:autoload) + \ : '' + + let l:memory_limit = ale#Var(a:buffer, 'php_phpstan_memory_limit') + let l:memory_limit_option = !empty(l:memory_limit) + \ ? ' --memory-limit=' . ale#Escape(l:memory_limit) + \ : '' + + let l:level = ale#Var(a:buffer, 'php_phpstan_level') + + if empty(l:level) && empty(ale_linters#php#phpstan#FindConfigFile(a:buffer)) + " if no configuration file is found, then use 4 as a default level + let l:level = '4' + endif + + let l:level_option = !empty(l:level) + \ ? ' -l ' . ale#Escape(l:level) + \ : '' + + let l:error_format = ale#semver#GTE(a:version, [0, 10, 3]) + \ ? ' --error-format json' + \ : ' --errorFormat json' + + return '%e analyze --no-progress' + \ . l:error_format + \ . l:configuration_option + \ . l:autoload_option + \ . l:level_option + \ . l:memory_limit_option + \ . ' %s' +endfunction + +function! ale_linters#php#phpstan#Handle(buffer, lines) abort + let l:res = ale#util#FuzzyJSONDecode(a:lines, {'files': []}) + let l:output = [] + + if type(l:res.files) is v:t_list + return l:output + endif + + for l:key in keys(l:res.files) + for l:err in l:res.files[l:key].messages + call add(l:output, { + \ 'lnum': l:err.line, + \ 'text': l:err.message, + \ 'type': 'E', + \}) + endfor + endfor + + return l:output +endfunction + +function! ale_linters#php#phpstan#GetCwd(buffer) abort + let l:result = ale#path#Dirname(ale_linters#php#phpstan#FindConfigFile(a:buffer)) + + return empty(l:result) ? v:null : l:result +endfunction + +function! ale_linters#php#phpstan#FindConfigFile(buffer) abort + let l:result = ale#path#FindNearestFile(a:buffer, 'phpstan.neon') + + if empty(l:result) + let l:result = ale#path#FindNearestFile(a:buffer, 'phpstan.neon.dist') + endif + + return l:result +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phpstan', +\ 'executable': {buffer -> ale#path#FindExecutable(buffer, 'php_phpstan', [ +\ 'vendor/bin/phpstan', +\ 'phpstan' +\ ])}, +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#path#FindExecutable(buffer, 'php_phpstan', [ +\ 'vendor/bin/phpstan', +\ 'phpstan' +\ ]), +\ '%e --version', +\ function('ale_linters#php#phpstan#GetCommand'), +\ )}, +\ 'callback': 'ale_linters#php#phpstan#Handle', +\ 'cwd': function('ale_linters#php#phpstan#GetCwd'), +\}) diff --git a/ale_linters/php/psalm.vim b/ale_linters/php/psalm.vim new file mode 100644 index 00000000..f1280057 --- /dev/null +++ b/ale_linters/php/psalm.vim @@ -0,0 +1,32 @@ +" Author: Matt Brown +" Description: plugin for Psalm, static analyzer for PHP + +call ale#Set('php_psalm_executable', 'psalm') +call ale#Set('php_psalm_options', '') +call ale#Set('php_psalm_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#php#psalm#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') : '' +endfunction + +function! ale_linters#php#psalm#GetCommand(buffer) abort + return '%e --language-server' . ale#Pad(ale#Var(a:buffer, 'php_psalm_options')) +endfunction + +call ale#linter#Define('php', { +\ 'name': 'psalm', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#path#FindExecutable(b, 'php_psalm', [ +\ 'vendor/bin/psalm', +\ ])}, +\ 'command': function('ale_linters#php#psalm#GetCommand'), +\ 'project_root': function('ale_linters#php#psalm#GetProjectRoot'), +\}) diff --git a/ale_linters/php/tlint.vim b/ale_linters/php/tlint.vim new file mode 100644 index 00000000..80bdd1f6 --- /dev/null +++ b/ale_linters/php/tlint.vim @@ -0,0 +1,80 @@ +" Author: Jose Soto +" +" Description: Tighten Opinionated PHP Linting +" Website: https://github.com/tightenco/tlint + +call ale#Set('php_tlint_executable', 'tlint') +call ale#Set('php_tlint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('php_tlint_options', '') + +function! ale_linters#php#tlint#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') : '' +endfunction + +function! ale_linters#php#tlint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'php_tlint', [ + \ 'vendor/bin/tlint', + \ 'tlint', + \]) +endfunction + +function! ale_linters#php#tlint#GetCommand(buffer) abort + let l:executable = ale_linters#php#tlint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'php_tlint_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' lint %s' +endfunction + +function! ale_linters#php#tlint#Handle(buffer, lines) abort + " Matches against lines like the following: + " + " ! There should be 1 space around `.` concatenations, and additional lines should always start with a `.` + " 22 : ` $something = 'a'.'name';` + " + let l:loop_count = 0 + let l:messages_pattern = '^\! \(.*\)' + let l:output = [] + let l:pattern = '^\(\d\+\) \:' + let l:temp_messages = [] + + for l:message in ale#util#GetMatches(a:lines, l:messages_pattern) + call add(l:temp_messages, l:message) + endfor + + let l:loop_count = 0 + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:num = l:match[1] + let l:text = l:temp_messages[l:loop_count] + + call add(l:output, { + \ 'lnum': l:num, + \ 'col': 0, + \ 'text': l:text, + \ 'type': 'W', + \ 'sub_type': 'style', + \}) + + let l:loop_count += 1 + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'tlint', +\ 'executable': function('ale_linters#php#tlint#GetExecutable'), +\ 'command': function('ale_linters#php#tlint#GetCommand'), +\ 'callback': 'ale_linters#php#tlint#Handle', +\ 'project_root': function('ale_linters#php#tlint#GetProjectRoot'), +\}) diff --git a/ale_linters/po/alex.vim b/ale_linters/po/alex.vim new file mode 100644 index 00000000..05c67f15 --- /dev/null +++ b/ale_linters/po/alex.vim @@ -0,0 +1,4 @@ +" Author: Cian Butler https://github.com/butlerx +" Description: alex for PO files + +call ale#handlers#alex#DefineLinter('po', '--text') diff --git a/ale_linters/po/msgfmt.vim b/ale_linters/po/msgfmt.vim new file mode 100644 index 00000000..8279ccdc --- /dev/null +++ b/ale_linters/po/msgfmt.vim @@ -0,0 +1,30 @@ +" Author: Cian Butler https://github.com/butlerx +" Description: msgfmt for PO files + +function! ale_linters#po#msgfmt#Handle(buffer, lines) abort + let l:results = ale#handlers#unix#HandleAsWarning(a:buffer, a:lines) + let l:index = 0 + + for l:item in l:results + if l:index > 0 && l:item.text =~? 'this is the location of the first definition' + let l:last_item = l:results[l:index - 1] + + if l:last_item.text =~? 'duplicate message definition' + let l:last_item.text = 'duplicate of message at line ' . l:item.lnum + let l:item.text = 'first location of duplicate of message at line ' . l:last_item.lnum + endif + endif + + let l:index += 1 + endfor + + return l:results +endfunction + +call ale#linter#Define('po', { +\ 'name': 'msgfmt', +\ 'executable': 'msgfmt', +\ 'output_stream': 'stderr', +\ 'command': 'msgfmt --statistics --output-file=- %t', +\ 'callback': 'ale_linters#po#msgfmt#Handle', +\}) diff --git a/ale_linters/po/proselint.vim b/ale_linters/po/proselint.vim new file mode 100644 index 00000000..ce132508 --- /dev/null +++ b/ale_linters/po/proselint.vim @@ -0,0 +1,9 @@ +" Author: Cian Butler https://github.com/butlerx +" Description: proselint for PO files + +call ale#linter#Define('po', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/po/writegood.vim b/ale_linters/po/writegood.vim new file mode 100644 index 00000000..14686473 --- /dev/null +++ b/ale_linters/po/writegood.vim @@ -0,0 +1,4 @@ +" Author: Cian Butler https://github.com/butlerx +" Description: write-good for PO files + +call ale#handlers#writegood#DefineLinter('po') diff --git a/ale_linters/pod/alex.vim b/ale_linters/pod/alex.vim new file mode 100644 index 00000000..c89f8330 --- /dev/null +++ b/ale_linters/pod/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for pod files + +call ale#handlers#alex#DefineLinter('pod', '--text') diff --git a/ale_linters/pod/proselint.vim b/ale_linters/pod/proselint.vim new file mode 100644 index 00000000..2eb83f56 --- /dev/null +++ b/ale_linters/pod/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for Pod files + +call ale#linter#Define('pod', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/pod/writegood.vim b/ale_linters/pod/writegood.vim new file mode 100644 index 00000000..9f5461e6 --- /dev/null +++ b/ale_linters/pod/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for Pod files + +call ale#handlers#writegood#DefineLinter('pod') diff --git a/ale_linters/pony/ponyc.vim b/ale_linters/pony/ponyc.vim new file mode 100644 index 00000000..6d4594f9 --- /dev/null +++ b/ale_linters/pony/ponyc.vim @@ -0,0 +1,16 @@ +" Description: ponyc linter for pony files + +call ale#Set('pony_ponyc_executable', 'ponyc') +call ale#Set('pony_ponyc_options', '--pass paint') + +function! ale_linters#pony#ponyc#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'pony_ponyc_options')) +endfunction + +call ale#linter#Define('pony', { +\ 'name': 'ponyc', +\ 'output_stream': 'stderr', +\ '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/cspell.vim b/ale_linters/powershell/cspell.vim new file mode 100644 index 00000000..4a66dbaa --- /dev/null +++ b/ale_linters/powershell/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for PowerShell files. + +call ale#handlers#cspell#DefineLinter('powershell') diff --git a/ale_linters/powershell/powershell.vim b/ale_linters/powershell/powershell.vim new file mode 100644 index 00000000..5f49f72c --- /dev/null +++ b/ale_linters/powershell/powershell.vim @@ -0,0 +1,100 @@ +" 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); + \ $ErrorView = "Normal"; + \ 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 + + " If the match is 0, it was a failed match + " probably due to an unexpected token which + " contained a newline. Reset matchcount. to + " continue to the next match + if !empty(l:match[1]) + let l:item = { + \ 'lnum': str2nr(l:match[1]), + \ 'col': str2nr(l:match[2]), + \ 'type': 'E', + \} + else + let l:matchcount = 0 + endif + 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': function('ale_linters#powershell#powershell#GetExecutable'), +\ 'command': function('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..8a8b4b45 --- /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 separate 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 new file mode 100644 index 00000000..82859eb0 --- /dev/null +++ b/ale_linters/prolog/swipl.vim @@ -0,0 +1,110 @@ +" Author: Takuya Fujiwara +" Description: swipl syntax / semantic check for Prolog files + +call ale#Set('prolog_swipl_executable', 'swipl') +call ale#Set('prolog_swipl_load', 'current_prolog_flag(argv, [File]), load_files(File, [sandboxed(true)]), halt.') +call ale#Set('prolog_swipl_timeout', 3) +call ale#Set('prolog_swipl_alarm', 'alarm(%t, (%h), _, [])') +call ale#Set('prolog_swipl_alarm_handler', 'writeln(user_error, "ERROR: Exceeded %t seconds, Please change g:prolog_swipl_timeout to modify the limit."), halt(1)') + +function! ale_linters#prolog#swipl#GetCommand(buffer) abort + let l:goals = ale#Var(a:buffer, 'prolog_swipl_load') + let l:goals = l:goals =~# '^\s*$' ? 'halt' : l:goals + let l:timeout = ale#Var(a:buffer, 'prolog_swipl_timeout') + 0 + + if l:timeout > 0 + let l:goals = s:GetAlarm(a:buffer, l:timeout) . ', ' . l:goals + endif + + return '%e -g ' . ale#Escape(l:goals) . ' -- %s' +endfunction + +function! s:GetAlarm(buffer, timeout) abort + let l:handler = ale#Var(a:buffer, 'prolog_swipl_alarm_handler') + let l:handler = s:Subst(l:handler, {'t': a:timeout}) + let l:alarm = ale#Var(a:buffer, 'prolog_swipl_alarm') + let l:alarm = s:Subst(l:alarm, {'t': a:timeout, 'h': l:handler}) + + return l:alarm +endfunction + +function! s:Subst(format, vars) abort + let l:vars = extend(copy(a:vars), {'%': '%'}) + + return substitute(a:format, '%\(.\)', '\=get(l:vars, submatch(1), "")', 'g') +endfunction + +function! ale_linters#prolog#swipl#Handle(buffer, lines) abort + let l:output = [] + let l:i = 0 + + let l:pattern = '\v^(ERROR|Warning)+%(:\s*[^:]+:(\d+)%(:(\d+))?)?:\s*(.*)$' + + while l:i < len(a:lines) + let l:match = matchlist(a:lines[l:i], l:pattern) + + if empty(l:match) + let l:i += 1 + continue + endif + + let [l:i, l:text] = s:GetErrMsg(l:i, a:lines, l:match[4]) + let l:item = { + \ 'lnum': (l:match[2] + 0 ? l:match[2] + 0 : 1), + \ 'col': l:match[3] + 0, + \ 'text': l:text, + \ 'type': (l:match[1] is# 'ERROR' ? 'E' : 'W'), + \} + + if !s:Ignore(l:item) + call add(l:output, l:item) + endif + endwhile + + return l:output +endfunction + +" This returns [, ] +function! s:GetErrMsg(i, lines, text) abort + if a:text !~# '^\s*$' + return [a:i + 1, a:text] + endif + + let l:i = a:i + 1 + let l:text = [] + + let l:pattern = '\v^(ERROR|Warning)?:?(.*)$' + + while l:i < len(a:lines) + let l:match = matchlist(a:lines[l:i], l:pattern) + + if empty(l:match) || empty(l:match[2]) + let l:i += 1 + break + endif + + call add(l:text, s:Trim(l:match[2])) + let l:i += 1 + endwhile + + return [l:i, join(l:text, '. ')] +endfunction + +function! s:Trim(str) abort + return substitute(a:str, '\v^\s+|\s+$', '', 'g') +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' +endfunction + +call ale#linter#Define('prolog', { +\ 'name': 'swipl', +\ 'output_stream': 'stderr', +\ '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/buf_lint.vim b/ale_linters/proto/buf_lint.vim new file mode 100644 index 00000000..a22f8e53 --- /dev/null +++ b/ale_linters/proto/buf_lint.vim @@ -0,0 +1,26 @@ +" Author: Alex McKinney +" Description: Run buf lint. + +call ale#Set('proto_buf_lint_executable', 'buf') +call ale#Set('proto_buf_lint_config', '') +call ale#Set('proto_buf_lint_options', '') + +function! ale_linters#proto#buf_lint#GetCommand(buffer) abort + let l:config = ale#Var(a:buffer, 'proto_buf_lint_config') + let l:options = ale#Var(a:buffer, 'proto_buf_lint_options') + + return '%e lint' + \ . (!empty(l:config) ? ' --config=' . ale#Escape(l:config) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %s#include_package_files=true' +endfunction + +call ale#linter#Define('proto', { +\ 'name': 'buf_lint', +\ 'aliases': ['buf-lint'], +\ 'lint_file': 1, +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'proto_buf_lint_executable')}, +\ 'command': function('ale_linters#proto#buf_lint#GetCommand'), +\ 'callback': 'ale#handlers#go#Handler', +\}) diff --git a/ale_linters/proto/protoc_gen_lint.vim b/ale_linters/proto/protoc_gen_lint.vim new file mode 100644 index 00000000..c3d10935 --- /dev/null +++ b/ale_linters/proto/protoc_gen_lint.vim @@ -0,0 +1,27 @@ +" Author: Jeff Willette +" Description: run the protoc-gen-lint plugin for the protoc binary + +call ale#Set('proto_protoc_gen_lint_options', '') + +function! ale_linters#proto#protoc_gen_lint#GetCommand(buffer) abort + let l:dirname = expand('#' . a:buffer . ':p:h') + + let l:options = ['-I ' . ale#Escape(l:dirname)] + + if !empty(ale#Var(a:buffer, 'proto_protoc_gen_lint_options')) + let l:options += [ale#Var(a:buffer, 'proto_protoc_gen_lint_options')] + endif + + let l:options += ['--lint_out=. ' . '%s'] + + return 'protoc' . ' ' . join(l:options) +endfunction + +call ale#linter#Define('proto', { +\ 'name': 'protoc-gen-lint', +\ 'lint_file': 1, +\ 'output_stream': 'stderr', +\ 'executable': 'protoc', +\ 'command': function('ale_linters#proto#protoc_gen_lint#GetCommand'), +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) diff --git a/ale_linters/proto/protolint.vim b/ale_linters/proto/protolint.vim new file mode 100644 index 00000000..2754c7b6 --- /dev/null +++ b/ale_linters/proto/protolint.vim @@ -0,0 +1,24 @@ +" Author: Yohei Yoshimuta +" Description: run the protolint for Protocol Buffer files + +call ale#Set('proto_protolint_executable', 'protolint') +call ale#Set('proto_protolint_config', '') + +function! ale_linters#proto#protolint#GetCommand(buffer) abort + let l:config = ale#Var(a:buffer, 'proto_protolint_config') + + return '%e lint' + \ . (!empty(l:config) ? ' -config_path=' . ale#Escape(l:config) : '') + \ . ' -reporter=unix' + \ . ' %s' +endfunction + +call ale#linter#Define('proto', { +\ 'name': 'protolint', +\ 'lint_file': 1, +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'proto_protolint_executable')}, +\ 'command': function('ale_linters#proto#protolint#GetCommand'), +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) + diff --git a/ale_linters/pug/puglint.vim b/ale_linters/pug/puglint.vim new file mode 100644 index 00000000..b552cc06 --- /dev/null +++ b/ale_linters/pug/puglint.vim @@ -0,0 +1,56 @@ +" Author: w0rp - +" Description: pug-lint for checking Pug/Jade files. + +call ale#Set('pug_puglint_options', '') +call ale#Set('pug_puglint_executable', 'pug-lint') +call ale#Set('pug_puglint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! s:FindConfig(buffer) abort + for l:filename in [ + \ '.pug-lintrc', + \ '.pug-lintrc.js', + \ '.pug-lintrc.json', + \ 'package.json', + \] + let l:config = ale#path#FindNearestFile(a:buffer, l:filename) + + if !empty(l:config) + return l:config + endif + endfor + + return '' +endfunction + +function! ale_linters#pug#puglint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'pug_puglint_options') + let l:config = s:FindConfig(a:buffer) + + return '%e' . ale#Pad(l:options) + \ . (!empty(l:config) ? ' -c ' . ale#Escape(l:config) : '') + \ . ' -r inline %t' +endfunction + +function! ale_linters#pug#puglint#Handle(buffer, lines) abort + for l:line in a:lines[:10] + if l:line =~# '^SyntaxError: ' + return [{ + \ 'lnum': 1, + \ 'text': 'puglint configuration error (type :ALEDetail for more information)', + \ 'detail': join(a:lines, "\n"), + \}] + endif + endfor + + return ale#handlers#unix#HandleAsError(a:buffer, a:lines) +endfunction + +call ale#linter#Define('pug', { +\ 'name': 'puglint', +\ 'executable': {b -> ale#path#FindExecutable(b, 'pug_puglint', [ +\ 'node_modules/.bin/pug-lint', +\ ])}, +\ 'output_stream': 'stderr', +\ 'command': function('ale_linters#pug#puglint#GetCommand'), +\ 'callback': 'ale_linters#pug#puglint#Handle', +\}) diff --git a/ale_linters/puppet/languageserver.vim b/ale_linters/puppet/languageserver.vim new file mode 100644 index 00000000..c6b12662 --- /dev/null +++ b/ale_linters/puppet/languageserver.vim @@ -0,0 +1,38 @@ +" Author: Alexander Olofsson +" Description: Puppet Language Server integration for ALE + +call ale#Set('puppet_languageserver_executable', 'puppet-languageserver') + +function! ale_linters#puppet#languageserver#GetProjectRoot(buffer) abort + " Note: The metadata.json file is recommended for Puppet 4+ modules, but + " there's no requirement to have it, so fall back to the other possible + " Puppet module directories + let l:root_path = ale#path#FindNearestFile(a:buffer, 'metadata.json') + + if !empty(l:root_path) + return fnamemodify(l:root_path, ':h') + endif + + for l:test_path in [ + \ 'manifests', + \ 'templates', + \] + let l:root_path = ale#path#FindNearestDirectory(a:buffer, l:test_path) + + if !empty(l:root_path) + return fnamemodify(l:root_path, ':h:h') + endif + endfor + + return '' +endfunction + +call ale#linter#Define('puppet', { +\ 'name': 'languageserver', +\ 'aliases': ['puppet_languageserver'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'puppet_languageserver_executable')}, +\ 'command': '%e --stdio', +\ 'language': 'puppet', +\ 'project_root': function('ale_linters#puppet#languageserver#GetProjectRoot'), +\}) diff --git a/ale_linters/puppet/puppet.vim b/ale_linters/puppet/puppet.vim new file mode 100644 index 00000000..59228dc8 --- /dev/null +++ b/ale_linters/puppet/puppet.vim @@ -0,0 +1,39 @@ +" Author: Alexander Olofsson + +call ale#Set('puppet_puppet_executable', 'puppet') +call ale#Set('puppet_puppet_options', '') + +function! ale_linters#puppet#puppet#Handle(buffer, lines) abort + " Matches patterns like the following: + " Error: Could not parse for environment production: Syntax error at ':' at /root/puppetcode/modules/nginx/manifests/init.pp:43:12 + " Error: Could not parse for environment production: Syntax error at '='; expected '}' at /root/puppetcode/modules/pancakes/manifests/init.pp:5" + " Error: Could not parse for environment production: Syntax error at 'parameter1' (file: /tmp/modules/mariadb/manifests/slave.pp, line: 4, column: 5) + " Error: Illegal attempt to assign to 'a Name'. Not an assignable reference (file: /tmp/modules/waffles/manifests/syrup.pp, line: 5, column: 11) + " Error: Could not parse for environment production: Syntax error at end of input (file: /tmp/modules/bob/manifests/init.pp) + let l:pattern = '^Error:\%(.*:\)\? \(.\+\) \((file:\|at\) .\+\.pp\(\(, line: \|:\)\(\d\+\)\(, column: \|:\)\=\(\d*\)\|)$\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[5] + 0, + \ 'col': l:match[7] + 0, + \ 'text': l:match[1], + \}) + endfor + + return l:output +endfunction + +function! ale_linters#puppet#puppet#GetCommand(buffer) abort + return '%e parser validate --color=false ' + \ . ale#Pad(ale#Var(a:buffer, 'puppet_puppet_options')) + \ . ' %t' +endfunction + +call ale#linter#Define('puppet', { +\ 'name': 'puppet', +\ 'executable': {b -> ale#Var(b, 'puppet_puppet_executable')}, +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..985d6a4d --- /dev/null +++ b/ale_linters/puppet/puppetlint.vim @@ -0,0 +1,18 @@ +" Author: Alexander Olofsson , Robert Flechtner +" Description: puppet-lint for puppet files + +call ale#Set('puppet_puppetlint_executable', 'puppet-lint') +call ale#Set('puppet_puppetlint_options', '--no-autoloader_layout-check') + +function! ale_linters#puppet#puppetlint#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'puppet_puppetlint_options')) + \ . ' --log-format "-:%{line}:%{column}: %{kind}: [%{check}] %{message}"' + \ . ' %t' +endfunction + +call ale#linter#Define('puppet', { +\ 'name': 'puppetlint', +\ '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/purescript/ls.vim b/ale_linters/purescript/ls.vim new file mode 100644 index 00000000..1eaf2af7 --- /dev/null +++ b/ale_linters/purescript/ls.vim @@ -0,0 +1,50 @@ +" Author: Drew Olson +" Description: Integrate ALE with purescript-language-server. + +call ale#Set('purescript_ls_executable', 'purescript-language-server') +call ale#Set('purescript_ls_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('purescript_ls_config', {}) + +function! ale_linters#purescript#ls#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'purescript_ls', [ + \ 'node_modules/.bin/purescript-language-server', + \]) +endfunction + +function! ale_linters#purescript#ls#GetCommand(buffer) abort + let l:executable = ale_linters#purescript#ls#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' --stdio' +endfunction + +function! ale_linters#purescript#ls#FindProjectRoot(buffer) abort + let l:config = ale#path#FindNearestFile(a:buffer, 'bower.json') + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + let l:config = ale#path#FindNearestFile(a:buffer, 'psc-package.json') + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + let l:config = ale#path#FindNearestFile(a:buffer, 'spago.dhall') + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + return '' +endfunction + +call ale#linter#Define('purescript', { +\ 'name': 'purescript-language-server', +\ 'aliases': ['purescriptls'], +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#purescript#ls#GetExecutable'), +\ 'command': function('ale_linters#purescript#ls#GetCommand'), +\ 'project_root': function('ale_linters#purescript#ls#FindProjectRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'purescript_ls_config')}, +\}) diff --git a/ale_linters/pyrex/cython.vim b/ale_linters/pyrex/cython.vim new file mode 100644 index 00000000..247c3060 --- /dev/null +++ b/ale_linters/pyrex/cython.vim @@ -0,0 +1,36 @@ +" Author: w0rp , +" Nicolas Pauss +" Description: cython syntax checking for cython files. + +call ale#Set('pyrex_cython_executable', 'cython') +call ale#Set('pyrex_cython_options', '--warning-extra') + +function! ale_linters#pyrex#cython#GetCommand(buffer) abort + return '%e --working %s:h --include-dir %s:h' + \ . ale#Pad(ale#Var(a:buffer, 'pyrex_cython_options')) + \ . ' --output-file ' . g:ale#util#nul_file . ' %t' +endfunction + +function! ale_linters#pyrex#cython#Handle(buffer, lines) abort + let l:pattern = '\v^(\w+: )?[^:]+:(\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': l:match[1][0] is# 'w' ? 'W' : 'E', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('pyrex', { +\ 'name': 'cython', +\ 'output_stream': 'stderr', +\ '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..b343a1c5 --- /dev/null +++ b/ale_linters/python/bandit.vim @@ -0,0 +1,82 @@ +" Author: Martino Pilia +" 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) +call ale#Set('python_bandit_auto_poetry', 0) +call ale#Set('python_bandit_auto_uv', 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 + + if ( + \ ale#Var(a:buffer, 'python_auto_poetry') + \ || ale#Var(a:buffer, 'python_bandit_auto_poetry') + \) && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_bandit_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + 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\|poetry\|uv\)$' + \ ? ' 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/cspell.vim b/ale_linters/python/cspell.vim new file mode 100644 index 00000000..a2325311 --- /dev/null +++ b/ale_linters/python/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Python files. + +call ale#handlers#cspell#DefineLinter('python') diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim new file mode 100644 index 00000000..0ec7fbb5 --- /dev/null +++ b/ale_linters/python/flake8.vim @@ -0,0 +1,176 @@ +" Author: w0rp +" Description: flake8 for python files + +call ale#Set('python_flake8_executable', 'flake8') +call ale#Set('python_flake8_options', '') +call ale#Set('python_flake8_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_flake8_change_directory', 'project') +call ale#Set('python_flake8_auto_pipenv', 0) +call ale#Set('python_flake8_auto_poetry', 0) +call ale#Set('python_flake8_auto_uv', 0) + +function! s:UsingModule(buffer) abort + return ale#Var(a:buffer, 'python_flake8_options') =~# ' *-m flake8' +endfunction + +function! ale_linters#python#flake8#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_flake8_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_flake8_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_flake8_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + if !s:UsingModule(a:buffer) + return ale#python#FindExecutable(a:buffer, 'python_flake8', ['flake8']) + endif + + return ale#Var(a:buffer, 'python_flake8_executable') +endfunction + +function! ale_linters#python#flake8#RunWithVersionCheck(buffer) abort + let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) + + let l:module_string = s:UsingModule(a:buffer) ? ' -m flake8' : '' + 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#GetCwd(buffer) abort + let l:change_directory = ale#Var(a:buffer, 'python_flake8_change_directory') + let l:cwd = '' + + if l:change_directory is# 'project' + let l:project_root = ale#python#FindProjectRootIni(a:buffer) + + if !empty(l:project_root) + let l:cwd = l:project_root + endif + endif + + if (l:change_directory is# 'project' && empty(l:cwd)) + \|| l:change_directory + \|| l:change_directory is# 'file' + let l:cwd = '%s:h' + endif + + return l:cwd +endfunction + +function! ale_linters#python#flake8#GetCommand(buffer, version) abort + let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run flake8' + \ : '' + + " Only include the --stdin-display-name argument if we can parse the + " flake8 version, and it is recent enough to support it. + let l:display_name_args = ale#semver#GTE(a:version, [3, 0, 0]) + \ ? ' --stdin-display-name %s' + \ : '' + + let l:options = ale#Var(a:buffer, 'python_flake8_options') + + return ale#Escape(l:executable) . l:exec_args + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --format=default' + \ . l:display_name_args . ' -' +endfunction + +let s:end_col_pattern_map = { +\ 'F405': '\(.\+\) may be undefined', +\ 'F821': 'undefined name ''\([^'']\+\)''', +\ 'F999': '^''\([^'']\+\)''', +\ 'F841': 'local variable ''\([^'']\+\)''', +\} + +function! ale_linters#python#flake8#Handle(buffer, lines) abort + 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:]]+):? (.*)$' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[3] + + if (l:code is# 'W291' || l:code is# 'W293') + \ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + if l:code is# 'W391' + \&& !ale#Var(a:buffer, 'warn_about_trailing_blank_lines') + " Skip warnings for trailing blank lines if the option is off + continue + endif + + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'vcol': 1, + \ 'text': l:match[4], + \ 'code': l:code, + \ 'type': 'W', + \} + + if l:code[:0] is# 'F' + if l:code isnot# 'F401' + let l:item.type = 'E' + endif + elseif l:code[:0] is# 'E' + let l:item.type = 'E' + + if l:code isnot# 'E999' && l:code isnot# 'E112' + let l:item.sub_type = 'style' + endif + elseif l:code[:0] is# 'W' + let l:item.sub_type = 'style' + endif + + let l:end_col_pattern = get(s:end_col_pattern_map, l:code, '') + + if !empty(l:end_col_pattern) + let l:end_col_match = matchlist(l:match[4], l:end_col_pattern) + + if !empty(l:end_col_match) + let l:item.end_col = l:item.col + len(l:end_col_match[1]) - 1 + endif + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'flake8', +\ 'executable': function('ale_linters#python#flake8#GetExecutable'), +\ 'cwd': function('ale_linters#python#flake8#GetCwd'), +\ 'command': function('ale_linters#python#flake8#RunWithVersionCheck'), +\ 'callback': 'ale_linters#python#flake8#Handle', +\}) diff --git a/ale_linters/python/flakehell.vim b/ale_linters/python/flakehell.vim new file mode 100644 index 00000000..d5182033 --- /dev/null +++ b/ale_linters/python/flakehell.vim @@ -0,0 +1,181 @@ +" Author: w0rp +" Description: flakehell for python files + +call ale#Set('python_flakehell_executable', 'flakehell') +call ale#Set('python_flakehell_options', '') +call ale#Set('python_flakehell_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_flakehell_change_directory', 'project') +call ale#Set('python_flakehell_auto_pipenv', 0) +call ale#Set('python_flakehell_auto_poetry', 0) +call ale#Set('python_flakehell_auto_uv', 0) + +function! s:UsingModule(buffer) abort + return ale#Var(a:buffer, 'python_flakehell_executable') is? 'python' +endfunction + +function! ale_linters#python#flakehell#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_flakehell_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_flakehell_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_flakehell_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + if !s:UsingModule(a:buffer) + return ale#python#FindExecutable(a:buffer, 'python_flakehell', ['flakehell']) + endif + + return ale#Var(a:buffer, 'python_flakehell_executable') +endfunction + +function! ale_linters#python#flakehell#RunWithVersionCheck(buffer) abort + let l:executable = ale_linters#python#flakehell#GetExecutable(a:buffer) + + let l:module_string = s:UsingModule(a:buffer) ? ' -m flakehell' : '' + 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#flakehell#GetCommand'), + \) +endfunction + +function! ale_linters#python#flakehell#GetCwd(buffer) abort + let l:change_directory = ale#Var(a:buffer, 'python_flakehell_change_directory') + let l:cwd = '' + + if l:change_directory is# 'project' + let l:project_root = ale#python#FindProjectRootIni(a:buffer) + + if !empty(l:project_root) + let l:cwd = l:project_root + endif + endif + + if (l:change_directory is# 'project' && empty(l:cwd)) + \|| l:change_directory + \|| l:change_directory is# 'file' + let l:cwd = '%s:h' + endif + + return l:cwd +endfunction + +function! ale_linters#python#flakehell#GetCommand(buffer, version) abort + let l:executable = ale_linters#python#flakehell#GetExecutable(a:buffer) + + if (l:executable =~? '\(pipenv\|poetry\|uv\)$') + let l:exec_args = ' run flakehell' + elseif (l:executable is? 'python') + let l:exec_args = ' -m flakehell' + else + let l:exec_args = '' + endif + + " Only include the --stdin-display-name argument if we can parse the + " flakehell version, and it is recent enough to support it. + let l:display_name_args = ale#semver#GTE(a:version, [0, 8, 0]) + \ ? ' --stdin-display-name %s' + \ : '' + + let l:options = ale#Var(a:buffer, 'python_flakehell_options') + + return ale#Escape(l:executable) + \ . l:exec_args + \ . (!empty(l:options) ? ' lint ' . l:options : ' lint') + \ . ' --format=default' + \ . l:display_name_args . ' -' +endfunction + +let s:end_col_pattern_map = { +\ 'F405': '\(.\+\) may be undefined', +\ 'F821': 'undefined name ''\([^'']\+\)''', +\ 'F999': '^''\([^'']\+\)''', +\ 'F841': 'local variable ''\([^'']\+\)''', +\} + +function! ale_linters#python#flakehell#Handle(buffer, lines) abort + 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:]]+):? (.*)$' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[3] + + if (l:code is# 'W291' || l:code is# 'W293') + \ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + if l:code is# 'W391' + \&& !ale#Var(a:buffer, 'warn_about_trailing_blank_lines') + " Skip warnings for trailing blank lines if the option is off + continue + endif + + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'vcol': 1, + \ 'text': l:match[4], + \ 'code': l:code, + \ 'type': 'W', + \} + + if l:code[:0] is# 'F' + if l:code isnot# 'F401' + let l:item.type = 'E' + endif + elseif l:code[:0] is# 'E' + let l:item.type = 'E' + + if l:code isnot# 'E999' && l:code isnot# 'E112' + let l:item.sub_type = 'style' + endif + elseif l:code[:0] is# 'W' + let l:item.sub_type = 'style' + endif + + let l:end_col_pattern = get(s:end_col_pattern_map, l:code, '') + + if !empty(l:end_col_pattern) + let l:end_col_match = matchlist(l:match[4], l:end_col_pattern) + + if !empty(l:end_col_match) + let l:item.end_col = l:item.col + len(l:end_col_match[1]) - 1 + endif + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'flakehell', +\ 'executable': function('ale_linters#python#flakehell#GetExecutable'), +\ 'cwd': function('ale_linters#python#flakehell#GetCwd'), +\ 'command': function('ale_linters#python#flakehell#RunWithVersionCheck'), +\ 'callback': 'ale_linters#python#flakehell#Handle', +\}) diff --git a/ale_linters/python/jedils.vim b/ale_linters/python/jedils.vim new file mode 100644 index 00000000..2d4a97c3 --- /dev/null +++ b/ale_linters/python/jedils.vim @@ -0,0 +1,51 @@ +" Author: Dalius Dobravolskas +" Description: https://github.com/pappasam/jedi-language-server + +call ale#Set('python_jedils_executable', 'jedi-language-server') +call ale#Set('python_jedils_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_jedils_auto_pipenv', 0) +call ale#Set('python_jedils_auto_poetry', 0) +call ale#Set('python_jedils_auto_uv', 0) + +function! ale_linters#python#jedils#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_jedils_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_jedils_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_jedils_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_jedils', ['jedi-language-server']) +endfunction + +function! ale_linters#python#jedils#GetCommand(buffer) abort + let l:executable = ale_linters#python#jedils#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run jedi-language-server' + \ : '' + let l:env_string = '' + + if ale#Var(a:buffer, 'python_auto_virtualenv') + let l:env_string = ale#python#AutoVirtualenvEnvString(a:buffer) + endif + + return l:env_string . ale#Escape(l:executable) . l:exec_args +endfunction + +call ale#linter#Define('python', { +\ 'name': 'jedils', +\ 'aliases': ['jedi_language_server'], +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#python#jedils#GetExecutable'), +\ 'command': function('ale_linters#python#jedils#GetCommand'), +\ 'project_root': function('ale#python#FindProjectRoot'), +\ 'completion_filter': 'ale#completion#python#CompletionItemFilter', +\}) diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim new file mode 100644 index 00000000..27d2726b --- /dev/null +++ b/ale_linters/python/mypy.vim @@ -0,0 +1,109 @@ +" Author: Keith Smiley , w0rp +" Description: mypy support for optional python typechecking + +call ale#Set('python_mypy_executable', 'mypy') +call ale#Set('python_mypy_ignore_invalid_syntax', 0) +call ale#Set('python_mypy_show_notes', 1) +call ale#Set('python_mypy_options', '') +call ale#Set('python_mypy_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_mypy_auto_pipenv', 0) +call ale#Set('python_mypy_auto_poetry', 0) +call ale#Set('python_mypy_auto_uv', 0) + +function! ale_linters#python#mypy#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_mypy_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_mypy_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_mypy_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_mypy', ['mypy']) +endfunction + +" The directory to change to before running mypy +function! ale_linters#python#mypy#GetCwd(buffer) abort + " If we find a directory with "mypy.ini" in it use that, + " else try and find the "python project" root, or failing + " that, run from the same folder as the current file + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + if filereadable(l:path . '/mypy.ini') + return l:path + endif + endfor + + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) + \ ? l:project_root + \ : expand('#' . a:buffer . ':p:h') +endfunction + +function! ale_linters#python#mypy#GetCommand(buffer) abort + let l:executable = ale_linters#python#mypy#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run mypy' + \ : '' + + return '%e' . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_mypy_options')) + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' +endfunction + +function! ale_linters#python#mypy#Handle(buffer, lines) abort + let l:dir = ale_linters#python#mypy#GetCwd(a:buffer) + " Look for lines like the following: + " + " file.py:4: error: No library stub file for module 'django.db' + " + " Lines like these should be ignored below: + " + " file.py:4: note: (Stub files are from https://github.com/python/typeshed) + + let l:types = 'error|warning' + + if ale#Var(a:buffer, 'python_mypy_show_notes') + let l:types = 'error|warning|note' + endif + + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?: (' + \ . l:types + \ . '): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " Skip invalid syntax errors if the option is on. + if l:match[5] is# 'invalid syntax' + \&& ale#Var(a:buffer, 'python_mypy_ignore_invalid_syntax') + continue + endif + + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:match[4] is# 'error' ? 'E' : (l:match[4] is# 'note' ? 'I': 'W'), + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'mypy', +\ 'executable': function('ale_linters#python#mypy#GetExecutable'), +\ 'cwd': function('ale_linters#python#mypy#GetCwd'), +\ 'command': function('ale_linters#python#mypy#GetCommand'), +\ 'callback': 'ale_linters#python#mypy#Handle', +\ 'output_stream': 'both' +\}) diff --git a/ale_linters/python/prospector.vim b/ale_linters/python/prospector.vim new file mode 100644 index 00000000..29aad060 --- /dev/null +++ b/ale_linters/python/prospector.vim @@ -0,0 +1,112 @@ +" Author: chocoelho +" Description: prospector linter python files + +call ale#Set('python_prospector_auto_pipenv', 0) +call ale#Set('python_prospector_auto_poetry', 0) +call ale#Set('python_prospector_auto_uv', 0) + +let g:ale_python_prospector_executable = +\ get(g:, 'ale_python_prospector_executable', 'prospector') + +let g:ale_python_prospector_options = +\ get(g:, 'ale_python_prospector_options', '') + +let g:ale_python_prospector_use_global = get(g:, 'ale_python_prospector_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#python#prospector#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_prospector_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_prospector_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_prospector_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_prospector', ['prospector']) +endfunction + +function! ale_linters#python#prospector#GetCommand(buffer) abort + let l:executable = ale_linters#python#prospector#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run prospector' + \ : '' + + return ale#Escape(l:executable) + \ . l:exec_args + \ . ' ' . ale#Var(a:buffer, 'python_prospector_options') + \ . ' --messages-only --absolute-paths --zero-exit --output-format json' + \ . ' %s' +endfunction + +function! ale_linters#python#prospector#Handle(buffer, lines) abort + let l:output = [] + + if empty(a:lines) + return [] + endif + + let l:prospector_error = json_decode(join(a:lines, '')) + + for l:error in l:prospector_error.messages + if (l:error.code is# 'W291' || l:error.code is# 'W293' || l:error.code is# 'trailing-whitespace') + \ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + if l:error.code is# 'W391' + \&& !ale#Var(a:buffer, 'warn_about_trailing_blank_lines') + " Skip warnings for trailing blank lines if the option is off + continue + endif + + if l:error.source =~# '\v\[%(dodgy|mccabe|pep8|pep257|pyroma)\]$' + let l:sub_type = 'style' + else + let l:sub_type = '' + endif + + if l:error.source =~# '\v\[pylint\]$' + let l:type = l:error.code =~? '\m^[CRW]' ? 'W' : 'E' + elseif l:error.source =~# '\v\[%(frosted|pep8)\]$' + let l:type = l:error.code =~? '\m^W' ? 'W' : 'E' + elseif l:error.source =~# '\v\[%(dodgy|pyroma|vulture)\]$' + let l:type = 'W' + else + let l:type = 'E' + endif + + let l:item = { + \ 'lnum': l:error.location.line, + \ 'col': l:error.location.character + 1, + \ 'text': l:error.message, + \ 'code': printf('(%s) %s', l:error.source, l:error.code), + \ 'type': l:type, + \ 'sub_type': l:sub_type, + \} + + if l:sub_type is# '' + unlet l:item.sub_type + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'prospector', +\ '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/pycln.vim b/ale_linters/python/pycln.vim new file mode 100644 index 00000000..23d48676 --- /dev/null +++ b/ale_linters/python/pycln.vim @@ -0,0 +1,92 @@ +" Author: Yining +" Description: pycln as linter for python files + +call ale#Set('python_pycln_executable', 'pycln') +call ale#Set('python_pycln_options', '') +call ale#Set('python_pycln_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pycln_change_directory', 1) +call ale#Set('python_pycln_auto_pipenv', 0) +call ale#Set('python_pycln_auto_poetry', 0) +call ale#Set('python_pycln_auto_uv', 0) +call ale#Set('python_pycln_config_file', '') + +function! ale_linters#python#pycln#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pycln_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pycln_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pycln_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pycln', ['pycln']) +endfunction + +function! ale_linters#python#pycln#GetCwd(buffer) abort + if ale#Var(a:buffer, 'python_pycln_change_directory') + " Run from project root if found, else from buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '' +endfunction + +function! ale_linters#python#pycln#GetCommand(buffer, version) abort + let l:executable = ale_linters#python#pycln#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pycln' + \ : '' + + let l:options = ale#Var(a:buffer, 'python_pycln_options') + let l:config_file = ale#Var(a:buffer, 'python_pycln_config_file') + let l:config_file = l:options !~# '\v(^| )--config ' && !empty(l:config_file) + \ ? ale#Escape(ale#path#Simplify(l:config_file)) + \ : '' + + " NOTE: pycln version `1.3.0` supports liniting input from stdin + return ale#Escape(l:executable) . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_pycln_options')) + \ . (empty(l:config_file) ? '' : ' --config ' . l:config_file) + \ . ' --check' + \ . (ale#semver#GTE(a:version, [1, 3, 0]) ? ' -' : ' %s') +endfunction + +function! ale_linters#python#pycln#Handle(buffer, lines) abort + " Example: tmp/test.py:3:0 'import os' would be removed! + 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[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pycln', +\ 'executable': function('ale_linters#python#pycln#GetExecutable'), +\ 'cwd': function('ale_linters#python#pycln#GetCwd'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#python#pycln#GetExecutable(buffer), +\ '%e --version', +\ function('ale_linters#python#pycln#GetCommand'), +\ )}, +\ 'callback': 'ale_linters#python#pycln#Handle', +\ 'output_stream': 'both', +\ 'read_buffer': 1, +\}) diff --git a/ale_linters/python/pycodestyle.vim b/ale_linters/python/pycodestyle.vim new file mode 100644 index 00000000..282e545b --- /dev/null +++ b/ale_linters/python/pycodestyle.vim @@ -0,0 +1,87 @@ +" Author: Michael Thiesen +" Description: pycodestyle linting for python files + +call ale#Set('python_pycodestyle_executable', 'pycodestyle') +call ale#Set('python_pycodestyle_options', '') +call ale#Set('python_pycodestyle_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pycodestyle_auto_pipenv', 0) +call ale#Set('python_pycodestyle_auto_poetry', 0) +call ale#Set('python_pycodestyle_auto_uv', 0) + +function! ale_linters#python#pycodestyle#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pycodestyle_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pycodestyle_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pycodestyle_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pycodestyle', ['pycodestyle']) +endfunction + +function! ale_linters#python#pycodestyle#GetCommand(buffer) abort + let l:executable = ale_linters#python#pycodestyle#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pycodestyle' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . ' ' + \ . ale#Var(a:buffer, 'python_pycodestyle_options') + \ . ' -' +endfunction + +function! ale_linters#python#pycodestyle#Handle(buffer, lines) abort + let l:pattern = '\v^(\S*):(\d*):(\d*): ([EW]\d+) (.*)$' + let l:output = [] + + " lines are formatted as follows: + " file.py:21:26: W291 trailing whitespace + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if(l:match[4] is# 'W291' || l:match[4] is# 'W293') + \&& !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + if l:match[4] is# 'W391' + \&& !ale#Var(a:buffer, 'warn_about_trailing_blank_lines') + " Skip warnings for trailing blank lines if the option is off + continue + endif + + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:match[4][0], + \ 'sub_type': 'style', + \ 'text': l:match[5], + \ 'code': l:match[4], + \} + + " E999 and E112 are syntax errors. + if l:match[4] is# 'E999' || l:match[4] is# 'E112' + unlet l:item.sub_type + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pycodestyle', +\ '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 new file mode 100644 index 00000000..6293df7b --- /dev/null +++ b/ale_linters/python/pydocstyle.vim @@ -0,0 +1,83 @@ +" Author: Pablo Acosta +" Description: pydocstyle for python files + +call ale#Set('python_pydocstyle_executable', 'pydocstyle') +call ale#Set('python_pydocstyle_options', '') +call ale#Set('python_pydocstyle_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pydocstyle_auto_pipenv', 0) +call ale#Set('python_pydocstyle_auto_poetry', 0) +call ale#Set('python_pydocstyle_auto_uv', 0) + +function! ale_linters#python#pydocstyle#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pydocstyle_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pydocstyle_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pydocstyle_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pydocstyle', ['pydocstyle']) +endfunction + +function! ale_linters#python#pydocstyle#GetCommand(buffer) abort + let l:executable = ale_linters#python#pydocstyle#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pydocstyle' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_pydocstyle_options')) + \ . ' %s' +endfunction + +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:line1_pattern = '\v^.*:\s*(\d+)\s+.*$' + let l:line2_pattern = '\v^.*([a-zA-Z]\d+):\s*(.*)$' + let l:output = [] + + let l:num_lines = len(a:lines) + let l:index = 0 + + while l:index < l:num_lines + let l:lnum = matchlist(a:lines[l:index], l:line1_pattern) + + if !empty(l:lnum) && (l:index + 1 < l:num_lines) + let l:desc = matchlist(a:lines[l:index + 1], l:line2_pattern) + + if !empty(l:desc) + call add(l:output, { + \ 'lnum': l:lnum[1] + 0, + \ 'col': 1, + \ 'type': 'W', + \ 'text': l:desc[2], + \ 'code': l:desc[1], + \}) + endif + + let l:index = l:index + 2 + else + let l:index = l:index + 1 + endif + endwhile + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pydocstyle', +\ 'executable': function('ale_linters#python#pydocstyle#GetExecutable'), +\ 'cwd': '%s:h', +\ '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 new file mode 100644 index 00000000..f46772dc --- /dev/null +++ b/ale_linters/python/pyflakes.vim @@ -0,0 +1,62 @@ +" Author: w0rp +" Description: pyflakes for python files + +call ale#Set('python_pyflakes_executable', 'pyflakes') +call ale#Set('python_pyflakes_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pyflakes_auto_pipenv', 0) +call ale#Set('python_pyflakes_auto_poetry', 0) +call ale#Set('python_pyflakes_auto_uv', 0) + +function! ale_linters#python#pyflakes#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pyflakes_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pyflakes_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pyflakes_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pyflakes', ['pyflakes']) +endfunction + +function! ale_linters#python#pyflakes#GetCommand(buffer) abort + let l:executable = ale_linters#python#pyflakes#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pyflakes' + \ : '' + + return ale#Escape(l:executable) + \ . l:exec_args + \ . ' %t' +endfunction + +function! ale_linters#python#pyflakes#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[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pyflakes', +\ '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..626974f8 --- /dev/null +++ b/ale_linters/python/pylama.vim @@ -0,0 +1,160 @@ +" Author: Kevin Locke +" 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_auto_poetry', 0) +call ale#Set('python_pylama_auto_uv', 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 + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pylama_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pylama_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pylama', ['pylama']) +endfunction + +function! ale_linters#python#pylama#RunWithVersionCheck(buffer) abort + let l:executable = ale_linters#python#pylama#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pylama' + \ : '' + + let l:command = ale#Escape(l:executable) . l:exec_args . ' --version' + + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ l:command, + \ function('ale_linters#python#pylama#GetCommand'), + \) +endfunction + +function! ale_linters#python#pylama#GetCwd(buffer) abort + 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) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '' +endfunction + +function! ale_linters#python#pylama#GetCommand(buffer, version) abort + let l:executable = ale_linters#python#pylama#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pylama' + \ : '' + + " json format is added in version 8.1.4 + " https://github.com/klen/pylama/blob/develop/Changelog + let l:format_json_args = ale#semver#GTE(a:version, [8, 1, 4]) + \ ? ' --format json' + \ : '' + + " 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 ale#Escape(l:executable) . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_pylama_options')) + \ . l:format_json_args + \ . ' %s' +endfunction + +function! ale_linters#python#pylama#Handle(buffer, version, lines) abort + if empty(a:lines) + return [] + endif + + let l:output = ale#python#HandleTraceback(a:lines, 1) + + " 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', + \} + + if ale#semver#GTE(a:version, [8, 1, 4]) + try + let l:errors = json_decode(join(a:lines, '')) + catch + return l:output + endtry + + if empty(l:errors) + return l:output + endif + + for l:error in l:errors + call add(l:output, { + \ 'lnum': l:error['lnum'], + \ 'col': l:error['col'], + \ 'code': l:error['number'], + \ 'type': get(l:pylint_type_to_ale_type, l:error['etype'], 'W'), + \ 'sub_type': get(l:pylint_type_to_ale_sub_type, l:error['etype'], ''), + \ 'text': printf('%s [%s]', l:error['message'], l:error['source']), + \}) + endfor + else + let l:pattern = '\v^.{-}:([0-9]+):([0-9]+): +%(([A-Z][0-9]+):? +)?(.*)$' + + 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 + endif + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pylama', +\ 'executable': function('ale_linters#python#pylama#GetExecutable'), +\ 'cwd': function('ale_linters#python#pylama#GetCwd'), +\ 'command': function('ale_linters#python#pylama#RunWithVersionCheck'), +\ 'callback': {buffer, lines -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#python#pylama#GetExecutable(buffer), +\ '%e --version', +\ {buffer, version -> ale_linters#python#pylama#Handle( +\ buffer, +\ l:version, +\ lines)}, +\ )}, +\ 'lint_file': 1, +\}) diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim new file mode 100644 index 00000000..7941b829 --- /dev/null +++ b/ale_linters/python/pylint.vim @@ -0,0 +1,126 @@ +" Author: keith +" Description: pylint for python files + +call ale#Set('python_pylint_executable', 'pylint') +call ale#Set('python_pylint_options', '') +call ale#Set('python_pylint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pylint_change_directory', 1) +call ale#Set('python_pylint_auto_pipenv', 0) +call ale#Set('python_pylint_auto_poetry', 0) +call ale#Set('python_pylint_auto_uv', 0) +call ale#Set('python_pylint_use_msg_id', 0) + +function! ale_linters#python#pylint#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pylint_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pylint_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pylint_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pylint', ['pylint']) +endfunction + +function! ale_linters#python#pylint#GetCwd(buffer) abort + 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) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '' +endfunction + +function! ale_linters#python#pylint#GetCommand(buffer, version) abort + let l:executable = ale_linters#python#pylint#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pylint' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_pylint_options')) + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n' + \ . (ale#semver#GTE(a:version, [2, 4, 0]) ? ' --from-stdin' : '') + \ . ' %s' +endfunction + +function! ale_linters#python#pylint#Handle(buffer, lines) abort + let l:output = ale#python#HandleTraceback(a:lines, 10) + + if !empty(l:output) + return l:output + endif + + " Matches patterns like the following: + " + " test.py:4:4: W0101 (unreachable) Unreachable code + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):(\d+): ([[:alnum:]]+) \(([^(]*)\) (.*)$' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + "let l:failed = append(0, l:match) + let l:code = l:match[3] + + if (l:code is# 'C0303') + \ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + if l:code is# 'I0011' + " Skip 'Locally disabling' message + continue + endif + + if ale#Var(a:buffer, 'python_pylint_use_msg_id') + let l:code_out = l:code + else + let l:code_out = l:match[4] + endif + + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 1, + \ 'text': l:match[5], + \ 'code': l:code_out, + \ 'type': 'W', + \} + + if l:code[:0] is# 'E' + let l:item.type = 'E' + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pylint', +\ 'executable': function('ale_linters#python#pylint#GetExecutable'), +\ 'lint_file': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'python_pylint_executable'), +\ '%e --version', +\ {buffer, version -> !ale#semver#GTE(version, [2, 4, 0])}, +\ )}, +\ 'cwd': function('ale_linters#python#pylint#GetCwd'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'python_pylint_executable'), +\ '%e --version', +\ function('ale_linters#python#pylint#GetCommand'), +\ )}, +\ 'callback': 'ale_linters#python#pylint#Handle', +\}) diff --git a/ale_linters/python/pylsp.vim b/ale_linters/python/pylsp.vim new file mode 100644 index 00000000..75ec3884 --- /dev/null +++ b/ale_linters/python/pylsp.vim @@ -0,0 +1,67 @@ +" Author: aurieh +" Description: A language server for Python + +call ale#Set('python_pylsp_executable', 'pylsp') +call ale#Set('python_pylsp_options', '') +call ale#Set('python_pylsp_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pylsp_auto_pipenv', 0) +call ale#Set('python_pylsp_auto_poetry', 0) +call ale#Set('python_pylsp_auto_uv', 0) +call ale#Set('python_pylsp_config', {}) + +function! ale_linters#python#pylsp#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pylsp_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pylsp_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pylsp_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pylsp', ['pylsp']) +endfunction + +" Force the cwd of the server to be the same as the project root to +" fix issues with treating local files matching first or third party library +" names being imported incorrectly. +function! ale_linters#python#pylsp#GetCwd(buffer) abort + let l:fake_linter = { + \ 'name': 'pylsp', + \ 'project_root': function('ale#python#FindProjectRoot'), + \} + let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, l:fake_linter) + + return !empty(l:root) ? l:root : v:null +endfunction + +function! ale_linters#python#pylsp#GetCommand(buffer) abort + let l:executable = ale_linters#python#pylsp#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pylsp' + \ : '' + let l:env_string = '' + + if ale#Var(a:buffer, 'python_auto_virtualenv') + let l:env_string = ale#python#AutoVirtualenvEnvString(a:buffer) + endif + + return l:env_string . ale#Escape(l:executable) . l:exec_args . ale#Pad(ale#Var(a:buffer, 'python_pylsp_options')) +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pylsp', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#python#pylsp#GetExecutable'), +\ 'cwd': function('ale_linters#python#pylsp#GetCwd'), +\ 'command': function('ale_linters#python#pylsp#GetCommand'), +\ 'project_root': function('ale#python#FindProjectRoot'), +\ 'completion_filter': 'ale#completion#python#CompletionItemFilter', +\ 'lsp_config': {b -> ale#Var(b, 'python_pylsp_config')}, +\}) diff --git a/ale_linters/python/pyre.vim b/ale_linters/python/pyre.vim new file mode 100644 index 00000000..745d669a --- /dev/null +++ b/ale_linters/python/pyre.vim @@ -0,0 +1,50 @@ +" Author: dsifford +" Description: A performant type-checker supporting LSP for Python 3 created by Facebook + +call ale#Set('python_pyre_executable', 'pyre') +call ale#Set('python_pyre_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pyre_auto_pipenv', 0) +call ale#Set('python_pyre_auto_poetry', 0) +call ale#Set('python_pyre_auto_uv', 0) + +function! ale_linters#python#pyre#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pyre_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pyre_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pyre_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pyre', ['pyre']) +endfunction + +function! ale_linters#python#pyre#GetCommand(buffer) abort + let l:executable = ale_linters#python#pyre#GetExecutable(a:buffer) + let l:exec_args = (l:executable =~? '\(pipenv\|poetry\|uv\)$' ? ' run pyre' : '') . ' persistent' + + return ale#Escape(l:executable) . l:exec_args +endfunction + +function! ale_linters#python#pyre#GetCwd(buffer) abort + let l:local_config = ale#path#FindNearestFile(a:buffer, '.pyre_configuration.local') + + return fnamemodify(l:local_config, ':h') +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pyre', +\ 'lsp': 'stdio', +\ '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', +\ 'cwd': function('ale_linters#python#pyre#GetCwd'), +\}) diff --git a/ale_linters/python/pyright.vim b/ale_linters/python/pyright.vim new file mode 100644 index 00000000..95443a13 --- /dev/null +++ b/ale_linters/python/pyright.vim @@ -0,0 +1,94 @@ +call ale#Set('python_pyright_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pyright_executable', 'pyright-langserver') +call ale#Set('python_pyright_config', {}) +call ale#Set('python_pyright_auto_pipenv', 0) +call ale#Set('python_pyright_auto_poetry', 0) +call ale#Set('python_pyright_auto_uv', 0) + +" Force the cwd of the server to be the same as the project root to +" fix issues with treating local files matching first or third party library +" names being imported incorrectly. +function! ale_linters#python#pyright#GetCwd(buffer) abort + let l:fake_linter = { + \ 'name': 'pyright', + \ 'project_root': function('ale#python#FindProjectRoot'), + \} + let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, l:fake_linter) + + return !empty(l:root) ? l:root : v:null +endfunction + +function! ale_linters#python#pyright#GetConfig(buffer) abort + let l:config = deepcopy(ale#Var(a:buffer, 'python_pyright_config')) + + if !has_key(l:config, 'python') + let l:config.python = {} + endif + + if type(l:config.python) is v:t_dict + " Automatically detect the virtualenv path and use it. + if !has_key(l:config.python, 'venvPath') + let l:venv = ale#python#FindVirtualenv(a:buffer) + + if !empty(l:venv) + let l:config.python.venvPath = l:venv + endif + endif + + " Automatically use the version of Python in virtualenv. + if type(get(l:config.python, 'venvPath')) is v:t_string + \&& !empty(l:config.python.venvPath) + \&& !has_key(l:config.python, 'pythonPath') + let l:config.python.pythonPath = ale#path#Simplify( + \ l:config.python.venvPath + \ . (has('win32') ? '/Scripts/python' : '/bin/python') + \) + endif + endif + + return l:config +endfunction + +function! ale_linters#python#pyright#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pyright_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pyright_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pyright_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pyright', ['pyright-langserver']) +endfunction + +function! ale_linters#python#pyright#GetCommand(buffer) abort + let l:executable = ale_linters#python#pyright#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pyright-langserver' + \ : '' + let l:env_string = '' + + if ale#Var(a:buffer, 'python_auto_virtualenv') + let l:env_string = ale#python#AutoVirtualenvEnvString(a:buffer) + endif + + return l:env_string . ale#Escape(l:executable) . l:exec_args . ' --stdio' +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pyright', +\ 'lsp': 'stdio', +\ 'cwd': function('ale_linters#python#pyright#GetCwd'), +\ 'executable': function('ale_linters#python#pyright#GetExecutable'), +\ 'command': function('ale_linters#python#pyright#GetCommand'), +\ 'project_root': function('ale#python#FindProjectRoot'), +\ 'completion_filter': 'ale#completion#python#CompletionItemFilter', +\ 'lsp_config': function('ale_linters#python#pyright#GetConfig'), +\}) diff --git a/ale_linters/python/refurb.vim b/ale_linters/python/refurb.vim new file mode 100644 index 00000000..1acd4cee --- /dev/null +++ b/ale_linters/python/refurb.vim @@ -0,0 +1,79 @@ +" Author: Yining +" Description: refurb as linter for python files + +call ale#Set('python_refurb_executable', 'refurb') +call ale#Set('python_refurb_options', '') +call ale#Set('python_refurb_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_refurb_change_directory', 1) +call ale#Set('python_refurb_auto_pipenv', 0) +call ale#Set('python_refurb_auto_poetry', 0) +call ale#Set('python_refurb_auto_uv', 0) + +function! ale_linters#python#refurb#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_refurb_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_refurb_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_refurb_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_refurb', ['refurb']) +endfunction + +function! ale_linters#python#refurb#GetCwd(buffer) abort + if ale#Var(a:buffer, 'python_refurb_change_directory') + " Run from project root if found, else from buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '' +endfunction + +function! ale_linters#python#refurb#GetCommand(buffer) abort + let l:executable = ale_linters#python#refurb#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run refurb' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_refurb_options')) + \ . ' %s' +endfunction + +function! ale_linters#python#refurb#Handle(buffer, lines) abort + "Example: path/to/file.py:3:17 [FURB109]: Replace `in [x, y, z]` with `in (x, y, z)` + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):(\d+)?:?\s*\[FURB(\d+)\]:\s*(.+)$' + 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, + \ 'code': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'refurb', +\ 'executable': function('ale_linters#python#refurb#GetExecutable'), +\ 'cwd': function('ale_linters#python#refurb#GetCwd'), +\ 'command': function('ale_linters#python#refurb#GetCommand'), +\ 'callback': 'ale_linters#python#refurb#Handle', +\ 'output_stream': 'both', +\ 'read_buffer': 0, +\}) diff --git a/ale_linters/python/ruff.vim b/ale_linters/python/ruff.vim new file mode 100644 index 00000000..8eb55164 --- /dev/null +++ b/ale_linters/python/ruff.vim @@ -0,0 +1,107 @@ +" Author: Yining +" Description: ruff as linter for python files + +call ale#Set('python_ruff_executable', 'ruff') +call ale#Set('python_ruff_options', '') +call ale#Set('python_ruff_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_ruff_change_directory', 1) +call ale#Set('python_ruff_auto_pipenv', 0) +call ale#Set('python_ruff_auto_poetry', 0) +call ale#Set('python_ruff_auto_uv', 0) + +call ale#fix#registry#Add('ruff', +\ 'ale#fixers#ruff#Fix', +\ ['python'], +\ 'A python linter/fixer for Python written in Rust' +\) + +function! ale_linters#python#ruff#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_ruff_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_ruff_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_ruff_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_ruff', ['ruff']) +endfunction + +function! ale_linters#python#ruff#GetCwd(buffer) abort + if ale#Var(a:buffer, 'python_ruff_change_directory') + " Run from project root if found, else from buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '' +endfunction + +function! ale_linters#python#ruff#GetCommand(buffer, version) abort + let l:executable = ale_linters#python#ruff#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run ruff' + \ : '' + + " NOTE: ruff 0.3.0 deprecates `ruff ` in favor of `ruff check ` + let l:exec_args = l:exec_args + \ . (ale#semver#GTE(a:version, [0, 3, 0]) ? ' check' : '') + + " NOTE: ruff version `0.0.69` supports linting input from stdin + " NOTE: ruff version `0.1.0` deprecates `--format text` + return ale#Escape(l:executable) . l:exec_args . ' -q' + \ . ' --no-fix' + \ . ale#Pad(ale#Var(a:buffer, 'python_ruff_options')) + \ . (ale#semver#GTE(a:version, [0, 1, 0]) ? ' --output-format json-lines' : ' --format json-lines') + \ . (ale#semver#GTE(a:version, [0, 0, 69]) ? ' --stdin-filename %s -' : ' %s') +endfunction + +function! ale_linters#python#ruff#Handle(buffer, lines) abort + let l:output = [] + + " Read all lines of ruff output and parse use all the valid JSONL lines. + for l:line in a:lines + try + let l:item = json_decode(l:line) + catch + let l:item = v:null + endtry + + if !empty(l:item) + call add(l:output, { + \ 'lnum': l:item.location.row, + \ 'col': l:item.location.column, + \ 'end_lnum': l:item.end_location.row, + \ 'end_col': l:item.end_location.column - 1, + \ 'code': l:item.code, + \ 'text': l:item.message, + \ 'type': l:item.code =~? '\vE\d+' ? 'E' : 'W', + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'ruff', +\ 'executable': function('ale_linters#python#ruff#GetExecutable'), +\ 'cwd': function('ale_linters#python#ruff#GetCwd'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#python#ruff#GetExecutable(buffer), +\ '%e --version', +\ function('ale_linters#python#ruff#GetCommand'), +\ )}, +\ 'callback': 'ale_linters#python#ruff#Handle', +\ 'output_stream': 'both', +\ 'read_buffer': 1, +\}) diff --git a/ale_linters/python/unimport.vim b/ale_linters/python/unimport.vim new file mode 100644 index 00000000..1496fc42 --- /dev/null +++ b/ale_linters/python/unimport.vim @@ -0,0 +1,81 @@ +" Author: Author: Jon Parise + +call ale#Set('python_unimport_executable', 'unimport') +call ale#Set('python_unimport_options', '') +call ale#Set('python_unimport_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_unimport_auto_pipenv', 0) +call ale#Set('python_unimport_auto_poetry', 0) +call ale#Set('python_unimport_auto_uv', 0) + +function! ale_linters#python#unimport#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_unimport_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_unimport_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_unimport_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_unimport', ['unimport']) +endfunction + +function! ale_linters#python#unimport#GetCommand(buffer) abort + let l:executable = ale_linters#python#unimport#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run unimport' + \ : '' + + return '%e' . l:exec_args + \ . ale#Pad(ale#Var(a:buffer, 'python_unimport_options')) + \ . ' --check' + \ . ' %t' +endfunction + + +function! ale_linters#python#unimport#GetCwd(buffer) abort + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) + \ ? l:project_root + \ : expand('#' . a:buffer . ':p:h') +endfunction + + +function! ale_linters#python#unimport#Handle(buffer, lines) abort + let l:output = ale#python#HandleTraceback(a:lines, 10) + + if !empty(l:output) + return l:output + endif + + " Matches lines like: + " + " urllib.parse at path/to/file.py:9 + let l:pattern = '\v(.+) at [^:]+:(\d+)$' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'type': 'W', + \ 'text': 'unused: ' . l:match[1], + \}) + endfor + + return l:output +endfunction + + +call ale#linter#Define('python', { +\ 'name': 'unimport', +\ 'executable': function('ale_linters#python#unimport#GetExecutable'), +\ 'cwd': function('ale_linters#python#unimport#GetCwd'), +\ 'command': function('ale_linters#python#unimport#GetCommand'), +\ 'callback': 'ale_linters#python#unimport#Handle', +\}) diff --git a/ale_linters/python/vulture.vim b/ale_linters/python/vulture.vim new file mode 100644 index 00000000..c44638b9 --- /dev/null +++ b/ale_linters/python/vulture.vim @@ -0,0 +1,97 @@ +" Author: Yauheni Kirylau +" Description: vulture linting for python files + +call ale#Set('python_vulture_executable', 'vulture') +call ale#Set('python_vulture_options', '') +call ale#Set('python_vulture_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_vulture_change_directory', 1) +call ale#Set('python_vulture_auto_pipenv', 0) +call ale#Set('python_vulture_auto_poetry', 0) +call ale#Set('python_vulture_auto_uv', 0) + +" The directory to change to before running vulture +function! s:GetDir(buffer) abort + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) + \ ? l:project_root + \ : expand('#' . a:buffer . ':p:h') +endfunction + +function! ale_linters#python#vulture#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_vulture_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_vulture_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_vulture_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_vulture', ['vulture']) +endfunction + +function! ale_linters#python#vulture#GetCwd(buffer) abort + if !ale#Var(a:buffer, 'python_vulture_change_directory') + return '' + endif + + return s:GetDir(a:buffer) +endfunction + +function! ale_linters#python#vulture#GetCommand(buffer) abort + let l:executable = ale_linters#python#vulture#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run vulture' + \ : '' + let l:lint_dest = ale#Var(a:buffer, 'python_vulture_change_directory') + \ ? ' .' + \ : ' %s' + + return ale#Escape(l:executable) . l:exec_args + \ . ' ' + \ . ale#Var(a:buffer, 'python_vulture_options') + \ . l:lint_dest +endfunction + + +function! ale_linters#python#vulture#Handle(buffer, lines) abort + 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:dir = s:GetDir(a:buffer) + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:abspath = ale#path#GetAbsPath(l:dir, l:match[1]) + let l:item = { + \ 'filename': l:abspath, + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[3], + \ 'type': 'W', + \} + call add(l:output, l:item) + endfor + + return l:output +endfunction + + +call ale#linter#Define('python', { +\ 'name': 'vulture', +\ 'executable': function('ale_linters#python#vulture#GetExecutable'), +\ 'cwd': function('ale_linters#python#vulture#GetCwd'), +\ '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 new file mode 100644 index 00000000..11cc9413 --- /dev/null +++ b/ale_linters/qml/qmlfmt.vim @@ -0,0 +1,25 @@ +" Author: pylipp (www.github.com/pylipp) +" Description: qmlfmt for QML files + +call ale#Set('qml_qmlfmt_executable', 'qmlfmt') + +" Find lines like +" Error:11:1: Expected token `}' +function! ale_linters#qml#qmlfmt#Handle(buffer, lines) abort + let l:pattern = '\v^(Error|Warning):(\d+):(\d+): (.+)$' + + return map(ale#util#GetMatches(a:lines, l:pattern), "{ + \ 'lnum': v:val[2] + 0, + \ 'col': v:val[3] + 0, + \ 'text': v:val[4], + \ 'type': v:val[1] is# 'Warning' ? 'W' : 'E', + \}") +endfunction + +call ale#linter#Define('qml', { +\ 'name': 'qmlfmt', +\ 'output_stream': 'stderr', +\ 'executable': {b -> ale#Var(b, 'qml_qmlfmt_executable')}, +\ 'command': '%e -e', +\ 'callback': 'ale_linters#qml#qmlfmt#Handle', +\}) diff --git a/ale_linters/qml/qmllint.vim b/ale_linters/qml/qmllint.vim new file mode 100644 index 00000000..c2258a14 --- /dev/null +++ b/ale_linters/qml/qmllint.vim @@ -0,0 +1,29 @@ +" Author: pylipp (www.github.com/pylipp) +" Description: qmllint for QML files + +" Find lines like +" /home/foo_user42/code-base/qml/Screen.qml:11 : Expected token `}' +function! ale_linters#qml#qmllint#Handle(buffer, lines) abort + let l:pattern = '\v^[/_-a-zA-z0-9\. ]+:(\d+) : (.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': 0, + \ 'text': l:match[2], + \ 'type': 'E', + \} + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('qml', { +\ 'name': 'qmllint', +\ 'output_stream': 'stderr', +\ 'executable': 'qmllint', +\ 'command': 'qmllint %t', +\ 'callback': 'ale_linters#qml#qmllint#Handle', +\}) diff --git a/ale_linters/r/languageserver.vim b/ale_linters/r/languageserver.vim new file mode 100644 index 00000000..1ff23fa9 --- /dev/null +++ b/ale_linters/r/languageserver.vim @@ -0,0 +1,28 @@ +" Author: Eric Zhao <21zhaoe@protonmail.com> +" Author: ourigen +" Description: Implementation of the Language Server Protocol for R. + +call ale#Set('r_languageserver_cmd', 'languageserver::run()') +call ale#Set('r_languageserver_config', {}) + +function! ale_linters#r#languageserver#GetCommand(buffer) abort + let l:cmd_string = ale#Var(a:buffer, 'r_languageserver_cmd') + + return 'Rscript --no-save --no-restore --no-site-file --no-init-file -e ' . ale#Escape(l:cmd_string) +endfunction + +function! ale_linters#r#languageserver#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile(a:buffer, '.Rprofile') + + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : fnamemodify(a:buffer, ':h') +endfunction + +call ale#linter#Define('r', { +\ 'name': 'languageserver', +\ 'aliases': ['r_language_server'], +\ 'lsp': 'stdio', +\ 'lsp_config': {b -> ale#Var(b, 'r_languageserver_config')}, +\ 'executable': 'Rscript', +\ 'command': function('ale_linters#r#languageserver#GetCommand'), +\ 'project_root': function('ale_linters#r#languageserver#GetProjectRoot') +\}) diff --git a/ale_linters/r/lintr.vim b/ale_linters/r/lintr.vim new file mode 100644 index 00000000..339ad2b0 --- /dev/null +++ b/ale_linters/r/lintr.vim @@ -0,0 +1,35 @@ +" Author: Michel Lang , w0rp , +" Fenner Macrae , +" ourigen +" Description: This file adds support for checking R code with lintr. + +let g:ale_r_lintr_options = get(g:, 'ale_r_lintr_options', 'with_defaults()') +" A reasonable alternative default: +" get(g:, 'ale_r_lintr_options', 'with_defaults(object_usage_linter = NULL)') + + +let g:ale_r_lintr_lint_package = get(g:, 'ale_r_lintr_lint_package', 0) + +function! ale_linters#r#lintr#GetCommand(buffer) abort + if ale#Var(a:buffer, 'r_lintr_lint_package') + let l:lint_cmd = 'lint_package(cache = FALSE, linters = ' + \ . ale#Var(a:buffer, 'r_lintr_options') . ')' + else + let l:lint_cmd = 'lint(cache = FALSE, commandArgs(TRUE), ' + \ . ale#Var(a:buffer, 'r_lintr_options') . ')' + endif + + let l:cmd_string = 'suppressPackageStartupMessages(library(lintr));' + \ . l:lint_cmd + + return 'Rscript --no-save --no-restore --no-site-file --no-init-file -e ' . ale#Escape(l:cmd_string) . ' %t' +endfunction + +call ale#linter#Define('r', { +\ 'name': 'lintr', +\ 'executable': 'Rscript', +\ 'cwd': '%s:h', +\ 'command': function('ale_linters#r#lintr#GetCommand'), +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\ 'output_stream': 'both', +\}) diff --git a/ale_linters/racket/langserver.vim b/ale_linters/racket/langserver.vim new file mode 100644 index 00000000..ec80ed7b --- /dev/null +++ b/ale_linters/racket/langserver.vim @@ -0,0 +1,7 @@ +call ale#linter#Define('racket', { +\ 'name': 'racket_langserver', +\ 'lsp': 'stdio', +\ 'executable': 'racket', +\ 'command': '%e -l racket-langserver', +\ 'project_root': function('ale#racket#FindProjectRoot'), +\}) diff --git a/ale_linters/racket/raco.vim b/ale_linters/racket/raco.vim new file mode 100644 index 00000000..5b26065f --- /dev/null +++ b/ale_linters/racket/raco.vim @@ -0,0 +1,34 @@ +" Author: 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 + " :: + " 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, { + \ 'filename': l:match[2], + \ '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/ls.vim b/ale_linters/reason/ls.vim new file mode 100644 index 00000000..a831b506 --- /dev/null +++ b/ale_linters/reason/ls.vim @@ -0,0 +1,24 @@ +" Author: David Buchan-Swanson +" Description: Integrate ALE with reason-language-server. + +call ale#Set('reason_ls_executable', '') + +function! ale_linters#reason#ls#FindProjectRoot(buffer) abort + let l:reason_config = ale#path#FindNearestFile(a:buffer, 'bsconfig.json') + + if !empty(l:reason_config) + return fnamemodify(l:reason_config, ':h') + endif + + return '' +endfunction + +call ale#linter#Define('reason', { +\ 'name': 'reason-language-server', +\ 'aliases': ['reason_ls'], +\ 'lsp': 'stdio', +\ 'executable': {buffer -> ale#Var(buffer, 'reason_ls_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#reason#ls#FindProjectRoot'), +\ 'language': 'reason', +\}) diff --git a/ale_linters/reason/merlin.vim b/ale_linters/reason/merlin.vim new file mode 100644 index 00000000..7bef7df8 --- /dev/null +++ b/ale_linters/reason/merlin.vim @@ -0,0 +1,17 @@ +" Author: Andrey Popp -- @andreypopp +" Description: Report errors in ReasonML code with Merlin + +if !exists('g:merlin') + finish +endif + +function! ale_linters#reason#merlin#Handle(buffer, lines) abort + return merlin#ErrorLocList() +endfunction + +call ale#linter#Define('reason', { +\ 'name': 'merlin', +\ 'executable': 'ocamlmerlin', +\ 'command': 'true', +\ 'callback': 'ale_linters#reason#merlin#Handle', +\}) diff --git a/ale_linters/reason/ols.vim b/ale_linters/reason/ols.vim new file mode 100644 index 00000000..e1f408f0 --- /dev/null +++ b/ale_linters/reason/ols.vim @@ -0,0 +1,15 @@ +" Author: Michael Jungo +" Description: A language server for Reason + +call ale#Set('reason_ols_executable', 'ocaml-language-server') +call ale#Set('reason_ols_use_global', get(g:, 'ale_use_global_executables', 0)) + +call ale#linter#Define('reason', { +\ 'name': 'ols', +\ 'aliases': ['ocaml-language-server'], +\ 'lsp': 'stdio', +\ 'executable': function('ale#handlers#ols#GetExecutable'), +\ 'command': function('ale#handlers#ols#GetCommand'), +\ 'language': function('ale#handlers#ols#GetLanguage'), +\ 'project_root': function('ale#handlers#ols#GetProjectRoot'), +\}) diff --git a/ale_linters/rego/cspell.vim b/ale_linters/rego/cspell.vim new file mode 100644 index 00000000..a54a5379 --- /dev/null +++ b/ale_linters/rego/cspell.vim @@ -0,0 +1,4 @@ +scriptencoding utf-8 +" Description: cspell support for rego files. + +call ale#handlers#cspell#DefineLinter('rego') diff --git a/ale_linters/rego/opacheck.vim b/ale_linters/rego/opacheck.vim new file mode 100644 index 00000000..479091d3 --- /dev/null +++ b/ale_linters/rego/opacheck.vim @@ -0,0 +1,56 @@ +" Description: opa check for rego files + +call ale#Set('rego_opacheck_executable', 'opa') +call ale#Set('rego_opacheck_options', '') + +function! ale_linters#rego#opacheck#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'rego_opacheck_executable') +endfunction + +function! ale_linters#rego#opacheck#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'rego_opacheck_options') + + return ale#Escape(ale_linters#rego#opacheck#GetExecutable(a:buffer)) + \ . ' check %s:h --format json ' + \ . (!empty(l:options) ? ' ' . l:options : '') +endfunction + +function! ale_linters#rego#opacheck#Handle(buffer, lines) abort + let l:output = [] + + let l:errors = ale#util#FuzzyJSONDecode(a:lines, {'errors': []}) + let l:dir = expand('#' . a:buffer . ':p:h') + let l:file = expand('#' . a:buffer . ':p') + + for l:error in l:errors['errors'] + if has_key(l:error, 'location') + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:error['location']['file']), + \ 'lnum': l:error['location']['row'], + \ 'col': l:error['location']['col'], + \ 'text': l:error['message'], + \ 'code': l:error['code'], + \ 'type': 'E', + \}) + else + call add(l:output, { + \ 'filename': l:file, + \ 'lnum': 0, + \ 'col': 0, + \ 'text': l:error['message'], + \ 'code': l:error['code'], + \ 'type': 'E', + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('rego', { +\ 'name': 'opacheck', +\ 'output_stream': 'both', +\ 'executable': function('ale_linters#rego#opacheck#GetExecutable'), +\ 'command': function('ale_linters#rego#opacheck#GetCommand'), +\ 'callback': 'ale_linters#rego#opacheck#Handle', +\}) diff --git a/ale_linters/review/redpen.vim b/ale_linters/review/redpen.vim new file mode 100644 index 00000000..0006cab9 --- /dev/null +++ b/ale_linters/review/redpen.vim @@ -0,0 +1,9 @@ +" Author: rhysd https://rhysd.github.io +" Description: Redpen, a proofreading tool (http://redpen.cc) + +call ale#linter#Define('review', { +\ 'name': 'redpen', +\ 'executable': 'redpen', +\ 'command': 'redpen -f review -r json %t', +\ 'callback': 'ale#handlers#redpen#HandleRedpenOutput', +\}) diff --git a/ale_linters/robot/rflint.vim b/ale_linters/robot/rflint.vim new file mode 100644 index 00000000..8fe2d797 --- /dev/null +++ b/ale_linters/robot/rflint.vim @@ -0,0 +1,46 @@ +" Author: Samuel Branisa +" Description: rflint linting for robot framework files + +call ale#Set('robot_rflint_executable', 'rflint') + +function! ale_linters#robot#rflint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'robot_rflint_executable') +endfunction + +function! ale_linters#robot#rflint#GetCommand(buffer) abort + let l:executable = ale_linters#robot#rflint#GetExecutable(a:buffer) + let l:flags = '--format' + \ . ' "{filename}:{severity}:{linenumber}:{char}:{rulename}:{message}"' + + return l:executable + \ . ' ' + \ . l:flags + \ . ' %s' +endfunction + +function! ale_linters#robot#rflint#Handle(buffer, lines) abort + let l:pattern = '\v^([[:alnum:][:punct:]]+):(W|E):([[:digit:]]+):([[:digit:]]+):([[:alnum:]]+):(.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'filename': l:match[1], + \ 'type': l:match[2], + \ 'lnum': str2nr(l:match[3]), + \ 'col': str2nr(l:match[4]), + \ 'text': l:match[5], + \ 'detail': l:match[6], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('robot', { +\ 'name': 'rflint', +\ 'executable': function('ale_linters#robot#rflint#GetExecutable'), +\ 'command': function('ale_linters#robot#rflint#GetCommand'), +\ 'callback': 'ale_linters#robot#rflint#Handle', +\}) + diff --git a/ale_linters/rst/alex.vim b/ale_linters/rst/alex.vim new file mode 100644 index 00000000..e7ca6fa0 --- /dev/null +++ b/ale_linters/rst/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for rst files + +call ale#handlers#alex#DefineLinter('rst', '--text') diff --git a/ale_linters/rst/cspell.vim b/ale_linters/rst/cspell.vim new file mode 100644 index 00000000..14cfb42e --- /dev/null +++ b/ale_linters/rst/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for ReStructuredText files. + +call ale#handlers#cspell#DefineLinter('rst') diff --git a/ale_linters/rst/proselint.vim b/ale_linters/rst/proselint.vim new file mode 100644 index 00000000..018347ae --- /dev/null +++ b/ale_linters/rst/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for reStructuredText files + +call ale#linter#Define('rst', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/rst/redpen.vim b/ale_linters/rst/redpen.vim new file mode 100644 index 00000000..ac966c56 --- /dev/null +++ b/ale_linters/rst/redpen.vim @@ -0,0 +1,9 @@ +" Author: rhysd https://rhysd.github.io +" Description: Redpen, a proofreading tool (http://redpen.cc) + +call ale#linter#Define('rst', { +\ 'name': 'redpen', +\ 'executable': 'redpen', +\ 'command': 'redpen -f rest -r json %t', +\ 'callback': 'ale#handlers#redpen#HandleRedpenOutput', +\}) diff --git a/ale_linters/rst/rstcheck.vim b/ale_linters/rst/rstcheck.vim new file mode 100644 index 00000000..e0cf0798 --- /dev/null +++ b/ale_linters/rst/rstcheck.vim @@ -0,0 +1,31 @@ +" Author: John Nduli https://github.com/jnduli +" Description: Rstcheck for reStructuredText files + +function! ale_linters#rst#rstcheck#Handle(buffer, lines) abort + " matches: 'bad_rst.rst:1: (SEVERE/4) Title overline & underline + " mismatch.' + let l:pattern = '\v^(.+):(\d*): \(([a-zA-Z]*)/\d*\) (.+)$' + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': 0, + \ 'type': l:match[3] is# 'SEVERE' ? 'E' : 'W', + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('rst', { +\ 'name': 'rstcheck', +\ 'executable': 'rstcheck', +\ 'cwd': '%s:h', +\ 'command': 'rstcheck %t', +\ '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 +" 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/rst/vale.vim b/ale_linters/rst/vale.vim new file mode 100644 index 00000000..2e654dc4 --- /dev/null +++ b/ale_linters/rst/vale.vim @@ -0,0 +1,9 @@ +" Author: chew-z https://github.com/chew-z +" Description: vale for RST files + +call ale#linter#Define('rst', { +\ 'name': 'vale', +\ 'executable': 'vale', +\ 'command': 'vale --output=JSON %t', +\ 'callback': 'ale#handlers#vale#Handle', +\}) diff --git a/ale_linters/rst/writegood.vim b/ale_linters/rst/writegood.vim new file mode 100644 index 00000000..26b1152a --- /dev/null +++ b/ale_linters/rst/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for reStructuredText files + +call ale#handlers#writegood#DefineLinter('rst') diff --git a/ale_linters/ruby/brakeman.vim b/ale_linters/ruby/brakeman.vim new file mode 100644 index 00000000..2dc48740 --- /dev/null +++ b/ale_linters/ruby/brakeman.vim @@ -0,0 +1,51 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Brakeman, a static analyzer for Rails security + +call ale#Set('ruby_brakeman_options', '') +call ale#Set('ruby_brakeman_executable', 'brakeman') +call ale#Set('ruby_brakeman_options', '') + +function! ale_linters#ruby#brakeman#Handle(buffer, lines) abort + let l:output = [] + let l:json = ale#util#FuzzyJSONDecode(a:lines, {}) + let l:sep = has('win32') ? '\' : '/' + " Brakeman always outputs paths relative to the Rails app root + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + for l:warning in get(l:json, 'warnings', []) + let l:text = l:warning.warning_type . ' ' . l:warning.message . ' (' . l:warning.confidence . ')' + let l:line = l:warning.line != v:null ? l:warning.line : 1 + + call add(l:output, { + \ 'filename': l:rails_root . l:sep . l:warning.file, + \ 'lnum': l:line, + \ 'type': 'W', + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +function! ale_linters#ruby#brakeman#GetCommand(buffer) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if l:rails_root is? '' + return '' + endif + + let l:executable = ale#Var(a:buffer, 'ruby_brakeman_executable') + + return ale#ruby#EscapeExecutable(l:executable, 'brakeman') + \ . ' -f json -q ' + \ . ale#Var(a:buffer, 'ruby_brakeman_options') + \ . ' -p ' . ale#Escape(l:rails_root) +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'brakeman', +\ '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/cspell.vim b/ale_linters/ruby/cspell.vim new file mode 100644 index 00000000..780356b1 --- /dev/null +++ b/ale_linters/ruby/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Ruby files. + +call ale#handlers#cspell#DefineLinter('ruby') diff --git a/ale_linters/ruby/debride.vim b/ale_linters/ruby/debride.vim new file mode 100644 index 00000000..3b2cc443 --- /dev/null +++ b/ale_linters/ruby/debride.vim @@ -0,0 +1,42 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: debride, a dead method detector for Ruby files + +call ale#Set('ruby_debride_executable', 'debride') +call ale#Set('ruby_debride_options', '') + +function! ale_linters#ruby#debride#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_debride_executable') + + return ale#ruby#EscapeExecutable(l:executable, 'debride') + \ . ale#Var(a:buffer, 'ruby_debride_options') + \ . ' %s' +endfunction + +function! ale_linters#ruby#debride#HandleOutput(buffer, lines) abort + let l:output = [] + + for l:line in a:lines + if l:line !~# '^ ' + continue + endif + + let l:elements = split(l:line) + let l:method_name = l:elements[0] + let l:lnum = split(l:elements[1], ':')[1] + + call add(l:output, { + \ 'lnum': 0 + l:lnum, + \ 'text': 'Possible unused method: ' . l:method_name, + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'debride', +\ 'executable': {b -> ale#Var(b, 'ruby_debride_executable')}, +\ 'command': function('ale_linters#ruby#debride#GetCommand'), +\ 'callback': 'ale_linters#ruby#debride#HandleOutput', +\}) diff --git a/ale_linters/ruby/packwerk.vim b/ale_linters/ruby/packwerk.vim new file mode 100644 index 00000000..4014b2da --- /dev/null +++ b/ale_linters/ruby/packwerk.vim @@ -0,0 +1,55 @@ +" Author: ymap - https://github.com/ymap +" Description: Packwerk, a static analyzer used to enforce boundaries and modularize Rails applications. + +call ale#Set('ruby_packwerk_executable', 'packwerk') +call ale#Set('ruby_packwerk_options', '') + +function! ale_linters#ruby#packwerk#Handle(buffer, lines) abort + let l:pattern = '\v^[^:]+:(\d+):(\d+)$' + let l:index = 0 + let l:output = [] + + while l:index < len(a:lines) - 1 + let l:cleaned_line = substitute(a:lines[l:index], '\v\e\[[0-9;]*m', '', 'g') + let l:match = matchlist(l:cleaned_line, l:pattern) + + if len(l:match) > 0 + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': a:lines[l:index + 1], + \}) + endif + + let l:index += 1 + endwhile + + return l:output +endfunction + +function! ale_linters#ruby#packwerk#GetCommand(buffer) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if l:rails_root is? '' + return '' + endif + + let l:executable = ale#Var(a:buffer, 'ruby_packwerk_executable') + let l:sep = has('win32') ? '\' : '/' + let l:abs_path = expand('#' . a:buffer . ':p') + let l:rel_path = substitute(l:abs_path, escape(l:rails_root . l:sep, '\'), '', '') + + return ale#ruby#EscapeExecutable(l:executable, 'packwerk') + \ . ' check' + \ . ale#Pad(ale#Var(a:buffer, 'ruby_packwerk_options')) + \ . ' ' + \ . ale#Escape(rel_path) +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'packwerk', +\ 'executable': {b -> ale#Var(b, 'ruby_packwerk_executable')}, +\ 'command': function('ale_linters#ruby#packwerk#GetCommand'), +\ 'callback': 'ale_linters#ruby#packwerk#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/ruby/rails_best_practices.vim b/ale_linters/ruby/rails_best_practices.vim new file mode 100644 index 00000000..36646647 --- /dev/null +++ b/ale_linters/ruby/rails_best_practices.vim @@ -0,0 +1,49 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: rails_best_practices, a code metric tool for rails projects + +call ale#Set('ruby_rails_best_practices_options', '') +call ale#Set('ruby_rails_best_practices_executable', 'rails_best_practices') + +function! ale_linters#ruby#rails_best_practices#Handle(buffer, lines) abort + let l:output = [] + + for l:warning in ale#util#FuzzyJSONDecode(a:lines, []) + if !ale#path#IsBufferPath(a:buffer, l:warning.filename) + continue + endif + + call add(l:output, { + \ 'lnum': l:warning.line_number + 0, + \ 'type': 'W', + \ 'text': l:warning.message, + \}) + endfor + + return l:output +endfunction + +function! ale_linters#ruby#rails_best_practices#GetCommand(buffer) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if l:rails_root is? '' + return '' + endif + + let l:executable = ale#Var(a:buffer, 'ruby_rails_best_practices_executable') + let l:output_file = has('win32') ? '%t ' : '/dev/stdout ' + let l:cat_file = has('win32') ? '; type %t' : '' + + return ale#ruby#EscapeExecutable(l:executable, 'rails_best_practices') + \ . ' --silent -f json --output-file ' . l:output_file + \ . ale#Var(a:buffer, 'ruby_rails_best_practices_options') + \ . ale#Escape(l:rails_root) + \ . l:cat_file +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'rails_best_practices', +\ '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 new file mode 100644 index 00000000..b6fa9d76 --- /dev/null +++ b/ale_linters/ruby/reek.vim @@ -0,0 +1,69 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Reek, a code smell detector for Ruby files + +call ale#Set('ruby_reek_show_context', 0) +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#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(a:version, [5, 0, 0]) + \ ? ' --stdin-filename %s' + \ : '' + + return ale#ruby#EscapeExecutable(l:executable, 'reek') + \ . ' -f json --no-progress --no-color --force-exclusion' + \ . l:display_name_args +endfunction + +function! s:GetDocumentationLink(error) abort + return get(a:error, 'documentation_link', get(a:error, 'wiki_link', '')) +endfunction + +function! s:BuildText(buffer, error) abort + let l:parts = [] + + if ale#Var(a:buffer, 'ruby_reek_show_context') + call add(l:parts, a:error.context) + endif + + call add(l:parts, a:error.message) + + if ale#Var(a:buffer, 'ruby_reek_show_wiki_link') + call add(l:parts, '[' . s:GetDocumentationLink(a:error) . ']') + endif + + return join(l:parts, ' ') +endfunction + +function! ale_linters#ruby#reek#Handle(buffer, lines) abort + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + for l:location in l:error.lines + call add(l:output, { + \ 'lnum': l:location, + \ 'type': 'W', + \ 'text': s:BuildText(a:buffer, l:error), + \ 'code': l:error.smell_type, + \}) + endfor + endfor + + return l:output +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'reek', +\ '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 new file mode 100644 index 00000000..483806a6 --- /dev/null +++ b/ale_linters/ruby/rubocop.vim @@ -0,0 +1,31 @@ +" Author: ynonp - https://github.com/ynonp, Eddie Lebow https://github.com/elebow +" Description: RuboCop, a code style analyzer for Ruby files + +call ale#Set('ruby_rubocop_executable', 'rubocop') +call ale#Set('ruby_rubocop_options', '') + +function! ale_linters#ruby#rubocop#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_rubocop_executable') + + return ale#ruby#EscapeExecutable(l:executable, 'rubocop') + \ . ' --format json --force-exclusion ' + \ . ale#Var(a:buffer, 'ruby_rubocop_options') + \ . ' --stdin %s' +endfunction + +function! ale_linters#ruby#rubocop#GetType(severity) abort + if a:severity is? 'convention' + \|| a:severity is? 'warning' + \|| a:severity is? 'refactor' + return 'W' + endif + + return 'E' +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'rubocop', +\ '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 new file mode 100644 index 00000000..621fcbc0 --- /dev/null +++ b/ale_linters/ruby/ruby.vim @@ -0,0 +1,12 @@ +" Author: Brandon Roehl - https://github.com/BrandonRoehl +" Description: Ruby MRI for Ruby files + +call ale#Set('ruby_ruby_executable', 'ruby') + +call ale#linter#Define('ruby', { +\ 'name': 'ruby', +\ 'executable': {b -> ale#Var(b, 'ruby_ruby_executable')}, +\ 'command': '%e -w -c %t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) diff --git a/ale_linters/ruby/solargraph.vim b/ale_linters/ruby/solargraph.vim new file mode 100644 index 00000000..bf54a55c --- /dev/null +++ b/ale_linters/ruby/solargraph.vim @@ -0,0 +1,22 @@ +" Author: Horacio Sanson - https://github.com/hsanson +" Description: Solargraph Language Server https://solargraph.org/ +" +" Author: Devon Meunier +" Description: updated to use stdio + +call ale#Set('ruby_solargraph_executable', 'solargraph') +call ale#Set('ruby_solargraph_options', {}) + +function! ale_linters#ruby#solargraph#GetCommand(buffer) abort + return '%e' . ale#Pad('stdio') +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'solargraph', +\ 'lsp': 'stdio', +\ 'language': 'ruby', +\ '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/sorbet.vim b/ale_linters/ruby/sorbet.vim new file mode 100644 index 00000000..c67e20cc --- /dev/null +++ b/ale_linters/ruby/sorbet.vim @@ -0,0 +1,26 @@ +call ale#Set('ruby_sorbet_executable', 'srb') +call ale#Set('ruby_sorbet_options', '') +call ale#Set('ruby_sorbet_enable_watchman', 0) + +function! ale_linters#ruby#sorbet#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_sorbet_executable') + let l:options = ale#Var(a:buffer, 'ruby_sorbet_options') + let l:enable_watchman = ale#Var(a:buffer, 'ruby_sorbet_enable_watchman') + + return ale#ruby#EscapeExecutable(l:executable, 'srb') + \ . ' tc' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --lsp' + \ . (l:enable_watchman ? '' : ' --disable-watchman') +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'sorbet', +\ 'aliases': ['srb'], +\ 'lsp': 'stdio', +\ 'language': 'ruby', +\ 'executable': {b -> ale#Var(b, 'ruby_sorbet_executable')}, +\ 'command': function('ale_linters#ruby#sorbet#GetCommand'), +\ 'project_root': function('ale#ruby#FindProjectRoot') +\}) + diff --git a/ale_linters/ruby/standardrb.vim b/ale_linters/ruby/standardrb.vim new file mode 100644 index 00000000..6ccfd2d6 --- /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#ruby#EscapeExecutable(l:executable, 'standardrb') + \ . ' --format json --force-exclusion ' + \ . ale#Var(a:buffer, 'ruby_standardrb_options') + \ . ' --stdin %s' +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/ruby/steep.vim b/ale_linters/ruby/steep.vim new file mode 100644 index 00000000..4fd52620 --- /dev/null +++ b/ale_linters/ruby/steep.vim @@ -0,0 +1,172 @@ +call ale#Set('ruby_steep_executable', 'steep') +call ale#Set('ruby_steep_options', '') + +" Find the nearest dir containing a Steepfile +function! ale_linters#ruby#steep#FindRoot(buffer) abort + for l:name in ['Steepfile'] + let l:dir = fnamemodify( + \ ale#path#FindNearestFile(a:buffer, l:name), + \ ':h' + \) + + if l:dir isnot# '.' && isdirectory(l:dir) + return l:dir + endif + endfor + + return '' +endfunction + +" Rename path relative to root +function! ale_linters#ruby#steep#RelativeToRoot(buffer, path) abort + let l:separator = has('win32') ? '\' : '/' + let l:steep_root = ale_linters#ruby#steep#FindRoot(a:buffer) + + " path isn't under root + if l:steep_root is# '' + return '' + endif + + let l:steep_root_prefix = l:steep_root . l:separator + + " win32 path separators get interpreted by substitute, escape them + if has('win32') + let l:steep_root_pat = substitute(l:steep_root_prefix, '\\', '\\\\', 'g') + else + let l:steep_root_pat = l:steep_root_prefix + endif + + return substitute(a:path, l:steep_root_pat, '', '') +endfunction + +function! ale_linters#ruby#steep#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_steep_executable') + + " steep check needs to apply some config from the file path so: + " - steep check can't use stdin (no path) + " - steep check can't use %t (path outside of project) + " => we can only use %s + + " somehow :ALEInfo shows that ALE still appends '< %t' to the command + " => luckily steep check ignores stdin + + " somehow steep has a problem with absolute path to file but a path + " relative to Steepfile directory works: + " see https://github.com/soutaro/steep/pull/975 + " => change to Steepfile directory and remove leading path + + let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p') + let l:buffer_filename = fnameescape(l:buffer_filename) + + let l:relative = ale_linters#ruby#steep#RelativeToRoot(a:buffer, l:buffer_filename) + + " if file is not under steep root, steep can't type check + if l:relative is# '' + " don't execute + return '' + endif + + return ale#ruby#EscapeExecutable(l:executable, 'steep') + \ . ' check ' + \ . ale#Var(a:buffer, 'ruby_steep_options') + \ . ' ' . fnameescape(l:relative) +endfunction + +function! ale_linters#ruby#steep#GetType(severity) abort + if a:severity is? 'information' + \|| a:severity is? 'hint' + return 'I' + endif + + if a:severity is? 'warning' + return 'W' + endif + + return 'E' +endfunction + +" Handle output from steep +function! ale_linters#ruby#steep#HandleOutput(buffer, lines) abort + let l:output = [] + + let l:in = 0 + let l:item = {} + + for l:line in a:lines + " Look for first line of a message block + " If not in-message (l:in == 0) that's expected + " If in-message (l:in > 0) that's less expected but let's recover + let l:match = matchlist(l:line, '^\([^:]*\):\([0-9]*\):\([0-9]*\): \[\([^]]*\)\] \(.*\)') + + if len(l:match) > 0 + " Something is lingering: recover by pushing what is there + if len(l:item) > 0 + call add(l:output, l:item) + let l:item = {} + endif + + let l:filename = l:match[1] + + " Steep's reported column is offset by 1 (zero-indexed?) + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 1, + \ 'type': ale_linters#ruby#steep#GetType(l:match[4]), + \ 'text': l:match[5], + \} + + " Done with this line, mark being in-message and go on with next line + let l:in = 1 + continue + endif + + " We're past the first line of a message block + if l:in > 0 + " Look for code in subsequent lines of the message block + if l:line =~# '^│ Diagnostic ID:' + let l:match = matchlist(l:line, '^│ Diagnostic ID: \(.*\)') + + if len(l:match) > 0 + let l:item.code = l:match[1] + endif + + " Done with the line + continue + endif + + " Look for last line of the message block + if l:line =~# '^└' + " Done with the line, mark looking for underline and go on with the next line + let l:in = 2 + continue + endif + + " Look for underline right after last line + if l:in == 2 + let l:match = matchlist(l:line, '\([~][~]*\)') + + if len(l:match) > 0 + let l:item.end_col = l:item['col'] + len(l:match[1]) - 1 + endif + + call add(l:output, l:item) + + " Done with the line, mark looking for first line and go on with the next line + let l:in = 0 + let l:item = {} + continue + endif + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'steep', +\ 'executable': {b -> ale#Var(b, 'ruby_steep_executable')}, +\ 'language': 'ruby', +\ 'command': function('ale_linters#ruby#steep#GetCommand'), +\ 'project_root': function('ale_linters#ruby#steep#FindRoot'), +\ 'callback': 'ale_linters#ruby#steep#HandleOutput', +\}) diff --git a/ale_linters/rust/analyzer.vim b/ale_linters/rust/analyzer.vim new file mode 100644 index 00000000..e3141cd3 --- /dev/null +++ b/ale_linters/rust/analyzer.vim @@ -0,0 +1,37 @@ +" Author: Jon Gjengset +" Description: The next generation language server for Rust + +call ale#Set('rust_analyzer_executable', 'rust-analyzer') +call ale#Set('rust_analyzer_config', {}) + +function! ale_linters#rust#analyzer#GetCommand(buffer) abort + return '%e' +endfunction + +function! ale_linters#rust#analyzer#GetProjectRoot(buffer) abort + " Try to find nearest Cargo.toml for cargo projects + let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') + + if !empty(l:cargo_file) + return fnamemodify(l:cargo_file, ':h') + endif + + " Try to find nearest rust-project.json for non-cargo projects + let l:rust_project = ale#path#FindNearestFile(a:buffer, 'rust-project.json') + + if !empty(l:rust_project) + return fnamemodify(l:rust_project, ':h') + endif + + return '' +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'analyzer', +\ 'aliases': ['rust_analyzer'], +\ 'lsp': 'stdio', +\ 'initialization_options': {b -> ale#Var(b, 'rust_analyzer_config')}, +\ 'executable': {b -> ale#Var(b, 'rust_analyzer_executable')}, +\ 'command': function('ale_linters#rust#analyzer#GetCommand'), +\ 'project_root': function('ale_linters#rust#analyzer#GetProjectRoot'), +\}) diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim new file mode 100644 index 00000000..37fd10a8 --- /dev/null +++ b/ale_linters/rust/cargo.vim @@ -0,0 +1,110 @@ +" Author: Daniel Schemala , +" Ivan Petkov +" Description: rustc invoked by cargo for rust files + +call ale#Set('rust_cargo_use_check', 1) +call ale#Set('rust_cargo_check_all_targets', 0) +call ale#Set('rust_cargo_check_examples', 0) +call ale#Set('rust_cargo_check_tests', 0) +call ale#Set('rust_cargo_avoid_whole_workspace', 1) +call ale#Set('rust_cargo_default_feature_behavior', 'default') +call ale#Set('rust_cargo_include_features', '') +call ale#Set('rust_cargo_use_clippy', 0) +call ale#Set('rust_cargo_clippy_options', '') +call ale#Set('rust_cargo_target_dir', '') + +function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort + if ale#path#FindNearestFile(a:bufnr, 'Cargo.toml') isnot# '' + return 'cargo' + else + " if there is no Cargo.toml file, we don't use cargo even if it exists, + " so we return '', because executable('') apparently always fails + return '' + endif +endfunction + +function! ale_linters#rust#cargo#GetCwd(buffer) abort + if ale#Var(a:buffer, 'rust_cargo_avoid_whole_workspace') + let l:nearest_cargo = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') + let l:nearest_cargo_dir = fnamemodify(l:nearest_cargo, ':h') + + if l:nearest_cargo_dir isnot# '.' + return l:nearest_cargo_dir + endif + endif + + return '' +endfunction + +function! ale_linters#rust#cargo#GetCommand(buffer, version) abort + let l:use_check = ale#Var(a:buffer, 'rust_cargo_use_check') + \ && ale#semver#GTE(a:version, [0, 17, 0]) + let l:use_all_targets = ale#Var(a:buffer, 'rust_cargo_check_all_targets') + \ && ale#semver#GTE(a:version, [0, 22, 0]) + let l:use_examples = ale#Var(a:buffer, 'rust_cargo_check_examples') + \ && ale#semver#GTE(a:version, [0, 22, 0]) + let l:use_tests = ale#Var(a:buffer, 'rust_cargo_check_tests') + \ && ale#semver#GTE(a:version, [0, 22, 0]) + let l:target_dir = ale#Var(a:buffer, 'rust_cargo_target_dir') + let l:use_target_dir = !empty(l:target_dir) + \ && ale#semver#GTE(a:version, [0, 17, 0]) + + let l:include_features = ale#Var(a:buffer, 'rust_cargo_include_features') + + if !empty(l:include_features) + let l:include_features = ' --features ' . ale#Escape(l:include_features) + endif + + let l:default_feature_behavior = ale#Var(a:buffer, 'rust_cargo_default_feature_behavior') + + if l:default_feature_behavior is# 'all' + let l:include_features = '' + let l:default_feature = ' --all-features' + elseif l:default_feature_behavior is# 'none' + let l:default_feature = ' --no-default-features' + else + let l:default_feature = '' + endif + + let l:subcommand = l:use_check ? 'check' : 'build' + let l:clippy_options = '' + + if ale#Var(a:buffer, 'rust_cargo_use_clippy') + let l:subcommand = 'clippy' + let l:clippy_options = ale#Var(a:buffer, 'rust_cargo_clippy_options') + + if l:clippy_options =~# '^-- ' + let l:clippy_options = join(split(l:clippy_options, '-- ')) + endif + + if l:clippy_options isnot# '' + let l:clippy_options = ' -- ' . l:clippy_options + endif + endif + + return 'cargo ' + \ . l:subcommand + \ . (l:use_all_targets ? ' --all-targets' : '') + \ . (l:use_examples ? ' --examples' : '') + \ . (l:use_tests ? ' --tests' : '') + \ . (l:use_target_dir ? (' --target-dir ' . ale#Escape(l:target_dir)) : '') + \ . ' --frozen --message-format=json -q' + \ . l:default_feature + \ . l:include_features + \ . l:clippy_options +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'cargo', +\ 'executable': function('ale_linters#rust#cargo#GetCargoExecutable'), +\ 'cwd': function('ale_linters#rust#cargo#GetCwd'), +\ '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/cspell.vim b/ale_linters/rust/cspell.vim new file mode 100644 index 00000000..d2523c7d --- /dev/null +++ b/ale_linters/rust/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Rust files. + +call ale#handlers#cspell#DefineLinter('rust') diff --git a/ale_linters/rust/rls.vim b/ale_linters/rust/rls.vim new file mode 100644 index 00000000..111d7558 --- /dev/null +++ b/ale_linters/rust/rls.vim @@ -0,0 +1,27 @@ +" Author: w0rp +" Description: A language server for Rust + +call ale#Set('rust_rls_executable', 'rls') +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') + + return '%e' . (!empty(l:toolchain) ? ' +' . ale#Escape(l:toolchain) : '') +endfunction + +function! ale_linters#rust#rls#GetProjectRoot(buffer) abort + let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') + + return !empty(l:cargo_file) ? fnamemodify(l:cargo_file, ':h') : '' +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'rls', +\ 'lsp': 'stdio', +\ '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 new file mode 100644 index 00000000..bc6431ba --- /dev/null +++ b/ale_linters/rust/rustc.vim @@ -0,0 +1,33 @@ +" Author: Daniel Schemala +" Description: rustc for rust files + +call ale#Set('rust_rustc_options', '--emit=mir -o /dev/null') + +function! ale_linters#rust#rustc#RustcCommand(buffer) abort + " Try to guess the library search path. If the project is managed by cargo, + " it's usually /target/debug/deps/ or + " /target/release/deps/ + let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') + + if l:cargo_file isnot# '' + let l:root = fnamemodify(l:cargo_file, ':h') + let l:dependencies = ' -L ' . ale#Escape(ale#path#GetAbsPath(l:root, 'target/debug/deps')) + \ . ' -L ' . ale#Escape(ale#path#GetAbsPath(l:root, 'target/release/deps')) + else + let l:dependencies = '' + endif + + let l:options = ale#Var(a:buffer, 'rust_rustc_options') + + return 'rustc --error-format=json' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . l:dependencies . ' -' +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'rustc', +\ 'executable': 'rustc', +\ 'command': function('ale_linters#rust#rustc#RustcCommand'), +\ 'callback': 'ale#handlers#rust#HandleRustErrors', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/salt/salt_lint.vim b/ale_linters/salt/salt_lint.vim new file mode 100644 index 00000000..47f66d83 --- /dev/null +++ b/ale_linters/salt/salt_lint.vim @@ -0,0 +1,33 @@ +" Author: Benjamin BINIER +" Description: salt-lint, saltstack linter + +call ale#Set('salt_salt_lint_executable', 'salt-lint') +call ale#Set('salt_salt_lint_options', '') + +function! ale_linters#salt#salt_lint#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'salt_salt_lint_options')) + \ . ' --json' +endfunction + +function! ale_linters#salt#salt_lint#Handle(buffer, lines) abort + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + call add(l:output, { + \ 'lnum': l:error.linenumber + 0, + \ 'code': l:error.id + 0, + \ 'text': l:error.message, + \ 'type': l:error.severity is# 'HIGH' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('salt', { +\ 'name': 'salt_lint', +\ 'aliases': ['salt-lint'], +\ 'executable': {b -> ale#Var(b, 'salt_salt_lint_executable')}, +\ 'command': function('ale_linters#salt#salt_lint#GetCommand'), +\ 'callback': 'ale_linters#salt#salt_lint#Handle' +\}) diff --git a/ale_linters/sass/sasslint.vim b/ale_linters/sass/sasslint.vim new file mode 100644 index 00000000..ff396e68 --- /dev/null +++ b/ale_linters/sass/sasslint.vim @@ -0,0 +1,28 @@ +" Author: sQVe - https://github.com/sQVe + +call ale#Set('sass_sasslint_executable', 'sass-lint') +call ale#Set('sass_sasslint_options', '') +call ale#Set('sass_sasslint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#sass#sasslint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'sass_sasslint', [ + \ 'node_modules/sass-lint/bin/sass-lint.js', + \ 'node_modules/.bin/sass-lint', + \]) +endfunction + +function! ale_linters#sass#sasslint#GetCommand(buffer) abort + let l:executable = ale_linters#sass#sasslint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'sass_sasslint_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -v -q -f compact %t' +endfunction + +call ale#linter#Define('sass', { +\ 'name': 'sasslint', +\ '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 new file mode 100644 index 00000000..651d7fd5 --- /dev/null +++ b/ale_linters/sass/stylelint.vim @@ -0,0 +1,14 @@ +" Author: diartyz + +call ale#Set('sass_stylelint_executable', 'stylelint') +call ale#Set('sass_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) + +call ale#linter#Define('sass', { +\ 'name': 'stylelint', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#path#FindExecutable(b, 'sass_stylelint', [ +\ 'node_modules/.bin/stylelint', +\ ])}, +\ 'command': '%e --stdin-filename %s', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/scala/cspell.vim b/ale_linters/scala/cspell.vim new file mode 100644 index 00000000..fa09d420 --- /dev/null +++ b/ale_linters/scala/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Scala files. + +call ale#handlers#cspell#DefineLinter('scala') diff --git a/ale_linters/scala/fsc.vim b/ale_linters/scala/fsc.vim new file mode 100644 index 00000000..94135235 --- /dev/null +++ b/ale_linters/scala/fsc.vim @@ -0,0 +1,14 @@ +" Author: Nils Leuzinger - https://github.com/PawkyPenguin +" Description: Basic scala support using fsc + +function! s:IsSbt(buffer) abort + return index(split(getbufvar(a:buffer, '&filetype'), '\.'), 'sbt') >= 0 +endfunction + +call ale#linter#Define('scala', { +\ 'name': '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/metals.vim b/ale_linters/scala/metals.vim new file mode 100644 index 00000000..1362e1a3 --- /dev/null +++ b/ale_linters/scala/metals.vim @@ -0,0 +1,50 @@ +" Author: Jeffrey Lau - https://github.com/zoonfafer +" Description: Metals Language Server for Scala https://scalameta.org/metals/ + +call ale#Set('scala_metals_executable', 'metals') +call ale#Set('scala_metals_project_root', '') + +function! ale_linters#scala#metals#GetProjectRoot(buffer) abort + let l:project_root = ale#Var(a:buffer, 'scala_metals_project_root') + + if !empty(l:project_root) + return l:project_root + endif + + let l:potential_roots = [ + \ 'build.sc', + \ 'build.sbt', + \ '.bloop', + \ '.metals', + \] + + for l:root in l:potential_roots + let l:project_root = ale#path#ResolveLocalPath( + \ a:buffer, + \ l:root, + \ '' + \) + + if !empty(l:project_root) + return fnamemodify( + \ l:project_root, + \ ':h', + \) + endif + endfor + + return '' +endfunction + +function! ale_linters#scala#metals#GetCommand(buffer) abort + return '%e' . ale#Pad('stdio') +endfunction + +call ale#linter#Define('scala', { +\ 'name': 'metals', +\ 'lsp': 'stdio', +\ 'language': 'scala', +\ 'executable': {b -> ale#Var(b, 'scala_metals_executable')}, +\ 'command': function('ale_linters#scala#metals#GetCommand'), +\ 'project_root': function('ale_linters#scala#metals#GetProjectRoot'), +\}) diff --git a/ale_linters/scala/sbtserver.vim b/ale_linters/scala/sbtserver.vim new file mode 100644 index 00000000..d4f137c2 --- /dev/null +++ b/ale_linters/scala/sbtserver.vim @@ -0,0 +1,31 @@ +" Author: ophirr33 +" Description: TCP lsp client for sbt Server + +call ale#Set('scala_sbtserver_address', '127.0.0.1:4273') +call ale#Set('scala_sbtserver_project_root', '') + +function! ale_linters#scala#sbtserver#GetProjectRoot(buffer) abort + let l:project_root = ale#Var(a:buffer, 'scala_sbtserver_project_root') + + if l:project_root is? '' + let l:project_root = ale#path#FindNearestFile(a:buffer, 'build.sbt') + + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : '' + endif + + return l:project_root +endfunction + +function! ale_linters#scala#sbtserver#GetAddress(buffer) abort + let l:address = ale#Var(a:buffer, 'scala_sbtserver_address') + + return l:address +endfunction + +call ale#linter#Define('scala', { +\ 'name': 'sbtserver', +\ 'lsp': 'socket', +\ 'address': function('ale_linters#scala#sbtserver#GetAddress'), +\ 'language': 'scala', +\ 'project_root': function('ale_linters#scala#sbtserver#GetProjectRoot'), +\}) diff --git a/ale_linters/scala/scalac.vim b/ale_linters/scala/scalac.vim new file mode 100644 index 00000000..1dd579b4 --- /dev/null +++ b/ale_linters/scala/scalac.vim @@ -0,0 +1,15 @@ +" Author: Zoltan Kalmar - https://github.com/kalmiz, +" w0rp +" Description: Basic scala support using scalac + +function! s:IsSbt(buffer) abort + return index(split(getbufvar(a:buffer, '&filetype'), '\.'), 'sbt') >= 0 +endfunction + +call ale#linter#Define('scala', { +\ 'name': '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 new file mode 100644 index 00000000..6e9e4c13 --- /dev/null +++ b/ale_linters/scala/scalastyle.vim @@ -0,0 +1,86 @@ +" Author: Kevin Kays - https://github.com/okkays +" Description: Support for the scalastyle checker. + +call ale#Set('scala_scalastyle_options', '') +" TODO: Remove support for the old option name in ALE 3.0. +call ale#Set('scala_scalastyle_config', +\ get(g:, 'ale_scalastyle_config_loc', '') +\) + +function! ale_linters#scala#scalastyle#Handle(buffer, lines) abort + " Look for help output from scalastyle first, which indicates that no + " configuration file was found. + for l:line in a:lines[:10] + if l:line =~# '-c, --config' + return [{ + \ 'lnum': 1, + \ 'text': '(See :help ale-scala-scalastyle)' + \ . ' No scalastyle configuration file was found.', + \}] + endif + endfor + + " Matches patterns like the following: + " + " warning file=/home/blurble/Doop.scala message=Missing or badly formed ScalaDoc: Extra @param foobles line=190 + let l:patterns = [ + \ '^\(.\+\) .\+ message=\(.\+\) line=\(\d\+\)$', + \ '^\(.\+\) .\+ message=\(.\+\) line=\(\d\+\) column=\(\d\+\)$', + \] + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:patterns) + let l:args = { + \ 'lnum': l:match[3] + 0, + \ 'type': l:match[1] =~? 'error' ? 'E' : 'W', + \ 'text': l:match[2] + \} + + if !empty(l:match[4]) + let l:args['col'] = l:match[4] + 1 + endif + + call add(l:output, l:args) + endfor + + return l:output +endfunction + +function! ale_linters#scala#scalastyle#GetCommand(buffer) abort + " Search for scalastyle config in parent directories. + let l:scalastyle_config = '' + let l:potential_configs = [ + \ 'scalastyle_config.xml', + \ 'scalastyle-config.xml' + \] + + for l:config in l:potential_configs + let l:scalastyle_config = ale#path#ResolveLocalPath( + \ a:buffer, + \ l:config, + \ '' + \) + + if !empty(l:scalastyle_config) + break + endif + endfor + + " If all else fails, try the global config. + if empty(l:scalastyle_config) + let l:scalastyle_config = ale#Var(a:buffer, 'scala_scalastyle_config') + endif + + return 'scalastyle' + \ . (!empty(l:scalastyle_config) ? ' --config ' . ale#Escape(l:scalastyle_config) : '') + \ . ale#Pad(ale#Var(a:buffer, 'scala_scalastyle_options')) + \ . ' %t' +endfunction + +call ale#linter#Define('scala', { +\ 'name': 'scalastyle', +\ 'executable': 'scalastyle', +\ 'output_stream': 'stdout', +\ '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 new file mode 100644 index 00000000..99027051 --- /dev/null +++ b/ale_linters/scss/sasslint.vim @@ -0,0 +1,28 @@ +" Author: sQVe - https://github.com/sQVe + +call ale#Set('scss_sasslint_executable', 'sass-lint') +call ale#Set('scss_sasslint_options', '') +call ale#Set('scss_sasslint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#scss#sasslint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'scss_sasslint', [ + \ 'node_modules/sass-lint/bin/sass-lint.js', + \ 'node_modules/.bin/sass-lint', + \]) +endfunction + +function! ale_linters#scss#sasslint#GetCommand(buffer) abort + let l:executable = ale_linters#scss#sasslint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'scss_sasslint_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -v -q -f compact %t' +endfunction + +call ale#linter#Define('scss', { +\ 'name': 'sasslint', +\ '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/scsslint.vim b/ale_linters/scss/scsslint.vim new file mode 100644 index 00000000..7ce57241 --- /dev/null +++ b/ale_linters/scss/scsslint.vim @@ -0,0 +1,34 @@ +" Author: w0rp +" Description: This file add scsslint support for SCSS support + +function! ale_linters#scss#scsslint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " test.scss:2:1 [W] Indentation: Line should be indented 2 spaces, but was indented 4 spaces + let l:pattern = '^.*:\(\d\+\):\(\d*\) \[\([^\]]\+\)\] \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + \&& l:match[4] =~# '^TrailingWhitespace' + " Skip trailing whitespace warnings if that option is off. + continue + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4], + \ 'type': l:match[3] is# 'E' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('scss', { +\ 'name': 'scsslint', +\ 'executable': 'scss-lint', +\ 'command': 'scss-lint --stdin-file-path=%s', +\ 'callback': 'ale_linters#scss#scsslint#Handle', +\}) diff --git a/ale_linters/scss/stylelint.vim b/ale_linters/scss/stylelint.vim new file mode 100644 index 00000000..4f82a98a --- /dev/null +++ b/ale_linters/scss/stylelint.vim @@ -0,0 +1,20 @@ +" Author: diartyz + +call ale#Set('scss_stylelint_executable', 'stylelint') +call ale#Set('scss_stylelint_options', '') +call ale#Set('scss_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#scss#stylelint#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'scss_stylelint_options')) + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('scss', { +\ 'name': 'stylelint', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#path#FindExecutable(b, 'scss_stylelint', [ +\ 'node_modules/.bin/stylelint', +\ ])}, +\ 'command': function('ale_linters#scss#stylelint#GetCommand'), +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/sh/bashate.vim b/ale_linters/sh/bashate.vim new file mode 100644 index 00000000..3cd84245 --- /dev/null +++ b/ale_linters/sh/bashate.vim @@ -0,0 +1,43 @@ +" Author: hsanson +" Description: Lints sh files using bashate +" URL: https://github.com/openstack/bashate + +call ale#Set('sh_bashate_executable', 'bashate') +call ale#Set('sh_bashate_options', '') + +function! ale_linters#sh#bashate#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'sh_bashate_executable') +endfunction + +function! ale_linters#sh#bashate#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'sh_bashate_options') + let l:executable = ale_linters#sh#bashate#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' ' . l:options . ' ' . '%t' +endfunction + +function! ale_linters#sh#bashate#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " /path/to/script/file:694:1: E003 Indent not multiple of 4 + let l:pattern = ':\(\d\+\):\(\d\+\): \(.*\)$' + let l:output = [] + + 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]), + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('sh', { +\ 'name': 'bashate', +\ 'output_stream': 'stdout', +\ 'executable': function('ale_linters#sh#bashate#GetExecutable'), +\ 'command': function('ale_linters#sh#bashate#GetCommand'), +\ 'callback': 'ale_linters#sh#bashate#Handle', +\}) diff --git a/ale_linters/sh/cspell.vim b/ale_linters/sh/cspell.vim new file mode 100644 index 00000000..e3c5a6f0 --- /dev/null +++ b/ale_linters/sh/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for shell scripts. + +call ale#handlers#cspell#DefineLinter('sh') diff --git a/ale_linters/sh/language_server.vim b/ale_linters/sh/language_server.vim new file mode 100644 index 00000000..c6781584 --- /dev/null +++ b/ale_linters/sh/language_server.vim @@ -0,0 +1,32 @@ +" Author: Christian Höltje (https://docwhat.org/) +" Description: BASH Language server integration for ALE +scriptencoding utf-8 + +call ale#Set('sh_language_server_executable', 'bash-language-server') +call ale#Set('sh_language_server_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#sh#language_server#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'sh_language_server', [ + \ 'node_modules/.bin/bash-language-server', + \]) +endfunction + +function! ale_linters#sh#language_server#GetCommand(buffer) abort + let l:exe = ale#Escape(ale_linters#sh#language_server#GetExecutable(a:buffer)) + + return l:exe . ' start' +endfunction + +function! ale_linters#sh#language_server#GetProjectRoot(buffer) abort + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' +endfunction + +call ale#linter#Define('sh', { +\ 'name': 'language_server', +\ 'lsp': 'stdio', +\ 'executable': 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 new file mode 100644 index 00000000..0605c285 --- /dev/null +++ b/ale_linters/sh/shell.vim @@ -0,0 +1,54 @@ +" Author: w0rp +" Description: Lints shell files by invoking the shell with -n + +" This option can be changed to change the default shell when the shell +" cannot be taken from the hashbang line. +if !exists('g:ale_sh_shell_default_shell') + let g:ale_sh_shell_default_shell = fnamemodify($SHELL, ':t') + + if g:ale_sh_shell_default_shell is# '' || g:ale_sh_shell_default_shell is# 'fish' + let g:ale_sh_shell_default_shell = 'bash' + endif +endif + +function! ale_linters#sh#shell#GetExecutable(buffer) abort + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) + + if !empty(l:shell_type) + return l:shell_type + endif + + return ale#Var(a:buffer, 'sh_shell_default_shell') +endfunction + +function! ale_linters#sh#shell#GetCommand(buffer) abort + return ale_linters#sh#shell#GetExecutable(a:buffer) . ' -n %t' +endfunction + +function! ale_linters#sh#shell#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " bash: line 13: syntax error near unexpected token `d' + " bash:行0: 未预期的符号“done”附近有语法错误 + " bash: 列 90: 尋找匹配的「"」時遇到了未預期的檔案結束符 + " sh: 11: Syntax error: "(" unexpected + let l:pattern = '\v([^:]+:\D*)(\d+): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[2]), + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('sh', { +\ 'name': 'shell', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 00000000..d9945126 --- /dev/null +++ b/ale_linters/sh/shellcheck.vim @@ -0,0 +1,4 @@ +" Author: w0rp +" Description: shellcheck linter for shell scripts. + +call ale#handlers#shellcheck#DefineLinter('sh') diff --git a/ale_linters/slim/slimlint.vim b/ale_linters/slim/slimlint.vim new file mode 100644 index 00000000..1b365e25 --- /dev/null +++ b/ale_linters/slim/slimlint.vim @@ -0,0 +1,55 @@ +" Author: Markus Doits - https://github.com/doits +" Description: slim-lint for Slim files + +function! ale_linters#slim#slimlint#GetCommand(buffer) abort + let l:command = 'slim-lint %t' + + let l:rubocop_config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml') + + " Set SLIM_LINT_RUBOCOP_CONF variable as it is needed for slim-lint to + " pick up the rubocop config. + " + " See https://github.com/sds/slim-lint/blob/master/lib/slim_lint/linter/README.md#rubocop + if !empty(l:rubocop_config) + 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 +endfunction + +function! ale_linters#slim#slimlint#Handle(buffer, lines) abort + " Matches patterns like the following: + " :5 [W] LineLength: Line is too long. [150/120] + let l:pattern = '\v^.*:(\d+) \[([EW])\] (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'type': l:match[2], + \ 'text': l:match[3] + \} + + let l:code_match = matchlist(l:item.text, '\v^([^:]+): (.+)$') + + if !empty(l:code_match) + let l:item.code = l:code_match[1] + let l:item.text = l:code_match[2] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('slim', { +\ 'name': 'slimlint', +\ 'executable': 'slim-lint', +\ '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 new file mode 100644 index 00000000..852ea170 --- /dev/null +++ b/ale_linters/sml/smlnj.vim @@ -0,0 +1,9 @@ +" Author: Paulo Alem , Jake Zimmerman +" Description: Single-file SML checking with SML/NJ compiler + +call ale#linter#Define('sml', { +\ 'name': 'smlnj', +\ '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 new file mode 100644 index 00000000..9ad24af4 --- /dev/null +++ b/ale_linters/sml/smlnj_cm.vim @@ -0,0 +1,21 @@ +" Author: Jake Zimmerman +" Description: SML checking with SML/NJ Compilation Manager + +function! ale_linters#sml#smlnj_cm#GetCommand(buffer) abort + let l:cmfile = ale#handlers#sml#GetCmFile(a:buffer) + + return 'sml -m ' . l:cmfile . ' < /dev/null' +endfunction + +" Using CM requires that we set "lint_file: 1", since it reads the files +" from the disk itself. +call ale#linter#Define('sml', { +\ 'name': 'smlnj_cm', +\ 'aliases': ['smlnj-cm'], +\ 'executable': function('ale#handlers#sml#GetExecutableSmlnjCm'), +\ 'lint_file': 1, +\ 'command': function('ale_linters#sml#smlnj_cm#GetCommand'), +\ 'callback': 'ale#handlers#sml#Handle', +\}) + +" vim:ts=4:sts=4:sw=4 diff --git a/ale_linters/solidity/solc.vim b/ale_linters/solidity/solc.vim new file mode 100644 index 00000000..28977083 --- /dev/null +++ b/ale_linters/solidity/solc.vim @@ -0,0 +1,53 @@ +" Author: Karl Bartel - http://karl.berlin/ +" Description: Report solc compiler errors in Solidity code + +call ale#Set('solidity_solc_executable', 'solc') +call ale#Set('solidity_solc_options', '') + +function! ale_linters#solidity#solc#Handle(buffer, lines) abort + " Matches patterns like the following: + " Error: Expected ';' but got '(' + " --> /path/to/file/file.sol:1:10:) + let l:pattern = '\v(Error|Warning): (.*)$' + let l:line_and_column_pattern = '\v\.sol:(\d+):(\d+):' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + let l:match = matchlist(l:line, l:line_and_column_pattern) + + if len(l:match) > 0 + let l:index = len(l:output) - 1 + let l:output[l:index]['lnum'] = l:match[1] + 0 + let l:output[l:index]['col'] = l:match[2] + 0 + endif + else + let l:isError = l:match[1] is? 'Error' + + call add(l:output, { + \ 'lnum': 0, + \ 'col': 0, + \ 'text': l:match[2], + \ 'type': l:isError ? 'E' : 'W', + \}) + endif + endfor + + return l:output +endfunction + +function! ale_linters#solidity#solc#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'solidity_solc_executable') + + return l:executable . ale#Pad(ale#Var(a:buffer, 'solidity_solc_options')) . ' %s' +endfunction + +call ale#linter#Define('solidity', { +\ 'name': 'solc', +\ 'executable': {b -> ale#Var(b, 'solidity_solc_executable')}, +\ 'command': function('ale_linters#solidity#solc#GetCommand'), +\ 'callback': 'ale_linters#solidity#solc#Handle', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/solidity/solhint.vim b/ale_linters/solidity/solhint.vim new file mode 100644 index 00000000..9dc308fe --- /dev/null +++ b/ale_linters/solidity/solhint.vim @@ -0,0 +1,83 @@ +" Authors: Franco Victorio <@fvictorio>, Henrique Barcelos <@hbarcelos> +" Description: Report errors in Solidity code with solhint + +call ale#Set('solidity_solhint_options', '') +call ale#Set('solidity_solhint_executable', 'solhint') +call ale#Set('solidity_solhint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#solidity#solhint#Handle(buffer, lines) abort + let l:output = [] + + " Matches lines like the following: + " contracts/Bounty.sol:14:3: Expected indentation of 4 spaces but found 2 [Error/indent] + let l:lint_pattern = '\v^[^:]+:(\d+):(\d+): %(Parse error: )@ +\ ale#node#Executable(b, ale_linters#solidity#solhint#GetExecutable(b)) +\ . ale#Pad(ale#Var(b, 'solidity_solhint_options')) +\ . ' --formatter unix %s' +\ }, +\ 'callback': 'ale_linters#solidity#solhint#Handle', +\}) diff --git a/ale_linters/solidity/solium.vim b/ale_linters/solidity/solium.vim new file mode 100644 index 00000000..61ab184d --- /dev/null +++ b/ale_linters/solidity/solium.vim @@ -0,0 +1,9 @@ +" Author: Jeff Sutherland - https://github.com/jdsutherland +" Description: Report errors in Solidity code with solium + +call ale#linter#Define('solidity', { +\ 'name': 'solium', +\ 'executable': 'solium', +\ 'command': 'solium --reporter gcc --file %t', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/spec/rpmlint.vim b/ale_linters/spec/rpmlint.vim new file mode 100644 index 00000000..5594e3b8 --- /dev/null +++ b/ale_linters/spec/rpmlint.vim @@ -0,0 +1,90 @@ +" Author: Jason Tibbitts +" Description: Adds support for checking RPM spec files with rpmlint + +" rpmlint will produce varions types of output: +" +" Lines like the following are output when the file is simply not able to be +" parsed by rpmspec -P: +" apcupsd.spec: E: specfile-error warning: bogus date in %changelog: Mon Oct 1 2005 - Foo +" apcupsd.spec: E: specfile-error error: %changelog not in descending chronological order +" They do not contain a line number, and there's not a whole lot that can be +" done to locate them besides grep for them. rpmlint is just passing the +" output from rpm along with the filename, an error indicator, and an error +" type. +" +" Lines like the following: +" cyrus-imapd.spec:23: W: macro-in-comment %version +" cyrus-imapd.spec:18: E: hardcoded-library-path in %_prefix/lib/%name +" indicate warnings and errors, respectively. No column numbers are provided +" +" Lines like: +" apcupsd.spec: I: checking +" apcupsd.spec: I: checking-url https://downloads.sourceforge.net/apcupsd/apcupsd-3.14.14.tar.gz (timeout 10 seconds) +" are merely informational and are only output when -v is passed. But they +" may be useful in a log to know why things are taking so long. +" +" And this is always output at the end and should just be ignored: +" 0 packages and 1 specfiles checked; 4 errors, 0 warnings. + +call ale#Set('spec_rpmlint_executable', 'rpmlint') +call ale#Set('spec_rpmlint_options', '') + +function! ale_linters#spec#rpmlint#GetCommand(buffer, version) abort + if ale#semver#GTE(a:version, [2, 0, 0]) + " The -o/--option flag was removed in version 2.0.0 + let l:version_dependent_args = '' + else + let l:version_dependent_args = ' -o "NetworkEnabled False"' + endif + + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'spec_rpmlint_options')) + \ . ' -v' + \ . l:version_dependent_args + \ . ' %t' +endfunction + +function! ale_linters#spec#rpmlint#Handle(buffer, lines) abort + " let l:pat_inform = '^.\+: I: \(.+\)' + let l:pat_errwarn = '^.\+:\(\d\+\): \([EW]\): \(.\+\)' + let l:pat_baderr = '^.\+: E: \(.\+\)' + let l:output = [] + + for l:line in a:lines + let l:match_errwarn = matchlist(l:line, l:pat_errwarn) + let l:match_baderr = matchlist(l:line, l:pat_baderr) + + if len(l:match_errwarn) > 0 + let l:text = l:match_errwarn[3] + let l:type = l:match_errwarn[2] + let l:lnum = l:match_errwarn[1] + 0 + elseif len(l:match_baderr) > 0 + let l:text = l:match_baderr[1] + let l:type = 'E' + let l:lnum = 1 + else + continue + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:lnum, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('spec', { +\ 'name': 'rpmlint', +\ 'executable': {b -> ale#Var(b, 'spec_rpmlint_executable')}, +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'spec_rpmlint_executable'), +\ '%e --version', +\ function('ale_linters#spec#rpmlint#GetCommand'), +\ )}, +\ 'callback': 'ale_linters#spec#rpmlint#Handle', +\}) diff --git a/ale_linters/sql/sqlfluff.vim b/ale_linters/sql/sqlfluff.vim new file mode 100644 index 00000000..91d69c12 --- /dev/null +++ b/ale_linters/sql/sqlfluff.vim @@ -0,0 +1,108 @@ +" Author: Carl Smedstad +" Description: sqlfluff for SQL files + +let g:ale_sql_sqlfluff_executable = +\ get(g:, 'ale_sql_sqlfluff_executable', 'sqlfluff') + +let g:ale_sql_sqlfluff_options = +\ get(g:, 'ale_sql_sqlfluff_options', '') + +function! ale_linters#sql#sqlfluff#Executable(buffer) abort + return ale#Var(a:buffer, 'sql_sqlfluff_executable') +endfunction + +function! ale_linters#sql#sqlfluff#Command(buffer, version) abort + let l:executable = ale_linters#sql#sqlfluff#Executable(a:buffer) + let l:options = ale#Var(a:buffer, 'sql_sqlfluff_options') + + let l:cmd = + \ ale#Escape(l:executable) + \ . ' lint' + + let l:config_file = ale#path#FindNearestFile(a:buffer, '.sqlfluff') + + if !empty(l:config_file) + let l:cmd .= ' --config ' . ale#Escape(l:config_file) + else + let l:cmd .= ' --dialect ansi' + endif + + let l:cmd .= + \ ' --format json ' + \ . l:options + \ . ' %t' + + return l:cmd +endfunction + +function! ale_linters#sql#sqlfluff#Handle(buffer, version, lines) abort + let l:output = [] + let l:json_lines = ale#util#FuzzyJSONDecode(a:lines, []) + + if empty(l:json_lines) + return l:output + endif + + let l:json = l:json_lines[0] + + " if there's no warning, 'result' is `null`. + if empty(get(l:json, 'violations')) + return l:output + endif + + if ale#semver#GTE(a:version, [3, 0, 0]) + for l:violation in get(l:json, 'violations', []) + let l:err = { + \ 'filename': l:json.filepath, + \ 'lnum': l:violation.start_line_no, + \ 'col': l:violation.start_line_pos, + \ 'text': l:violation.description, + \ 'code': l:violation.code, + \ 'type': 'W', + \} + + if has_key(l:violation, 'end_line_no') + let l:err.end_lnum = l:violation.end_line_no + endif + + if has_key(l:violation, 'end_line_pos') + let l:err.end_col = l:violation.end_line_pos + endif + + call add(l:output, l:err) + endfor + else + for l:violation in get(l:json, 'violations', []) + call add(l:output, { + \ 'filename': l:json.filepath, + \ 'lnum': l:violation.line_no, + \ 'col': l:violation.line_pos, + \ 'text': l:violation.description, + \ 'code': l:violation.code, + \ 'type': 'W', + \}) + endfor + endif + + return l:output +endfunction + +call ale#linter#Define('sql', { +\ 'name': 'sqlfluff', +\ 'executable': function('ale_linters#sql#sqlfluff#Executable'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#sql#sqlfluff#Executable(buffer), +\ '%e --version', +\ function('ale_linters#sql#sqlfluff#Command'), +\ )}, +\ 'callback': {buffer, lines -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#sql#sqlfluff#Executable(buffer), +\ '%e --version', +\ {buffer, version -> ale_linters#sql#sqlfluff#Handle( +\ buffer, +\ l:version, +\ lines)}, +\ )}, +\}) diff --git a/ale_linters/sql/sqlint.vim b/ale_linters/sql/sqlint.vim new file mode 100644 index 00000000..ca893724 --- /dev/null +++ b/ale_linters/sql/sqlint.vim @@ -0,0 +1,28 @@ +" Author: Adriaan Zonnenberg +" Description: sqlint for SQL files + +function! ale_linters#sql#sqlint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " stdin:3:1:ERROR syntax error at or near "WIBBLE" + let l:pattern = '\v^[^:]+:(\d+):(\d+):(\u+) (.*)' + 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, + \ 'type': l:match[3][0], + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('sql', { +\ 'name': 'sqlint', +\ 'executable': 'sqlint', +\ 'command': 'sqlint', +\ 'callback': 'ale_linters#sql#sqlint#Handle', +\}) diff --git a/ale_linters/sql/sqllint.vim b/ale_linters/sql/sqllint.vim new file mode 100644 index 00000000..78396fe9 --- /dev/null +++ b/ale_linters/sql/sqllint.vim @@ -0,0 +1,33 @@ +" ale_linters/sql/sqllint.vim +" Author: Joe Reynolds +" Description: sql-lint for SQL files. +" sql-lint can be found at +" https://www.npmjs.com/package/sql-lint +" https://github.com/joereynolds/sql-lint + +function! ale_linters#sql#sqllint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " stdin:1 [ER_NO_DB_ERROR] No database selected + let l:pattern = '\v^[^:]+:(\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, + \ 'type': l:match[3][0], + \ 'text': l:match[0], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('sql', { +\ 'name': 'sqllint', +\ 'aliases': ['sql-lint'], +\ 'executable': 'sql-lint', +\ 'command': 'sql-lint', +\ 'callback': 'ale_linters#sql#sqllint#Handle', +\}) diff --git a/ale_linters/stylus/stylelint.vim b/ale_linters/stylus/stylelint.vim new file mode 100644 index 00000000..652e15d2 --- /dev/null +++ b/ale_linters/stylus/stylelint.vim @@ -0,0 +1,21 @@ +" Author: diartyz , w0rp + +call ale#Set('stylus_stylelint_executable', 'stylelint') +call ale#Set('stylus_stylelint_options', '') +call ale#Set('stylus_stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#stylus#stylelint#GetCommand(buffer) abort + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'stylus_stylelint_options')) + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('stylus', { +\ 'name': 'stylelint', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#path#FindExecutable(b, 'stylus_stylelint', [ +\ 'node_modules/.bin/stylelint', +\ ])}, +\ '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..a12191b8 --- /dev/null +++ b/ale_linters/sugarss/stylelint.vim @@ -0,0 +1,22 @@ +" Author: toastal +" 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', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#path#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/svelte/svelteserver.vim b/ale_linters/svelte/svelteserver.vim new file mode 100644 index 00000000..2200b582 --- /dev/null +++ b/ale_linters/svelte/svelteserver.vim @@ -0,0 +1,21 @@ +" Author: Joakim Repomaa +" Description: Svelte Language Server integration for ALE + +call ale#Set('svelte_svelteserver_executable', 'svelteserver') +call ale#Set('svelte_svelteserver_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#svelte#svelteserver#GetProjectRoot(buffer) abort + let l:package_path = ale#path#FindNearestFile(a:buffer, 'package.json') + + return !empty(l:package_path) ? fnamemodify(l:package_path, ':h') : '' +endfunction + +call ale#linter#Define('svelte', { +\ 'name': 'svelteserver', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#path#FindExecutable(b, 'svelte_svelteserver', [ +\ 'node_modules/.bin/svelteserver', +\ ])}, +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#svelte#svelteserver#GetProjectRoot'), +\}) diff --git a/ale_linters/swift/appleswiftformat.vim b/ale_linters/swift/appleswiftformat.vim new file mode 100644 index 00000000..4c61764d --- /dev/null +++ b/ale_linters/swift/appleswiftformat.vim @@ -0,0 +1,43 @@ +" Authors: Klaas Pieter Annema , bosr +" Description: Support for swift-format https://github.com/apple/swift-format + +function! ale_linters#swift#appleswiftformat#GetLinterCommand(buffer) abort + let l:command_args = ale#swift#GetAppleSwiftFormatCommand(a:buffer) . ' lint %t' + let l:config_args = ale#swift#GetAppleSwiftFormatConfigArgs(a:buffer) + + if l:config_args isnot# '' + let l:command_args = l:command_args . ' ' . l:config_args + endif + + return l:command_args +endfunction + +function! ale_linters#swift#appleswiftformat#Handle(buffer, lines) abort + " Matches the typical output of swift-format, that is lines of the following pattern: + " + " Sources/main.swift:4:21: warning: [DoNotUseSemicolons] remove ';' and move the next statement to the new line + " Sources/main.swift:3:12: warning: [Spacing] remove 1 space + let l:pattern = '\v^.*:(\d+):(\d+): (\S+): \[(\S+)\] (.*)$' + 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, + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \ 'code': l:match[4], + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('swift', { +\ 'name': 'apple-swift-format', +\ 'executable': function('ale#swift#GetAppleSwiftFormatExecutable'), +\ 'command': function('ale_linters#swift#appleswiftformat#GetLinterCommand'), +\ 'output_stream': 'stderr', +\ 'language': 'swift', +\ 'callback': 'ale_linters#swift#appleswiftformat#Handle' +\}) diff --git a/ale_linters/swift/cspell.vim b/ale_linters/swift/cspell.vim new file mode 100644 index 00000000..25451e9d --- /dev/null +++ b/ale_linters/swift/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Swift files. + +call ale#handlers#cspell#DefineLinter('swift') diff --git a/ale_linters/swift/sourcekitlsp.vim b/ale_linters/swift/sourcekitlsp.vim new file mode 100644 index 00000000..a403dcc1 --- /dev/null +++ b/ale_linters/swift/sourcekitlsp.vim @@ -0,0 +1,14 @@ +" Author: Dan Loman +" 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', +\ 'aliases': ['sourcekit'], +\ '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 new file mode 100644 index 00000000..d08c68f6 --- /dev/null +++ b/ale_linters/swift/swiftlint.vim @@ -0,0 +1,69 @@ +" Author: David Mohundro , Gordon Fontenot +" Description: swiftlint for swift files + +call ale#Set('swift_swiftlint_executable', 'swiftlint') +call ale#Set('swift_swiftlint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#swift#swiftlint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'swift_swiftlint', [ + \ 'Pods/SwiftLint/swiftlint', + \ 'ios/Pods/SwiftLint/swiftlint', + \ 'swiftlint', + \]) +endfunction + +function! ale_linters#swift#swiftlint#GetCommand(buffer) abort + let l:executable = ale_linters#swift#swiftlint#GetExecutable(a:buffer) + let l:args = 'lint --use-stdin' + + return ale#Escape(l:executable) + \ . ' ' .l:args +endfunction + +function! ale_linters#swift#swiftlint#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) + let l:item = { + \ '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' + elseif l:match[4] is# 'note' + let l:item.type = 'I' + endif + + if !empty(l:match[3]) + let l:item.col = str2nr(l:match[3]) + endif + + " If the filename is something like , or -, then + " this is an error for the file we checked. + if l:match[1] isnot# '-' && l:match[1][0] isnot# '<' + let l:item['filename'] = l:match[1] + endif + + " Parse the code if it's there. + let l:code_match = matchlist(l:item.text, '\v^(.+) \(([^ (]+)\)$') + + if !empty(l:code_match) + let l:item.text = l:code_match[1] + let l:item.code = l:code_match[2] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('swift', { +\ 'name': 'swiftlint', +\ '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/systemd/systemd_analyze.vim b/ale_linters/systemd/systemd_analyze.vim new file mode 100644 index 00000000..64eef8cf --- /dev/null +++ b/ale_linters/systemd/systemd_analyze.vim @@ -0,0 +1,18 @@ +function! ale_linters#systemd#systemd_analyze#Handle(buffer, lines) abort + return ale#util#MapMatches(a:lines, '\v(.+):([0-9]+): (.+)', {match -> { + \ 'lnum': str2nr(match[2]), + \ 'col': 1, + \ 'type': 'W', + \ 'text': match[3], + \}}) +endfunction + +call ale#linter#Define('systemd', { +\ 'name': 'systemd_analyze', +\ 'aliases': ['systemd-analyze'], +\ 'executable': 'systemd-analyze', +\ 'command': 'SYSTEMD_LOG_COLOR=0 %e --user verify %s', +\ 'callback': 'ale_linters#systemd#systemd_analyze#Handle', +\ 'output_stream': 'both', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/tcl/nagelfar.vim b/ale_linters/tcl/nagelfar.vim new file mode 100644 index 00000000..5a4940e1 --- /dev/null +++ b/ale_linters/tcl/nagelfar.vim @@ -0,0 +1,39 @@ +" Author: Nick James +" Description: nagelfar linter for tcl files + +call ale#Set('tcl_nagelfar_executable', 'nagelfar.tcl') +call ale#Set('tcl_nagelfar_options', '') + +function! ale_linters#tcl#nagelfar#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'tcl_nagelfar_options') + + return '%e' . ale#Pad(l:options) . ' %s' +endfunction + +function! ale_linters#tcl#nagelfar#Handle(buffer, lines) abort + " Matches patterns like the following: + " Line 5: W Found constant "bepa" which is also a variable. + " Line 13: E Wrong number of arguments (3) to "set" + " Line 93: N Close brace not aligned with line 90 (4 0) + let l:pattern = '^Line\s\+\([0-9]\+\): \([NEW]\) \(.*\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'type': l:match[2] is# 'N' ? 'W' : l:match[2], + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('tcl', { +\ 'name': 'nagelfar', +\ 'output_stream': 'stdout', +\ '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/checkov.vim b/ale_linters/terraform/checkov.vim new file mode 100644 index 00000000..568b46e1 --- /dev/null +++ b/ale_linters/terraform/checkov.vim @@ -0,0 +1,41 @@ +" Author: Thyme-87 +" Description: use checkov for providing warnings via ale + +call ale#Set('terraform_checkov_executable', 'checkov') +call ale#Set('terraform_checkov_options', '') + +function! ale_linters#terraform#checkov#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'terraform_checkov_executable') +endfunction + +function! ale_linters#terraform#checkov#GetCommand(buffer) abort + return '%e ' . '-f %t -o json --quiet ' . ale#Var(a:buffer, 'terraform_checkov_options') +endfunction + +function! ale_linters#terraform#checkov#Handle(buffer, lines) abort + let l:output = [] + + let l:results = get(get(ale#util#FuzzyJSONDecode(a:lines, {}), 'results', []), 'failed_checks', []) + + for l:violation in l:results + call add(l:output, { + \ 'filename': l:violation['file_path'], + \ 'lnum': l:violation['file_line_range'][0], + \ 'end_lnum': l:violation['file_line_range'][1], + \ 'text': l:violation['check_name'] . ' [' . l:violation['check_id'] . ']', + \ 'detail': l:violation['check_id'] . ': ' . l:violation['check_name'] . "\n" . + \ 'For more information, see: '. l:violation['guideline'], + \ 'type': 'W', + \ }) + endfor + + return l:output +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'checkov', +\ 'output_stream': 'stdout', +\ 'executable': function('ale_linters#terraform#checkov#GetExecutable'), +\ 'command': function('ale_linters#terraform#checkov#GetCommand'), +\ 'callback': 'ale_linters#terraform#checkov#Handle', +\}) diff --git a/ale_linters/terraform/terraform.vim b/ale_linters/terraform/terraform.vim new file mode 100644 index 00000000..1beb8501 --- /dev/null +++ b/ale_linters/terraform/terraform.vim @@ -0,0 +1,69 @@ +" Author: Keith Maxwell +" Description: terraform fmt to check for errors + +call ale#Set('terraform_terraform_executable', 'terraform') + +function! ale_linters#terraform#terraform#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'terraform_terraform_executable') +endfunction + +function! ale_linters#terraform#terraform#GetCommand(buffer) abort + return ale#Escape(ale_linters#terraform#terraform#GetExecutable(a:buffer)) + \ . ' validate -no-color -json ' +endfunction + +function! ale_linters#terraform#terraform#GetType(severity) abort + if a:severity is? 'warning' + return 'W' + endif + + return 'E' +endfunction + +function! ale_linters#terraform#terraform#GetDetail(error) abort + let l:detail = get(a:error, 'detail', '') + + if strlen(l:detail) > 0 + return l:detail + else + return get(a:error, 'summary', '') + endif +endfunction + +function! ale_linters#terraform#terraform#Handle(buffer, lines) abort + let l:output = [] + + let l:errors = ale#util#FuzzyJSONDecode(a:lines, {'diagnostics': []}) + let l:dir = expand('#' . a:buffer . ':p:h') + let l:file = expand('#' . a:buffer . ':p') + + for l:error in l:errors['diagnostics'] + if has_key(l:error, 'range') + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:error['range']['filename']), + \ 'lnum': l:error['range']['start']['line'], + \ 'col': l:error['range']['start']['column'], + \ 'text': ale_linters#terraform#terraform#GetDetail(l:error), + \ 'type': ale_linters#terraform#terraform#GetType(l:error['severity']), + \}) + else + call add(l:output, { + \ 'filename': l:file, + \ 'lnum': 0, + \ 'col': 0, + \ 'text': ale_linters#terraform#terraform#GetDetail(l:error), + \ 'type': ale_linters#terraform#terraform#GetType(l:error['severity']), + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'terraform', +\ 'output_stream': 'stdout', +\ 'executable': function('ale_linters#terraform#terraform#GetExecutable'), +\ 'command': function('ale_linters#terraform#terraform#GetCommand'), +\ 'callback': 'ale_linters#terraform#terraform#Handle', +\}) diff --git a/ale_linters/terraform/terraform_ls.vim b/ale_linters/terraform/terraform_ls.vim new file mode 100644 index 00000000..7dc77941 --- /dev/null +++ b/ale_linters/terraform/terraform_ls.vim @@ -0,0 +1,39 @@ +" Author: Horacio Sanson +" Description: terraform-ls integration for ALE (cf. https://github.com/hashicorp/terraform-ls) + +call ale#Set('terraform_terraform_executable', 'terraform') +call ale#Set('terraform_ls_executable', 'terraform-ls') +call ale#Set('terraform_ls_options', '') + +function! ale_linters#terraform#terraform_ls#GetTerraformExecutable(buffer) abort + let l:terraform_executable = ale#Var(a:buffer, 'terraform_terraform_executable') + + if(ale#path#IsAbsolute(l:terraform_executable)) + return '-tf-exec ' . l:terraform_executable + endif + + return '' +endfunction + +function! ale_linters#terraform#terraform_ls#GetCommand(buffer) abort + return '%e' + \ . ale#Pad('serve') + \ . ale#Pad(ale_linters#terraform#terraform_ls#GetTerraformExecutable(a:buffer)) + \ . ale#Pad(ale#Var(a:buffer, 'terraform_ls_options')) +endfunction + +function! ale_linters#terraform#terraform_ls#GetProjectRoot(buffer) abort + let l:tf_dir = ale#path#FindNearestDirectory(a:buffer, '.terraform') + + return !empty(l:tf_dir) ? fnamemodify(l:tf_dir, ':h:h') : '' +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'terraform_ls', +\ 'aliases': ['terraformls'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'terraform_ls_executable')}, +\ 'command': function('ale_linters#terraform#terraform_ls#GetCommand'), +\ 'project_root': function('ale_linters#terraform#terraform_ls#GetProjectRoot'), +\ 'language': 'terraform', +\}) diff --git a/ale_linters/terraform/terraform_lsp.vim b/ale_linters/terraform/terraform_lsp.vim new file mode 100644 index 00000000..e2408c15 --- /dev/null +++ b/ale_linters/terraform/terraform_lsp.vim @@ -0,0 +1,25 @@ +" Author: OJFord +" Description: terraform-lsp integration for ALE (cf. https://github.com/juliosueiras/terraform-lsp) + +call ale#Set('terraform_langserver_executable', 'terraform-lsp') +call ale#Set('terraform_langserver_options', '') + +function! ale_linters#terraform#terraform_lsp#GetCommand(buffer) abort + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'terraform_langserver_options')) +endfunction + +function! ale_linters#terraform#terraform_lsp#GetProjectRoot(buffer) abort + let l:tf_dir = ale#path#FindNearestDirectory(a:buffer, '.terraform') + + return !empty(l:tf_dir) ? fnamemodify(l:tf_dir, ':h:h') : '' +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'terraform_lsp', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'terraform_langserver_executable')}, +\ 'command': function('ale_linters#terraform#terraform_lsp#GetCommand'), +\ 'project_root': function('ale_linters#terraform#terraform_lsp#GetProjectRoot'), +\ 'language': 'terraform', +\}) diff --git a/ale_linters/terraform/tflint.vim b/ale_linters/terraform/tflint.vim new file mode 100644 index 00000000..86b5b74a --- /dev/null +++ b/ale_linters/terraform/tflint.vim @@ -0,0 +1,105 @@ +" Author: Nat Williams +" Description: tflint for Terraform files +" +" See: https://www.terraform.io/ +" https://github.com/wata727/tflint + +call ale#Set('terraform_tflint_options', '') +call ale#Set('terraform_tflint_executable', 'tflint') + +function! ale_linters#terraform#tflint#Handle(buffer, lines) abort + let l:output = [] + let l:pattern = '\v^(.*):(\d+),(\d+)-(\d+)?,?(\d+): (.{-1,}); (.+)$' + let l:json = ale#util#FuzzyJSONDecode(a:lines, {}) + + " This is a rough test for tflint's output format + " On versions prior to 0.11 it outputs all errors as a single level list + if type(l:json) is v:t_list + for l:error in l:json + if l:error.type is# 'ERROR' + let l:type = 'E' + elseif l:error.type is# 'NOTICE' + let l:type = 'I' + else + let l:type = 'W' + endif + + call add(l:output, { + \ 'lnum': l:error.line, + \ 'text': l:error.message, + \ 'type': l:type, + \ 'code': l:error.detector, + \}) + endfor + else + for l:error in get(l:json, 'errors', []) + for l:match in ale#util#GetMatches(l:error.message, [l:pattern]) + if l:match[4] is# '' + let l:match[4] = l:match[2] + endif + + call add(l:output, { + \ 'filename': l:match[1], + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'end_lnum': str2nr(l:match[4]), + \ 'end_col': str2nr(l:match[5]), + \ 'text': l:match[7], + \ 'code': l:match[6], + \ 'type': 'E', + \}) + endfor + endfor + + for l:error in get(l:json, 'issues', []) + if l:error.rule.severity is# 'ERROR' + let l:type = 'E' + elseif l:error.rule.severity is# 'NOTICE' + let l:type = 'I' + else + let l:type = 'W' + endif + + call add(l:output, { + \ 'filename': l:error.range.filename, + \ 'lnum': l:error.range.start.line, + \ 'col': l:error.range.start.column, + \ 'end_lnum': l:error.range.end.line, + \ 'end_col': l:error.range.end.column, + \ 'text': l:error.message, + \ 'code': l:error.rule.name, + \ 'type': l:type, + \}) + endfor + endif + + return l:output +endfunction + +function! ale_linters#terraform#tflint#GetCommand(buffer) abort + let l:cmd = '%e' + + let l:config_file = ale#path#FindNearestFile(a:buffer, '.tflint.hcl') + + if !empty(l:config_file) + let l:cmd .= ' --config ' . ale#Escape(l:config_file) + endif + + let l:opts = ale#Var(a:buffer, 'terraform_tflint_options') + + if !empty(l:opts) + let l:cmd .= ' ' . l:opts + endif + + let l:cmd .= ' -f json' + + return l:cmd +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'tflint', +\ 'executable': {b -> ale#Var(b, 'terraform_tflint_executable')}, +\ 'cwd': '%s:h', +\ 'command': function('ale_linters#terraform#tflint#GetCommand'), +\ 'callback': 'ale_linters#terraform#tflint#Handle', +\}) diff --git a/ale_linters/terraform/tfsec.vim b/ale_linters/terraform/tfsec.vim new file mode 100644 index 00000000..d29cdd13 --- /dev/null +++ b/ale_linters/terraform/tfsec.vim @@ -0,0 +1,87 @@ +" Description: tfsec for Terraform files +" +" See: https://www.terraform.io/ +" https://github.com/aquasecurity/tfsec + +call ale#Set('terraform_tfsec_options', '') +call ale#Set('terraform_tfsec_executable', 'tfsec') + +let s:separator = has('win32') ? '\' : '/' + +function! ale_linters#terraform#tfsec#Handle(buffer, lines) abort + let l:output = [] + let l:json = ale#util#FuzzyJSONDecode(a:lines, {}) + + " if there's no warning, 'result' is `null`. + if empty(get(l:json, 'results')) + return l:output + endif + + for l:result in get(l:json, 'results', []) + if l:result.severity is# 'LOW' + let l:type = 'I' + elseif l:result.severity is# 'CRITICAL' + let l:type = 'E' + else + let l:type = 'W' + endif + + call add(l:output, { + \ 'filename': l:result.location.filename, + \ 'lnum': l:result.location.start_line, + \ 'end_lnum': l:result.location.end_line, + \ 'text': l:result.description, + \ 'code': l:result.long_id, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +" Construct command arguments to tfsec with `terraform_tfsec_options`. +function! ale_linters#terraform#tfsec#GetCommand(buffer) abort + let l:cmd = '%e' + + let l:config = ale_linters#terraform#tfsec#FindConfig(a:buffer) + + if !empty(l:config) + let l:cmd .= ' --config-file ' . l:config + endif + + let l:opts = ale#Var(a:buffer, 'terraform_tfsec_options') + + if !empty(l:opts) + let l:cmd .= ' ' . l:opts + endif + + let l:cmd .= ' --format json' + + return l:cmd +endfunction + +" Find the nearest configuration file of tfsec. +function! ale_linters#terraform#tfsec#FindConfig(buffer) abort + let l:config_dir = ale#path#FindNearestDirectory(a:buffer, '.tfsec') + + if !empty(l:config_dir) + " https://aquasecurity.github.io/tfsec/v1.28.0/guides/configuration/config/ + for l:basename in ['config.yml', 'config.json'] + let l:config = ale#path#Simplify(join([l:config_dir, l:basename], s:separator)) + + if filereadable(l:config) + return ale#Escape(l:config) + endif + endfor + endif + + return '' +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'tfsec', +\ 'executable': {b -> ale#Var(b, 'terraform_tfsec_executable')}, +\ 'cwd': '%s:h', +\ 'command': function('ale_linters#terraform#tfsec#GetCommand'), +\ 'callback': 'ale_linters#terraform#tfsec#Handle', +\}) diff --git a/ale_linters/testft/testlinter.vim b/ale_linters/testft/testlinter.vim new file mode 100644 index 00000000..65e0b205 --- /dev/null +++ b/ale_linters/testft/testlinter.vim @@ -0,0 +1,10 @@ +" Author: neersighted +" Description: dummy linter to use in tests + +call ale#linter#Define('testft', { +\ 'name': 'testlinter', +\ 'output_stream': 'stdout', +\ 'executable': 'testlinter', +\ 'command': 'testlinter', +\ 'callback': 'testCB', +\}) diff --git a/ale_linters/tex/alex.vim b/ale_linters/tex/alex.vim new file mode 100644 index 00000000..5d9aec66 --- /dev/null +++ b/ale_linters/tex/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for TeX files + +call ale#handlers#alex#DefineLinter('tex', '--text') diff --git a/ale_linters/tex/chktex.vim b/ale_linters/tex/chktex.vim new file mode 100644 index 00000000..9e7be587 --- /dev/null +++ b/ale_linters/tex/chktex.vim @@ -0,0 +1,64 @@ +" Author: Andrew Balmos - +" Description: chktex for LaTeX files + +call ale#Set('tex_chktex_executable', 'chktex') +call ale#Set('tex_chktex_options', '-I') + +function! ale_linters#tex#chktex#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'tex_chktex_executable') +endfunction + +function! ale_linters#tex#chktex#GetCommand(buffer, version) abort + let l:options = '' + + " Avoid bug when used without -p (last warning has gibberish for a filename) + let l:options .= ' -v0 -p stdin -q' + + " Avoid bug of reporting wrong column when using tabs (issue #723) + if ale#semver#GTE(a:version, [1, 7, 7]) + let l:options .= ' -S TabSize=1' + endif + + " Check for optional .chktexrc + let l:chktex_config = ale#path#FindNearestFile(a:buffer, '.chktexrc') + + if !empty(l:chktex_config) + let l:options .= ' -l ' . ale#Escape(l:chktex_config) + endif + + let l:options .= ' ' . ale#Var(a:buffer, 'tex_chktex_options') + + return '%e' . l:options +endfunction + +function! ale_linters#tex#chktex#Handle(buffer, lines) abort + " Mattes lines like: + " + " stdin:499:2:24:Delete this space to maintain correct pagereferences. + " stdin:507:81:3:You should enclose the previous parenthesis with `{}'. + let l:pattern = '^stdin:\(\d\+\):\(\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[4] . ' (' . (l:match[3]+0) . ')', + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('tex', { +\ 'name': 'chktex', +\ 'executable': function('ale_linters#tex#chktex#GetExecutable'), +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale_linters#tex#chktex#GetExecutable(buffer), +\ '%e --version', +\ function('ale_linters#tex#chktex#GetCommand'), +\ )}, +\ 'callback': 'ale_linters#tex#chktex#Handle' +\}) diff --git a/ale_linters/tex/cspell.vim b/ale_linters/tex/cspell.vim new file mode 100644 index 00000000..4cf2b08e --- /dev/null +++ b/ale_linters/tex/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for TeX files. + +call ale#handlers#cspell#DefineLinter('tex') diff --git a/ale_linters/tex/lacheck.vim b/ale_linters/tex/lacheck.vim new file mode 100644 index 00000000..35aad083 --- /dev/null +++ b/ale_linters/tex/lacheck.vim @@ -0,0 +1,43 @@ +" Author: Andrew Balmos - +" Description: lacheck for LaTeX files + +call ale#Set('tex_lacheck_executable', 'lacheck') + +function! ale_linters#tex#lacheck#Handle(buffer, lines) abort + " Mattes lines like: + " + " "book.tex", line 37: possible unwanted space at "{" + " "book.tex", line 38: missing `\ ' after "etc." + 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 inputted items. We do not + " want warnings from those items anyway + 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[2] + 0, + \ 'text': l:match[3], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('tex', { +\ 'name': 'lacheck', +\ 'executable': {b -> ale#Var(b, 'tex_lacheck_executable')}, +\ 'command': '%e %t', +\ 'callback': 'ale_linters#tex#lacheck#Handle' +\}) diff --git a/ale_linters/tex/proselint.vim b/ale_linters/tex/proselint.vim new file mode 100644 index 00000000..35e764e2 --- /dev/null +++ b/ale_linters/tex/proselint.vim @@ -0,0 +1,9 @@ +" Author: poohzrn https://github.com/poohzrn +" Description: proselint for TeX files + +call ale#linter#Define('tex', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/tex/redpen.vim b/ale_linters/tex/redpen.vim new file mode 100644 index 00000000..952a6004 --- /dev/null +++ b/ale_linters/tex/redpen.vim @@ -0,0 +1,9 @@ +" Author: rhysd https://rhysd.github.io +" Description: Redpen, a proofreading tool (http://redpen.cc) + +call ale#linter#Define('tex', { +\ 'name': 'redpen', +\ 'executable': 'redpen', +\ 'command': 'redpen -f latex -r json %t', +\ 'callback': 'ale#handlers#redpen#HandleRedpenOutput', +\}) diff --git a/ale_linters/tex/texlab.vim b/ale_linters/tex/texlab.vim new file mode 100644 index 00000000..8e96b80b --- /dev/null +++ b/ale_linters/tex/texlab.vim @@ -0,0 +1,26 @@ +" Author: Ricardo Liang +" Author: ourigen +" Description: Texlab language server (Rust rewrite) + +call ale#Set('tex_texlab_executable', 'texlab') +call ale#Set('tex_texlab_options', '') +call ale#Set('tex_texlab_config', {}) + +function! ale_linters#tex#texlab#GetProjectRoot(buffer) abort + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' +endfunction + +function! ale_linters#tex#texlab#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'tex_texlab_options')) +endfunction + +call ale#linter#Define('tex', { +\ 'name': 'texlab', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'tex_texlab_executable')}, +\ 'command': function('ale_linters#tex#texlab#GetCommand'), +\ 'project_root': function('ale_linters#tex#texlab#GetProjectRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'tex_texlab_config')}, +\}) 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 +" 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/tex/vale.vim b/ale_linters/tex/vale.vim new file mode 100644 index 00000000..f64e72ac --- /dev/null +++ b/ale_linters/tex/vale.vim @@ -0,0 +1,9 @@ +" Author: chew-z https://github.com/chew-z +" Description: vale for LaTeX files + +call ale#linter#Define('tex', { +\ 'name': 'vale', +\ 'executable': 'vale', +\ 'command': 'vale --output=JSON %t', +\ 'callback': 'ale#handlers#vale#Handle', +\}) diff --git a/ale_linters/tex/writegood.vim b/ale_linters/tex/writegood.vim new file mode 100644 index 00000000..c1aeace9 --- /dev/null +++ b/ale_linters/tex/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for TeX files + +call ale#handlers#writegood#DefineLinter('tex') diff --git a/ale_linters/texinfo/alex.vim b/ale_linters/texinfo/alex.vim new file mode 100644 index 00000000..4d245524 --- /dev/null +++ b/ale_linters/texinfo/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for texinfo files + +call ale#handlers#alex#DefineLinter('texinfo', '--text') diff --git a/ale_linters/texinfo/cspell.vim b/ale_linters/texinfo/cspell.vim new file mode 100644 index 00000000..d691b3a7 --- /dev/null +++ b/ale_linters/texinfo/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for TeXInfo files. + +call ale#handlers#cspell#DefineLinter('texinfo') diff --git a/ale_linters/texinfo/proselint.vim b/ale_linters/texinfo/proselint.vim new file mode 100644 index 00000000..003e3a0f --- /dev/null +++ b/ale_linters/texinfo/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for Texinfo files + +call ale#linter#Define('texinfo', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/texinfo/writegood.vim b/ale_linters/texinfo/writegood.vim new file mode 100644 index 00000000..4427f056 --- /dev/null +++ b/ale_linters/texinfo/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for Texinfo files + +call ale#handlers#writegood#DefineLinter('texinfo') diff --git a/ale_linters/text/alex.vim b/ale_linters/text/alex.vim new file mode 100644 index 00000000..d87ed915 --- /dev/null +++ b/ale_linters/text/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for text files + +call ale#handlers#alex#DefineLinter('text', '--text') diff --git a/ale_linters/text/cspell.vim b/ale_linters/text/cspell.vim new file mode 100644 index 00000000..813ef3b8 --- /dev/null +++ b/ale_linters/text/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for general text files. + +call ale#handlers#cspell#DefineLinter('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/proselint.vim b/ale_linters/text/proselint.vim new file mode 100644 index 00000000..281b4ffa --- /dev/null +++ b/ale_linters/text/proselint.vim @@ -0,0 +1,9 @@ +" Author: poohzrn https://github.com/poohzrn +" Description: proselint for text files + +call ale#linter#Define('text', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/text/redpen.vim b/ale_linters/text/redpen.vim new file mode 100644 index 00000000..ec4433b9 --- /dev/null +++ b/ale_linters/text/redpen.vim @@ -0,0 +1,9 @@ +" Author: rhysd https://rhysd.github.io +" Description: Redpen, a proofreading tool (http://redpen.cc) + +call ale#linter#Define('text', { +\ 'name': 'redpen', +\ 'executable': 'redpen', +\ 'command': 'redpen -f plain -r json %t', +\ 'callback': 'ale#handlers#redpen#HandleRedpenOutput', +\}) diff --git a/ale_linters/text/textlint.vim b/ale_linters/text/textlint.vim new file mode 100644 index 00000000..67c4e378 --- /dev/null +++ b/ale_linters/text/textlint.vim @@ -0,0 +1,9 @@ +" Author: Yasuhiro Kiyota +" Description: textlint, a proofreading tool (https://textlint.github.io/) + +call ale#linter#Define('text', { +\ 'name': 'textlint', +\ 'executable': function('ale#handlers#textlint#GetExecutable'), +\ 'command': function('ale#handlers#textlint#GetCommand'), +\ 'callback': 'ale#handlers#textlint#HandleTextlintOutput', +\}) diff --git a/ale_linters/text/vale.vim b/ale_linters/text/vale.vim new file mode 100644 index 00000000..cf37c2f8 --- /dev/null +++ b/ale_linters/text/vale.vim @@ -0,0 +1,9 @@ +" Author: chew-z https://github.com/chew-z +" Description: vale for text files + +call ale#linter#Define('text', { +\ 'name': 'vale', +\ 'executable': 'vale', +\ 'command': 'vale --output=JSON %t', +\ 'callback': 'ale#handlers#vale#Handle', +\}) diff --git a/ale_linters/text/writegood.vim b/ale_linters/text/writegood.vim new file mode 100644 index 00000000..81b935d4 --- /dev/null +++ b/ale_linters/text/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for text files + +call ale#handlers#writegood#DefineLinter('text') diff --git a/ale_linters/thrift/thrift.vim b/ale_linters/thrift/thrift.vim new file mode 100644 index 00000000..345c7abe --- /dev/null +++ b/ale_linters/thrift/thrift.vim @@ -0,0 +1,87 @@ +" Author: Jon Parise + +call ale#Set('thrift_thrift_executable', 'thrift') +call ale#Set('thrift_thrift_generators', ['cpp']) +call ale#Set('thrift_thrift_includes', ['.']) +call ale#Set('thrift_thrift_options', '-strict') + +function! ale_linters#thrift#thrift#GetCommand(buffer) abort + let l:generators = ale#Var(a:buffer, 'thrift_thrift_generators') + let l:includes = ale#Var(a:buffer, 'thrift_thrift_includes') + + " The thrift compiler requires at least one generator. If none are set, + " fall back to our default value to avoid silently failing. We could also + " `throw` here, but that seems even less helpful. + if empty(l:generators) + let l:generators = ['cpp'] + endif + + let l:output_dir = ale#command#CreateDirectory(a:buffer) + + return '%e' + \ . ale#Pad(join(map(copy(l:generators), "'--gen ' . v:val"))) + \ . ale#Pad(join(map(copy(l:includes), "'-I ' . v:val"))) + \ . ale#Pad(ale#Var(a:buffer, 'thrift_thrift_options')) + \ . ' -out ' . ale#Escape(l:output_dir) + \ . ' %t' +endfunction + +function! ale_linters#thrift#thrift#Handle(buffer, lines) abort + " Matches lines like the following: + " + " [SEVERITY:/path/filename.thrift:31] Message text + " [ERROR:/path/filename.thrift:31] (last token was ';') + let l:pattern = '\v^\[(\u+):(.*):(\d+)\] (.*)$' + + let l:index = 0 + let l:output = [] + + " Roll our own output-matching loop instead of using ale#util#GetMatches + " because we need to support error messages that span multiple lines. + while l:index < len(a:lines) + let l:line = a:lines[l:index] + + let l:match = matchlist(l:line, l:pattern) + + if empty(l:match) + let l:index += 1 + continue + endif + + let l:severity = l:match[1] + + if l:severity is# 'WARNING' + let l:type = 'W' + else + let l:type = 'E' + endif + + " If our text looks like "(last token was ';')", the *next* line + " should contain a more descriptive error message. + let l:text = l:match[4] + + if l:text =~# '\(last token was .*\)' + let l:index += 1 + let l:text = get(a:lines, l:index, 'Unknown error ' . l:text) + endif + + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + + let l:index += 1 + endwhile + + return l:output +endfunction + +call ale#linter#Define('thrift', { +\ 'name': 'thrift', +\ 'output_stream': 'both', +\ '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/thrift/thriftcheck.vim b/ale_linters/thrift/thriftcheck.vim new file mode 100644 index 00000000..bf929d10 --- /dev/null +++ b/ale_linters/thrift/thriftcheck.vim @@ -0,0 +1,46 @@ +" Author: Jon Parise + +call ale#Set('thrift_thriftcheck_executable', 'thriftcheck') +call ale#Set('thrift_thriftcheck_options', '') + +function! ale_linters#thrift#thriftcheck#GetCommand(buffer) abort + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'thrift_thriftcheck_options')) + \ . ' --stdin-filename %s' + \ . ' %t' +endfunction + +function! ale_linters#thrift#thriftcheck#Handle(buffer, lines) abort + " Matches lines like the following: + " + " file.thrift:1:1: error: "py" namespace must match "^idl\\." (namespace.pattern) + " file.thrift:3:5: warning: 64-bit integer constant -2147483649 may not work in all languages (int.64bit) + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):(\d+): ?([^:]+): (.+) \(([^\)]+)\)$' + + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if l:match[3] is# 'warning' + let l:type = 'W' + else + let l:type = 'E' + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:type, + \ 'text': l:match[4], + \ 'code': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('thrift', { +\ 'name': 'thriftcheck', +\ 'executable': {b -> ale#Var(b, 'thrift_thriftcheck_executable')}, +\ 'command': function('ale_linters#thrift#thriftcheck#GetCommand'), +\ 'callback': 'ale_linters#thrift#thriftcheck#Handle', +\}) diff --git a/ale_linters/typescript/biome.vim b/ale_linters/typescript/biome.vim new file mode 100644 index 00000000..1730da51 --- /dev/null +++ b/ale_linters/typescript/biome.vim @@ -0,0 +1,11 @@ +" Author: Filip Gospodinov +" Description: biome for TypeScript files + +call ale#linter#Define('typescript', { +\ 'name': 'biome', +\ 'lsp': 'stdio', +\ 'language': function('ale#handlers#biome#GetLanguage'), +\ 'executable': function('ale#handlers#biome#GetExecutable'), +\ 'command': '%e lsp-proxy', +\ 'project_root': function('ale#handlers#biome#GetProjectRoot'), +\}) diff --git a/ale_linters/typescript/cspell.vim b/ale_linters/typescript/cspell.vim new file mode 100644 index 00000000..6061b75c --- /dev/null +++ b/ale_linters/typescript/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for TypeScript files. + +call ale#handlers#cspell#DefineLinter('typescript') diff --git a/ale_linters/typescript/deno.vim b/ale_linters/typescript/deno.vim new file mode 100644 index 00000000..f47fac7a --- /dev/null +++ b/ale_linters/typescript/deno.vim @@ -0,0 +1,12 @@ +" Author: Mohammed Chelouti - https://github.com/motato1 +" Arnold Chand +" Description: Deno lsp linter for TypeScript files. + +call ale#linter#Define('typescript', { +\ 'name': 'deno', +\ 'lsp': 'stdio', +\ 'executable': function('ale#handlers#deno#GetExecutable'), +\ 'command': '%e lsp', +\ 'project_root': function('ale#handlers#deno#GetProjectRoot'), +\ 'initialization_options': function('ale#handlers#deno#GetInitializationOptions'), +\}) diff --git a/ale_linters/typescript/eslint.vim b/ale_linters/typescript/eslint.vim new file mode 100644 index 00000000..eaeac307 --- /dev/null +++ b/ale_linters/typescript/eslint.vim @@ -0,0 +1,10 @@ +" Author: w0rp +" Description: eslint for JavaScript files + +call ale#linter#Define('typescript', { +\ 'name': 'eslint', +\ 'executable': function('ale#handlers#eslint#GetExecutable'), +\ 'cwd': function('ale#handlers#eslint#GetCwd'), +\ 'command': function('ale#handlers#eslint#GetCommand'), +\ 'callback': 'ale#handlers#eslint#HandleJSON', +\}) diff --git a/ale_linters/typescript/standard.vim b/ale_linters/typescript/standard.vim new file mode 100644 index 00000000..1d524a10 --- /dev/null +++ b/ale_linters/typescript/standard.vim @@ -0,0 +1,31 @@ +" Author: Ahmed El Gabri <@ahmedelgabri> +" Description: standardjs for typescript files + +call ale#Set('typescript_standard_executable', 'standard') +call ale#Set('typescript_standard_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('typescript_standard_options', '') + +function! ale_linters#typescript#standard#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'typescript_standard', [ + \ 'node_modules/standardx/bin/cmd.js', + \ 'node_modules/standard/bin/cmd.js', + \ 'node_modules/.bin/standard', + \]) +endfunction + +function! ale_linters#typescript#standard#GetCommand(buffer) abort + let l:executable = ale_linters#typescript#standard#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'typescript_standard_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin %s' +endfunction + +" standard uses eslint and the output format is the same +call ale#linter#Define('typescript', { +\ 'name': 'standard', +\ 'executable': function('ale_linters#typescript#standard#GetExecutable'), +\ 'command': function('ale_linters#typescript#standard#GetCommand'), +\ 'callback': 'ale#handlers#eslint#Handle', +\}) diff --git a/ale_linters/typescript/tslint.vim b/ale_linters/typescript/tslint.vim new file mode 100644 index 00000000..886a3cd4 --- /dev/null +++ b/ale_linters/typescript/tslint.vim @@ -0,0 +1,75 @@ +" Author: Prashanth Chandra , Jonathan Clem +" Description: tslint for TypeScript files + +call ale#handlers#tslint#InitVariables() + +function! ale_linters#typescript#tslint#Handle(buffer, lines) abort + " Do not output any errors for empty files if the option is on. + if ale#Var(a:buffer, 'typescript_tslint_ignore_empty_files') + \&& getbufline(a:buffer, 1, '$') == [''] + return [] + endif + + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + if get(l:error, 'ruleName', '') is# 'no-implicit-dependencies' + continue + endif + + let l:item = { + \ 'type': (get(l:error, 'ruleSeverity', '') is# 'WARNING' ? 'W' : 'E'), + \ 'text': l:error.failure, + \ 'lnum': l:error.startPosition.line + 1, + \ 'col': l:error.startPosition.character + 1, + \ 'end_lnum': l:error.endPosition.line + 1, + \ 'end_col': l:error.endPosition.character + 1, + \} + + let l:filename = ale#path#GetAbsPath(l:dir, l:error.name) + + " Assume temporary files are this file. + if !ale#path#IsTempName(l:filename) + let l:item.filename = l:filename + endif + + if has_key(l:error, 'ruleName') + let l:item.code = l:error.ruleName + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +function! ale_linters#typescript#tslint#GetCommand(buffer) abort + let l:tslint_config_path = ale#path#ResolveLocalPath( + \ a:buffer, + \ 'tslint.json', + \ ale#Var(a:buffer, 'typescript_tslint_config_path') + \) + let l:tslint_config_option = !empty(l:tslint_config_path) + \ ? ' -c ' . ale#Escape(l:tslint_config_path) + \ : '' + + let l:tslint_rules_dir = ale#Var(a:buffer, 'typescript_tslint_rules_dir') + let l:tslint_rules_option = !empty(l:tslint_rules_dir) + \ ? ' -r ' . ale#Escape(l:tslint_rules_dir) + \ : '' + + return ale#Escape(ale#handlers#tslint#GetExecutable(a:buffer)) + \ . ' --format json' + \ . l:tslint_config_option + \ . l:tslint_rules_option + \ . ' %t' +endfunction + +call ale#linter#Define('typescript', { +\ 'name': 'tslint', +\ 'executable': function('ale#handlers#tslint#GetExecutable'), +\ 'cwd': '%s:h', +\ '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 new file mode 100644 index 00000000..d97becca --- /dev/null +++ b/ale_linters/typescript/tsserver.vim @@ -0,0 +1,18 @@ +" Author: w0rp +" Description: tsserver integration for ALE + +call ale#Set('typescript_tsserver_executable', 'tsserver') +call ale#Set('typescript_tsserver_config_path', '') +call ale#Set('typescript_tsserver_use_global', get(g:, 'ale_use_global_executables', 0)) + +call ale#linter#Define('typescript', { +\ 'name': 'tsserver', +\ 'lsp': 'tsserver', +\ 'executable': {b -> ale#path#FindExecutable(b, 'typescript_tsserver', [ +\ '.yarn/sdks/typescript/bin/tsserver', +\ 'node_modules/.bin/tsserver', +\ ])}, +\ 'command': '%e', +\ 'project_root': function('ale#handlers#tsserver#GetProjectRoot'), +\ 'language': '', +\}) diff --git a/ale_linters/typescript/typecheck.vim b/ale_linters/typescript/typecheck.vim new file mode 100644 index 00000000..2f18691b --- /dev/null +++ b/ale_linters/typescript/typecheck.vim @@ -0,0 +1,33 @@ +" Author: Prashanth Chandra https://github.com/prashcr, Aleh Kashnikau https://github.com/mkusher +" Description: type checker for TypeScript files + +function! ale_linters#typescript#typecheck#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " hello.ts[7, 41]: Property 'a' does not exist on type 'A' + " hello.ts[16, 7]: Type 'A' is not assignable to type 'B' + " + let l:pattern = '.\+\.ts\[\(\d\+\), \(\d\+\)\]: \(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[1] + 0 + let l:column = l:match[2] + 0 + let l:text = l:match[3] + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:column, + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('typescript', { +\ 'name': 'typecheck', +\ 'executable': 'typecheck', +\ 'command': 'typecheck %s', +\ 'callback': 'ale_linters#typescript#typecheck#Handle', +\}) diff --git a/ale_linters/typescript/xo.vim b/ale_linters/typescript/xo.vim new file mode 100644 index 00000000..6f4ee50c --- /dev/null +++ b/ale_linters/typescript/xo.vim @@ -0,0 +1,6 @@ +call ale#linter#Define('typescript', { +\ 'name': 'xo', +\ 'executable': function('ale#handlers#xo#GetExecutable'), +\ 'command': function('ale#handlers#xo#GetLintCommand'), +\ 'callback': 'ale#handlers#xo#HandleJSON', +\}) diff --git a/ale_linters/v/v.vim b/ale_linters/v/v.vim new file mode 100644 index 00000000..dfe6f562 --- /dev/null +++ b/ale_linters/v/v.vim @@ -0,0 +1,73 @@ +" Author: fiatjaf +" Description: v build for V files + +call ale#Set('v_v_executable', 'v') +call ale#Set('v_v_options', '') + +function! ale_linters#v#v#Handler(buffer, lines) abort + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] + + " Matches patterns like the following: + " + " ./const.v:4:3: warning: const names cannot contain uppercase letters, use snake_case instead + " 2 | + " 3 | const ( + " 4 | BUTTON_TEXT = 'OK' + " | ~~~~~~~~~~~ + " 5 | ) + " ./main.v:4:8: warning: module 'os' is imported but never used + " 2 | + " 3 | import ui + " 4 | import os + " | ~~ + " 5 | + " 6 | const ( + " ./main.v:20:10: error: undefined ident: `win_widt` + " 18 | mut app := &App{} + " 19 | app.window = ui.window({ + " 20 | width: win_widt + " | ~~~~~~~~ + " 21 | height: win_height + " 22 | title: 'Counter' + let l:current = {} + + for l:line in a:lines + " matches basic error description + let l:match = matchlist(l:line, + \ '\([^:]\+\):\([^:]\+\):\([^:]\+\): \([^:]\+\): \(.*\)') + + if !empty(l:match) + let l:current = { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[5], + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \} + call add(l:output, l:current) + continue + endif + + " try to get information about the ending column + let l:tildematch = matchstr(l:line, '\~\+') + + if !empty(l:tildematch) + let l:current['end_col'] = l:current['col'] + len(l:tildematch) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('v', { +\ 'name': 'v', +\ 'executable': {b -> ale#Var(b, 'v_v_executable')}, +\ 'command': {b -> +\ '%e' . ale#Pad(ale#Var(b, 'v_v_options')) +\ . ' . -o /tmp/vim-ale-v' +\ }, +\ 'output_stream': 'stderr', +\ 'callback': 'ale_linters#v#v#Handler', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/vala/vala_lint.vim b/ale_linters/vala/vala_lint.vim new file mode 100644 index 00000000..7f8a566a --- /dev/null +++ b/ale_linters/vala/vala_lint.vim @@ -0,0 +1,66 @@ +" Author: Atsuya Takagi +" Description: A linter for Vala using Vala-Lint. + +call ale#Set('vala_vala_lint_config_filename', 'vala-lint.conf') +call ale#Set('vala_vala_lint_executable', 'io.elementary.vala-lint') + +function! ale_linters#vala#vala_lint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'vala_vala_lint_executable') +endfunction + +function! ale_linters#vala#vala_lint#GetCommand(buffer) abort + let l:command = ale_linters#vala#vala_lint#GetExecutable(a:buffer) + + let l:config_filename = ale#Var(a:buffer, 'vala_vala_lint_config_filename') + let l:config_path = ale#path#FindNearestFile(a:buffer, l:config_filename) + + if !empty(l:config_path) + let l:command .= ' -c ' . l:config_path + endif + + return l:command . ' %s' +endfunction + +function! ale_linters#vala#vala_lint#Handle(buffer, lines) abort + let l:pattern = '^\s*\(\d\+\)\.\(\d\+\)\s\+\(error\|warn\)\s\+\(.\+\)\s\([A-Za-z0-9_\-]\+\)' + let l:output = [] + + for l:line in a:lines + " remove color escape sequences since vala-lint doesn't support + " output without colors + let l:cleaned_line = substitute(l:line, '\e\[[0-9;]\+[mK]', '', 'g') + let l:match = matchlist(l:cleaned_line, l:pattern) + + if len(l:match) == 0 + continue + endif + + let l:refined_type = l:match[3] is# 'warn' ? 'W' : 'E' + let l:cleaned_text = substitute(l:match[4], '^\s*\(.\{-}\)\s*$', '\1', '') + + let l:lnum = l:match[1] + 0 + let l:column = l:match[2] + 0 + let l:type = l:refined_type + let l:text = l:cleaned_text + let l:code = l:match[5] + + call add(l:output, { + \ 'lnum': l:lnum, + \ 'col': l:column, + \ 'text': l:text, + \ 'type': l:type, + \ 'code': l:code, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('vala', { +\ 'name': 'vala_lint', +\ 'output_stream': 'stdout', +\ 'executable': function('ale_linters#vala#vala_lint#GetExecutable'), +\ 'command': function('ale_linters#vala#vala_lint#GetCommand'), +\ 'callback': 'ale_linters#vala#vala_lint#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/verilog/hdl_checker.vim b/ale_linters/verilog/hdl_checker.vim new file mode 100644 index 00000000..b05d8565 --- /dev/null +++ b/ale_linters/verilog/hdl_checker.vim @@ -0,0 +1,5 @@ +" Author: suoto +" Description: Adds support for HDL Code Checker, which wraps vcom/vlog, ghdl +" or xvhdl. More info on https://github.com/suoto/hdl_checker + +call ale#handlers#hdl_checker#DefineLinter('verilog') diff --git a/ale_linters/verilog/iverilog.vim b/ale_linters/verilog/iverilog.vim new file mode 100644 index 00000000..54d55d79 --- /dev/null +++ b/ale_linters/verilog/iverilog.vim @@ -0,0 +1,44 @@ +" Author: Masahiro H https://github.com/mshr-h +" Description: iverilog for verilog files + +call ale#Set('verilog_iverilog_options', '') + +function! ale_linters#verilog#iverilog#GetCommand(buffer) abort + return 'iverilog -t null -Wall ' + \ . '-y%s:h ' + \ . ale#Var(a:buffer, 'verilog_iverilog_options') + \ . ' %t' +endfunction + +function! ale_linters#verilog#iverilog#Handle(buffer, lines) abort + " Look for lines like the following. + " + " tb_me_top.v:37: warning: Instantiating module me_top with dangling input port 1 (rst_n) floating. + " tb_me_top.v:17: syntax error + " memory_single_port.v:2: syntax error + " tb_me_top.v:17: error: Invalid module instantiation + let l:pattern = '^[^:]\+:\(\d\+\): \(warning\|error\|syntax error\)\(: \(.\+\)\)\?' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[1] + 0 + let l:type = l:match[2] =~# 'error' ? 'E' : 'W' + let l:text = l:match[2] is# 'syntax error' ? 'syntax error' : l:match[4] + + call add(l:output, { + \ 'lnum': l:line, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'iverilog', +\ 'output_stream': 'stderr', +\ 'executable': 'iverilog', +\ 'command': function('ale_linters#verilog#iverilog#GetCommand'), +\ 'callback': 'ale_linters#verilog#iverilog#Handle', +\}) diff --git a/ale_linters/verilog/slang.vim b/ale_linters/verilog/slang.vim new file mode 100644 index 00000000..40299a34 --- /dev/null +++ b/ale_linters/verilog/slang.vim @@ -0,0 +1,53 @@ +" Author: Alvin Rolling +" Description: slang for verilog files + +" Set this option to change Slang lint options +if !exists('g:ale_verilog_slang_options') + let g:ale_verilog_slang_options = '' +endif + +" --lint-only +function! ale_linters#verilog#slang#GetCommand(buffer) abort + return 'slang -Weverything ' + \ . '-I%s:h ' + \ . ale#Var(a:buffer, 'verilog_slang_options') .' ' + \ . '%t' +endfunction + +function! s:RemoveUnicodeQuotes(text) abort + let l:text = a:text + let l:text = substitute(l:text, '[`´‘’]', '''', 'g') + let l:text = substitute(l:text, '[“”]', '"', 'g') + + return l:text +endfunction + +function! ale_linters#verilog#slang#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) + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'type': (l:match[4] is# 'error') ? 'E' : 'W', + \ 'text': s:RemoveUnicodeQuotes(l:match[5]), + \} + + if !empty(l:match[3]) + let l:item.col = str2nr(l:match[3]) + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'slang', +\ 'output_stream': 'stderr', +\ 'executable': 'slang', +\ 'command': function('ale_linters#verilog#slang#GetCommand'), +\ 'callback': 'ale_linters#verilog#slang#Handle', +\ 'read_buffer': 0, +\}) diff --git a/ale_linters/verilog/verilator.vim b/ale_linters/verilog/verilator.vim new file mode 100644 index 00000000..006e310d --- /dev/null +++ b/ale_linters/verilog/verilator.vim @@ -0,0 +1,60 @@ +" Author: Masahiro H https://github.com/mshr-h +" Description: verilator for verilog files + +" Set this option to change Verilator lint options +if !exists('g:ale_verilog_verilator_options') + let g:ale_verilog_verilator_options = '' +endif + +function! ale_linters#verilog#verilator#GetCommand(buffer) abort + " the path to the current file is systematically added to the search path + return 'verilator --lint-only -Wall -Wno-DECLFILENAME ' + \ . '-I%s:h ' + \ . ale#Var(a:buffer, 'verilog_verilator_options') .' ' + \ . '%t' +endfunction + +function! ale_linters#verilog#verilator#Handle(buffer, lines) abort + " Look for lines like the following. + " + " %Error: addr_gen.v:3: syntax error, unexpected IDENTIFIER + " %Warning-WIDTH: addr_gen.v:26: Operator ASSIGNDLY expects 12 bits on the Assign RHS, but Assign RHS's CONST '20'h0' generates 20 bits. + " %Warning-UNUSED: test.v:3: Signal is not used: a + " %Warning-UNDRIVEN: test.v:3: Signal is not driven: clk + " %Warning-UNUSED: test.v:4: Signal is not used: dout + " %Warning-BLKSEQ: test.v:10: Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=). + " Since version 4.032 (04/2020) verilator linter messages also contain the column number, + " and look like: + " %Error: /tmp/test.sv:3:1: syntax error, unexpected endmodule, expecting ';' + " + " to stay compatible with old versions of the tool, the column number is + " optional in the researched pattern + let l:pattern = '^%\(Warning\|Error\)[^:]*:\s*\([^:]\+\):\(\d\+\):\(\d\+\)\?:\? \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': str2nr(l:match[3]), + \ 'text': l:match[5], + \ 'type': l:match[1] is# 'Error' ? 'E' : 'W', + \ 'filename': l:match[2], + \} + + if !empty(l:match[4]) + let l:item.col = str2nr(l:match[4]) + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'verilator', +\ 'output_stream': 'stderr', +\ 'executable': 'verilator', +\ '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..45e1977c --- /dev/null +++ b/ale_linters/verilog/vlog.vim @@ -0,0 +1,52 @@ +" Author: John Gentile +" 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[3] + 0, + \ 'type': l:match[1] is? 'Error' ? 'E' : 'W', + \ 'text': l:match[4], + \ 'filename': l:match[2], + \}) + endfor + + "Matches patterns like the following: + "** Warning: (vlog-2623) add.v(7): Undefined variable: C. + "** Error: (vlog-13294) file.v(1): Identifier must be declared with a port mode: C. + " let l:pattern = '^**\s\(\w*\):[a-zA-Z0-9\-\.\_\/ ]\+(\(\d\+\)):\s\+\(.*\)' + let l:pattern = '^**\s\(\w*\):\s\([^)]*)\) \([a-zA-Z0-9\-\.\_\/ ]\+\)(\(\d\+\)):\s\+\(.*\)' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[4] + 0, + \ 'type': l:match[1] is? 'Error' ? 'E' : 'W', + \ 'text': l:match[2] . ' ' . l:match[5], + \ 'filename': 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 +" 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/verilog/yosys.vim b/ale_linters/verilog/yosys.vim new file mode 100644 index 00000000..29657755 --- /dev/null +++ b/ale_linters/verilog/yosys.vim @@ -0,0 +1,42 @@ +" Author: Nathan Sharp +" Description: Yosys for Verilog files + +call ale#Set('verilog_yosys_executable', 'yosys') +call ale#Set('verilog_yosys_options', '-Q -T -p ''read_verilog %s''') + +function! ale_linters#verilog#yosys#GetCommand(buffer) abort + return '%e ' . ale#Var(a:buffer, 'verilog_yosys_options') . ' 2>&1' +endfunction + +function! ale_linters#verilog#yosys#Handle(buffer, lines) abort + let l:output = [] + let l:path = fnamemodify(bufname(a:buffer), ':p') + + for l:match in ale#util#GetMatches(a:lines, '^\([^:]\+\):\(\d\+\): \(WARNING\|ERROR\): \(.\+\)$') + call add(l:output, { + \ 'lnum': str2nr(l:match[2]), + \ 'text': l:match[4], + \ 'type': l:match[3][0], + \ 'filename': l:match[1], + \}) + endfor + + for l:match in ale#util#GetMatches(a:lines, '^\(Warning\|ERROR\): \(.\+\)$') + call add(l:output, { + \ 'lnum': 1, + \ 'text': l:match[2], + \ 'type': l:match[1][0], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'yosys', +\ 'output_stream': 'stdout', +\ 'executable': {b -> ale#Var(b, 'verilog_yosys_executable')}, +\ 'command': function('ale_linters#verilog#yosys#GetCommand'), +\ 'callback': 'ale_linters#verilog#yosys#Handle', +\ 'lint_file': 1, +\}) 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 +" 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/hdl_checker.vim b/ale_linters/vhdl/hdl_checker.vim new file mode 100644 index 00000000..c9d306b3 --- /dev/null +++ b/ale_linters/vhdl/hdl_checker.vim @@ -0,0 +1,5 @@ +" Author: suoto +" Description: Adds support for HDL Code Checker, which wraps vcom/vlog, ghdl +" or xvhdl. More info on https://github.com/suoto/hdl_checker + +call ale#handlers#hdl_checker#DefineLinter('vhdl') 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 +" 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 +" 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 new file mode 100644 index 00000000..5ca2f149 --- /dev/null +++ b/ale_linters/vim/ale_custom_linting_rules.vim @@ -0,0 +1,70 @@ +" Author: w0rp +" Description: A linter for checking ALE project code itself. + +function! ale_linters#vim#ale_custom_linting_rules#GetExecutable(buffer) abort + let l:filename = expand('#' . a:buffer . ':p') + let l:dir_list = [] + + for l:dir in split(&runtimepath, ',') + if l:filename[:len(l:dir) - 1] is# l:dir + call add(l:dir_list, l:dir) + endif + endfor + + return !empty(l:dir_list) + \ ? findfile('test/script/custom-linting-rules', join(l:dir_list, ',')) + \ : '' +endfunction + +function! s:GetALEProjectDir(buffer) abort + let l:executable = ale_linters#vim#ale_custom_linting_rules#GetExecutable(a:buffer) + + return ale#path#Dirname(ale#path#Dirname(ale#path#Dirname(l:executable))) +endfunction + +function! ale_linters#vim#ale_custom_linting_rules#GetCwd(buffer) abort + let l:executable = ale_linters#vim#ale_custom_linting_rules#GetExecutable(a:buffer) + + return ale#path#Dirname(ale#path#Dirname(ale#path#Dirname(l:executable))) +endfunction + +function! ale_linters#vim#ale_custom_linting_rules#GetCommand(buffer) abort + 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, '$') + call ale#util#Writefile(a:buffer, l:lines, l:temp_file) + + return '%e ' . ale#Escape(l:temp_dir) +endfunction + +function! ale_linters#vim#ale_custom_linting_rules#Handle(buffer, lines) abort + let l:dir = s:GetALEProjectDir(a:buffer) + let l:output = [] + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+) (.+)$' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " Ignore trailing whitespace errors if we've turned them off. + if !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + \&& l:match[3] is# 'Trailing whitespace' + continue + endif + + call add(l:output, { + \ 'lnum': l:match[2], + \ 'text': l:match[3], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('vim', { +\ 'name': 'ale_custom_linting_rules', +\ 'executable': function('ale_linters#vim#ale_custom_linting_rules#GetExecutable'), +\ 'cwd': function('ale_linters#vim#ale_custom_linting_rules#GetCwd'), +\ '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/vimls.vim b/ale_linters/vim/vimls.vim new file mode 100644 index 00000000..7003eb04 --- /dev/null +++ b/ale_linters/vim/vimls.vim @@ -0,0 +1,61 @@ +" Author: Jeffrey Lau - https://github.com/zoonfafer +" Description: Vim Language Server integration for ALE + +call ale#Set('vim_vimls_executable', 'vim-language-server') +call ale#Set('vim_vimls_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('vim_vimls_config', {}) + +function! ale_linters#vim#vimls#GetProjectRoot(buffer) abort + let l:trigger_file_candidates = [ + \ '.vimrc', + \ 'init.vim', + \] + + for l:candidate in l:trigger_file_candidates + let l:trigger_file = fnamemodify(bufname(a:buffer), ':t') + + if l:trigger_file is# l:candidate + return fnamemodify( + \ bufname(a:buffer), + \ ':h', + \) + endif + endfor + + let l:trigger_dir_candidates = [ + \ 'autoload', + \ 'plugin', + \ '.git', + \] + + let l:path_upwards = ale#path#Upwards(fnamemodify(bufname(a:buffer), ':p:h')) + + for l:path in l:path_upwards + for l:candidate in l:trigger_dir_candidates + let l:trigger_dir = ale#path#Simplify( + \ l:path . '/' . l:candidate, + \) + + if isdirectory(l:trigger_dir) + return fnamemodify( + \ l:trigger_dir, + \ ':p:h:h', + \) + endif + endfor + endfor + + return '' +endfunction + +call ale#linter#Define('vim', { +\ 'name': 'vimls', +\ 'lsp': 'stdio', +\ 'lsp_config': {b -> ale#Var(b, 'vim_vimls_config')}, +\ 'executable': {b -> ale#path#FindExecutable(b, 'vim_vimls', [ +\ 'node_modules/.bin/vim-language-server', +\ ])}, +\ 'command': '%e --stdio', +\ 'language': 'vim', +\ 'project_root': function('ale_linters#vim#vimls#GetProjectRoot'), +\}) diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim new file mode 100644 index 00000000..f7054ffb --- /dev/null +++ b/ale_linters/vim/vint.vim @@ -0,0 +1,65 @@ +" Author: w0rp , KabbAmine +" Description: This file adds support for checking Vim code with Vint. + +" This flag can be used to change enable/disable style issues. +call ale#Set('vim_vint_show_style_issues', 1) +call ale#Set('vim_vint_executable', 'vint') +let s:enable_neovim = has('nvim') ? ' --enable-neovim' : '' +let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {policy_name} - {description} (see {reference})"' + +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' + + " Use the --stdin-display-name argument if supported, temp file otherwise. + let l:stdin_or_temp = ale#semver#GTE(a:version, [0, 4, 0]) + \ ? ' --stdin-display-name %s -' + \ : ' %t' + + return '%e' + \ . ' ' . l:warning_flag + \ . (l:can_use_no_color_flag ? ' --no-color' : '') + \ . s:enable_neovim + \ . ' ' . s:format + \ . l:stdin_or_temp +endfunction + +let s:word_regex_list = [ +\ '\v^Undefined variable: ([^ ]+)', +\ '\v^Make the scope explicit like ...([^ ]+). ', +\ '\v^.*start with a capital or contain a colon: ([^ ]+)', +\ '\v.*instead of .(\=[=~]).', +\] + +function! ale_linters#vim#vint#Handle(buffer, lines) abort + let l:loclist = ale#handlers#gcc#HandleGCCFormat(a:buffer, a:lines) + + for l:item in l:loclist + let l:match = [] + + for l:regex in s:word_regex_list + let l:match = matchlist(l:item.text, l:regex) + + if !empty(l:match) + let l:item.end_col = l:item.col + len(l:match[1]) - 1 + break + endif + endfor + endfor + + return l:loclist +endfunction + +call ale#linter#Define('vim', { +\ 'name': 'vint', +\ 'executable': {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/cspell.vim b/ale_linters/vue/cspell.vim new file mode 100644 index 00000000..2d8bfdc3 --- /dev/null +++ b/ale_linters/vue/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for Vue files. + +call ale#handlers#cspell#DefineLinter('vue') diff --git a/ale_linters/vue/vls.vim b/ale_linters/vue/vls.vim new file mode 100644 index 00000000..009effd0 --- /dev/null +++ b/ale_linters/vue/vls.vim @@ -0,0 +1,23 @@ +" Author: Alexander Olofsson +" Description: Vue vls Language Server integration for ALE + +call ale#Set('vue_vls_executable', 'vls') +call ale#Set('vue_vls_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#vue#vls#GetProjectRoot(buffer) abort + let l:package_path = ale#path#FindNearestFile(a:buffer, 'package.json') + + return !empty(l:package_path) ? fnamemodify(l:package_path, ':h') : '' +endfunction + +call ale#linter#Define('vue', { +\ 'name': 'vls', +\ 'aliases': ['vuels'], +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#path#FindExecutable(b, 'vue_vls', [ +\ 'node_modules/.bin/vls', +\ ])}, +\ 'command': '%e --stdio', +\ 'language': 'vue', +\ 'project_root': function('ale_linters#vue#vls#GetProjectRoot'), +\}) diff --git a/ale_linters/vue/volar.vim b/ale_linters/vue/volar.vim new file mode 100644 index 00000000..953262b5 --- /dev/null +++ b/ale_linters/vue/volar.vim @@ -0,0 +1,59 @@ +" Author: Arnold Chand +" Description: Volar Language Server integration for ALE adopted from +" nvim-lspconfig and volar/packages/shared/src/types.ts + +call ale#Set('vue_volar_executable', 'vue-language-server') +call ale#Set('vue_volar_use_global', 1) +call ale#Set('vue_volar_init_options', { +\ 'typescript': { 'tsdk': '' }, +\}) + +function! ale_linters#vue#volar#GetProjectRoot(buffer) abort + let l:project_roots = [ + \ 'package.json', + \ 'vite.config.js', + \ 'vite.config.mjs', + \ 'vite.config.cjs', + \ 'vite.config.ts', + \ '.git', + \ bufname(a:buffer) + \] + + for l:project_root in l:project_roots + let l:nearest_filepath = ale#path#FindNearestFile(a:buffer, l:project_root) + + if !empty(l:nearest_filepath) + return fnamemodify(l:nearest_filepath, ':h') + endif + endfor + + return '' +endfunction + +function! ale_linters#vue#volar#GetInitializationOptions(buffer) abort + let l:tsserver_path = ale#path#FindNearestDirectory(a:buffer, 'node_modules/typescript/lib') + + if l:tsserver_path is# '' + " no-custom-checks + echohl WarningMsg + " no-custom-checks + echom '[volar] Must have typescript installed in project, please install via `npm install -D typescript`.' + " no-custom-checks + echohl None + endif + + let l:init_options = ale#Var(a:buffer, 'vue_volar_init_options') + let l:init_options.typescript.tsdk = l:tsserver_path + + return l:init_options +endfunction + +call ale#linter#Define('vue', { +\ 'name': 'volar', +\ 'language': 'vue', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#path#FindExecutable(b, 'vue_volar', ['node_modules/.bin/vue-language-server'])}, +\ 'command': '%e --stdio', +\ 'project_root': function('ale_linters#vue#volar#GetProjectRoot'), +\ 'initialization_options': function('ale_linters#vue#volar#GetInitializationOptions'), +\}) diff --git a/ale_linters/wgsl/naga.vim b/ale_linters/wgsl/naga.vim new file mode 100644 index 00000000..2816751b --- /dev/null +++ b/ale_linters/wgsl/naga.vim @@ -0,0 +1,12 @@ +" Author: rhysd +" Description: naga-cli linter for WGSL syntax. + +call ale#Set('wgsl_naga_executable', 'naga') + +call ale#linter#Define('wgsl', { +\ 'name': 'naga', +\ 'executable': {b -> ale#Var(b, 'wgsl_naga_executable')}, +\ 'output_stream': 'stderr', +\ 'command': {b -> '%e --stdin-file-path %s'}, +\ 'callback': 'ale#handlers#naga#Handle', +\}) diff --git a/ale_linters/xhtml/alex.vim b/ale_linters/xhtml/alex.vim new file mode 100644 index 00000000..97f3b59a --- /dev/null +++ b/ale_linters/xhtml/alex.vim @@ -0,0 +1,4 @@ +" Author: Johannes Wienke +" Description: alex for XHTML files + +call ale#handlers#alex#DefineLinter('xhtml', '--text') diff --git a/ale_linters/xhtml/cspell.vim b/ale_linters/xhtml/cspell.vim new file mode 100644 index 00000000..c465b41b --- /dev/null +++ b/ale_linters/xhtml/cspell.vim @@ -0,0 +1,5 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: cspell support for XHTML files. + +call ale#handlers#cspell#DefineLinter('xhtml') diff --git a/ale_linters/xhtml/proselint.vim b/ale_linters/xhtml/proselint.vim new file mode 100644 index 00000000..dfad921f --- /dev/null +++ b/ale_linters/xhtml/proselint.vim @@ -0,0 +1,9 @@ +" Author: Daniel M. Capella https://github.com/polyzen +" Description: proselint for XHTML files + +call ale#linter#Define('xhtml', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/xhtml/writegood.vim b/ale_linters/xhtml/writegood.vim new file mode 100644 index 00000000..1fcba182 --- /dev/null +++ b/ale_linters/xhtml/writegood.vim @@ -0,0 +1,4 @@ +" Author: Sumner Evans +" Description: write-good for XHTML files + +call ale#handlers#writegood#DefineLinter('xhtml') diff --git a/ale_linters/xml/xmllint.vim b/ale_linters/xml/xmllint.vim new file mode 100644 index 00000000..553d0883 --- /dev/null +++ b/ale_linters/xml/xmllint.vim @@ -0,0 +1,65 @@ +" Author: q12321q +" Description: This file adds support for checking XML code with xmllint. + +" CLI options +let g:ale_xml_xmllint_executable = get(g:, 'ale_xml_xmllint_executable', 'xmllint') +let g:ale_xml_xmllint_options = get(g:, 'ale_xml_xmllint_options', '') + +function! ale_linters#xml#xmllint#GetCommand(buffer) abort + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'xml_xmllint_options')) + \ . ' --noout -' +endfunction + +function! ale_linters#xml#xmllint#Handle(buffer, lines) abort + " Matches patterns lines like the following: + " file/path:123: error level : error message + let l:pattern_message = '\v^([^:]+):(\d+):\s*(([^:]+)\s*:\s+.*)$' + + " parse column token line like that: + " file/path:123: parser error : Opening and ending tag mismatch: foo line 1 and bar + " + " ^ + let l:pattern_column_token = '\v^\s*\^$' + + let l:output = [] + + for l:line in a:lines + " Parse error/warning lines + 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] + + call add(l:output, { + \ 'lnum': l:line, + \ 'text': l:text, + \ 'type': l:type, + \}) + + 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]) + + continue + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('xml', { +\ 'name': 'xmllint', +\ 'output_stream': 'stderr', +\ '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/actionlint.vim b/ale_linters/yaml/actionlint.vim new file mode 100644 index 00000000..902da729 --- /dev/null +++ b/ale_linters/yaml/actionlint.vim @@ -0,0 +1,87 @@ +" Author: Peter Benjamin +" Description: Linter for GitHub Workflows + +call ale#Set('yaml_actionlint_executable', 'actionlint') +call ale#Set('yaml_actionlint_options', '') + +function! ale_linters#yaml#actionlint#GetCommand(buffer) abort + " Only execute actionlint on YAML files in /.github/ paths. + if expand('#' . a:buffer . ':p') !~# '\v[/\\]\.github[/\\]' + return '' + endif + + let l:options = ale#Var(a:buffer, 'yaml_actionlint_options') + + if l:options !~# '-no-color' + let l:options .= ale#Pad('-no-color') + endif + + if l:options !~# '-oneline' + let l:options .= ale#Pad('-oneline') + endif + + let l:configfile = ale_linters#yaml#actionlint#GitRepoHasConfig(a:buffer) + + if !empty(l:configfile) + let l:options .= ale#Pad('-config-file ' . l:configfile) + endif + + return '%e' . ale#Pad(l:options) . ' - ' +endfunction + +" If we have a actionlint.yml or actionlint.yaml in our github directory +" use that as our config file. +function! ale_linters#yaml#actionlint#GitRepoHasConfig(buffer) abort + let l:filename = expand('#' . a:buffer . ':p') + let l:configfilebase = substitute(l:filename, '\.github/.*', '.github/actionlint.','') + + for l:ext in ['yml', 'yaml'] + let l:configfile = l:configfilebase . l:ext + + if filereadable(l:configfile) + return l:configfile + endif + endfor + + return '' +endfunction + +function! ale_linters#yaml#actionlint#Handle(buffer, lines) abort + " Matches patterns line the following: + ".github/workflows/main.yml:19:0: could not parse as YAML: yaml: line 19: mapping values are not allowed in this context [yaml-syntax] + let l:pattern = '\v^.{-}:(\d+):(\d+): (.+) \[(.+)\]$' + let l:output = [] + + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[4] + let l:text = l:match[3] + + " Handle sub-linter errors like the following: + "validate.yml:19:9: shellcheck reported issue in this script: SC2086:info:1:15: Double quote to prevent globbing and word splitting [shellcheck] + if l:code is# 'shellcheck' + let l:shellcheck_match = matchlist(l:text, '\v^.+: (SC\d{4}):.+:\d+:\d+: (.+)$') + let l:text = l:shellcheck_match[2] + let l:code = 'shellcheck ' . l:shellcheck_match[1] + endif + + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:text, + \ 'code': l:code, + \ 'type': 'E', + \} + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('yaml', { +\ 'name': 'actionlint', +\ 'executable': {b -> ale#Var(b, 'yaml_actionlint_executable')}, +\ 'command': function('ale_linters#yaml#actionlint#GetCommand'), +\ 'callback': 'ale_linters#yaml#actionlint#Handle', +\}) diff --git a/ale_linters/yaml/circleci.vim b/ale_linters/yaml/circleci.vim new file mode 100644 index 00000000..20835454 --- /dev/null +++ b/ale_linters/yaml/circleci.vim @@ -0,0 +1,35 @@ +function! ale_linters#yaml#circleci#Handle(buffer, lines) abort + let l:match_index = -1 + let l:output = [] + + for l:index in range(len(a:lines)) + let l:line = a:lines[l:index] + + if l:line =~? 'Error: ERROR IN CONFIG FILE:' + let l:match_index = l:index + 1 + break + endif + endfor + + if l:match_index > 0 + return [{ + \ 'type': 'E', + \ 'lnum': 1, + \ 'text': a:lines[l:match_index], + \ 'detail': join(a:lines[l:match_index :], "\n"), + \}] + endif + + return [] +endfunction + +" The circleci validate requires network requests, so we'll only run it when +" files are saved to prevent the server from being hammered. +call ale#linter#Define('yaml', { +\ 'name': 'circleci', +\ 'executable': {b -> expand('#' . b . ':p') =~? '\.circleci' ? 'circleci' : ''}, +\ 'command': 'circleci --skip-update-check config validate - < %s', +\ 'callback': 'ale_linters#yaml#circleci#Handle', +\ 'output_stream': 'stderr', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/yaml/gitlablint.vim b/ale_linters/yaml/gitlablint.vim new file mode 100644 index 00000000..ec48115a --- /dev/null +++ b/ale_linters/yaml/gitlablint.vim @@ -0,0 +1,49 @@ +call ale#Set('yaml_gitlablint_executable', 'gll') +call ale#Set('yaml_gitlablint_options', '') + +function! ale_linters#yaml#gitlablint#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'yaml_gitlablint_options')) + \ . ' -p %t' +endfunction + +function! ale_linters#yaml#gitlablint#Handle(buffer, lines) abort + " Matches patterns line the following: + " (): mapping values are not allowed in this context at line 68 column 8 + " jobs:build:dev config contains unknown keys: ony + let l:pattern = '^\(.*\) at line \(\d\+\) column \(\d\+\)$' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if !empty(l:match) + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[1], + \ 'type': 'E', + \} + call add(l:output, l:item) + else + if l:line isnot# 'GitLab CI configuration is invalid' + let l:item = { + \ 'lnum': 0, + \ 'col': 0, + \ 'text': l:line, + \ 'type': 'E', + \} + call add(l:output, l:item) + endif + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('yaml', { +\ 'name': 'gitlablint', +\ 'executable': {b -> ale#Var(b, 'yaml_gitlablint_executable')}, +\ 'command': function('ale_linters#yaml#gitlablint#GetCommand'), +\ 'callback': 'ale_linters#yaml#gitlablint#Handle', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/yaml/ls.vim b/ale_linters/yaml/ls.vim new file mode 100644 index 00000000..79510ffe --- /dev/null +++ b/ale_linters/yaml/ls.vim @@ -0,0 +1,35 @@ +" Author: Jeffrey Lau - https://github.com/zoonfafer +" Description: YAML Language Server https://github.com/redhat-developer/yaml-language-server + +call ale#Set('yaml_ls_executable', 'yaml-language-server') +call ale#Set('yaml_ls_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('yaml_ls_config', {}) + +function! ale_linters#yaml#ls#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'yaml_ls', [ + \ 'node_modules/.bin/yaml-language-server', + \]) +endfunction + +function! ale_linters#yaml#ls#GetCommand(buffer) abort + let l:executable = ale_linters#yaml#ls#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' --stdio' +endfunction + +" Just use the current file +function! ale_linters#yaml#ls#FindProjectRoot(buffer) abort + let l:project_file = expand('#' . a:buffer . ':p') + + return fnamemodify(l:project_file, ':h') +endfunction + +call ale#linter#Define('yaml', { +\ 'name': 'yaml-language-server', +\ 'aliases': ['yamlls'], +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#yaml#ls#GetExecutable'), +\ 'command': function('ale_linters#yaml#ls#GetCommand'), +\ 'project_root': function('ale_linters#yaml#ls#FindProjectRoot'), +\ 'lsp_config': {b -> ale#Var(b, 'yaml_ls_config')}, +\}) diff --git a/ale_linters/yaml/spectral.vim b/ale_linters/yaml/spectral.vim new file mode 100644 index 00000000..13654f06 --- /dev/null +++ b/ale_linters/yaml/spectral.vim @@ -0,0 +1,14 @@ +" Author: t2h5 +" Description: Integration of Stoplight Spectral CLI with ALE. + +call ale#Set('yaml_spectral_executable', 'spectral') +call ale#Set('yaml_spectral_use_global', get(g:, 'ale_use_global_executables', 0)) + +call ale#linter#Define('yaml', { +\ 'name': 'spectral', +\ 'executable': {b -> ale#path#FindExecutable(b, 'yaml_spectral', [ +\ 'node_modules/.bin/spectral', +\ ])}, +\ 'command': '%e lint --ignore-unknown-format -q -f text %t', +\ 'callback': 'ale#handlers#spectral#HandleSpectralOutput' +\}) diff --git a/ale_linters/yaml/swaglint.vim b/ale_linters/yaml/swaglint.vim new file mode 100644 index 00000000..7fc2b430 --- /dev/null +++ b/ale_linters/yaml/swaglint.vim @@ -0,0 +1,40 @@ +" Author: Matthew Turland +" Description: This file adds support for linting Swagger / OpenAPI documents using swaglint + +call ale#Set('yaml_swaglint_executable', 'swaglint') +call ale#Set('yaml_swaglint_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale_linters#yaml#swaglint#Handle(buffer, lines) abort + let l:pattern = ': \([^\s]\+\) @ \(\d\+\):\(\d\+\) - \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:obj = { + \ 'type': l:match[1] is# 'error' ? 'E' : 'W', + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \} + + " Parse the code if it's there. + let l:code_match = matchlist(l:obj.text, '\v^(.+) \(([^ (]+)\)$') + + if !empty(l:code_match) + let l:obj.text = l:code_match[1] + let l:obj.code = l:code_match[2] + endif + + call add(l:output, l:obj) + endfor + + return l:output +endfunction + +call ale#linter#Define('yaml', { +\ 'name': 'swaglint', +\ 'executable': {b -> ale#path#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 new file mode 100644 index 00000000..39011df1 --- /dev/null +++ b/ale_linters/yaml/yamllint.vim @@ -0,0 +1,11 @@ +" Author: KabbAmine + +call ale#Set('yaml_yamllint_executable', 'yamllint') +call ale#Set('yaml_yamllint_options', '') + +call ale#linter#Define('yaml', { +\ 'name': 'yamllint', +\ 'executable': {b -> ale#Var(b, 'yaml_yamllint_executable')}, +\ 'command': function('ale#handlers#yamllint#GetCommand'), +\ 'callback': 'ale#handlers#yamllint#Handle', +\}) diff --git a/ale_linters/yaml/yq.vim b/ale_linters/yaml/yq.vim new file mode 100644 index 00000000..35aef654 --- /dev/null +++ b/ale_linters/yaml/yq.vim @@ -0,0 +1,22 @@ +" Author: axhav +call ale#Set('yaml_yq_executable', 'yq') +call ale#Set('yaml_yq_options', '') +call ale#Set('yaml_yq_filters', '.') + +" Matches patterns like the following: +let s:pattern = '^Error\:.* line \(\d\+\)\: \(.\+\)$' + +function! ale_linters#yaml#yq#Handle(buffer, lines) abort + return ale#util#MapMatches(a:lines, s:pattern, {match -> { + \ 'lnum': match[1] + 0, + \ 'text': match[2], + \}}) +endfunction + +call ale#linter#Define('yaml', { +\ 'name': 'yq', +\ 'executable': {b -> ale#Var(b, 'yaml_yq_executable')}, +\ 'output_stream': 'stderr', +\ 'command': '%e', +\ 'callback': 'ale_linters#yaml#yq#Handle', +\}) diff --git a/ale_linters/yang/yang_lsp.vim b/ale_linters/yang/yang_lsp.vim new file mode 100644 index 00000000..81fcaa0e --- /dev/null +++ b/ale_linters/yang/yang_lsp.vim @@ -0,0 +1,15 @@ +call ale#Set('yang_lsp_executable', 'yang-language-server') + +function! ale_linters#yang#yang_lsp#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile(a:buffer, 'yang.settings') + + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : '' +endfunction + +call ale#linter#Define('yang', { +\ 'name': 'yang_lsp', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'yang_lsp_executable')}, +\ 'project_root': function('ale_linters#yang#yang_lsp#GetProjectRoot'), +\ 'command': '%e', +\}) diff --git a/ale_linters/yara/yls.vim b/ale_linters/yara/yls.vim new file mode 100644 index 00000000..d8371a25 --- /dev/null +++ b/ale_linters/yara/yls.vim @@ -0,0 +1,18 @@ +" Author: TcM1911 +" Description: A language server for Yara. + +call ale#Set('yara_yls_executable', 'yls') + +function! ale_linters#yara#yls#FindProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:project_root) ? (ale#path#Upwards(l:project_root)[1]) : '' +endfunction + +call ale#linter#Define('yara', { +\ 'name': 'yls', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'yara_yls_executable')}, +\ 'command': '%e -v', +\ 'project_root': function('ale_linters#yara#yls#FindProjectRoot'), +\}) diff --git a/ale_linters/zeek/zeek.vim b/ale_linters/zeek/zeek.vim new file mode 100644 index 00000000..1c93094f --- /dev/null +++ b/ale_linters/zeek/zeek.vim @@ -0,0 +1,23 @@ +" Author: Benjamin Bannier +" Description: Support for checking Zeek files. +" +call ale#Set('zeek_zeek_executable', 'zeek') + +function! ale_linters#zeek#zeek#HandleErrors(buffer, lines) abort + let l:pattern = '\(error\|warning\) in \v.*, line (\d+): (.*)$' + + return map(ale#util#GetMatches(a:lines, l:pattern), "{ + \ 'lnum': str2nr(v:val[2]), + \ 'text': v:val[3], + \ 'type': (v:val[1] is# 'error') ? 'E': 'W', + \}") +endfunction + +call ale#linter#Define('zeek', { +\ 'name': 'zeek', +\ 'executable': {b -> ale#Var(b, 'zeek_zeek_executable')}, +\ 'output_stream': 'stderr', +\ 'command': {-> '%e --parse-only %s'}, +\ 'callback': 'ale_linters#zeek#zeek#HandleErrors', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/zig/zlint.vim b/ale_linters/zig/zlint.vim new file mode 100644 index 00000000..b7be1e22 --- /dev/null +++ b/ale_linters/zig/zlint.vim @@ -0,0 +1,30 @@ +" Author: Don Isaac +" Description: A linter for the Zig programming language + +call ale#Set('zig_zlint_executable', 'zlint') + +function! ale_linters#zig#zlint#Handle(buffer, lines) abort + " GitHub Actions format: ::severity file=file,line=line,col=col,title=code::message + let l:pattern = '::\([a-z]\+\) file=\([^,]\+\),line=\(\d\+\),col=\(\d\+\),title=\([^:]\+\)::\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'filename': l:match[2], + \ 'lnum': str2nr(l:match[3]), + \ 'col': str2nr(l:match[4]), + \ 'text': l:match[6], + \ 'type': l:match[1] =~? 'error\|fail' ? 'E' : 'W', + \ 'code': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('zig', { +\ 'name': 'zlint', +\ 'executable': {b -> ale#Var(b, "zig_zlint_executable")}, +\ 'command': '%e %s -f gh', +\ 'callback': 'ale_linters#zig#zlint#Handle', +\}) diff --git a/ale_linters/zig/zls.vim b/ale_linters/zig/zls.vim new file mode 100644 index 00000000..1390f6b1 --- /dev/null +++ b/ale_linters/zig/zls.vim @@ -0,0 +1,20 @@ +" Author: CherryMan +" Description: A language server for Zig + +call ale#Set('zig_zls_executable', 'zls') +call ale#Set('zig_zls_config', {}) + +function! ale_linters#zig#zls#GetProjectRoot(buffer) abort + let l:build_rs = ale#path#FindNearestFile(a:buffer, 'build.zig') + + return !empty(l:build_rs) ? fnamemodify(l:build_rs, ':h') : '' +endfunction + +call ale#linter#Define('zig', { +\ 'name': 'zls', +\ 'lsp': 'stdio', +\ 'lsp_config': {b -> ale#Var(b, 'zig_zls_config')}, +\ 'executable': {b -> ale#Var(b, 'zig_zls_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#zig#zls#GetProjectRoot'), +\}) diff --git a/autoload/ale.vim b/autoload/ale.vim new file mode 100644 index 00000000..2f46bce5 --- /dev/null +++ b/autoload/ale.vim @@ -0,0 +1,299 @@ +" Author: w0rp , David Alexander +" Description: Primary code path for the plugin +" Manages execution of linters when requested by autocommands + +" Strings used for severity in the echoed message +let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error') +let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info') +let g:ale_echo_msg_log_str = get(g:, 'ale_echo_msg_log_str', 'Log') +let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') + +" LSP window/showMessage format +let g:ale_lsp_show_message_format = get(g:, 'ale_lsp_show_message_format', '%severity%:%linter%: %s') +" Valid values mimic LSP definitions (error, warning and information; log is +" never shown) +let g:ale_lsp_show_message_severity = get(g:, 'ale_lsp_show_message_severity', 'error') + +let s:lint_timer = -1 +let s:getcmdwintype_exists = exists('*getcmdwintype') + +" Return 1 if a file is too large for ALE to handle. +function! ale#FileTooLarge(buffer) abort + let l:max = getbufvar(a:buffer, 'ale_maximum_file_size', get(g:, 'ale_maximum_file_size', 0)) + + return l:max > 0 ? (line2byte(line('$') + 1) > l:max) : 0 +endfunction + +" A function for checking various conditions whereby ALE just shouldn't +" attempt to do anything, say if particular buffer types are open in Vim. +function! ale#ShouldDoNothing(buffer) abort + " The checks are split into separate if statements to make it possible to + " profile each check individually with Vim's profiling tools. + " + " Do nothing if ALE is disabled. + if !getbufvar(a:buffer, 'ale_enabled', get(g:, 'ale_enabled', 0)) + return 1 + endif + + " Don't perform any checks when newer NeoVim versions are exiting. + if get(v:, 'exiting', v:null) isnot v:null + return 1 + endif + + let l:filetype = getbufvar(a:buffer, '&filetype') + + " Do nothing when there's no filetype. + if l:filetype is# '' + 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 + endif + + " Do nothing if running from command mode. + if s:getcmdwintype_exists && !empty(getcmdwintype()) + return 1 + endif + + let l:filename = fnamemodify(bufname(a:buffer), ':t') + + " Do nothing for directories. + if l:filename is# '.' + return 1 + endif + + " Don't start linting and so on when an operator is pending. + if ale#util#Mode(1) is# 'no' + return 1 + endif + + " Do nothing if running in the sandbox. + if ale#util#InSandbox() + return 1 + endif + + " Do nothing if the file is too large. + if ale#FileTooLarge(a:buffer) + return 1 + endif + + " Do nothing from CtrlP buffers with CtrlP-funky. + if exists(':CtrlPFunky') is 2 + \&& getbufvar(a:buffer, '&l:statusline') =~# 'CtrlPMode.*funky' + return 1 + endif + + return 0 +endfunction + +function! s:Lint(buffer, should_lint_file, timer_id) abort + " Use the filetype from the buffer + let l:filetype = getbufvar(a:buffer, '&filetype') + let l:linters = ale#linter#Get(l:filetype) + + let l:ignore_config = ale#Var(a:buffer, 'linters_ignore') + let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') + + " Load code to ignore linters only if we need to. + if ( + \ !empty(l:ignore_config) + \ || l:disable_lsp is 1 + \ || l:disable_lsp is v:true + \ || (l:disable_lsp is# 'auto' && get(g:, 'lspconfig', 0)) + \) + let l:linters = ale#engine#ignore#Exclude( + \ l:filetype, + \ l:linters, + \ l:ignore_config, + \ l:disable_lsp, + \) + endif + + " Tell other sources that they can start checking the buffer now. + let g:ale_want_results_buffer = a:buffer + silent doautocmd User ALEWantResults + unlet! g:ale_want_results_buffer + + " Don't set up buffer data and so on if there are no linters to run. + if !has_key(g:ale_buffer_info, a:buffer) && empty(l:linters) + return + endif + + " Clear lint_file linters, or only run them if the file exists. + let l:lint_file = empty(l:linters) + \ || (a:should_lint_file && filereadable(expand('#' . a:buffer . ':p'))) + + call ale#engine#RunLinters(a:buffer, l:linters, l:lint_file) +endfunction + +" (delay, [linting_flag, buffer_number]) +function! ale#Queue(delay, ...) abort + if a:0 > 2 + throw 'too many arguments!' + endif + + let l:buffer = get(a:000, 1, v:null) + + if l:buffer is v:null + let l:buffer = bufnr('') + endif + + if type(l:buffer) isnot v:t_number + throw 'buffer_number must be a Number' + endif + + if ale#ShouldDoNothing(l:buffer) + return + endif + + " Default linting_flag to '' + let l:should_lint_file = get(a:000, 0) is# 'lint_file' + + if s:lint_timer != -1 + call timer_stop(s:lint_timer) + let s:lint_timer = -1 + endif + + if a:delay > 0 + let s:lint_timer = timer_start( + \ a:delay, + \ function('s:Lint', [l:buffer, l:should_lint_file]) + \) + else + call s:Lint(l:buffer, l:should_lint_file, 0) + endif +endfunction + +let s:current_ale_version = [4, 0, 0] + +" A function used to check for ALE features in files outside of the project. +function! ale#Has(feature) abort + 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 +" buffer scope, then in global scope. If the name does not exist in the global +" scope, an exception will be thrown. +" +" Every variable name will be prefixed with 'ale_'. +function! ale#Var(buffer, variable_name) abort + let l:full_name = 'ale_' . a:variable_name + let l:vars = getbufvar(str2nr(a:buffer), '', {}) + + return get(l:vars, l:full_name, g:[l:full_name]) +endfunction + +" Initialize a variable with a default value, if it isn't already set. +" +" Every variable name will be prefixed with 'ale_'. +function! ale#Set(variable_name, default) abort + let l:full_name = 'ale_' . a:variable_name + + if !has_key(g:, l:full_name) + let g:[l:full_name] = a:default + endif +endfunction + +" Given a string for adding to a command, return the string padded with a +" space on the left if it is not empty. Otherwise return an empty string. +" +" This can be used for making command strings cleaner and easier to test. +function! ale#Pad(string) abort + return !empty(a:string) ? ' ' . a:string : '' +endfunction + +" Given a environment variable name and a value, produce part of a command for +" setting an environment variable before running a command. The syntax will be +" valid for cmd on Windows, or most shells on Unix. +function! ale#Env(variable_name, value) abort + if has('win32') + return 'set ' . ale#Escape(a:variable_name . '=' . a:value) . ' && ' + endif + + return a:variable_name . '=' . ale#Escape(a:value) . ' ' +endfunction + +" Escape a string suitably for each platform. +" shellescape does not work on Windows. +function! ale#Escape(str) abort + if fnamemodify(&shell, ':t') is? 'cmd.exe' + " If the string contains spaces, it will be surrounded by quotes. + " Otherwise, special characters will be escaped with carets (^). + return substitute( + \ a:str =~# ' ' + \ ? '"' . substitute(a:str, '"', '""', 'g') . '"' + \ : substitute(a:str, '\v([&|<>^])', '^\1', 'g'), + \ '%', + \ '%%', + \ 'g', + \) + endif + + return shellescape (a:str) +endfunction + +" Get the loclist item message according to a given format string. +" +" See `:help g:ale_loclist_msg_format` and `:help g:ale_echo_msg_format` +function! ale#GetLocItemMessage(item, format_string) abort + let l:msg = a:format_string + let l:severity = g:ale_echo_msg_warning_str + let l:code = get(a:item, 'code', '') + let l:type = get(a:item, 'type', 'E') + let l:linter_name = get(a:item, 'linter_name', '') + let l:code_repl = !empty(l:code) ? '\=submatch(1) . l:code . submatch(2)' : '' + + if l:type is# 'E' + let l:severity = g:ale_echo_msg_error_str + elseif l:type is# 'I' + let l:severity = g:ale_echo_msg_info_str + endif + + " Replace special markers with certain information. + " \=l:variable is used to avoid escaping issues. + let l:msg = substitute(l:msg, '\v\%([^\%]*)code([^\%]*)\%', l:code_repl, 'g') + let l:msg = substitute(l:msg, '\V%severity%', '\=l:severity', 'g') + let l:msg = substitute(l:msg, '\V%type%', '\=l:type', 'g') + let l:msg = substitute(l:msg, '\V%linter%', '\=l:linter_name', 'g') + " Replace %s with the text. + let l:msg = substitute(l:msg, '\V%s', '\=a:item.text', 'g') + " Windows may insert carriage return line endings (^M), strip these characters. + let l:msg = substitute(l:msg, '\r', '', 'g') + + return l:msg +endfunction + +" Given a buffer and a linter or fixer name, return an Array of two-item +" Arrays describing how to map filenames to and from the local to foreign file +" systems. +function! ale#GetFilenameMappings(buffer, name) abort + let l:linter_mappings = ale#Var(a:buffer, 'filename_mappings') + + if type(l:linter_mappings) is v:t_list + return l:linter_mappings + endif + + let l:name = a:name + + if !has_key(l:linter_mappings, l:name) + " Use * as a default setting for all tools. + let l:name = '*' + endif + + return get(l:linter_mappings, l:name, []) +endfunction diff --git a/autoload/ale/ant.vim b/autoload/ale/ant.vim new file mode 100644 index 00000000..b6d4217f --- /dev/null +++ b/autoload/ale/ant.vim @@ -0,0 +1,45 @@ +" Author: Andrew Lee . +" Inspired by ale/gradle.vim by Michael Pardo +" Description: Functions for working with Ant projects. + +" Given a buffer number, find an Ant project root +function! ale#ant#FindProjectRoot(buffer) abort + let l:build_xml_path = ale#path#FindNearestFile(a:buffer, 'build.xml') + + if !empty(l:build_xml_path) + return fnamemodify(l:build_xml_path, ':h') + endif + + return '' +endfunction + +" Given a buffer number, find the path to the `ant` executable. Returns an empty +" string if cannot find the executable. +function! ale#ant#FindExecutable(buffer) abort + if executable('ant') + return 'ant' + endif + + return '' +endfunction + +" Given a buffer number, get a working directory and command to print the +" classpath of the root project. +" +" Returns an empty string for the command if Ant is not detected. +function! ale#ant#BuildClasspathCommand(buffer) abort + let l:executable = ale#ant#FindExecutable(a:buffer) + + if !empty(l:executable) + let l:project_root = ale#ant#FindProjectRoot(a:buffer) + + if !empty(l:project_root) + return [ + \ l:project_root, + \ ale#Escape(l:executable) .' classpath -S -q' + \] + endif + endif + + return ['', ''] +endfunction 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 +" 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 new file mode 100644 index 00000000..c5157dba --- /dev/null +++ b/autoload/ale/assert.vim @@ -0,0 +1,427 @@ +let s:command_output = [] + +function! ale#assert#GivenCommandOutput(...) abort + let s:command_output = a:000 +endfunction + +function! s:GetLinter() abort + let l:linters = ale#linter#GetLintersLoaded() + let l:filetype_linters = get(values(l:linters), 0, []) + + if len(l:linters) is 0 || len(l:filetype_linters) is 0 + throw 'No linters were loaded' + endif + + if len(l:linters) > 1 || len(l:filetype_linters) > 1 + throw 'More than one linter was loaded' + endif + + 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 + +function! s:ProcessDeferredCwds(initial_command, initial_cwd) abort + let l:result = a:initial_command + let l:last_cwd = v:null + let l:command_index = 0 + let l:cwd_list = [] + + while ale#command#IsDeferred(l:result) + call add(l:cwd_list, l:result.cwd) + + 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:cwd_list, a:initial_cwd is v:null ? l:last_cwd : a:initial_cwd) + + return l:cwd_list +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 + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + let l:executable = ale#linter#GetExecutable(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 = 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 + \ [a:expected_executable, a:expected_command], + \ [l:executable, l:command] +endfunction + +function! ale#assert#LinterCwd(expected_cwd) abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + + let l:initial_cwd = ale#linter#GetCwd(l:buffer, l:linter) + call ale#command#SetCwd(l:buffer, l:initial_cwd) + + let l:cwd = s:ProcessDeferredCwds( + \ ale#linter#GetCommand(l:buffer, l:linter), + \ l:initial_cwd, + \) + + call ale#command#ResetCwd(l:buffer) + + if type(a:expected_cwd) isnot v:t_list + let l:cwd = l:cwd[-1] + endif + + AssertEqual a:expected_cwd, l:cwd +endfunction + +function! ale#assert#FixerCwd(expected_cwd) abort + let l:buffer = bufnr('') + let l:cwd = s:ProcessDeferredCwds(s:FixerFunction(l:buffer), v:null) + + if type(a:expected_cwd) isnot v:t_list + let l:cwd = l:cwd[-1] + endif + + AssertEqual a:expected_cwd, l:cwd +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#FixerNotExecuted() abort + let l:buffer = bufnr('') + let l:result = s:ProcessDeferredCommands(s:FixerFunction(l:buffer))[-1] + + Assert empty(l:result), "The fixer will be executed when it shouldn't be" +endfunction + +function! ale#assert#LinterNotExecuted() abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + let l:executable = ale#linter#GetExecutable(l:buffer, l:linter) + let l:executed = 1 + + if !empty(l:executable) + let l:command = ale#linter#GetCommand(l:buffer, l:linter) + + if type(l:command) is v:t_list + let l:command = l:command[-1] + endif + + let l:executed = !empty(l:command) + else + let l:executed = 0 + endif + + Assert !l:executed, "The linter will be executed when it shouldn't be" +endfunction + +function! ale#assert#LSPOptions(expected_options) abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + let l:initialization_options = ale#lsp_linter#GetOptions(l:buffer, l:linter) + + AssertEqual a:expected_options, l:initialization_options +endfunction + +function! ale#assert#LSPConfig(expected_config) abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + let l:config = ale#lsp_linter#GetConfig(l:buffer, l:linter) + + AssertEqual a:expected_config, l:config +endfunction + +function! ale#assert#LSPLanguage(expected_language) abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + let l:Language = l:linter.language + let l:language = type(l:Language) is v:t_func + \ ? l:Language(l:buffer) + \ : l:Language + + AssertEqual a:expected_language, l:language +endfunction + +function! ale#assert#LSPProject(expected_root) abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + let l:root = ale#lsp_linter#FindProjectRoot(l:buffer, l:linter) + + AssertEqual a:expected_root, l:root +endfunction + +function! ale#assert#LSPAddress(expected_address) abort + let l:buffer = bufnr('') + let l:linter = s:GetLinter() + 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() + command! -nargs=+ AssertLinterCwd :call ale#assert#LinterCwd() + command! -nargs=+ AssertLinter :call ale#assert#Linter() + command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted() + command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions() + command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig() + command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage() + command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject() + command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress() +endfunction + +function! ale#assert#SetUpFixerTestCommands() abort + command! -nargs=+ GivenCommandOutput :call ale#assert#GivenCommandOutput() + command! -nargs=+ AssertFixerCwd :call ale#assert#FixerCwd() + command! -nargs=+ AssertFixer :call ale#assert#Fixer() + command! -nargs=0 AssertFixerNotExecuted :call ale#assert#FixerNotExecuted() +endfunction + +function! ale#assert#ResetVariables(filetype, name, ...) abort + " If the suffix of the option names format is different, an additional + " argument can be used for that instead. + if a:0 > 1 + throw 'Too many arguments' + endif + + let l:option_suffix = get(a:000, 0, a:name) + let l:prefix = 'ale_' . a:filetype . '_' + \ . substitute(l:option_suffix, '-', '_', 'g') + let l:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix' + + " Save and clear linter variables. + " We'll load the runtime file to reset them to defaults. + for l:key in filter(keys(g:), l:filter_expr) + execute 'Save g:' . l:key + unlet g:[l:key] + endfor + + for l:key in filter(keys(b:), l:filter_expr) + unlet b:[l:key] + endfor +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. + let g:ale_create_dummy_temporary_file = 1 + + " Remove current linters. + call ale#linter#Reset() + call ale#linter#PreventLoading(a:filetype) + + Save g:ale_root + let g:ale_root = {} + + Save b:ale_root + unlet! b:ale_root + + call ale#assert#ResetVariables(a:filetype, a:name) + + Save g:ale_c_build_dir + unlet! g:ale_c_build_dir + unlet! b:ale_c_build_dir + + execute 'runtime ale_linters/' . a:filetype . '/' . a:name . '.vim' + + if !exists('g:dir') + call ale#test#SetDirectory('/testplugin/test/linter') + endif + + 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 + 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(':AssertLinterCwd') + delcommand AssertLinterCwd + endif + + if exists(':AssertLinter') + delcommand AssertLinter + endif + + if exists(':AssertLinterNotExecuted') + delcommand AssertLinterNotExecuted + endif + + if exists(':AssertLSPOptions') + delcommand AssertLSPOptions + endif + + if exists(':AssertLSPConfig') + delcommand AssertLSPConfig + endif + + if exists(':AssertLSPLanguage') + delcommand AssertLSPLanguage + endif + + if exists(':AssertLSPProject') + delcommand AssertLSPProject + endif + + if exists(':AssertLSPAddress') + delcommand AssertLSPAddress + endif + + if exists('g:dir') + call ale#test#RestoreDirectory() + endif + + Restore + + call ale#linter#Reset() + + if exists('*ale#semver#ResetVersionCache') + call ale#semver#ResetVersionCache() + endif +endfunction + +function! ale#assert#SetUpFixerTest(filetype, name, ...) abort + " If the suffix of the option names format is different, an additional + " argument can be used for that instead. + if a:0 > 1 + throw 'Too many arguments' + endif + + " 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:option_suffix = get(a:000, 0, a:name) + call ale#assert#ResetVariables(a:filetype, a:name, l:option_suffix) + + execute 'runtime autoload/ale/fixers/' . substitute(a:name, '-', '_', 'g') . '.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(':AssertFixerCwd') + delcommand AssertFixerCwd + endif + + if exists(':AssertFixer') + delcommand AssertFixer + endif + + if exists(':AssertFixerNotExecuted') + delcommand AssertFixerNotExecuted + endif +endfunction diff --git a/autoload/ale/balloon.vim b/autoload/ale/balloon.vim new file mode 100644 index 00000000..8678376f --- /dev/null +++ b/autoload/ale/balloon.vim @@ -0,0 +1,74 @@ +" Author: w0rp +" Description: balloonexpr support for ALE. + +function! ale#balloon#MessageForPos(bufnr, lnum, col) abort + let l:set_balloons = ale#Var(a:bufnr, 'set_balloons') + let l:show_problems = 0 + let l:show_hover = 0 + + if l:set_balloons is 1 + let l:show_problems = 1 + let l:show_hover = 1 + elseif l:set_balloons is# 'hover' + let l:show_hover = 1 + endif + + " Don't show balloons if they are disabled, or linting is disabled. + if !(l:show_problems || l:show_hover) + \|| !g:ale_enabled + \|| !getbufvar(a:bufnr, 'ale_enabled', 1) + return '' + endif + + if l:show_problems + let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist + let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col) + endif + + " Show the diagnostics message if found, 'Hover' output otherwise + if l:show_problems && l:index >= 0 + return l:loclist[l:index].text + elseif l:show_hover && ( + \ exists('*balloon_show') + \ || getbufvar( + \ a:bufnr, + \ 'ale_set_balloons_legacy_echo', + \ get(g:, 'ale_set_balloons_legacy_echo', 0) + \ ) + \) + " Request LSP/tsserver hover information, but only if this version of + " Vim supports the balloon_show function, or if we turned a legacy + " setting on. + call ale#hover#Show(a:bufnr, a:lnum, a:col, {'called_from_balloonexpr': 1}) + endif + + return '' +endfunction + +function! ale#balloon#Expr() abort + return ale#balloon#MessageForPos(v:beval_bufnr, v:beval_lnum, v:beval_col) +endfunction + +function! ale#balloon#Disable() abort + if has('balloon_eval') + set noballooneval + set balloonexpr= + endif + + if has('balloon_eval_term') + set noballoonevalterm + set balloonexpr= + endif +endfunction + +function! ale#balloon#Enable() abort + if has('balloon_eval') + set ballooneval + set balloonexpr=ale#balloon#Expr() + endif + + if has('balloon_eval_term') + set balloonevalterm + set balloonexpr=ale#balloon#Expr() + endif +endfunction diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim new file mode 100644 index 00000000..4a22c6fe --- /dev/null +++ b/autoload/ale/c.vim @@ -0,0 +1,622 @@ +" Author: gagbo , w0rp , roel0 +" Description: Functions for integrating with C-family linters. + +call ale#Set('c_parse_makefile', 0) +call ale#Set('c_always_make', has('unix') && !has('macunix')) +call ale#Set('c_parse_compile_commands', 1) + +let s:sep = has('win32') ? '\' : '/' + +" Set just so tests can override it. +let g:__ale_c_project_filenames = ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt'] + +let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [ +\ 'build', +\ 'bin', +\]) + +function! s:CanParseMakefile(buffer) abort + " Something somewhere seems to delete this setting in tests, so ensure we + " always have a default value. + call ale#Set('c_parse_makefile', 0) + + return ale#Var(a:buffer, 'c_parse_makefile') +endfunction + +function! ale#c#GetBuildDirectory(buffer) abort + let l:build_dir = ale#Var(a:buffer, 'c_build_dir') + + " c_build_dir has the priority if defined + if !empty(l:build_dir) + return l:build_dir + endif + + let [l:root, l:json_file] = ale#c#FindCompileCommands(a:buffer) + + return ale#path#Dirname(l:json_file) +endfunction + +function! ale#c#ShellSplit(line) abort + let l:stack = [] + let l:args = [''] + let l:prev = '' + + for l:char in split(a:line, '\zs') + if l:char is# '''' + if len(l:stack) > 0 && get(l:stack, -1) is# '''' + call remove(l:stack, -1) + elseif (len(l:stack) == 0 || get(l:stack, -1) isnot# '"') && l:prev isnot# '\' + call add(l:stack, l:char) + endif + elseif (l:char is# '"' || l:char is# '`') && l:prev isnot# '\' + if len(l:stack) > 0 && get(l:stack, -1) is# l:char + call remove(l:stack, -1) + elseif len(l:stack) == 0 || get(l:stack, -1) isnot# '''' + call add(l:stack, l:char) + endif + elseif (l:char is# '(' || l:char is# '[' || l:char is# '{') && l:prev isnot# '\' + if len(l:stack) == 0 || get(l:stack, -1) isnot# '''' + call add(l:stack, l:char) + endif + elseif (l:char is# ')' || l:char is# ']' || l:char is# '}') && l:prev isnot# '\' + if len(l:stack) > 0 && get(l:stack, -1) is# {')': '(', ']': '[', '}': '{'}[l:char] + call remove(l:stack, -1) + endif + elseif l:char is# ' ' && len(l:stack) == 0 + if len(get(l:args, -1)) > 0 + call add(l:args, '') + endif + + continue + endif + + let l:args[-1] = get(l:args, -1) . l:char + endfor + + return l:args +endfunction + +" Takes the path prefix and a list of cflags and expands @file arguments to +" the contents of the file. +" +" @file arguments are command line arguments recognised by gcc and clang. For +" instance, if @./path/to/file was given to gcc, it would load .path/to/file +" and use the contents of that file as arguments. +function! ale#c#ExpandAtArgs(path_prefix, raw_split_lines) abort + let l:out_lines = [] + + for l:option in a:raw_split_lines + if stridx(l:option, '@') == 0 + " This is an argument specifying a location of a file containing other arguments + let l:path = join(split(l:option, '\zs')[1:], '') + + " Make path absolute + if !ale#path#IsAbsolute(l:path) + let l:rel_path = substitute(l:path, '"', '', 'g') + let l:rel_path = substitute(l:rel_path, '''', '', 'g') + let l:path = ale#path#GetAbsPath(a:path_prefix, l:rel_path) + endif + + " Read the file and add all the arguments + try + let l:additional_args = readfile(l:path) + catch + continue " All we can really do is skip this argument + endtry + + let l:file_lines = [] + + for l:line in l:additional_args + let l:file_lines += ale#c#ShellSplit(l:line) + endfor + + " @file arguments can include other @file arguments, so we must + " recurse. + let l:out_lines += ale#c#ExpandAtArgs(a:path_prefix, l:file_lines) + else + " This is not an @file argument, so don't touch it. + let l:out_lines += [l:option] + endif + endfor + + return l:out_lines +endfunction + +" Quote C/C++ a compiler argument, if needed. +" +" Quoting arguments might cause issues with some systems/compilers, so we only +" quote them if we need to. +function! ale#c#QuoteArg(arg) abort + if a:arg !~# '\v[#$&*()\\|[\]{};''"<>/?! ^%]' + return a:arg + endif + + return ale#Escape(a:arg) +endfunction + +function! ale#c#ParseCFlags(path_prefix, should_quote, raw_arguments) abort + " Expand @file arguments now before parsing + let l:arguments = ale#c#ExpandAtArgs(a:path_prefix, a:raw_arguments) + " A list of [already_quoted, argument] + let l:items = [] + let l:option_index = 0 + + while l:option_index < len(l:arguments) + let l:option = l:arguments[l:option_index] + let l:option_index = l:option_index + 1 + + " Include options, that may need relative path fix + if stridx(l:option, '-I') == 0 + \ || stridx(l:option, '-iquote') == 0 + \ || stridx(l:option, '-isystem') == 0 + \ || stridx(l:option, '-idirafter') == 0 + \ || stridx(l:option, '-iframework') == 0 + if stridx(l:option, '-I') == 0 && l:option isnot# '-I' + let l:arg = join(split(l:option, '\zs')[2:], '') + let l:option = '-I' + else + let l:arg = l:arguments[l:option_index] + let l:option_index = l:option_index + 1 + endif + + " Fix relative paths if needed + if !ale#path#IsAbsolute(l:arg) + let l:rel_path = substitute(l:arg, '"', '', 'g') + let l:rel_path = substitute(l:rel_path, '''', '', 'g') + let l:arg = ale#path#GetAbsPath(a:path_prefix, l:rel_path) + endif + + call add(l:items, [1, l:option]) + call add(l:items, [1, ale#Escape(l:arg)]) + " Options with arg that can be grouped with the option or separate + elseif stridx(l:option, '-D') == 0 || stridx(l:option, '-B') == 0 + if l:option is# '-D' || l:option is# '-B' + call add(l:items, [1, l:option]) + call add(l:items, [0, l:arguments[l:option_index]]) + let l:option_index = l:option_index + 1 + else + call add(l:items, [0, l:option]) + endif + " Options that have an argument (always separate) + elseif l:option is# '-iprefix' || stridx(l:option, '-iwithprefix') == 0 + \ || l:option is# '-isysroot' || l:option is# '-imultilib' + \ || l:option is# '-include' || l:option is# '-imacros' + call add(l:items, [0, l:option]) + call add(l:items, [0, l:arguments[l:option_index]]) + let l:option_index = l:option_index + 1 + " Options without argument + elseif (stridx(l:option, '-W') == 0 && stridx(l:option, '-Wa,') != 0 && stridx(l:option, '-Wl,') != 0 && stridx(l:option, '-Wp,') != 0) + \ || l:option is# '-w' || stridx(l:option, '-pedantic') == 0 + \ || l:option is# '-ansi' || stridx(l:option, '-std=') == 0 + \ || stridx(l:option, '-f') == 0 && l:option !~# '\v^-f(dump|diagnostics|no-show-column|stack-usage)' + \ || stridx(l:option, '-O') == 0 + \ || l:option is# '-C' || l:option is# '-CC' || l:option is# '-trigraphs' + \ || stridx(l:option, '-nostdinc') == 0 || stridx(l:option, '-iplugindir=') == 0 + \ || stridx(l:option, '--sysroot=') == 0 || l:option is# '--no-sysroot-suffix' + \ || stridx(l:option, '-m') == 0 + call add(l:items, [0, l:option]) + endif + endwhile + + if a:should_quote + " Quote C arguments that haven't already been quoted above. + " If and only if we've been asked to quote them. + call map(l:items, 'v:val[0] ? v:val[1] : ale#c#QuoteArg(v:val[1])') + else + call map(l:items, 'v:val[1]') + endif + + return join(l:items, ' ') +endfunction + +function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort + if !s:CanParseMakefile(a:buffer) + return v:null + endif + + let l:buffer_filename = expand('#' . a:buffer . ':t') + let l:cflag_line = '' + + " Find a line matching this buffer's filename in the make output. + for l:line in a:make_output + if stridx(l:line, l:buffer_filename) >= 0 + let l:cflag_line = l:line + break + endif + endfor + + let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile') + let l:makefile_dir = fnamemodify(l:makefile_path, ':p:h') + + return ale#c#ParseCFlags(l:makefile_dir, 0, ale#c#ShellSplit(l:cflag_line)) +endfunction + +" Given a buffer number, find the project directory containing +" compile_commands.json, and the path to the compile_commands.json file. +" +" If compile_commands.json cannot be found, two empty strings will be +" returned. +function! ale#c#FindCompileCommands(buffer) abort + " Look above the current source file to find compile_commands.json + let l:json_file = ale#path#FindNearestFile(a:buffer, 'compile_commands.json') + + if !empty(l:json_file) + return [fnamemodify(l:json_file, ':h'), l:json_file] + endif + + " Search in build directories if we can't find it in the project. + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + for l:dirname in ale#Var(a:buffer, 'c_build_dir_names') + let l:c_build_dir = l:path . s:sep . l:dirname + let l:json_file = l:c_build_dir . s:sep . 'compile_commands.json' + + if filereadable(l:json_file) + return [l:path, l:json_file] + endif + endfor + endfor + + return ['', ''] +endfunction + +" Find the project root for C/C++ projects. +" +" The location of compile_commands.json will be used to find project roots. +" +" If compile_commands.json cannot be found, other common configuration files +" will be used to detect the project root. +function! ale#c#FindProjectRoot(buffer) abort + let [l:root, l:json_file] = ale#c#FindCompileCommands(a:buffer) + + " Fall back on detecting the project root based on other filenames. + if empty(l:root) + for l:project_filename in g:__ale_c_project_filenames + let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename) + + if !empty(l:full_path) + let l:path = fnamemodify(l:full_path, ':h') + + " Correct .git path detection. + if fnamemodify(l:path, ':t') is# '.git' + let l:path = fnamemodify(l:path, ':h') + endif + + return l:path + endif + endfor + endif + + return l:root +endfunction + +" Cache compile_commands.json data in a Dictionary, so we don't need to read +" the same files over and over again. The key in the dictionary will include +" the last modified time of the file. +if !exists('s:compile_commands_cache') + let s:compile_commands_cache = {} +endif + +function! ale#c#ResetCompileCommandsCache() abort + let s:compile_commands_cache = {} +endfunction + +function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort + let l:empty = [{}, {}] + + if empty(a:compile_commands_file) + return l:empty + endif + + let l:time = getftime(a:compile_commands_file) + + if l:time < 0 + return l:empty + endif + + let l:key = a:compile_commands_file . ':' . l:time + + if has_key(s:compile_commands_cache, l:key) + return s:compile_commands_cache[l:key] + endif + + let l:raw_data = [] + silent! let l:raw_data = json_decode(join(readfile(a:compile_commands_file), '')) + + if type(l:raw_data) isnot v:t_list + let l:raw_data = [] + endif + + let l:file_lookup = {} + let l:dir_lookup = {} + + for l:entry in (type(l:raw_data) is v:t_list ? l:raw_data : []) + let l:filename = ale#path#GetAbsPath(l:entry.directory, l:entry.file) + + " Store a key for lookups by the absolute path to the filename. + let l:file_lookup[l:filename] = get(l:file_lookup, l:filename, []) + [l:entry] + + " Store a key for fuzzy lookups by the absolute path to the directory. + let l:dirname = fnamemodify(l:filename, ':h') + let l:dir_lookup[l:dirname] = get(l:dir_lookup, l:dirname, []) + [l:entry] + + " Store a key for fuzzy lookups by just the basename of the file. + let l:basename = tolower(fnamemodify(l:entry.file, ':t')) + let l:file_lookup[l:basename] = get(l:file_lookup, l:basename, []) + [l:entry] + + " Store a key for fuzzy lookups by just the basename of the directory. + 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 + + 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 l:empty +endfunction + +" Get [should_quote, arguments] from either 'command' or 'arguments' +" 'arguments' should be quoted later, the split 'command' strings should not. +function! s:GetArguments(json_item) abort + if has_key(a:json_item, 'arguments') + return [1, a:json_item.arguments] + elseif has_key(a:json_item, 'command') + return [0, ale#c#ShellSplit(a:json_item.command)] + endif + + return [0, []] +endfunction + +function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort + let l:buffer_filename = ale#path#Simplify(expand('#' . a:buffer . ':p')) + let l:basename = tolower(fnamemodify(l:buffer_filename, ':t')) + " Look for any file in the same directory if we can't find an exact match. + let l:dir = fnamemodify(l:buffer_filename, ':h') + + " Search for an exact file match first. + let l:file_list = get(a:file_lookup, l:buffer_filename, []) + + " We may have to look for /foo/bar instead of C:\foo\bar + if empty(l:file_list) && has('win32') + let l:file_list = get( + \ a:file_lookup, + \ ale#path#RemoveDriveLetter(l:buffer_filename), + \ [] + \) + endif + + " Try the absolute path to the directory second. + let l:dir_list = get(a:dir_lookup, l:dir, []) + + if empty(l:dir_list) && has('win32') + let l:dir_list = get( + \ a:dir_lookup, + \ ale#path#RemoveDriveLetter(l:dir), + \ [] + \) + endif + + if empty(l:file_list) && empty(l:dir_list) + " If we can't find matches with the path to the file, try a + " case-insensitive match for any similarly-named file. + let l:file_list = get(a:file_lookup, l:basename, []) + + " If we can't find matches with the path to the directory, try a + " case-insensitive match for anything in similarly-named directory. + let l:dir_list = get(a:dir_lookup, tolower(fnamemodify(l:dir, ':t')), []) + endif + + " 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'] + " Try to find a source file by an absolute path first. + let l:key = fnamemodify(l:buffer_filename, ':r') . l:suffix + let l:file_list = get(a:file_lookup, l:key, []) + + if empty(l:file_list) && has('win32') + let l:file_list = get( + \ a:file_lookup, + \ ale#path#RemoveDriveLetter(l:key), + \ [] + \) + endif + + if empty(l:file_list) + " Look fuzzy matches on the basename second. + let l:key = fnamemodify(l:basename, ':r') . l:suffix + let l:file_list = get(a:file_lookup, l:key, []) + endif + + if !empty(l:file_list) + let l:source_file = l:key + break + endif + endfor + endif + + for l:item in l:file_list + let l:filename = ale#path#GetAbsPath(l:item.directory, l:item.file) + + " Load the flags for this file, or for a source file matching the + " header file. + if ( + \ bufnr(l:filename) is a:buffer + \ || ( + \ !empty(l:source_file) + \ && l:filename[-len(l:source_file):] is? l:source_file + \ ) + \) + let [l:should_quote, l:args] = s:GetArguments(l:item) + + return ale#c#ParseCFlags(l:item.directory, l:should_quote, l:args) + endif + endfor + + for l:item in l:dir_list + let l:filename = ale#path#GetAbsPath(l:item.directory, l:item.file) + + if ale#path#RemoveDriveLetter(fnamemodify(l:filename, ':h')) + \ is? ale#path#RemoveDriveLetter(l:dir) + let [l:should_quote, l:args] = s:GetArguments(l:item) + + return ale#c#ParseCFlags(l:item.directory, l:should_quote, l:args) + endif + endfor + + return '' +endfunction + +function! ale#c#FlagsFromCompileCommands(buffer, compile_commands_file) abort + 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:file_lookup, l:dir_lookup) +endfunction + +function! ale#c#GetCFlags(buffer, output) abort + let l:cflags = v:null + + if ale#Var(a:buffer, 'c_parse_compile_commands') + let [l:root, l:json_file] = ale#c#FindCompileCommands(a:buffer) + + if !empty(l:json_file) + let l:cflags = ale#c#FlagsFromCompileCommands(a:buffer, l:json_file) + endif + endif + + if empty(l:cflags) && s:CanParseMakefile(a:buffer) && !empty(a:output) + let l:cflags = ale#c#ParseCFlagsFromMakeOutput(a:buffer, a:output) + endif + + if l:cflags is v:null + let l:cflags = ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer)) + endif + + return l:cflags isnot v:null ? l:cflags : '' +endfunction + +function! ale#c#GetMakeCommand(buffer) abort + if s:CanParseMakefile(a:buffer) + let l:path = ale#path#FindNearestFile(a:buffer, 'Makefile') + + if empty(l:path) + let l:path = ale#path#FindNearestFile(a:buffer, 'GNUmakefile') + endif + + if !empty(l:path) + let l:always_make = ale#Var(a:buffer, 'c_always_make') + + return [ + \ fnamemodify(l:path, ':h'), + \ 'make -n' . (l:always_make ? ' --always-make' : ''), + \] + endif + endif + + return ['', ''] +endfunction + +function! ale#c#RunMakeCommand(buffer, Callback) abort + let [l:cwd, 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)}, + \ {'cwd': l:cwd}, + \) +endfunction + +" Given a buffer number, search for a project root, and output a List +" of directories to include based on some heuristics. +" +" For projects with headers in the project root, the project root will +" be returned. +" +" For projects with an 'include' directory, that directory will be returned. +function! ale#c#FindLocalHeaderPaths(buffer) abort + let l:project_root = ale#c#FindProjectRoot(a:buffer) + + if empty(l:project_root) + return [] + endif + + " See if we can find .h files directory in the project root. + " If we can, that's our include directory. + if !empty(globpath(l:project_root, '*.h', 0)) + return [l:project_root] + endif + + " Look for .hpp files too. + if !empty(globpath(l:project_root, '*.hpp', 0)) + return [l:project_root] + endif + + " If we find an 'include' directory in the project root, then use that. + if isdirectory(l:project_root . '/include') + return [ale#path#Simplify(l:project_root . s:sep . 'include')] + endif + + return [] +endfunction + +" Given a List of include paths, create a string containing the -I include +" options for those paths, with the paths escaped for use in the shell. +function! ale#c#IncludeOptions(include_paths) abort + let l:option_list = [] + + for l:path in a:include_paths + call add(l:option_list, '-I' . ale#Escape(l:path)) + endfor + + if empty(l:option_list) + return '' + endif + + return join(l:option_list) +endfunction + +" Get the language flag depending on on the executable, options and +" file extension +function! ale#c#GetLanguageFlag( +\ buffer, +\ executable, +\ use_header_lang_flag, +\ header_exts, +\ linter_lang_flag +\) abort + " Use only '-header' if the executable is 'clang' by default + if a:use_header_lang_flag == -1 + let l:use_header_lang_flag = a:executable =~# 'clang' + else + let l:use_header_lang_flag = a:use_header_lang_flag + endif + + " If we don't use the header language flag, return the default linter + " language flag + if !l:use_header_lang_flag + return a:linter_lang_flag + endif + + " Get the buffer file extension + let l:buf_ext = expand('#' . a:buffer . ':e') + + " If the buffer file is an header according to its extension, use + " the linter language flag + '-header', ex: 'c-header' + if index(a:header_exts, l:buf_ext) >= 0 + return a:linter_lang_flag . '-header' + endif + + " Else, use the default linter language flag + return a:linter_lang_flag +endfunction diff --git a/autoload/ale/code_action.vim b/autoload/ale/code_action.vim new file mode 100644 index 00000000..4167e907 --- /dev/null +++ b/autoload/ale/code_action.vim @@ -0,0 +1,379 @@ +" Author: Jerko Steiner +" Description: Code action support for LSP / tsserver + +function! ale#code_action#ReloadBuffer() abort + let l:buffer = bufnr('') + + execute 'augroup ALECodeActionReloadGroup' . l:buffer + autocmd! + augroup END + + silent! execute 'augroup! ALECodeActionReloadGroup' . l:buffer + + call ale#util#Execute(':e!') +endfunction + +function! ale#code_action#HandleCodeAction(code_action, options) abort + let l:current_buffer = bufnr('') + let l:changes = a:code_action.changes + + for l:file_code_edit in l:changes + call ale#code_action#ApplyChanges( + \ l:file_code_edit.fileName, + \ l:file_code_edit.textChanges, + \ a:options, + \) + endfor +endfunction + +function! s:ChangeCmp(left, right) abort + if a:left.start.line < a:right.start.line + return -1 + endif + + if a:left.start.line > a:right.start.line + return 1 + endif + + if a:left.start.offset < a:right.start.offset + return -1 + endif + + if a:left.start.offset > a:right.start.offset + return 1 + endif + + if a:left.end.line < a:right.end.line + return -1 + endif + + if a:left.end.line > a:right.end.line + return 1 + endif + + if a:left.end.offset < a:right.end.offset + return -1 + endif + + if a:left.end.offset > a:right.end.offset + return 1 + endif + + return 0 +endfunction + +function! ale#code_action#ApplyChanges(filename, changes, options) abort + let l:should_save = get(a:options, 'should_save') + let l:conn_id = get(a:options, 'conn_id') + + let l:orig_buffer = bufnr('') + + " The buffer is used to determine the fileformat, if available. + let l:buffer = bufnr(a:filename) + + if l:buffer != l:orig_buffer + call ale#util#Execute('silent edit ' . a:filename) + let l:buffer = bufnr('') + endif + + let l:lines = getbufline(l:buffer, 1, '$') + + " Add empty line if there's trailing newline, like readfile() does. + if getbufvar(l:buffer, '&eol') + let l:lines += [''] + endif + + let l:pos = getpos('.')[1:2] + + " Changes have to be sorted so we apply them from bottom-to-top + for l:code_edit in reverse(sort(copy(a:changes), function('s:ChangeCmp'))) + let l:line = l:code_edit.start.line + let l:column = l:code_edit.start.offset + let l:end_line = l:code_edit.end.line + let l:end_column = l:code_edit.end.offset + let l:text = l:code_edit.newText + + let l:insertions = split(l:text, '\n', 1) + + " Fix invalid columns + let l:column = l:column > 0 ? l:column : 1 + let l:end_column = l:end_column > 0 ? l:end_column : 1 + + " Clamp start to BOF + if l:line < 1 + let [l:line, l:column] = [1, 1] + endif + + " Clamp start to EOF + if l:line > len(l:lines) || l:line == len(l:lines) && l:column > len(l:lines[-1]) + 1 + let [l:line, l:column] = [len(l:lines), len(l:lines[-1]) + 1] + " Special case when start is after EOL + elseif l:line < len(l:lines) && l:column > len(l:lines[l:line - 1]) + 1 + let [l:line, l:column] = [l:line + 1, 1] + endif + + " Adjust end: clamp if invalid and/or adjust if we moved start + if l:end_line < l:line || l:end_line == l:line && l:end_column < l:column + let [l:end_line, l:end_column] = [l:line, l:column] + endif + + " Clamp end to EOF + if l:end_line > len(l:lines) || l:end_line == len(l:lines) && l:end_column > len(l:lines[-1]) + 1 + let [l:end_line, l:end_column] = [len(l:lines), len(l:lines[-1]) + 1] + " Special case when end is after EOL + elseif l:end_line < len(l:lines) && l:end_column > len(l:lines[l:end_line - 1]) + 1 + let [l:end_line, l:end_column] = [l:end_line + 1, 1] + endif + + " Careful, [:-1] is not an empty list + let l:start = l:line is 1 ? [] : l:lines[: l:line - 2] + let l:middle = l:column is 1 ? [''] : [l:lines[l:line - 1][: l:column - 2]] + + let l:middle[-1] .= l:insertions[0] + let l:middle += l:insertions[1:] + let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :] + + let l:end_line_len = len(l:lines[l:end_line - 1]) + let l:lines_before_change = len(l:lines) + let l:lines = l:start + l:middle + l:lines[l:end_line :] + + let l:current_line_offset = len(l:lines) - l:lines_before_change + let l:column_offset = len(l:middle[-1]) - l:end_line_len + + " Keep cursor where it was (if outside of changes) or move it after + " the changed text (if inside), but don't touch it when the change + " spans the entire buffer, in which case we have no clue and it's + " better to not do anything. + if l:line isnot 1 || l:column isnot 1 + \|| l:end_line < l:lines_before_change + \|| l:end_line == l:lines_before_change && l:end_column <= l:end_line_len + let l:pos = s:UpdateCursor(l:pos, + \ [l:line, l:column], + \ [l:end_line, l:end_column], + \ [l:current_line_offset, l:column_offset]) + endif + endfor + + " Make sure to add a trailing newline if and only if it should be added. + if l:lines[-1] is# '' && getbufvar(l:buffer, '&eol') + call remove(l:lines, -1) + else + call setbufvar(l:buffer, '&eol', 0) + endif + + call ale#util#SetBufferContents(l:buffer, l:lines) + + call ale#lsp#NotifyForChanges(l:conn_id, l:buffer) + + if l:should_save + call ale#util#Execute('silent w!') + endif + + call setpos('.', [0, l:pos[0], l:pos[1], 0]) + + if l:orig_buffer != l:buffer && bufexists(l:orig_buffer) + call ale#util#Execute('silent buf ' . string(l:orig_buffer)) + endif +endfunction + +function! s:UpdateCursor(cursor, start, end, offset) abort + let l:cur_line = a:cursor[0] + let l:cur_column = a:cursor[1] + let l:line = a:start[0] + let l:column = a:start[1] + let l:end_line = a:end[0] + let l:end_column = a:end[1] + let l:line_offset = a:offset[0] + let l:column_offset = a:offset[1] + + if l:end_line < l:cur_line + " both start and end lines are before the cursor. only line offset + " needs to be updated + let l:cur_line += l:line_offset + elseif l:end_line == l:cur_line + " end line is at the same location as cursor, which means + " l:line <= l:cur_line + if l:line < l:cur_line || l:column <= l:cur_column + " updates are happening either before or around the cursor + if l:end_column < l:cur_column + " updates are happening before the cursor, update the + " column offset for cursor + let l:cur_line += l:line_offset + let l:cur_column += l:column_offset + else + " updates are happening around the cursor, move the cursor + " to the end of the changes + let l:cur_line += l:line_offset + let l:cur_column = l:end_column + l:column_offset + endif + " else is not necessary, it means modifications are happening + " after the cursor so no cursor updates need to be done + endif + else + " end line is after the cursor + if l:line < l:cur_line || l:line == l:cur_line && l:column <= l:cur_column + " changes are happening around the cursor, move the cursor + " to the end of the changes + let l:cur_line = l:end_line + l:line_offset + let l:cur_column = l:end_column + l:column_offset + " else is not necessary, it means modifications are happening + " after the cursor so no cursor updates need to be done + endif + endif + + return [l:cur_line, l:cur_column] +endfunction + +function! ale#code_action#GetChanges(workspace_edit) abort + if a:workspace_edit is v:null + return {} + endif + + let l:changes = {} + + if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes) + return a:workspace_edit.changes + elseif has_key(a:workspace_edit, 'documentChanges') + let l:document_changes = [] + + if type(a:workspace_edit.documentChanges) is v:t_dict + \ && has_key(a:workspace_edit.documentChanges, 'edits') + call add(l:document_changes, a:workspace_edit.documentChanges) + elseif type(a:workspace_edit.documentChanges) is v:t_list + let l:document_changes = a:workspace_edit.documentChanges + endif + + for l:text_document_edit in l:document_changes + let l:filename = l:text_document_edit.textDocument.uri + let l:edits = l:text_document_edit.edits + let l:changes[l:filename] = l:edits + endfor + endif + + return l:changes +endfunction + +function! ale#code_action#BuildChangesList(changes_map) abort + let l:changes = [] + + for l:file_name in keys(a:changes_map) + let l:text_edits = a:changes_map[l:file_name] + let l:text_changes = [] + + for l:edit in l:text_edits + let l:range = l:edit.range + let l:new_text = l:edit.newText + + call add(l:text_changes, { + \ 'start': { + \ 'line': l:range.start.line + 1, + \ 'offset': l:range.start.character + 1, + \ }, + \ 'end': { + \ 'line': l:range.end.line + 1, + \ 'offset': l:range.end.character + 1, + \ }, + \ 'newText': l:new_text, + \}) + endfor + + call add(l:changes, { + \ 'fileName': ale#util#ToResource(l:file_name), + \ 'textChanges': l:text_changes, + \}) + endfor + + return l:changes +endfunction + +function! s:EscapeMenuName(text) abort + return substitute(a:text, '\\\| \|\.\|&', '\\\0', 'g') +endfunction + +function! s:UpdateMenu(data, menu_items) abort + silent! aunmenu PopUp.Refactor\.\.\. + + if empty(a:data) + return + endif + + for [l:type, l:item] in a:menu_items + let l:name = l:type is# 'tsserver' ? l:item.name : l:item.title + let l:func_name = l:type is# 'tsserver' + \ ? 'ale#codefix#ApplyTSServerCodeAction' + \ : 'ale#codefix#ApplyLSPCodeAction' + + execute printf( + \ 'anoremenu PopUp.&Refactor\.\.\..%s' + \ . ' :call %s(%s, %s)', + \ s:EscapeMenuName(l:name), + \ l:func_name, + \ string(a:data), + \ string(l:item), + \) + endfor + + if empty(a:menu_items) + silent! anoremenu PopUp.Refactor\.\.\..(None) :silent + endif +endfunction + +function! s:GetCodeActions(linter, options) abort + let l:buffer = bufnr('') + let [l:line, l:column] = getpos('.')[1:2] + let l:column = min([l:column, len(getline(l:line))]) + + let l:location = { + \ 'buffer': l:buffer, + \ 'line': l:line, + \ 'column': l:column, + \ 'end_line': l:line, + \ 'end_column': l:column, + \} + let l:Callback = function('s:OnReady', [l:location, a:options]) + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) +endfunction + +function! ale#code_action#GetCodeActions(options) abort + silent! aunmenu PopUp.Rename + silent! aunmenu PopUp.Refactor\.\.\. + + " Only display the menu items if there's an LSP server. + if len(ale#lsp_linter#GetEnabled(bufnr(''))) > 0 + if !empty(expand('')) + silent! anoremenu PopUp.Rename :ALERename + endif + + silent! anoremenu PopUp.Refactor\.\.\..(None) :silent + + call ale#codefix#Execute( + \ mode() is# 'v' || mode() is# "\", + \ function('s:UpdateMenu') + \) + endif +endfunction + +function! s:Setup(enabled) abort + augroup ALECodeActionsGroup + autocmd! + + if a:enabled + autocmd MenuPopup * :call ale#code_action#GetCodeActions({}) + endif + augroup END + + if !a:enabled + silent! augroup! ALECodeActionsGroup + + silent! aunmenu PopUp.Rename + silent! aunmenu PopUp.Refactor\.\.\. + endif +endfunction + +function! ale#code_action#EnablePopUpMenu() abort + call s:Setup(1) +endfunction + +function! ale#code_action#DisablePopUpMenu() abort + call s:Setup(0) +endfunction diff --git a/autoload/ale/codefix.vim b/autoload/ale/codefix.vim new file mode 100644 index 00000000..6eaadb23 --- /dev/null +++ b/autoload/ale/codefix.vim @@ -0,0 +1,491 @@ +" Author: Dalius Dobravolskas +" Description: Code Fix support for tsserver and LSP servers + +let s:codefix_map = {} + +" Used to get the codefix map in tests. +function! ale#codefix#GetMap() abort + return deepcopy(s:codefix_map) +endfunction + +" Used to set the codefix map in tests. +function! ale#codefix#SetMap(map) abort + let s:codefix_map = a:map +endfunction + +function! ale#codefix#ClearLSPData() abort + let s:codefix_map = {} +endfunction + +function! s:message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +function! ale#codefix#ApplyTSServerCodeAction(data, item) abort + if has_key(a:item, 'changes') + let l:changes = a:item.changes + + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'codefix', + \ 'changes': l:changes, + \ }, + \ {}, + \) + else + let l:message = ale#lsp#tsserver_message#GetEditsForRefactor( + \ a:data.buffer, + \ a:data.line, + \ a:data.column, + \ a:data.end_line, + \ a:data.end_column, + \ a:item.id[0], + \ a:item.id[1], + \) + + let l:request_id = ale#lsp#Send(a:data.connection_id, l:message) + + let s:codefix_map[l:request_id] = a:data + endif +endfunction + +function! ale#codefix#HandleTSServerResponse(conn_id, response) abort + if !has_key(a:response, 'request_seq') + \ || !has_key(s:codefix_map, a:response.request_seq) + return + endif + + let l:data = remove(s:codefix_map, a:response.request_seq) + let l:MenuCallback = get(l:data, 'menu_callback', v:null) + + if get(a:response, 'command', '') is# 'getCodeFixes' + if get(a:response, 'success', v:false) is v:false + \&& l:MenuCallback is v:null + let l:message = get(a:response, 'message', 'unknown') + call s:message('Error while getting code fixes. Reason: ' . l:message) + + return + endif + + let l:result = get(a:response, 'body', []) + call filter(l:result, 'has_key(v:val, ''changes'')') + + if l:MenuCallback isnot v:null + call l:MenuCallback( + \ l:data, + \ map(copy(l:result), '[''tsserver'', v:val]') + \) + + return + endif + + if len(l:result) == 0 + call s:message('No code fixes available.') + + return + endif + + let l:code_fix_to_apply = 0 + + if len(l:result) == 1 + let l:code_fix_to_apply = 1 + else + let l:codefix_no = 1 + let l:codefixstring = "Code Fixes:\n" + + for l:codefix in l:result + let l:codefixstring .= l:codefix_no . ') ' + \ . l:codefix.description . "\n" + let l:codefix_no += 1 + endfor + + let l:codefixstring .= 'Type number and (empty cancels): ' + + let l:code_fix_to_apply = ale#util#Input(l:codefixstring, '') + let l:code_fix_to_apply = str2nr(l:code_fix_to_apply) + + if l:code_fix_to_apply == 0 + return + endif + endif + + call ale#codefix#ApplyTSServerCodeAction( + \ l:data, + \ l:result[l:code_fix_to_apply - 1], + \) + elseif get(a:response, 'command', '') is# 'getApplicableRefactors' + if get(a:response, 'success', v:false) is v:false + \&& l:MenuCallback is v:null + let l:message = get(a:response, 'message', 'unknown') + call s:message('Error while getting applicable refactors. Reason: ' . l:message) + + return + endif + + let l:result = get(a:response, 'body', []) + + if len(l:result) == 0 + call s:message('No applicable refactors available.') + + return + endif + + let l:refactors = [] + + for l:item in l:result + for l:action in l:item.actions + call add(l:refactors, { + \ 'name': l:action.description, + \ 'id': [l:item.name, l:action.name], + \}) + endfor + endfor + + if l:MenuCallback isnot v:null + call l:MenuCallback( + \ l:data, + \ map(copy(l:refactors), '[''tsserver'', v:val]') + \) + + return + endif + + let l:refactor_no = 1 + let l:refactorstring = "Applicable refactors:\n" + + for l:refactor in l:refactors + let l:refactorstring .= l:refactor_no . ') ' + \ . l:refactor.name . "\n" + let l:refactor_no += 1 + endfor + + let l:refactorstring .= 'Type number and (empty cancels): ' + + let l:refactor_to_apply = ale#util#Input(l:refactorstring, '') + let l:refactor_to_apply = str2nr(l:refactor_to_apply) + + if l:refactor_to_apply == 0 + return + endif + + let l:id = l:refactors[l:refactor_to_apply - 1].id + + call ale#codefix#ApplyTSServerCodeAction( + \ l:data, + \ l:refactors[l:refactor_to_apply - 1], + \) + elseif get(a:response, 'command', '') is# 'getEditsForRefactor' + if get(a:response, 'success', v:false) is v:false + let l:message = get(a:response, 'message', 'unknown') + call s:message('Error while getting edits for refactor. Reason: ' . l:message) + + return + endif + + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'editsForRefactor', + \ 'changes': a:response.body.edits, + \ }, + \ {}, + \) + endif +endfunction + +function! ale#codefix#ApplyLSPCodeAction(data, item) abort + if has_key(a:item, 'command') + \&& type(a:item.command) == v:t_dict + let l:command = a:item.command + let l:message = ale#lsp#message#ExecuteCommand( + \ l:command.command, + \ l:command.arguments, + \) + + let l:request_id = ale#lsp#Send(a:data.connection_id, l:message) + elseif has_key(a:item, 'command') && has_key(a:item, 'arguments') + \&& type(a:item.command) == v:t_string + let l:message = ale#lsp#message#ExecuteCommand( + \ a:item.command, + \ a:item.arguments, + \) + + let l:request_id = ale#lsp#Send(a:data.connection_id, l:message) + elseif has_key(a:item, 'edit') || has_key(a:item, 'arguments') + if has_key(a:item, 'edit') + let l:topass = a:item.edit + else + let l:topass = a:item.arguments[0] + endif + + let l:changes_map = ale#code_action#GetChanges(l:topass) + + if empty(l:changes_map) + return + endif + + let l:changes = ale#code_action#BuildChangesList(l:changes_map) + + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'codeaction', + \ 'changes': l:changes, + \ }, + \ {}, + \) + endif +endfunction + +function! ale#codefix#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'method') + \ && a:response.method is# 'workspace/applyEdit' + \ && has_key(a:response, 'params') + let l:params = a:response.params + + let l:changes_map = ale#code_action#GetChanges(l:params.edit) + + if empty(l:changes_map) + return + endif + + let l:changes = ale#code_action#BuildChangesList(l:changes_map) + + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'applyEdit', + \ 'changes': l:changes, + \ }, + \ {} + \) + elseif has_key(a:response, 'id') + \&& has_key(s:codefix_map, a:response.id) + let l:data = remove(s:codefix_map, a:response.id) + let l:MenuCallback = get(l:data, 'menu_callback', v:null) + + let l:result = get(a:response, 'result') + + if type(l:result) != v:t_list + let l:result = [] + endif + + " Send the results to the menu callback, if set. + if l:MenuCallback isnot v:null + call l:MenuCallback( + \ l:data, + \ map(copy(l:result), '[''lsp'', v:val]') + \) + + return + endif + + if len(l:result) == 0 + call s:message('No code actions received from server') + + return + endif + + let l:codeaction_no = 1 + let l:codeactionstring = "Code Fixes:\n" + + for l:codeaction in l:result + let l:codeactionstring .= l:codeaction_no . ') ' + \ . l:codeaction.title . "\n" + let l:codeaction_no += 1 + endfor + + let l:codeactionstring .= 'Type number and (empty cancels): ' + + let l:codeaction_to_apply = ale#util#Input(l:codeactionstring, '') + let l:codeaction_to_apply = str2nr(l:codeaction_to_apply) + + if l:codeaction_to_apply == 0 + return + endif + + let l:item = l:result[l:codeaction_to_apply - 1] + + call ale#codefix#ApplyLSPCodeAction(l:data, l:item) + endif +endfunction + +function! s:FindError(buffer, line, column, end_line, end_column, linter_name) abort + let l:nearest_error = v:null + + if a:line == a:end_line + \&& a:column == a:end_column + \&& has_key(g:ale_buffer_info, a:buffer) + let l:nearest_error_diff = -1 + + for l:error in get(g:ale_buffer_info[a:buffer], 'loclist', []) + if has_key(l:error, 'code') + \ && (a:linter_name is v:null || l:error.linter_name is# a:linter_name) + \ && l:error.lnum == a:line + let l:diff = abs(l:error.col - a:column) + + if l:nearest_error_diff == -1 || l:diff < l:nearest_error_diff + let l:nearest_error_diff = l:diff + let l:nearest_error = l:error + endif + endif + endfor + endif + + return l:nearest_error +endfunction + +function! s:OnReady( +\ line, +\ column, +\ end_line, +\ end_column, +\ MenuCallback, +\ linter, +\ lsp_details, +\) abort + let l:id = a:lsp_details.connection_id + + if !ale#lsp#HasCapability(l:id, 'code_actions') + return + endif + + let l:buffer = a:lsp_details.buffer + + if a:linter.lsp is# 'tsserver' + let l:nearest_error = + \ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column, a:linter.lsp) + + if l:nearest_error isnot v:null + let l:message = ale#lsp#tsserver_message#GetCodeFixes( + \ l:buffer, + \ a:line, + \ a:column, + \ a:line, + \ a:column, + \ [l:nearest_error.code], + \) + else + let l:message = ale#lsp#tsserver_message#GetApplicableRefactors( + \ l:buffer, + \ a:line, + \ a:column, + \ a:end_line, + \ a:end_column, + \) + endif + else + " Send a message saying the buffer has changed first, otherwise + " completions won't know what text is nearby. + call ale#lsp#NotifyForChanges(l:id, l:buffer) + + let l:diagnostics = [] + let l:nearest_error = + \ s:FindError(l:buffer, a:line, a:column, a:end_line, a:end_column, v:null) + + if l:nearest_error isnot v:null + let l:diagnostics = [ + \ { + \ 'code': l:nearest_error.code, + \ 'message': l:nearest_error.text, + \ 'range': { + \ 'start': { + \ 'line': l:nearest_error.lnum - 1, + \ 'character': l:nearest_error.col - 1, + \ }, + \ 'end': { + \ 'line': get(l:nearest_error, 'end_lnum', 1) - 1, + \ 'character': get(l:nearest_error, 'end_col', 0) + \ }, + \ }, + \ }, + \] + endif + + let l:message = ale#lsp#message#CodeAction( + \ l:buffer, + \ a:line, + \ a:column, + \ a:end_line, + \ a:end_column, + \ l:diagnostics, + \) + endif + + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#codefix#HandleTSServerResponse') + \ : function('ale#codefix#HandleLSPResponse') + + call ale#lsp#RegisterCallback(l:id, l:Callback) + + let l:request_id = ale#lsp#Send(l:id, l:message) + + let s:codefix_map[l:request_id] = { + \ 'connection_id': l:id, + \ 'buffer': l:buffer, + \ 'line': a:line, + \ 'column': a:column, + \ 'end_line': a:end_line, + \ 'end_column': a:end_column, + \ 'menu_callback': a:MenuCallback, + \} +endfunction + +function! s:ExecuteGetCodeFix(linter, range, MenuCallback) abort + let l:buffer = bufnr('') + + if a:range == 0 + let [l:line, l:column] = getpos('.')[1:2] + let l:end_line = l:line + let l:end_column = l:column + + " Expand the range to cover the current word, if there is one. + let l:cword = expand('') + + if !empty(l:cword) + let l:search_pos = searchpos('\V' . l:cword, 'bn', l:line) + + if l:search_pos != [0, 0] + let l:column = l:search_pos[1] + let l:end_column = l:column + len(l:cword) - 1 + endif + endif + elseif mode() is# 'v' || mode() is# "\" + " You need to get the start and end in a different way when you're in + " visual mode. + let [l:line, l:column] = getpos('v')[1:2] + let [l:end_line, l:end_column] = getpos('.')[1:2] + else + let [l:line, l:column] = getpos("'<")[1:2] + let [l:end_line, l:end_column] = getpos("'>")[1:2] + endif + + let l:column = max([min([l:column, len(getline(l:line))]), 1]) + let l:end_column = min([l:end_column, len(getline(l:end_line))]) + + let l:Callback = function( + \ 's:OnReady', [l:line, l:column, l:end_line, l:end_column, a:MenuCallback] + \) + + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) +endfunction + +function! ale#codefix#Execute(range, ...) abort + if a:0 > 1 + throw 'Too many arguments' + endif + + let l:MenuCallback = get(a:000, 0, v:null) + let l:linters = ale#lsp_linter#GetEnabled(bufnr('')) + + if empty(l:linters) + if l:MenuCallback is v:null + call s:message('No active LSPs') + else + call l:MenuCallback({}, []) + endif + + return + endif + + for l:linter in l:linters + call s:ExecuteGetCodeFix(l:linter, a:range, l:MenuCallback) + endfor +endfunction diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim new file mode 100644 index 00000000..c9dc8d94 --- /dev/null +++ b/autoload/ale/command.vim @@ -0,0 +1,473 @@ +" Author: w0rp +" 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 + +" The regular expression used for formatting filenames with modifiers. +let s:path_format_regex = '\v\%s(%(:h|:t|:r|:e)*)' + +" 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 + +" Set the cwd for commands that are about to run. +" Used internally. +function! ale#command#SetCwd(buffer, cwd) abort + call ale#command#InitData(a:buffer) + let s:buffer_data[a:buffer].cwd = a:cwd +endfunction + +function! ale#command#ResetCwd(buffer) abort + if has_key(s:buffer_data, a:buffer) + let s:buffer_data[a:buffer].cwd = v:null + 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') + + if empty(l:filename) + " If the buffer's filename is empty, create a dummy filename. + let l:ft = getbufvar(a:buffer, '&filetype') + let l:filename = 'file' . ale#filetypes#GuessExtension(l:ft) + endif + + " Create a temporary filename, / + " The file itself will not be created by this function. + 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 + +" Format a filename, converting it with filename mappings, if non-empty, +" and escaping it for putting into a command string. +" +" The filename can be modified. +function! s:FormatFilename(filename, mappings, modifiers) abort + let l:filename = a:filename + + if !empty(a:mappings) + let l:filename = ale#filename_mapping#Map(l:filename, a:mappings) + endif + + if !empty(a:modifiers) + let l:filename = fnamemodify(l:filename, a:modifiers) + endif + + return ale#Escape(l:filename) +endfunction + +" Produce a command prefix to check to a particular directory for a command. +" %s format markers with filename-modifiers can be used as the directory, and +" will be returned verbatim for formatting in paths relative to files. +function! ale#command#CdString(directory) abort + let l:match = matchstrpos(a:directory, s:path_format_regex) + " Do not escape the directory here if it's a valid format string. + " This allows us to use sequences like %s:h, %s:h:h, etc. + let l:directory = l:match[1:] == [0, len(a:directory)] + \ ? a:directory + \ : ale#Escape(a:directory) + + if has('win32') + return 'cd /d ' . l:directory . ' && ' + endif + + return 'cd ' . l:directory . ' && ' +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, +\ input, +\ cwd, +\ mappings, +\) abort + let l:temporary_file = '' + let l:command = a:command + + if !empty(a:cwd) + let l:command = ale#command#CdString(a:cwd) . l:command + endif + + " First replace all uses of %%, used for literal percent characters, + " with an ugly string. + let l:command = substitute(l:command, '%%', '<>', 'g') + + " Replace %e with the escaped executable, if available. + if !empty(a:executable) && l:command =~# '%e' + let l:command = substitute(l:command, '%e', '\=ale#Escape(a:executable)', 'g') + endif + + " Replace all %s occurrences in the string with the name of the current + " file. + if l:command =~# '%s' + let l:filename = fnamemodify(bufname(a:buffer), ':p') + let l:command = substitute( + \ l:command, + \ s:path_format_regex, + \ '\=s:FormatFilename(l:filename, a:mappings, submatch(1))', + \ 'g' + \) + endif + + if a:input isnot v:false && l:command =~# '%t' + " Create a temporary filename, / + " The file itself will not be created by this function. + let l:temporary_file = s:TemporaryFilename(a:buffer) + let l:command = substitute( + \ l:command, + \ '\v\%t(%(:h|:t|:r|:e)*)', + \ '\=s:FormatFilename(l:temporary_file, a:mappings, submatch(1))', + \ 'g' + \) + endif + + " Finish formatting so %% becomes %. + let l:command = substitute(l:command, '<>', '%', 'g') + + if a:pipe_file_if_needed && empty(l:temporary_file) + " If we are to send the Vim buffer to a command, we'll do it + " in the shell. We'll write out the file to a temporary file, + " and then read it back in, in the shell. + let l:temporary_file = s:TemporaryFilename(a:buffer) + let l:command = l:command . ' < ' . ale#Escape(l:temporary_file) + endif + + 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 + + " Set the default cwd for this buffer in this call stack. + call ale#command#SetCwd(a:buffer, l:result.cwd) + + try + if get(l:result, 'result_callback', v:null) isnot v:null + call call(l:result.result_callback, [l:value]) + endif + finally + call ale#command#ResetCwd(a:buffer) + endtry +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:cwd = get(l:options, 'cwd', v:null) + + if l:cwd is v:null + " Default the working directory to whatever it was for the last + " command run in the chain. + let l:cwd = get(get(s:buffer_data, a:buffer, {}), 'cwd', v:null) + endif + + 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), + \ l:cwd, + \ get(l:options, 'filename_mappings', []), + \) + 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 cwd is kept and used as the default value for the next command in + " the chain. + " + " The original command here is used in tests. + let l:result = { + \ '_deferred_job_id': l:job_id, + \ 'executable': get(l:options, 'executable', ''), + \ 'cwd': l:cwd, + \ '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 new file mode 100644 index 00000000..bd9fdf72 --- /dev/null +++ b/autoload/ale/completion.vim @@ -0,0 +1,1068 @@ +" Author: w0rp +" Description: Completion support for LSP linters +scriptencoding utf-8 + +" The omnicompletion menu is shown through a special Plug mapping which is +" only valid in Insert mode. This way, feedkeys() won't send these keys if you +" quit Insert mode quickly enough. +inoremap (ale_show_completion_menu) +" If we hit the key sequence in normal mode, then we won't show the menu, so +" we should restore the old settings right away. +nnoremap (ale_show_completion_menu) :call ale#completion#RestoreCompletionOptions() +cnoremap (ale_show_completion_menu) +vnoremap (ale_show_completion_menu) +onoremap (ale_show_completion_menu) + +let g:ale_completion_delay = get(g:, 'ale_completion_delay', 100) +let g:ale_completion_excluded_words = get(g:, 'ale_completion_excluded_words', []) +let g:ale_completion_max_suggestions = get(g:, 'ale_completion_max_suggestions', 50) +let g:ale_completion_autoimport = get(g:, 'ale_completion_autoimport', v:true) +let g:ale_completion_tsserver_remove_warnings = get(g:, 'ale_completion_tsserver_remove_warnings', 0) + +let s:timer_id = -1 +let s:last_done_pos = [] + +" CompletionItemKind values from the LSP protocol. +let g:ale_lsp_types = { +\ 1: 'text', +\ 2: 'method', +\ 3: 'function', +\ 4: 'constructor', +\ 5: 'field', +\ 6: 'variable', +\ 7: 'class', +\ 8: 'interface', +\ 9: 'module', +\ 10: 'property', +\ 11: 'unit', +\ 12: 'value', +\ 13: 'enum', +\ 14: 'keyword', +\ 15: 'snippet', +\ 16: 'color', +\ 17: 'file', +\ 18: 'reference', +\ 19: 'folder', +\ 20: 'enum_member', +\ 21: 'constant', +\ 22: 'struct', +\ 23: 'event', +\ 24: 'operator', +\ 25: 'type_parameter', +\ } + +" from https://github.com/microsoft/TypeScript/blob/29becf05012bfa7ba20d50b0d16813971e46b8a6/lib/protocol.d.ts#L2472 +let g:ale_tsserver_types = { +\ 'warning': 'text', +\ 'keyword': 'keyword', +\ 'script': 'file', +\ 'module': 'module', +\ 'class': 'class', +\ 'local class': 'class', +\ 'interface': 'interface', +\ 'type': 'class', +\ 'enum': 'enum', +\ 'enum member': 'enum_member', +\ 'var': 'variable', +\ 'local var': 'variable', +\ 'function': 'function', +\ 'local function': 'function', +\ 'method': 'method', +\ 'getter': 'property', +\ 'setter': 'method', +\ 'property': 'property', +\ 'constructor': 'constructor', +\ 'call': 'method', +\ 'index': 'index', +\ 'construct': 'constructor', +\ 'parameter': 'parameter', +\ 'type parameter': 'type_parameter', +\ 'primitive type': 'unit', +\ 'label': 'text', +\ 'alias': 'class', +\ 'const': 'constant', +\ 'let': 'variable', +\ 'directory': 'folder', +\ 'external module name': 'text', +\ 'JSX attribute': 'parameter', +\ 'string': 'text' +\ } + +" For compatibility reasons, we only use built in VIM completion kinds +" See :help complete-items for Vim completion kinds +let g:ale_completion_symbols = get(g:, 'ale_completion_symbols', { +\ 'text': 'v', +\ 'method': 'f', +\ 'function': 'f', +\ 'constructor': 'f', +\ 'field': 'm', +\ 'variable': 'v', +\ 'class': 't', +\ 'interface': 't', +\ 'module': 'd', +\ 'property': 'm', +\ 'unit': 'v', +\ 'value': 'v', +\ 'enum': 't', +\ 'keyword': 'v', +\ 'snippet': 'v', +\ 'color': 'v', +\ 'file': 'v', +\ 'reference': 'v', +\ 'folder': 'v', +\ 'enum_member': 'm', +\ 'constant': 'm', +\ 'struct': 't', +\ 'event': 'v', +\ 'operator': 'f', +\ 'type_parameter': 'p', +\ '': 'v' +\ }) + +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 +" the insert cursor is. If one of these matches, we'll check for completions. +let s:should_complete_map = { +\ '': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$', +\ 'clojure': s:lisp_regex, +\ 'lisp': s:lisp_regex, +\ 'racket': '\k\+$', +\ 'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|''$|"$', +\ 'rust': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|::$', +\ 'cpp': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|::$|-\>$', +\ 'c': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$|-\>$', +\} + +" Regular expressions for finding the start column to replace with completion. +let s:omni_start_map = { +\ '': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$', +\ 'racket': '\k\+$', +\} + +" A map of exact characters for triggering LSP completions. Do not forget to +" update self.input_patterns in ale.py in updating entries in this map. +let s:trigger_character_map = { +\ '': ['.'], +\ 'typescript': ['.', '''', '"'], +\ 'rust': ['.', '::'], +\ 'cpp': ['.', '::', '->'], +\ 'c': ['.', '->'], +\} + +function! s:GetFiletypeValue(map, filetype) abort + for l:part in reverse(split(a:filetype, '\.')) + let l:regex = get(a:map, l:part, []) + + if !empty(l:regex) + return l:regex + endif + endfor + + " Use the default regex for other files. + return a:map[''] +endfunction + +" Check if we should look for completions for a language. +function! ale#completion#GetPrefix(filetype, line, column) abort + let l:regex = s:GetFiletypeValue(s:should_complete_map, a:filetype) + + " The column we're using completions for is where we are inserting text, + " like so: + " abc + " ^ + " So we need check the text in the column before that position. + return matchstr(getline(a:line)[: a:column - 2], l:regex) +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 + return a:prefix + endif + + return '' +endfunction + +function! ale#completion#Filter( +\ buffer, +\ filetype, +\ suggestions, +\ prefix, +\ exact_prefix_match, +\) abort + let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words') + + if empty(a:prefix) + let l:filtered_suggestions = a:suggestions + else + 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 + + if a:exact_prefix_match + " Add suggestions if the word is an exact match. + if l:word is# a:prefix + call add(l:filtered_suggestions, l:item) + endif + else + " 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 + endif + endfor + endif + endif + + if !empty(l:excluded_words) + " Copy the List if needed. We don't want to modify the argument. + " We shouldn't make a copy if we don't need to. + if l:filtered_suggestions is a:suggestions + let l:filtered_suggestions = copy(a:suggestions) + endif + + " Remove suggestions with words in the exclusion List. + call filter( + \ l:filtered_suggestions, + \ 'index(l:excluded_words, type(v:val) is v:t_string ? v:val : v:val.word) < 0', + \) + endif + + return l:filtered_suggestions +endfunction + +function! s:ReplaceCompletionOptions(source) 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#AutomaticOmniFunc' + + if a:source is# 'ale-automatic' + if !exists('b:ale_old_completeopt') + let b:ale_old_completeopt = &l:completeopt + endif + + let l:opt_list = split(&l:completeopt, ',') + " The menu and noinsert options must be set, or automatic completion + " will be annoying. + let l:new_opt_list = ['menu', 'menuone', 'noinsert'] + + " Permit some other completion options, provided users have set them. + for l:opt in ['preview', 'popup', 'noselect'] + if index(l:opt_list, l:opt) >= 0 + call add(l:new_opt_list, l:opt) + endif + endfor + + let &l:completeopt = join(l:new_opt_list, ',') + endif +endfunction + +function! ale#completion#RestoreCompletionOptions() abort + " Reset settings when completion is done. + if exists('b:ale_old_omnifunc') + if b:ale_old_omnifunc isnot# 'pythoncomplete#Complete' + let &l:omnifunc = b:ale_old_omnifunc + endif + + unlet b:ale_old_omnifunc + endif + + if exists('b:ale_old_completeopt') + let &l:completeopt = b:ale_old_completeopt + unlet b:ale_old_completeopt + endif +endfunction + +function! ale#completion#GetCompletionPosition() abort + if !exists('b:ale_completion_info') + return 0 + endif + + 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) + + return l:column - len(l:match) - 1 +endfunction + +function! ale#completion#GetCompletionPositionForDeoplete(input) abort + return match(a:input, '\k*$') +endfunction + +function! ale#completion#GetCompletionResult() abort + if exists('b:ale_completion_result') + return b:ale_completion_result + endif + + return v:null +endfunction + +function! ale#completion#AutomaticOmniFunc(findstart, base) abort + if a:findstart + return ale#completion#GetCompletionPosition() + else + let l:result = ale#completion#GetCompletionResult() + + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if l:source is# 'ale-automatic' || l:source is# 'ale-manual' + call s:ReplaceCompletionOptions(l:source) + endif + + return l:result isnot v:null ? l:result : [] + endif +endfunction + +function! s:OpenCompletionMenu(...) abort + if !&l:paste + call ale#util#FeedKeys("\(ale_show_completion_menu)") + endif +endfunction + +function! ale#completion#Show(result) abort + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if ale#util#Mode() isnot# 'i' && l:source isnot# 'ale-import' + return + endif + + " Set the list in the buffer. + let b:ale_completion_result = a:result + + " Don't try to open the completion menu if there's nothing to show. + if empty(b:ale_completion_result) + if l:source is# 'ale-import' + " If we ran completion from :ALEImport, + " tell the user that nothing is going to happen. + call s:message('No possible imports found.') + endif + + return + endif + + " Replace completion options shortly before opening the menu. + if l:source is# 'ale-automatic' || l:source is# 'ale-manual' + call s:ReplaceCompletionOptions(l:source) + + call timer_start(0, function('s:OpenCompletionMenu')) + endif + + if l:source is# 'ale-callback' + call b:CompleteCallback(b:ale_completion_result) + endif + + if l:source is# 'ale-import' + call ale#completion#HandleUserData(b:ale_completion_result[0]) + + let l:text_changed = '' . g:ale_lint_on_text_changed + + " Check the buffer again right away, if linting is enabled. + if g:ale_enabled + \&& ( + \ l:text_changed is# '1' + \ || g:ale_lint_on_text_changed is v:true + \ || l:text_changed is# 'always' + \ || l:text_changed is# 'normal' + \ || l:text_changed is# 'insert' + \) + call ale#Queue(0, '') + endif + endif +endfunction + +function! ale#completion#GetAllTriggers() abort + return deepcopy(s:trigger_character_map) +endfunction + +function! ale#completion#GetCompletionKind(kind) abort + let l:lsp_symbol = get(g:ale_lsp_types, a:kind, '') + + if !empty(l:lsp_symbol) + return l:lsp_symbol + endif + + return get(g:ale_tsserver_types, a:kind, '') +endfunction + +function! ale#completion#GetCompletionSymbols(kind) abort + let l:kind = ale#completion#GetCompletionKind(a:kind) + let l:symbol = get(g:ale_completion_symbols, l:kind, '') + + if !empty(l:symbol) + return l:symbol + endif + + return get(g:ale_completion_symbols, '', 'v') +endfunction + +function! s:CompletionStillValid(request_id) abort + let [l:line, l:column] = getpos('.')[1:2] + + return has_key(b:, 'ale_completion_info') + \&& ( + \ ale#util#Mode() is# 'i' + \ || b:ale_completion_info.source is# 'ale-import' + \) + \&& 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.source is# 'ale-omnifunc' + \ || b:ale_completion_info.source is# 'ale-callback' + \ || b:ale_completion_info.source is# 'ale-import' + \) +endfunction + +function! ale#completion#ParseTSServerCompletions(response) abort + let l:names = [] + + for l:suggestion in a:response.body + let l:kind = get(l:suggestion, 'kind', '') + + if g:ale_completion_tsserver_remove_warnings == 0 || l:kind isnot# 'warning' + call add(l:names, { + \ 'word': l:suggestion.name, + \ 'source': get(l:suggestion, 'source', ''), + \}) + endif + endfor + + return l:names +endfunction + +function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort + let l:buffer = bufnr('') + let l:results = [] + let l:names_with_details = [] + let l:info = get(b:, 'ale_completion_info', {}) + + for l:suggestion in a:response.body + let l:displayParts = [] + let l:local_name = v:null + + for l:action in get(l:suggestion, 'codeActions', []) + call add(l:displayParts, l:action.description . ' ') + endfor + + for l:part in l:suggestion.displayParts + " Stop on stop on line breaks for the menu. + if get(l:part, 'kind') is# 'lineBreak' + break + endif + + if get(l:part, 'kind') is# 'localName' + let l:local_name = l:part.text + endif + + call add(l:displayParts, l:part.text) + endfor + + " Each one of these parts has 'kind' properties + let l:documentationParts = [] + + for l:part in get(l:suggestion, 'documentation', []) + call add(l:documentationParts, l:part.text) + endfor + + " See :help complete-items + let l:result = { + \ 'word': ( + \ l:suggestion.name is# 'default' + \ && l:suggestion.kind is# 'alias' + \ && !empty(l:local_name) + \ ? l:local_name + \ : l:suggestion.name + \ ), + \ 'kind': ale#completion#GetCompletionSymbols(l:suggestion.kind), + \ 'icase': 1, + \ 'menu': join(l:displayParts, ''), + \ 'dup': get(l:info, 'additional_edits_only', 0) + \ || (g:ale_completion_autoimport + 0), + \ 'info': join(l:documentationParts, ''), + \} + " This flag is used to tell if this completion came from ALE or not. + let l:user_data = {'_ale_completion_item': 1} + + if has_key(l:suggestion, 'codeActions') + let l:user_data.code_actions = l:suggestion.codeActions + endif + + let l:result.user_data = json_encode(l:user_data) + + " Include this item if we'll accept any items, + " or if we only want items with additional edits, and this has them. + if !get(l:info, 'additional_edits_only', 0) + \|| has_key(l:user_data, 'code_actions') + call add(l:results, l:result) + endif + endfor + + let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', []) + + if !empty(l:names) && len(l:names) != len(l:results) + let l:names_with_details = map(copy(l:results), 'v:val.word') + let l:missing_names = filter( + \ copy(l:names), + \ 'index(l:names_with_details, v:val.word) < 0', + \) + + for l:name in l:missing_names + call add(l:results, { + \ 'word': l:name.word, + \ 'kind': 'v', + \ 'icase': 1, + \ 'menu': '', + \ 'info': '', + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \}) + endfor + endif + + return l:results +endfunction + +function! ale#completion#NullFilter(buffer, item) abort + return 1 +endfunction + +function! ale#completion#ParseLSPCompletions(response) abort + let l:buffer = bufnr('') + let l:info = get(b:, 'ale_completion_info', {}) + let l:Filter = get(l:info, 'completion_filter', v:null) + + if l:Filter is v:null + let l:Filter = function('ale#completion#NullFilter') + else + let l:Filter = ale#util#GetFunction(l:Filter) + endif + + let l:item_list = [] + + if type(get(a:response, 'result')) is v:t_list + let l:item_list = a:response.result + elseif type(get(a:response, 'result')) is v:t_dict + \&& type(get(a:response.result, 'items')) is v:t_list + let l:item_list = a:response.result.items + endif + + let l:results = [] + + for l:item in l:item_list + if !call(l:Filter, [l:buffer, l:item]) + continue + endif + + if get(l:item, 'insertTextFormat', s:LSP_INSERT_TEXT_FORMAT_PLAIN) is s:LSP_INSERT_TEXT_FORMAT_PLAIN + \&& type(get(l:item, 'textEdit')) is v:t_dict + let l:text = l:item.textEdit.newText + elseif type(get(l:item, 'insertText')) is v:t_string + let l:text = l:item.insertText + else + let l:text = l:item.label + endif + + let l:word = matchstr(l:text, '\v^[^(]+') + + if empty(l:word) + continue + endif + + " Don't use LSP items with additional text edits when autoimport for + " completions is turned off. + if !empty(get(l:item, 'additionalTextEdits')) + \&& !( + \ get(l:info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport + \) + continue + endif + + let l:doc = get(l:item, 'documentation', '') + + if type(l:doc) is v:t_dict && has_key(l:doc, 'value') + let l:doc = l:doc.value + endif + + " Collapse whitespaces and line breaks into a single space. + let l:detail = substitute(get(l:item, 'detail', ''), '\_s\+', ' ', 'g') + + let l:result = { + \ 'word': l:word, + \ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')), + \ 'icase': 1, + \ 'menu': l:detail, + \ 'dup': get(l:info, 'additional_edits_only', 0) + \ || (g:ale_completion_autoimport + 0), + \ 'info': (type(l:doc) is v:t_string ? l:doc : ''), + \} + " This flag is used to tell if this completion came from ALE or not. + let l:user_data = {'_ale_completion_item': 1} + + if has_key(l:item, 'additionalTextEdits') + \ && l:item.additionalTextEdits isnot v:null + let l:text_changes = [] + + for l:edit in l:item.additionalTextEdits + call add(l:text_changes, { + \ 'start': { + \ 'line': l:edit.range.start.line + 1, + \ 'offset': l:edit.range.start.character + 1, + \ }, + \ 'end': { + \ 'line': l:edit.range.end.line + 1, + \ 'offset': l:edit.range.end.character + 1, + \ }, + \ 'newText': l:edit.newText, + \}) + endfor + + if !empty(l:text_changes) + let l:user_data.code_actions = [{ + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('#' . l:buffer . ':p'), + \ 'textChanges': l:text_changes, + \ }, + \ ], + \}] + endif + endif + + let l:result.user_data = json_encode(l:user_data) + + " Include this item if we'll accept any items, + " or if we only want items with additional edits, and this has them. + if !get(l:info, 'additional_edits_only', 0) + \|| has_key(l:user_data, 'code_actions') + call add(l:results, l:result) + endif + endfor + + if has_key(l:info, 'prefix') + let l:results = ale#completion#Filter( + \ l:buffer, + \ &filetype, + \ l:results, + \ l:info.prefix, + \ get(l:info, 'additional_edits_only', 0), + \) + endif + + return l:results[: g:ale_completion_max_suggestions - 1] +endfunction + +function! ale#completion#HandleTSServerResponse(conn_id, response) abort + if !s:CompletionStillValid(get(a:response, 'request_seq')) + return + endif + + if !has_key(a:response, 'body') + return + endif + + let l:buffer = bufnr('') + let l:command = get(a:response, 'command', '') + + if l:command is# 'completions' + let l:names = ale#completion#Filter( + \ l:buffer, + \ &filetype, + \ ale#completion#ParseTSServerCompletions(a:response), + \ b:ale_completion_info.prefix, + \ get(b:ale_completion_info, 'additional_edits_only', 0), + \)[: g:ale_completion_max_suggestions - 1] + + " We need to remember some names for tsserver, as it doesn't send + " details back for everything we send. + call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names) + + if empty(l:names) + " Response with no results now and skip making a redundant request + " for nothing. + call ale#completion#Show([]) + else + let l:identifiers = [] + + for l:name in l:names + let l:identifier = { + \ 'name': l:name.word, + \} + let l:source = get(l:name, 'source', '') + + " Empty source results in no details for the completed item + if !empty(l:source) + call extend(l:identifier, { 'source': l:source }) + endif + + call add(l:identifiers, l:identifier) + endfor + + let b:ale_completion_info.request_id = ale#lsp#Send( + \ b:ale_completion_info.conn_id, + \ ale#lsp#tsserver_message#CompletionEntryDetails( + \ l:buffer, + \ b:ale_completion_info.line, + \ b:ale_completion_info.column, + \ l:identifiers, + \ ), + \) + endif + elseif l:command is# 'completionEntryDetails' + call ale#completion#Show( + \ ale#completion#ParseTSServerCompletionEntryDetails(a:response), + \) + endif +endfunction + + +function! ale#completion#HandleLSPResponse(conn_id, response) abort + if !s:CompletionStillValid(get(a:response, 'id')) + return + endif + + call ale#completion#Show( + \ ale#completion#ParseLSPCompletions(a:response), + \) +endfunction + +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 + endif + + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#completion#HandleTSServerResponse') + \ : function('ale#completion#HandleLSPResponse') + call ale#lsp#RegisterCallback(l:id, l:Callback) + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Completions( + \ l:buffer, + \ b:ale_completion_info.line, + \ b:ale_completion_info.column, + \ b:ale_completion_info.prefix, + \ ( + \ get(b:ale_completion_info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport + \ ) ? v:true : v:false, + \) + else + " Send a message saying the buffer has changed first, otherwise + " completions won't know what text is nearby. + call ale#lsp#NotifyForChanges(l:id, l:buffer) + + " 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#Completion( + \ l:buffer, + \ b:ale_completion_info.line, + \ b:ale_completion_info.column, + \ ale#completion#GetTriggerCharacter(&filetype, b:ale_completion_info.prefix), + \) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message) + + if l:request_id + let b:ale_completion_info.conn_id = l:id + let b:ale_completion_info.request_id = l:request_id + + if has_key(a:linter, 'completion_filter') + let b:ale_completion_info.completion_filter = a:linter.completion_filter + endif + endif +endfunction + +" This function can be called to check if ALE can provide completion data for +" the current buffer. 1 will be returned if there's a potential source of +" completion data ALE can use, and 0 will be returned otherwise. +function! ale#completion#CanProvideCompletions() abort + " NOTE: We can report that ALE can provide completions to Deoplete from + " here, and we might ignore linters still below. + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + return 1 + endif + endfor + + return 0 +endfunction + +" This function can be used to manually trigger autocomplete, even when +" g:ale_completion_enabled is set to false +function! ale#completion#GetCompletions(...) abort + let l:source = get(a:000, 0, '') + let l:options = get(a:000, 1, {}) + + if len(a:000) > 2 + throw 'Too many arguments!' + endif + + let l:CompleteCallback = get(l:options, 'callback', v:null) + + if l:CompleteCallback isnot v:null + let b:CompleteCallback = l:CompleteCallback + endif + + if has_key(l:options, 'line') && has_key(l:options, 'column') + " Use a provided line and column, if given. + let l:line = l:options.line + let l:column = l:options.column + else + let [l:line, l:column] = getpos('.')[1:2] + endif + + if has_key(l:options, 'prefix') + let l:prefix = l:options.prefix + else + let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) + endif + + if l:source is# 'ale-automatic' && empty(l:prefix) + return 0 + endif + + let l:line_length = len(getline('.')) + + let b:ale_completion_info = { + \ 'line': l:line, + \ 'line_length': l:line_length, + \ 'column': l:column, + \ 'prefix': l:prefix, + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': l:source, + \} + unlet! b:ale_completion_result + + if has_key(l:options, 'additional_edits_only') + let b:ale_completion_info.additional_edits_only = + \ l:options.additional_edits_only + endif + + let l:buffer = bufnr('') + let l:Callback = function('s:OnReady') + + let l:started = 0 + + for l:linter in ale#lsp_linter#GetEnabled(l:buffer) + if ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback) + let l:started = 1 + endif + endfor + + return l:started +endfunction + +function! s:message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +" This function implements the :ALEImport command. +function! ale#completion#Import() abort + let l:word = expand('') + + if empty(l:word) + call s:message('Nothing to complete at cursor!') + + return + endif + + let [l:line, l:column] = getpos('.')[1:2] + let l:column = searchpos('\V' . escape(l:word, '/\'), 'bnc', l:line)[1] + let l:column = l:column + len(l:word) - 1 + + if l:column isnot 0 + let l:started = ale#completion#GetCompletions('ale-import', { + \ 'line': l:line, + \ 'column': l:column, + \ 'prefix': l:word, + \ 'additional_edits_only': 1, + \}) + + if !l:started + call s:message('No completion providers are available.') + endif + endif +endfunction + +function! ale#completion#OmniFunc(findstart, base) abort + if a:findstart + let l:started = ale#completion#GetCompletions('ale-omnifunc') + + if !l:started + " This is the special value for cancelling completions silently. + " See :help complete-functions + return -3 + endif + + return ale#completion#GetCompletionPosition() + else + let l:result = ale#completion#GetCompletionResult() + + while l:result is v:null && !complete_check() + sleep 2ms + let l:result = ale#completion#GetCompletionResult() + endwhile + + return l:result isnot v:null ? l:result : [] + endif +endfunction + +function! s:TimerHandler(...) abort + if !get(b:, 'ale_completion_enabled', g:ale_completion_enabled) + return + endif + + let s:timer_id = -1 + + 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('ale-automatic') + endif +endfunction + +" Stop any completion timer that is queued. This is useful for tests. +function! ale#completion#StopTimer() abort + if s:timer_id != -1 + call timer_stop(s:timer_id) + endif + + let s:timer_id = -1 +endfunction + +function! ale#completion#Queue() abort + if !get(b:, 'ale_completion_enabled', g:ale_completion_enabled) + return + endif + + 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 + " last completed on. + return + endif + + " If we changed the text again while we're still waiting for a response, + " then invalidate the requests before the timer ticks again. + if exists('b:ale_completion_info') + let b:ale_completion_info.request_id = 0 + endif + + call ale#completion#StopTimer() + + let s:timer_id = timer_start(g:ale_completion_delay, function('s:TimerHandler')) +endfunction + +function! ale#completion#HandleUserData(completed_item) abort + let l:user_data_json = get(a:completed_item, 'user_data', '') + let l:user_data = type(l:user_data_json) is v:t_dict + \ ? l:user_data_json + \ : ale#util#FuzzyJSONDecode(l:user_data_json, {}) + + if !has_key(l:user_data, '_ale_completion_item') + return + endif + + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if l:source is# 'ale-automatic' + \|| l:source is# 'ale-manual' + \|| l:source is# 'ale-callback' + \|| l:source is# 'ale-import' + \|| l:source is# 'ale-omnifunc' + for l:code_action in get(l:user_data, 'code_actions', []) + call ale#code_action#HandleCodeAction(l:code_action, {}) + endfor + endif + + silent doautocmd User ALECompletePost +endfunction + +function! ale#completion#Done() abort + silent! pclose + + call ale#completion#RestoreCompletionOptions() + + let s:last_done_pos = getpos('.')[1:2] +endfunction + +augroup ALECompletionActions + autocmd! + + autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item) +augroup END + +function! s:Setup(enabled) abort + augroup ALECompletionGroup + autocmd! + + if a:enabled + autocmd TextChangedI * call ale#completion#Queue() + autocmd CompleteDone * call ale#completion#Done() + endif + augroup END + + if !a:enabled + augroup! ALECompletionGroup + endif +endfunction + +function! ale#completion#Enable() abort + let g:ale_completion_enabled = 1 + call s:Setup(1) +endfunction + +function! ale#completion#Disable() abort + let g:ale_completion_enabled = 0 + call s:Setup(0) +endfunction diff --git a/autoload/ale/completion/python.vim b/autoload/ale/completion/python.vim new file mode 100644 index 00000000..6b65c5b5 --- /dev/null +++ b/autoload/ale/completion/python.vim @@ -0,0 +1,3 @@ +function! ale#completion#python#CompletionItemFilter(buffer, item) abort + return a:item.label !~# '\v^__[a-z_]+__' +endfunction diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim new file mode 100644 index 00000000..da3b6922 --- /dev/null +++ b/autoload/ale/cursor.vim @@ -0,0 +1,191 @@ +scriptencoding utf-8 +" Author: w0rp +" Author: João Paulo S. de Souza +" Description: Echoes lint message for the current line, if any + +" Controls the milliseconds delay before echoing a message. +let g:ale_echo_delay = get(g:, 'ale_echo_delay', 10) +" A string format for the echoed message. +let g:ale_echo_msg_format = get(g:, 'ale_echo_msg_format', '%code: %%s') + +let s:cursor_timer = -1 + +" A wrapper for echon so we can test messages we echo in Vader tests. +function! ale#cursor#Echom(message) abort + if mode() is# 'n' + " no-custom-checks + exec "norm! :echom a:message\n" + endif +endfunction + +function! ale#cursor#TruncatedEcho(original_message) abort + let l:message = a:original_message + " Change tabs to spaces. + let l:message = substitute(l:message, "\t", ' ', 'g') + " Remove any newlines in the message. + let l:message = substitute(l:message, "\n", '', 'g') + " Convert indentation groups into single spaces for better legibility when + " put on a single line + let l:message = substitute(l:message, ' \+', ' ', 'g') + + " We need to remember the setting for shortmess and reset it again. + let l:shortmess_options = &l:shortmess + + try + let l:cursor_position = getpos('.') + + " The message is truncated and saved to the history. + silent! setlocal shortmess+=T + + try + call ale#cursor#Echom(l:message) + catch /^Vim\%((\a\+)\)\=:E523/ + " Fallback into manual truncate (#1987) + let l:winwidth = winwidth(0) + + if l:winwidth < strdisplaywidth(l:message) + " Truncate message longer than window width with trailing '...' + let l:message = l:message[:l:winwidth - 4] . '...' + endif + + exec 'echomsg l:message' + catch /E481/ + " Do nothing if running from a visual selection. + endtry + + " 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 != getpos('.') + call setpos('.', l:cursor_position) + endif + finally + let &l:shortmess = l:shortmess_options + endtry +endfunction + +function! s:StopCursorTimer() abort + if s:cursor_timer != -1 + call timer_stop(s:cursor_timer) + let s:cursor_timer = -1 + endif +endfunction + +function! ale#cursor#EchoCursorWarning(...) abort + let l:buffer = bufnr('') + + if !g:ale_echo_cursor && !g:ale_cursor_detail + return + endif + + " Only echo the warnings in normal mode, otherwise we will get problems. + if mode(1) isnot# 'n' + return + endif + + if ale#ShouldDoNothing(l:buffer) + return + endif + + let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer) + + if g:ale_echo_cursor + if !empty(l:loc) + let l:format = ale#Var(l:buffer, 'echo_msg_format') + let l:msg = ale#GetLocItemMessage(l:loc, l:format) + call ale#cursor#TruncatedEcho(l:msg) + let l:info.echoed = 1 + elseif get(l:info, 'echoed') + " We'll only clear the echoed message when moving off errors once, + " so we don't continually clear the echo line. + " + " no-custom-checks + echo + let l:info.echoed = 0 + endif + endif + + if g:ale_cursor_detail + if !empty(l:loc) + call s:ShowCursorDetailForItem(l:loc, {'stay_here': 1}) + else + call ale#preview#CloseIfTypeMatches('ale-preview') + endif + endif +endfunction + +function! ale#cursor#EchoCursorWarningWithDelay() abort + let l:buffer = bufnr('') + + if !g:ale_echo_cursor && !g:ale_cursor_detail + return + endif + + " Only echo the warnings in normal mode, otherwise we will get problems. + if mode(1) isnot# 'n' + return + endif + + call s:StopCursorTimer() + + let l:pos = getpos('.')[0:2] + + if !exists('w:last_pos') + let w:last_pos = [0, 0, 0] + endif + + " Check the current buffer, line, and column number against the last + " recorded position. If the position has actually changed, *then* + " we should echo something. Otherwise we can end up doing processing + " the echo message far too frequently. + if l:pos != w:last_pos + let l:delay = ale#Var(l:buffer, 'echo_delay') + + let w:last_pos = l:pos + let s:cursor_timer = timer_start( + \ l:delay, + \ function('ale#cursor#EchoCursorWarning') + \) + endif +endfunction + +function! s:ShowCursorDetailForItem(loc, options) abort + let l:stay_here = get(a:options, 'stay_here', 0) + + let s:last_detailed_line = line('.') + let l:message = get(a:loc, 'detail', a:loc.text) + let l:lines = split(l:message, "\n") + + if g:ale_floating_preview || g:ale_detail_to_floating_preview + call ale#floating_preview#Show(l:lines) + else + call ale#preview#Show(l:lines, {'stay_here': l:stay_here}) + + " Clear the echo message if we manually displayed details. + if !l:stay_here + " no-custom-checks + echo + endif + endif +endfunction + +function! ale#cursor#ShowCursorDetail() abort + let l:buffer = bufnr('') + + " Only echo the warnings in normal mode, otherwise we will get problems. + if mode() isnot# 'n' + return + endif + + if ale#ShouldDoNothing(l:buffer) + return + endif + + call s:StopCursorTimer() + + let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer) + + if !empty(l:loc) + call s:ShowCursorDetailForItem(l:loc, {'stay_here': 0}) + endif +endfunction diff --git a/autoload/ale/d.vim b/autoload/ale/d.vim new file mode 100644 index 00000000..0e232203 --- /dev/null +++ b/autoload/ale/d.vim @@ -0,0 +1,16 @@ +" Author: Auri +" Description: Functions for integrating with D linters. + +function! ale#d#FindDUBConfig(buffer) abort + " Find a DUB configuration file in ancestor paths. + " The most DUB-specific names will be tried first. + for l:possible_filename in ['dub.sdl', 'dub.json', 'package.json'] + let l:dub_file = ale#path#FindNearestFile(a:buffer, l:possible_filename) + + if !empty(l:dub_file) + return l:dub_file + endif + endfor + + return '' +endfunction diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim new file mode 100644 index 00000000..fe145baf --- /dev/null +++ b/autoload/ale/debugging.vim @@ -0,0 +1,368 @@ +" Author: w0rp +" Description: This file implements debugging information for ALE + +let g:ale_info_default_mode = get(g:, 'ale_info_default_mode', 'preview') + +let s:global_variable_list = [ +\ 'ale_cache_executable_check_failures', +\ 'ale_change_sign_column_color', +\ 'ale_command_wrapper', +\ 'ale_completion_delay', +\ 'ale_completion_enabled', +\ 'ale_completion_max_suggestions', +\ 'ale_disable_lsp', +\ 'ale_echo_cursor', +\ 'ale_echo_msg_error_str', +\ 'ale_echo_msg_format', +\ 'ale_echo_msg_info_str', +\ 'ale_echo_msg_warning_str', +\ 'ale_enabled', +\ 'ale_fix_on_save', +\ 'ale_fixers', +\ 'ale_history_enabled', +\ 'ale_info_default_mode', +\ 'ale_history_log_output', +\ 'ale_keep_list_window_open', +\ 'ale_lint_delay', +\ 'ale_lint_on_enter', +\ 'ale_lint_on_filetype_changed', +\ 'ale_lint_on_insert_leave', +\ 'ale_lint_on_save', +\ 'ale_lint_on_text_changed', +\ 'ale_linter_aliases', +\ 'ale_linters', +\ 'ale_linters_explicit', +\ 'ale_linters_ignore', +\ 'ale_list_vertical', +\ 'ale_list_window_size', +\ 'ale_loclist_msg_format', +\ 'ale_max_buffer_history_size', +\ 'ale_max_signs', +\ 'ale_maximum_file_size', +\ 'ale_open_list', +\ 'ale_pattern_options', +\ 'ale_pattern_options_enabled', +\ 'ale_root', +\ 'ale_set_balloons', +\ 'ale_set_highlights', +\ 'ale_set_loclist', +\ 'ale_set_quickfix', +\ 'ale_set_signs', +\ 'ale_sign_column_always', +\ 'ale_sign_error', +\ 'ale_sign_info', +\ 'ale_sign_offset', +\ 'ale_sign_style_error', +\ 'ale_sign_style_warning', +\ 'ale_sign_warning', +\ 'ale_sign_highlight_linenrs', +\ 'ale_type_map', +\ 'ale_use_neovim_diagnostics_api', +\ 'ale_use_global_executables', +\ 'ale_virtualtext_cursor', +\ 'ale_warn_about_trailing_blank_lines', +\ 'ale_warn_about_trailing_whitespace', +\] + +function! s:Echo(message) abort + " no-custom-checks + echo a:message +endfunction + +function! s:GetLinterVariables(filetype, exclude_linter_names) abort + let l:variable_list = [] + let l:filetype_parts = split(a:filetype, '\.') + + for l:key in keys(g:) + " Extract variable names like: 'ale_python_flake8_executable' + let l:match = matchlist(l:key, '\v^ale_([^_]+)_([^_]+)_.+$') + + " Include matching variables. + if !empty(l:match) + \&& index(l:filetype_parts, l:match[1]) >= 0 + \&& index(a:exclude_linter_names, l:match[2]) == -1 + call add(l:variable_list, l:key) + endif + endfor + + call sort(l:variable_list) + + return l:variable_list +endfunction + +function! s:EchoLinterVariables(variable_list) abort + for l:key in a:variable_list + call s:Echo('let g:' . l:key . ' = ' . string(g:[l:key])) + + if has_key(b:, l:key) + call s:Echo('let b:' . l:key . ' = ' . string(b:[l:key])) + endif + endfor +endfunction + +function! s:EchoGlobalVariables() abort + for l:key in s:global_variable_list + call s:Echo('let g:' . l:key . ' = ' . string(get(g:, l:key, v:null))) + + if has_key(b:, l:key) + call s:Echo('let b:' . l:key . ' = ' . string(b:[l:key])) + endif + endfor +endfunction + +" Echo a command that was run. +function! s:EchoCommand(item) abort + let l:status_message = a:item.status + + " Include the exit code in output if we have it. + if a:item.status is# 'finished' + let l:status_message .= ' - exit code ' . a:item.exit_code + endif + + call s:Echo('(' . l:status_message . ') ' . string(a:item.command)) + + if g:ale_history_log_output && has_key(a:item, 'output') + if empty(a:item.output) + call s:Echo('') + call s:Echo('<<>>') + call s:Echo('') + else + call s:Echo('') + call s:Echo('<<>>') + + for l:line in a:item.output + call s:Echo(l:line) + endfor + + call s:Echo('<<>>') + call s:Echo('') + endif + endif +endfunction + +" Echo the results of an executable check. +function! s:EchoExecutable(item) abort + call s:Echo(printf( + \ '(executable check - %s) %s', + \ a:item.status ? 'success' : 'failure', + \ a:item.command, + \)) +endfunction + +function! s:EchoCommandHistory() abort + let l:buffer = bufnr('%') + + for l:item in ale#history#Get(l:buffer) + if l:item.job_id is# 'executable' + call s:EchoExecutable(l:item) + else + call s:EchoCommand(l:item) + endif + endfor +endfunction + +function! s:EchoLinterAliases(all_linters) abort + let l:first = 1 + + for l:linter in a:all_linters + if !empty(l:linter.aliases) + if l:first + call s:Echo(' Linter Aliases:') + endif + + let l:first = 0 + + call s:Echo(string(l:linter.name) . ' -> ' . string(l:linter.aliases)) + endif + endfor +endfunction + +function! s:EchoLSPErrorMessages(all_linter_names) abort + let l:lsp_error_messages = get(g:, 'ale_lsp_error_messages', {}) + let l:header_echoed = 0 + + for l:linter_name in a:all_linter_names + let l:error_list = get(l:lsp_error_messages, l:linter_name, []) + + if !empty(l:error_list) + if !l:header_echoed + call s:Echo(' LSP Error Messages:') + call s:Echo('') + endif + + call s:Echo('(Errors for ' . l:linter_name . ')') + + for l:message in l:error_list + for l:line in split(l:message, "\n") + call s:Echo(l:line) + endfor + endfor + endif + endfor +endfunction + +function! s:GetIgnoredLinters(buffer, enabled_linters) abort + let l:filetype = &filetype + let l:ignore_config = ale#Var(a:buffer, 'linters_ignore') + let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') + + if ( + \ !empty(l:ignore_config) + \ || l:disable_lsp is 1 + \ || l:disable_lsp is v:true + \ || (l:disable_lsp is# 'auto' && get(g:, 'lspconfig', 0)) + \) + let l:non_ignored = ale#engine#ignore#Exclude( + \ l:filetype, + \ a:enabled_linters, + \ l:ignore_config, + \ l:disable_lsp, + \) + else + let l:non_ignored = copy(a:enabled_linters) + endif + + call map(l:non_ignored, 'v:val.name') + + return filter( + \ copy(a:enabled_linters), + \ 'index(l:non_ignored, v:val.name) < 0' + \) +endfunction + +function! ale#debugging#Info(...) abort + let l:options = (a:0 > 0) ? a:1 : {} + let l:show_preview_info = get(l:options, 'preview') + + let l:buffer = bufnr('') + let l:filetype = &filetype + + let l:enabled_linters = deepcopy(ale#linter#Get(l:filetype)) + + " But have to build the list of available linters ourselves. + let l:all_linters = [] + let l:linter_variable_list = [] + + for l:part in split(l:filetype, '\.') + let l:aliased_filetype = ale#linter#ResolveFiletype(l:part) + call extend(l:all_linters, ale#linter#GetAll(l:aliased_filetype)) + endfor + + let l:all_names = map(copy(l:all_linters), 'v:val[''name'']') + let l:enabled_names = map(copy(l:enabled_linters), 'v:val[''name'']') + let l:exclude_names = filter(copy(l:all_names), 'index(l:enabled_names, v:val) == -1') + + " Load linter variables to display + " This must be done after linters are loaded. + let l:variable_list = s:GetLinterVariables(l:filetype, l:exclude_names) + + let l:fixers = ale#fix#registry#SuggestedFixers(l:filetype) + let l:fixers = uniq(sort(l:fixers[0] + l:fixers[1])) + let l:fixers_string = join(map(copy(l:fixers), '"\n " . v:val'), '') + + " Get the names of ignored linters. + let l:ignored_names = map( + \ s:GetIgnoredLinters(l:buffer, l:enabled_linters), + \ 'v:val.name' + \) + + call s:Echo(' Current Filetype: ' . l:filetype) + call s:Echo('Available Linters: ' . string(l:all_names)) + call s:EchoLinterAliases(l:all_linters) + call s:Echo(' Enabled Linters: ' . string(l:enabled_names)) + call s:Echo(' Ignored Linters: ' . string(l:ignored_names)) + call s:Echo(' Suggested Fixers:' . l:fixers_string) + " We use this line with only a space to know where to end highlights. + call s:Echo(' ') + + " Only show Linter Variables directive if there are any. + if !empty(l:variable_list) + call s:Echo(' Linter Variables:') + + if l:show_preview_info + call s:Echo('" Press Space to read :help for a setting') + endif + + call s:EchoLinterVariables(l:variable_list) + " We use this line with only a space to know where to end highlights. + call s:Echo(' ') + endif + + call s:Echo(' Global Variables:') + + if l:show_preview_info + call s:Echo('" Press Space to read :help for a setting') + endif + + call s:EchoGlobalVariables() + call s:Echo(' ') + call s:EchoLSPErrorMessages(l:all_names) + call s:Echo(' Command History:') + call s:Echo('') + call s:EchoCommandHistory() +endfunction + +function! ale#debugging#InfoToClipboard() abort + if !has('clipboard') + call s:Echo('clipboard not available. Try :ALEInfoToFile instead.') + + return + endif + + let l:output = execute('call ale#debugging#Info()') + + let @+ = l:output + call s:Echo('ALEInfo copied to your clipboard') +endfunction + +function! ale#debugging#InfoToFile(filename) abort + let l:expanded_filename = expand(a:filename) + + let l:output = execute('call ale#debugging#Info()') + + call writefile(split(l:output, "\n"), l:expanded_filename) + call s:Echo('ALEInfo written to ' . l:expanded_filename) +endfunction + +function! ale#debugging#InfoToPreview() abort + let l:output = execute('call ale#debugging#Info({''preview'': 1})') + + call ale#preview#Show(split(l:output, "\n"), { + \ 'filetype': 'ale-info', + \}) +endfunction + +function! ale#debugging#InfoCommand(...) abort + if len(a:000) > 1 + " no-custom-checks + echom 'Invalid ALEInfo arguments!' + + return + endif + + " Do not show info for the info window itself. + if &filetype is# 'ale-info' + return + endif + + " Get 'echo' from '-echo', if there's an argument. + let l:mode = get(a:000, '')[1:] + + if empty(l:mode) + let l:mode = ale#Var(bufnr(''), 'info_default_mode') + endif + + if l:mode is# 'echo' + call ale#debugging#Info() + elseif l:mode is# 'clip' || l:mode is# 'clipboard' + call ale#debugging#InfoToClipboard() + else + call ale#debugging#InfoToPreview() + endif +endfunction + +function! ale#debugging#InfoToClipboardDeprecatedCommand() abort + " no-custom-checks + echom 'ALEInfoToClipboard is deprecated. Use ALEInfo -clipboard instead.' + call ale#debugging#InfoToClipboard() +endfunction diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim new file mode 100644 index 00000000..e58a5642 --- /dev/null +++ b/autoload/ale/definition.vim @@ -0,0 +1,305 @@ +" Author: w0rp +" Description: Go to definition support for LSP linters. + +let s:go_to_definition_map = {} + +" Enable automatic updates of the tagstack +let g:ale_update_tagstack = get(g:, 'ale_update_tagstack', v:true) +let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer') + +" Used to get the definition map in tests. +function! ale#definition#GetMap() abort + return deepcopy(s:go_to_definition_map) +endfunction + +" Used to set the definition map in tests. +function! ale#definition#SetMap(map) abort + let s:go_to_definition_map = a:map +endfunction + +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('') + 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#FormatTSServerResponse(response_item, options) abort + if get(a:options, 'open_in') is# 'quickfix' + return { + \ 'filename': a:response_item.file, + \ 'lnum': a:response_item.start.line, + \ 'col': a:response_item.start.offset, + \} + else + return { + \ 'filename': a:response_item.file, + \ 'line': a:response_item.start.line, + \ 'column': a:response_item.start.offset, + \} + endif +endfunction + +function! ale#definition#HandleTSServerResponse(conn_id, response) abort + if has_key(a:response, 'request_seq') + \&& has_key(s:go_to_definition_map, a:response.request_seq) + let l:options = remove(s:go_to_definition_map, a:response.request_seq) + + if get(a:response, 'success', v:false) is v:true && !empty(a:response.body) + let l:item_list = [] + + for l:response_item in a:response.body + call add( + \ l:item_list, + \ ale#definition#FormatTSServerResponse(l:response_item, l:options) + \) + endfor + + if empty(l:item_list) + call ale#util#Execute('echom ''No definitions found''') + elseif len(l:item_list) == 1 + let l:filename = l:item_list[0].filename + + if get(l:options, 'open_in') is# 'quickfix' + let l:line = l:item_list[0].lnum + let l:column = l:item_list[0].col + else + let l:line = l:item_list[0].line + let l:column = l:item_list[0].column + endif + + call ale#definition#UpdateTagStack() + call ale#util#Open(l:filename, l:line, l:column, l:options) + else + if get(l:options, 'open_in') is# 'quickfix' + call setqflist([], 'r') + call setqflist(l:item_list, 'a') + call ale#util#Execute('cc 1') + else + call ale#definition#UpdateTagStack() + call ale#preview#ShowSelection(l:item_list, l:options) + endif + endif + endif + endif +endfunction + +function! ale#definition#FormatLSPResponse(response_item, options) abort + if has_key(a:response_item, 'targetUri') + " LocationLink items use targetUri + let l:uri = a:response_item.targetUri + let l:line = a:response_item.targetRange.start.line + 1 + let l:column = a:response_item.targetRange.start.character + 1 + else + " LocationLink items use uri + let l:uri = a:response_item.uri + let l:line = a:response_item.range.start.line + 1 + let l:column = a:response_item.range.start.character + 1 + endif + + if get(a:options, 'open_in') is# 'quickfix' + return { + \ 'filename': ale#util#ToResource(l:uri), + \ 'lnum': l:line, + \ 'col': l:column, + \} + else + return { + \ 'filename': ale#util#ToResource(l:uri), + \ 'line': l:line, + \ 'column': l:column, + \} + endif +endfunction + +function! ale#definition#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:go_to_definition_map, a:response.id) + let l:options = remove(s:go_to_definition_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', v:null) + + if type(l:result) is v:t_dict + let l:result = [l:result] + elseif type(l:result) isnot v:t_list + let l:result = [] + endif + + let l:item_list = [] + + for l:response_item in l:result + call add(l:item_list, + \ ale#definition#FormatLSPResponse(l:response_item, l:options) + \) + endfor + + if empty(l:item_list) + call ale#util#Execute('echom ''No definitions found''') + elseif len(l:item_list) == 1 + call ale#definition#UpdateTagStack() + + let l:uri = ale#util#ToURI(l:item_list[0].filename) + + if get(l:options, 'open_in') is# 'quickfix' + let l:line = l:item_list[0].lnum + let l:column = l:item_list[0].col + else + let l:line = l:item_list[0].line + let l:column = l:item_list[0].column + endif + + let l:uri_handler = ale#uri#GetURIHandler(l:uri) + + if l:uri_handler is# v:null + let l:filename = ale#path#FromFileURI(l:uri) + call ale#util#Open(l:filename, l:line, l:column, l:options) + else + call l:uri_handler.OpenURILink(l:uri, l:line, l:column, l:options, a:conn_id) + endif + else + if get(l:options, 'open_in') is# 'quickfix' + call setqflist([], 'r') + call setqflist(l:item_list, 'a') + call ale#util#Execute('cc 1') + else + call ale#definition#UpdateTagStack() + call ale#preview#ShowSelection(l:item_list, l:options) + endif + endif + endif +endfunction + +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') + call ale#lsp#RegisterCallback(l:id, l:Callback) + + if a:linter.lsp is# 'tsserver' + if a:capability is# 'definition' + let l:message = ale#lsp#tsserver_message#Definition( + \ l:buffer, + \ a:line, + \ a:column + \) + elseif a:capability is# 'typeDefinition' + let l:message = ale#lsp#tsserver_message#TypeDefinition( + \ l:buffer, + \ a:line, + \ a:column + \) + elseif a:capability is# 'implementation' + let l:message = ale#lsp#tsserver_message#Implementation( + \ l:buffer, + \ a:line, + \ a:column + \) + endif + else + " Send a message saying the buffer has changed first, or the + " definition position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:buffer) + + " 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. + 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) + elseif a:capability is# 'implementation' + let l:message = ale#lsp#message#Implementation(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': get(a:options, 'open_in', 'current-buffer'), + \} +endfunction + +function! s:GoToLSPDefinition(linter, options, capability) abort + 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, 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#lsp_linter#GetEnabled(bufnr('')) + call s:GoToLSPDefinition(l:linter, a:options, 'definition') + endfor +endfunction + +function! ale#definition#GoToType(options) abort + for l:linter in ale#lsp_linter#GetEnabled(bufnr('')) + call s:GoToLSPDefinition(l:linter, a:options, 'typeDefinition') + endfor +endfunction + +function! ale#definition#GoToImpl(options) abort + for l:linter in ale#lsp_linter#GetEnabled(bufnr('')) + call s:GoToLSPDefinition(l:linter, a:options, 'implementation') + endfor +endfunction + +function! ale#definition#GoToCommandHandler(command, ...) abort + let l:options = {} + + if len(a:000) > 0 + for l:option in a:000 + if l:option is? '-tab' + let l:options.open_in = 'tab' + elseif l:option is? '-split' + let l:options.open_in = 'split' + elseif l:option is? '-vsplit' + let l:options.open_in = 'vsplit' + endif + endfor + endif + + if !has_key(l:options, 'open_in') + let l:default_navigation = ale#Var(bufnr(''), 'default_navigation') + + if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0 + let l:options.open_in = l:default_navigation + endif + endif + + if a:command is# 'type' + call ale#definition#GoToType(l:options) + elseif a:command is# 'implementation' + call ale#definition#GoToImpl(l:options) + else + call ale#definition#GoTo(l:options) + endif +endfunction diff --git a/autoload/ale/dhall.vim b/autoload/ale/dhall.vim new file mode 100644 index 00000000..cc54418f --- /dev/null +++ b/autoload/ale/dhall.vim @@ -0,0 +1,24 @@ +" Author: Pat Brisbin , toastal +" Description: Functions for working with Dhall’s executable + +call ale#Set('dhall_executable', 'dhall') +call ale#Set('dhall_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('dhall_options', '') + +function! ale#dhall#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'dhall_executable') + + " Dhall is written in Haskell and commonly installed with Stack + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'dhall') +endfunction + +function! ale#dhall#GetExecutableWithOptions(buffer) abort + let l:executable = ale#dhall#GetExecutable(a:buffer) + + return l:executable + \ . ale#Pad(ale#Var(a:buffer, 'dhall_options')) +endfunction + +function! ale#dhall#GetCommand(buffer) abort + return '%e ' . ale#Pad(ale#Var(a:buffer, 'dhall_options')) +endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim new file mode 100644 index 00000000..b19b2761 --- /dev/null +++ b/autoload/ale/engine.vim @@ -0,0 +1,763 @@ +" Author: w0rp +" Description: Backend execution and job management +" Executes linters in the background, using NeoVim or Vim 8 jobs + +" Remapping of linter problems. +let g:ale_type_map = get(g:, 'ale_type_map', {}) +let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {}) + +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 + " convert it to a number. We need a number for the other + " functions. + let l:buffer = str2nr(l:key) + + if l:buffer > 0 + " Stop all jobs and clear the results for everything, and delete + " all of the data we stored for the buffer. + call ale#engine#Cleanup(l:buffer) + endif + endfor +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 + +" Check if files are executable, and if they are, remember that they are +" for subsequent calls. We'll keep checking until programs can be executed. +function! ale#engine#IsExecutable(buffer, executable) abort + if empty(a:executable) + " Don't log the executable check if the executable string is empty. + return 0 + endif + + " Check for a cached executable() check. + let l:result = get(s:executable_cache_map, a:executable, v:null) + + if l:result isnot v:null + return l:result + endif + + " Check if the file is executable, and convert -1 to 1. + let l:result = executable(a:executable) isnot 0 + + " Cache the executable check if we found it, or if the option to cache + " failing checks is on. + if l:result || get(g:, 'ale_cache_executable_check_failures') + let s:executable_cache_map[a:executable] = l:result + endif + + if g:ale_history_enabled + call ale#history#Add(a:buffer, l:result, 'executable', a:executable) + endif + + return l:result +endfunction + +function! ale#engine#InitBufferInfo(buffer) abort + if !has_key(g:ale_buffer_info, a:buffer) + " active_linter_list will hold the list of active linter names + " loclist holds the loclist items after all jobs have completed. + let g:ale_buffer_info[a:buffer] = { + \ 'active_linter_list': [], + \ 'active_other_sources_list': [], + \ 'loclist': [], + \} + + return 1 + endif + + return 0 +endfunction + +" This function is documented and part of the public API. +" +" Return 1 if ALE is busy checking a given buffer +function! ale#engine#IsCheckingBuffer(buffer) abort + let l:info = get(g:ale_buffer_info, a:buffer, {}) + + return !empty(get(l:info, 'active_linter_list', [])) + \ || !empty(get(l:info, 'active_other_sources_list', [])) +endfunction + +function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort + let l:info = get(g:ale_buffer_info, a:buffer, {}) + + if empty(l:info) + return + endif + + 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.name isnot# a:linter_name') + endif + + " Make some adjustments to the loclists to fix common problems, and also + " to set default values for loclist items. + let l:linter_loclist = ale#engine#FixLocList( + \ a:buffer, + \ a:linter_name, + \ a:from_other_source, + \ a:loclist, + \) + + " Remove previous items for this linter. + call filter(l:info.loclist, 'v:val.linter_name isnot# a:linter_name') + + " We don't need to add items or sort the list when this list is empty. + if !empty(l:linter_loclist) + " Add the new items. + call extend(l:info.loclist, l:linter_loclist) + + " Sort the loclist again. + " We need a sorted list so we can run a binary search against it + " for efficient lookup of the messages in the cursor handler. + call sort(l:info.loclist, 'ale#util#LocItemCompare') + endif + + if ale#ShouldDoNothing(a:buffer) + return + endif + + call ale#engine#SetResults(a:buffer, l:info.loclist) +endfunction + +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:linter = a:job_info.linter + let l:executable = a:job_info.executable + + " Remove this job from the list. + 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. + if ale#util#InSandbox() + return + endif + + if has('nvim') && !empty(a:output) && empty(a:output[-1]) + call remove(a:output, -1) + endif + + try + 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 + + if type(l:loclist) isnot# v:t_list + " we only expect the list type; don't pass anything else down to + " `ale#engine#HandleLoclist` since it won't understand it + let l:loclist = [] + endif + + call ale#engine#HandleLoclist(l:linter.name, a:buffer, l:loclist, 0) +endfunction + +function! ale#engine#SetResults(buffer, loclist) abort + let l:linting_is_done = !ale#engine#IsCheckingBuffer(a:buffer) + + if g:ale_use_neovim_diagnostics_api + call ale#engine#SendResultsToNeovimDiagnostics(a:buffer, a:loclist) + endif + + " Set signs first. This could potentially fix some line numbers. + " The List could be sorted again here by SetSigns. + if !g:ale_use_neovim_diagnostics_api && g:ale_set_signs + call ale#sign#SetSigns(a:buffer, a:loclist) + endif + + if g:ale_set_quickfix || g:ale_set_loclist + call ale#list#SetLists(a:buffer, a:loclist) + endif + + if exists('*ale#statusline#Update') + " Don't load/run if not already loaded. + call ale#statusline#Update(a:buffer, a:loclist) + endif + + if !g:ale_use_neovim_diagnostics_api && g:ale_set_highlights + call ale#highlight#SetHighlights(a:buffer, a:loclist) + endif + + if !g:ale_use_neovim_diagnostics_api + \&& (g:ale_virtualtext_cursor is# 'all' || g:ale_virtualtext_cursor == 2) + call ale#virtualtext#SetTexts(a:buffer, a:loclist) + endif + + if l:linting_is_done + if g:ale_echo_cursor + " Try and echo the warning now. + " This will only do something meaningful if we're in normal mode. + call ale#cursor#EchoCursorWarning() + endif + + if !g:ale_use_neovim_diagnostics_api + \&& (g:ale_virtualtext_cursor is# 'current' || g:ale_virtualtext_cursor == 1) + " Try and show the warning now. + " This will only do something meaningful if we're in normal mode. + call ale#virtualtext#ShowCursorWarning() + endif + + " Reset the save event marker, used for opening windows, etc. + call setbufvar(a:buffer, 'ale_save_event_fired', 0) + " Set a marker showing how many times a buffer has been checked. + call setbufvar( + \ a:buffer, + \ 'ale_linted', + \ getbufvar(a:buffer, 'ale_linted', 0) + 1 + \) + + " Automatically remove all managed temporary files and directories + " now that all jobs have completed. + call ale#command#RemoveManagedFiles(a:buffer) + + " Call user autocommands. This allows users to hook into ALE's lint cycle. + silent doautocmd User ALELintPost + endif +endfunction + +function! ale#engine#SendResultsToNeovimDiagnostics(buffer, loclist) abort + if !has('nvim-0.6') + " We will warn the user on startup as well if they try to set + " g:ale_use_neovim_diagnostics_api outside of a Neovim context. + return + endif + + " Keep the Lua surface area really small in the VimL part of ALE, + " and just require the diagnostics.lua module on demand. + let l:SendDiagnostics = luaeval('require("ale.diagnostics").send') + call l:SendDiagnostics(a:buffer, a:loclist) +endfunction + +function! s:RemapItemTypes(type_map, loclist) abort + for l:item in a:loclist + let l:key = l:item.type + \ . (get(l:item, 'sub_type', '') is# 'style' ? 'S' : '') + let l:new_key = get(a:type_map, l:key, '') + + if l:new_key is# 'E' + \|| l:new_key is# 'ES' + \|| l:new_key is# 'W' + \|| l:new_key is# 'WS' + \|| l:new_key is# 'I' + let l:item.type = l:new_key[0] + + if l:new_key is# 'ES' || l:new_key is# 'WS' + let l:item.sub_type = 'style' + elseif has_key(l:item, 'sub_type') + call remove(l:item, 'sub_type') + endif + endif + endfor +endfunction + +function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist) abort + let l:mappings = ale#GetFilenameMappings(a:buffer, a:linter_name) + + if !empty(l:mappings) + " We need to apply reverse filename mapping here. + let l:mappings = ale#filename_mapping#Invert(l:mappings) + endif + + let l:bufnr_map = {} + let l:new_loclist = [] + + " Some errors have line numbers beyond the end of the file, + " so we need to adjust them so they set the error at the last line + " of the file instead. + let l:last_line_number = ale#util#GetLineCount(a:buffer) + + for l:old_item in a:loclist + " Copy the loclist item with some default values and corrections. + " + " line and column numbers will be converted to numbers. + " The buffer will default to the buffer being checked. + " The vcol setting will default to 0, a byte index. + " The error type will default to 'E' for errors. + " The error number will default to -1. + " + " The line number and text are the only required keys. + " + " The linter_name will be set on the errors so it can be used in + " output, filtering, etc.. + let l:item = { + \ 'bufnr': a:buffer, + \ 'text': l:old_item.text, + \ 'lnum': str2nr(l:old_item.lnum), + \ 'col': str2nr(get(l:old_item, 'col', 0)), + \ 'vcol': 0, + \ 'type': get(l:old_item, 'type', 'E'), + \ 'nr': get(l:old_item, 'nr', -1), + \ 'linter_name': a:linter_name, + \} + + if a:from_other_source + let l:item.from_other_source = 1 + endif + + if has_key(l:old_item, 'code') + let l:item.code = l:old_item.code + endif + + let l:old_name = get(l:old_item, 'filename', '') + + " Map parsed from output to local filesystem files. + if !empty(l:old_name) && !empty(l:mappings) + let l:old_name = ale#filename_mapping#Map(l:old_name, l:mappings) + endif + + if !empty(l:old_name) && !ale#path#IsTempName(l:old_name) + " Use the filename given. + " Temporary files are assumed to be for this buffer, + " and the filename is not included then, because it looks bad + " in the loclist window. + let l:filename = l:old_name + let l:item.filename = l:filename + + if has_key(l:old_item, 'bufnr') + " If a buffer number is also given, include that too. + " If Vim detects that he buffer number is valid, it will + " be used instead of the filename. + let l:item.bufnr = l:old_item.bufnr + elseif has_key(l:bufnr_map, l:filename) + " Get the buffer number from the map, which can be faster. + let l:item.bufnr = l:bufnr_map[l:filename] + else + " Look up the buffer number. + let l:item.bufnr = bufnr(l:filename) + let l:bufnr_map[l:filename] = l:item.bufnr + endif + elseif has_key(l:old_item, 'bufnr') + let l:item.bufnr = l:old_item.bufnr + endif + + if has_key(l:old_item, 'detail') + let l:item.detail = l:old_item.detail + endif + + " Pass on a end_col key if set, used for highlights. + if has_key(l:old_item, 'end_col') + let l:item.end_col = str2nr(l:old_item.end_col) + endif + + if has_key(l:old_item, 'end_lnum') + let l:item.end_lnum = str2nr(l:old_item.end_lnum) + + " When the error ends after the end of the file, put it at the + " end. This is only done for the current buffer. + if l:item.bufnr == a:buffer && l:item.end_lnum > l:last_line_number + let l:item.end_lnum = l:last_line_number + endif + endif + + if has_key(l:old_item, 'sub_type') + let l:item.sub_type = l:old_item.sub_type + endif + + if l:item.lnum < 1 + " When errors appear before line 1, put them at line 1. + let l:item.lnum = 1 + elseif l:item.bufnr == a:buffer && l:item.lnum > l:last_line_number + " When errors go beyond the end of the file, put them at the end. + " This is only done for the current buffer. + let l:item.lnum = l:last_line_number + elseif get(l:old_item, 'vcol', 0) + " Convert virtual column positions to byte positions. + " The positions will be off if the buffer has changed recently. + let l:line = getbufline(a:buffer, l:item.lnum)[0] + + let l:item.col = ale#util#Col(l:line, l:item.col) + + if has_key(l:item, 'end_col') + let l:end_line = get(l:item, 'end_lnum', l:line) != l:line + \ ? getbufline(a:buffer, l:item.end_lnum)[0] + \ : l:line + + let l:item.end_col = ale#util#Col(l:end_line, l:item.end_col) + endif + endif + + call add(l:new_loclist, l:item) + endfor + + let l:type_map = get(ale#Var(a:buffer, 'type_map'), a:linter_name, {}) + + if !empty(l:type_map) + call s:RemapItemTypes(l:type_map, l:new_loclist) + endif + + return l:new_loclist +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 + " TODO: Emit deprecation warning here later. + return ale#command#EscapeCommandPart(a:command_part) +endfunction + +" Run a job. +" +" 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 + endif + + let l:cwd = a:options.cwd + let l:executable = a:options.executable + let l:buffer = a:options.buffer + let l:linter = a:options.linter + let l:output_stream = a:options.output_stream + let l:read_buffer = a:options.read_buffer && !a:options.lint_file + let l:info = g:ale_buffer_info[l:buffer] + + let l:Callback = function('s:HandleExit', [{ + \ 'linter': l:linter, + \ 'executable': l:executable, + \}]) + let l:result = ale#command#Run(l:buffer, l:command, l:Callback, { + \ 'cwd': l:cwd, + \ 'output_stream': l:output_stream, + \ 'executable': l:executable, + \ 'read_buffer': l:read_buffer, + \ 'log_output': 1, + \ 'filename_mappings': ale#GetFilenameMappings(l:buffer, l:linter.name), + \}) + + " Only proceed if the job is being run. + if empty(l:result) + return 0 + endif + + call ale#engine#MarkLinterActive(l:info, l:linter) + + silent doautocmd User ALEJobStarted + + return 1 +endfunction + +function! s:StopCurrentJobs(buffer, clear_lint_file_jobs, linter_slots) abort + let l:info = get(g:ale_buffer_info, a:buffer, {}) + call ale#command#StopJobs(a:buffer, 'linter') + + " Update the active linter list, clearing out anything not running. + if a:clear_lint_file_jobs + call ale#command#StopJobs(a:buffer, 'file_linter') + let l:info.active_linter_list = [] + else + let l:lint_file_map = {} + + " Use a previously computed map of `lint_file` values to find + " linters that are used for linting files. + for [l:lint_file, l:linter] in a:linter_slots + if l:lint_file is 1 + let l:lint_file_map[l:linter.name] = 1 + endif + endfor + + " Keep jobs for linting files when we're only linting buffers. + call filter(l:info.active_linter_list, 'get(l:lint_file_map, v:val.name)') + endif +endfunction + +function! ale#engine#Stop(buffer) abort + call s:StopCurrentJobs(a:buffer, 1, []) +endfunction + +function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort + " Figure out which linters are still enabled, and remove + " problems for linters which are no longer enabled. + " Problems from other sources will be kept. + let l:name_map = {} + + for l:linter in a:linters + let l:name_map[l:linter.name] = 1 + endfor + + call filter( + \ get(g:ale_buffer_info[a:buffer], 'loclist', []), + \ 'get(v:val, ''from_other_source'') || get(l:name_map, get(v:val, ''linter_name''))', + \) +endfunction + +function! s:AddProblemsFromOtherBuffers(buffer, linters) abort + let l:filename = expand('#' . a:buffer . ':p') + let l:loclist = [] + let l:name_map = {} + + " Build a map of the active linters. + for l:linter in a:linters + let l:name_map[l:linter.name] = 1 + endfor + + " Find the items from other buffers, for the linters that are enabled. + for l:info in values(g:ale_buffer_info) + for l:item in l:info.loclist + if has_key(l:item, 'filename') + \&& l:item.filename is# l:filename + \&& has_key(l:name_map, l:item.linter_name) + " Copy the items and set the buffer numbers to this one. + let l:new_item = copy(l:item) + let l:new_item.bufnr = a:buffer + call add(l:loclist, l:new_item) + endif + endfor + endfor + + if !empty(l:loclist) + call sort(l:loclist, function('ale#util#LocItemCompareWithText')) + call uniq(l:loclist, function('ale#util#LocItemCompareWithText')) + + " Set the loclist variable, used by some parts of ALE. + let g:ale_buffer_info[a:buffer].loclist = l:loclist + call ale#engine#SetResults(a:buffer, l:loclist) + endif +endfunction + +function! s:RunIfExecutable(buffer, linter, lint_file, executable) abort + if ale#command#IsDeferred(a:executable) + let a:executable.result_callback = { + \ executable -> s:RunIfExecutable( + \ a:buffer, + \ a:linter, + \ a:lint_file, + \ 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:lint_file ? 'file_linter' : 'linter' + call setbufvar(a:buffer, 'ale_job_type', l:job_type) + + " Get the cwd for the linter and set it before we call GetCommand. + " This will ensure that ale#command#Run uses it by default. + let l:cwd = ale#linter#GetCwd(a:buffer, a:linter) + + if l:cwd isnot v:null + call ale#command#SetCwd(a:buffer, l:cwd) + endif + + let l:command = ale#linter#GetCommand(a:buffer, a:linter) + + if l:cwd isnot v:null + call ale#command#ResetCwd(a:buffer) + endif + + let l:options = { + \ 'cwd': l:cwd, + \ 'executable': a:executable, + \ 'buffer': a:buffer, + \ 'linter': a:linter, + \ 'output_stream': get(a:linter, 'output_stream', 'stdout'), + \ 'read_buffer': a:linter.read_buffer, + \ 'lint_file': a:lint_file, + \} + + 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. +function! s:RunLinter(buffer, linter, lint_file) abort + if !empty(a:linter.lsp) + return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter) + else + let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) + + return s:RunIfExecutable(a:buffer, a:linter, a:lint_file, l:executable) + endif + + return 0 +endfunction + +function! s:GetLintFileSlots(buffer, linters) abort + let l:linter_slots = [] + + for l:linter in a:linters + let l:LintFile = l:linter.lint_file + + if type(l:LintFile) is v:t_func + let l:LintFile = l:LintFile(a:buffer) + endif + + call add(l:linter_slots, [l:LintFile, l:linter]) + endfor + + return l:linter_slots +endfunction + +function! s:GetLintFileValues(slots, Callback) abort + let l:deferred_list = [] + let l:new_slots = [] + + for [l:lint_file, l:linter] in a:slots + while ale#command#IsDeferred(l:lint_file) && has_key(l:lint_file, 'value') + " If we've already computed the return value, use it. + let l:lint_file = l:lint_file.value + endwhile + + if ale#command#IsDeferred(l:lint_file) + " If we are going to return the result later, wait for it. + call add(l:deferred_list, l:lint_file) + else + " If we have the value now, coerce it to 0 or 1. + let l:lint_file = l:lint_file is 1 + endif + + call add(l:new_slots, [l:lint_file, l:linter]) + endfor + + if !empty(l:deferred_list) + for l:deferred in l:deferred_list + let l:deferred.result_callback = + \ {-> s:GetLintFileValues(l:new_slots, a:Callback)} + endfor + else + call a:Callback(l:new_slots) + endif +endfunction + +function! s:RunLinters( +\ buffer, +\ linters, +\ slots, +\ should_lint_file, +\ new_buffer, +\) abort + call s:StopCurrentJobs(a:buffer, a:should_lint_file, a:slots) + call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters) + + " We can only clear the results if we aren't checking the buffer. + let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer) + + silent doautocmd User ALELintPre + + for [l:lint_file, l:linter] in a:slots + " Only run lint_file linters if we should. + if !l:lint_file || a:should_lint_file + if s:RunLinter(a:buffer, l:linter, l:lint_file) + " If a single linter ran, we shouldn't clear everything. + let l:can_clear_results = 0 + endif + else + " If we skipped running a lint_file linter still in the list, + " we shouldn't clear everything. + let l:can_clear_results = 0 + endif + endfor + + " Clear the results if we can. This needs to be done when linters are + " disabled, or ALE itself is disabled. + if l:can_clear_results + call ale#engine#SetResults(a:buffer, []) + elseif a:new_buffer + call s:AddProblemsFromOtherBuffers( + \ a:buffer, + \ map(copy(a:slots), 'v:val[1]') + \) + endif +endfunction + +function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort + " Initialise the buffer information if needed. + let l:new_buffer = ale#engine#InitBufferInfo(a:buffer) + + call s:GetLintFileValues( + \ s:GetLintFileSlots(a:buffer, a:linters), + \ { + \ slots -> s:RunLinters( + \ a:buffer, + \ a:linters, + \ slots, + \ a:should_lint_file, + \ l:new_buffer, + \ ) + \ } + \) +endfunction + +" Clean up a buffer. +" +" This function will stop all current jobs for the buffer, +" clear the state of everything, and remove the Dictionary for managing +" the buffer. +function! ale#engine#Cleanup(buffer) abort + " Don't bother with cleanup code when newer NeoVim versions are exiting. + if get(v:, 'exiting', v:null) isnot v:null + return + endif + + if exists('*ale#lsp#CloseDocument') + call ale#lsp#CloseDocument(a:buffer) + endif + + if !has_key(g:ale_buffer_info, a:buffer) + return + endif + + call ale#engine#RunLinters(a:buffer, [], 1) + + call remove(g:ale_buffer_info, a:buffer) +endfunction + +" Given a buffer number, return the warnings and errors for a given buffer. +function! ale#engine#GetLoclist(buffer) abort + if !has_key(g:ale_buffer_info, a:buffer) + return [] + endif + + return g:ale_buffer_info[a:buffer].loclist +endfunction diff --git a/autoload/ale/engine/ignore.vim b/autoload/ale/engine/ignore.vim new file mode 100644 index 00000000..8ac36eb5 --- /dev/null +++ b/autoload/ale/engine/ignore.vim @@ -0,0 +1,97 @@ +" Author: w0rp +" Description: Code for ignoring linters. Only loaded and if configured. + +" A map for remapping lspconfig server names to linter names or aliases in +" ALE. We should change the names where they will conflict with names in ALE. +" +" Notes on names from nvim-lspconfig not included here. +" +" * 'rubocop' is run in a language server mode +" * 'eslint' is run via 'vscode-eslint-language-server' +let s:lspconfig_map = { +\ 'als': 'adals', +\ 'ansiblels': 'ansible-language-server', +\ 'bicep': 'bicep_language_server', +\ 'cmake': 'cmake_language_server', +\ 'denols': 'deno', +\ 'erlangls': 'erlang_ls', +\ 'html': 'vscodehtml', +\ 'ocamlls': 'ocaml-language-server', +\ 'ols': 'odin-lsp', +\ 'puppet': 'puppet_languageserver', +\} + +" Given a filetype and a configuration for ignoring linters, return a List of +" Strings for linter names to ignore. +function! ale#engine#ignore#GetList(filetype, config) abort + if type(a:config) is v:t_list + return a:config + endif + + if type(a:config) is v:t_dict + let l:names_to_remove = [] + + for l:part in split(a:filetype , '\.') + call extend(l:names_to_remove, get(a:config, l:part, [])) + endfor + + return l:names_to_remove + endif + + return [] +endfunction + +" This function can be mocked in tests. +function! ale#engine#ignore#GetLSPConfigNames() abort + return luaeval('require ''ale.util''.configured_lspconfig_servers()') +endfunction + +function! s:GetMappedLSPConfigNames() abort + " Check the lspconfig flag before calling luaeval. + if !get(g:, 'lspconfig', 0) + return [] + endif + + let l:lspconfig_servers = ale#engine#ignore#GetLSPConfigNames() + + return map( + \ !empty(l:lspconfig_servers) ? l:lspconfig_servers : [], + \ {_, val -> get(s:lspconfig_map, val, val) } + \) +endfunction + +" Given a List of linter descriptions, exclude the linters to be ignored. +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) + + " If configured to automatically ignore otherwise configured LSP linter + " names, add them to the names to remove. This could ignore linters + " with matching names that are not marked as LSP linters. + if a:disable_lsp is# 'auto' + call extend(l:names_to_remove, s:GetMappedLSPConfigNames()) + endif + + let l:ignore_all_lsps = a:disable_lsp is 1 || a:disable_lsp is v:true + let l:filtered_linters = [] + + for l:linter in a:all_linters + let l:should_include = index(l:names_to_remove, l:linter.name) == -1 + let l:i = 0 + + while l:should_include && l:i < len(l:linter.aliases) + let l:name = l:linter.aliases[l:i] + let l:should_include = index(l:names_to_remove, l:name) == -1 + let l:i += 1 + endwhile + + if l:should_include && l:ignore_all_lsps + let l:should_include = empty(get(l:linter, 'lsp')) + endif + + if l:should_include + call add(l:filtered_linters, l:linter) + endif + endfor + + return l:filtered_linters +endfunction diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim new file mode 100644 index 00000000..c85deceb --- /dev/null +++ b/autoload/ale/events.vim @@ -0,0 +1,253 @@ +" Author: w0rp +" Description: ALE functions for autocmd events. + +" Get the number of milliseconds since some vague, but consistent, point in +" the past. +" +" This function can be used for timing execution, etc. +" +" The time will be returned as a Number. +function! ale#events#ClockMilliseconds() abort + return float2nr(reltimefloat(reltime()) * 1000) +endfunction + +function! ale#events#QuitEvent(buffer) abort + " Remember when ALE is quitting for BufWrite, etc. + call setbufvar(a:buffer, 'ale_quitting', ale#events#ClockMilliseconds()) +endfunction + +function! ale#events#QuitRecently(buffer) abort + let l:time = getbufvar(a:buffer, 'ale_quitting', 0) + + return l:time && ale#events#ClockMilliseconds() - l:time < 1000 +endfunction + +function! ale#events#SaveEvent(buffer) abort + let l:should_lint = ale#Var(a:buffer, 'enabled') && g:ale_lint_on_save + + if l:should_lint + call setbufvar(a:buffer, 'ale_save_event_fired', 1) + endif + + if ale#Var(a:buffer, 'fix_on_save') && !ale#events#QuitRecently(a:buffer) + let l:will_fix = ale#fix#Fix(a:buffer, 'save_file') + let l:should_lint = l:should_lint && !l:will_fix + endif + + if l:should_lint && !ale#events#QuitRecently(a:buffer) + call ale#Queue(0, 'lint_file', a:buffer) + endif +endfunction + +function! ale#events#LintOnEnter(buffer) abort + " Unmark a file as being changed outside of Vim after we try to check it. + call setbufvar(a:buffer, 'ale_file_changed', 0) + + if ale#Var(a:buffer, 'enabled') && g:ale_lint_on_enter + call ale#Queue(0, 'lint_file', a:buffer) + endif +endfunction + +function! ale#events#ReadOrEnterEvent(buffer) abort + " Apply pattern options if the variable is set. + if get(g:, 'ale_pattern_options_enabled', 1) + \&& !empty(get(g:, 'ale_pattern_options')) + call ale#pattern_options#SetOptions(a:buffer) + endif + + " When entering a buffer, we are no longer quitting it. + call setbufvar(a:buffer, 'ale_quitting', 0) + let l:filetype = getbufvar(a:buffer, '&filetype') + call setbufvar(a:buffer, 'ale_original_filetype', l:filetype) + + " If the file changed outside of Vim, check it on BufEnter,BufRead + if getbufvar(a:buffer, 'ale_file_changed') + call ale#events#LintOnEnter(a:buffer) + endif +endfunction + +function! ale#events#FileTypeEvent(buffer, new_filetype) abort + " The old filetype will be set to an empty string by the BuFEnter event, + " and not linting when the old filetype hasn't been set yet prevents + " buffers being checked when you enter them when linting on enter is off. + let l:old_filetype = getbufvar(a:buffer, 'ale_original_filetype', v:null) + + if l:old_filetype isnot v:null + \&& !empty(a:new_filetype) + \&& a:new_filetype isnot# l:old_filetype + " Remember what the new filetype is. + call setbufvar(a:buffer, 'ale_original_filetype', a:new_filetype) + + if g:ale_lint_on_filetype_changed + call ale#Queue(300, 'lint_file', a:buffer) + endif + endif +endfunction + +function! ale#events#FileChangedEvent(buffer) abort + call setbufvar(a:buffer, 'ale_file_changed', 1) + + if bufnr('') == a:buffer + call ale#events#LintOnEnter(a:buffer) + endif +endfunction + +" A timer for emulating InsertLeave. +" +" We only need a single timer, and we'll lint the last buffer we entered +" insert mode on. +if !exists('s:insert_leave_timer') + let s:insert_leave_timer = -1 +endif + +" True if the ModeChanged event exists. +" In this case, ModeChanged will be used instead of InsertLeave emulation. +let s:mode_changed_exists = exists('##ModeChanged') + +function! ale#events#EmulateInsertLeave(buffer) abort + if mode() is# 'n' + call timer_stop(s:insert_leave_timer) + call ale#Queue(0, '', a:buffer) + endif +endfunction + +function! ale#events#InsertEnterEvent(buffer) abort + if g:ale_close_preview_on_insert && exists('*ale#preview#CloseIfTypeMatches') + call ale#preview#CloseIfTypeMatches('ale-preview') + endif + + " Start a repeating timer if the use might not trigger InsertLeave, so we + " can emulate its behavior. + " If the ModeChanged autocmd exists, it will be used instead of this + " timer; as ModeChanged will be sent regardless of how the insert mode is + " exited, including , and . + if ale#Var(a:buffer, 'lint_on_insert_leave') + \&& maparg("\", 'i') isnot# '' + \&& !s:mode_changed_exists + call timer_stop(s:insert_leave_timer) + let s:insert_leave_timer = timer_start( + \ 100, + \ {-> ale#events#EmulateInsertLeave(a:buffer) }, + \ {'repeat': -1} + \) + endif +endfunction + +function! ale#events#InsertLeaveEvent(buffer) abort + " Kill the InsertLeave emulation if the event fired. + " If the ModeChanged event is available, it will be used instead of + " a timer. + if !s:mode_changed_exists + call timer_stop(s:insert_leave_timer) + endif + + if ale#Var(a:buffer, 'lint_on_insert_leave') + call ale#Queue(0, '', a:buffer) + endif + + " Look for a warning to echo as soon as we leave Insert mode. + " The script's position variable used when moving the cursor will + " not be changed here. + " + " We don't echo this message in emulated insert leave mode, as the user + " may want less work to happen on pressing versus + if exists('*ale#engine#Cleanup') + call ale#cursor#EchoCursorWarning() + + if g:ale_virtualtext_cursor is# 'current' || g:ale_virtualtext_cursor is# 1 || g:ale_virtualtext_cursor is# '1' + " Show a virtualtext message if enabled. + call ale#virtualtext#ShowCursorWarning() + endif + endif +endfunction + +function! ale#events#Init() abort + " This value used to be a Boolean as a Number, and is now a String. + let l:text_changed = '' . g:ale_lint_on_text_changed + + augroup ALEEvents + autocmd! + + " These events always need to be set up. + autocmd BufEnter,BufRead * call ale#events#ReadOrEnterEvent(str2nr(expand(''))) + autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand(''))) + + if g:ale_enabled + if l:text_changed is? 'always' + \|| l:text_changed is# '1' + \|| g:ale_lint_on_text_changed is v:true + autocmd TextChanged,TextChangedI * call ale#Queue(ale#Var(str2nr(expand('')), 'lint_delay')) + elseif l:text_changed is? 'normal' + autocmd TextChanged * call ale#Queue(ale#Var(str2nr(expand('')), 'lint_delay')) + elseif l:text_changed is? 'insert' + autocmd TextChangedI * call ale#Queue(ale#Var(str2nr(expand('')), 'lint_delay')) + endif + + if g:ale_lint_on_enter + autocmd BufWinEnter * call ale#events#LintOnEnter(str2nr(expand(''))) + " Track when the file is changed outside of Vim. + autocmd FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand(''))) + endif + + if g:ale_lint_on_filetype_changed + " Only start linting if the FileType actually changes after + " opening a buffer. The FileType will fire when buffers are opened. + autocmd FileType * call ale#events#FileTypeEvent( + \ str2nr(expand('')), + \ expand('') + \) + endif + + " Add an InsertEnter event if we need to close the preview window + " on entering insert mode, or if we want to run ALE on leaving + " insert mode and is not the same as . + " + " We will emulate leaving insert mode for users that might not + " trigger InsertLeave. + " + " If the ModeChanged event is available, this timer will not + " be used. + if g:ale_close_preview_on_insert + \|| (g:ale_lint_on_insert_leave && maparg("\", 'i') isnot# '' && !s:mode_changed_exists) + autocmd InsertEnter * call ale#events#InsertEnterEvent(str2nr(expand(''))) + endif + + let l:add_insert_leave_event = g:ale_lint_on_insert_leave + + if g:ale_echo_cursor || g:ale_cursor_detail + " We need to make the message display on InsertLeave + let l:add_insert_leave_event = 1 + + autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarningWithDelay() | endif + endif + + if g:ale_virtualtext_cursor is# 'current' || g:ale_virtualtext_cursor is# 1 || g:ale_virtualtext_cursor is# '1' + " We need to make the message display on InsertLeave + let l:add_insert_leave_event = 1 + + autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarningWithDelay() | endif + endif + + if l:add_insert_leave_event + if s:mode_changed_exists + " If the ModeChanged event is available, handle any + " transition from the Insert mode to any other mode. + autocmd ModeChanged i*:* call ale#events#InsertLeaveEvent(str2nr(expand(''))) + else + " If ModeChanged is not available, handle InsertLeave events. + autocmd InsertLeave * call ale#events#InsertLeaveEvent(str2nr(expand(''))) + endif + endif + + if g:ale_hover_cursor + autocmd CursorHold * if exists('*ale#lsp#Send') | call ale#hover#ShowTruncatedMessageAtCursor() | endif + endif + endif + augroup END + + augroup AleURISchemes + autocmd! + + autocmd BufNewFile,BufReadPre jdt://** call ale#uri#jdt#ReadJDTLink(expand('')) + augroup END +endfunction diff --git a/autoload/ale/filename_mapping.vim b/autoload/ale/filename_mapping.vim new file mode 100644 index 00000000..76d47acc --- /dev/null +++ b/autoload/ale/filename_mapping.vim @@ -0,0 +1,22 @@ +" Author: w0rp +" Description: Logic for handling mappings between files + +" Invert filesystem mappings so they can be mapped in reverse. +function! ale#filename_mapping#Invert(filename_mappings) abort + return map(copy(a:filename_mappings), '[v:val[1], v:val[0]]') +endfunction + +" Given a filename and some filename_mappings, map a filename. +function! ale#filename_mapping#Map(filename, filename_mappings) abort + let l:simplified_filename = ale#path#Simplify(a:filename) + + for [l:mapping_from, l:mapping_to] in a:filename_mappings + let l:mapping_from = ale#path#Simplify(l:mapping_from) + + if l:simplified_filename[:len(l:mapping_from) - 1] is# l:mapping_from + return l:mapping_to . l:simplified_filename[len(l:mapping_from):] + endif + endfor + + return a:filename +endfunction diff --git a/autoload/ale/filerename.vim b/autoload/ale/filerename.vim new file mode 100644 index 00000000..93cf78e1 --- /dev/null +++ b/autoload/ale/filerename.vim @@ -0,0 +1,133 @@ +" Author: Dalius Dobravolskas +" Description: Rename file support for tsserver + +let s:filerename_map = {} + +" Used to get the rename map in tests. +function! ale#filerename#GetMap() abort + return deepcopy(s:filerename_map) +endfunction + +" Used to set the rename map in tests. +function! ale#filerename#SetMap(map) abort + let s:filerename_map = a:map +endfunction + +function! ale#filerename#ClearLSPData() abort + let s:filerename_map = {} +endfunction + +function! s:message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +function! ale#filerename#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') isnot# 'getEditsForFileRename' + return + endif + + if !has_key(s:filerename_map, a:response.request_seq) + return + endif + + let l:options = remove(s:filerename_map, a:response.request_seq) + + let l:old_name = l:options.old_name + let l:new_name = l:options.new_name + + if get(a:response, 'success', v:false) is v:false + let l:message = get(a:response, 'message', 'unknown') + call s:message('Error renaming file "' . l:old_name . '" to "' . l:new_name + \ . '". Reason: ' . l:message) + + return + endif + + let l:changes = a:response.body + + if empty(l:changes) + call s:message('No changes while renaming "' . l:old_name . '" to "' . l:new_name . '"') + else + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'filerename', + \ 'changes': l:changes, + \ }, + \ { + \ 'should_save': 1, + \ }, + \) + endif + + silent! noautocmd execute 'saveas ' . l:new_name + call delete(l:old_name) +endfunction + +function! s:OnReady(options, linter, lsp_details) abort + let l:id = a:lsp_details.connection_id + + if !ale#lsp#HasCapability(l:id, 'filerename') + return + endif + + let l:buffer = a:lsp_details.buffer + + let l:Callback = function('ale#filerename#HandleTSServerResponse') + + call ale#lsp#RegisterCallback(l:id, l:Callback) + + let l:message = ale#lsp#tsserver_message#GetEditsForFileRename( + \ a:options.old_name, + \ a:options.new_name, + \) + + let l:request_id = ale#lsp#Send(l:id, l:message) + + let s:filerename_map[l:request_id] = a:options +endfunction + +function! s:ExecuteFileRename(linter, options) abort + let l:buffer = bufnr('') + + let l:Callback = function('s:OnReady', [a:options]) + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) +endfunction + +function! ale#filerename#Execute() abort + let l:buffer = bufnr('') + let l:lsp_linters = [] + + for l:linter in ale#lsp_linter#GetEnabled(l:buffer) + if l:linter.lsp is# 'tsserver' + call add(l:lsp_linters, l:linter) + endif + endfor + + if empty(l:lsp_linters) + call s:message('No active tsserver LSPs') + + return + endif + + let l:old_name = expand('#' . l:buffer . ':p') + let l:new_name = ale#util#Input('New file name: ', l:old_name, 'file') + + if l:old_name is# l:new_name + call s:message('New file name matches old file name') + + return + endif + + if empty(l:new_name) + call s:message('New name cannot be empty!') + + return + endif + + for l:lsp_linter in l:lsp_linters + call s:ExecuteFileRename(l:lsp_linter, { + \ 'old_name': l:old_name, + \ 'new_name': l:new_name, + \}) + endfor +endfunction diff --git a/autoload/ale/filetypes.vim b/autoload/ale/filetypes.vim new file mode 100644 index 00000000..340a9c4e --- /dev/null +++ b/autoload/ale/filetypes.vim @@ -0,0 +1,58 @@ +" Author: w0rp +" Description: This file handles guessing file extensions for filetypes, etc. + +function! ale#filetypes#LoadExtensionMap() abort + " Output includes: + " '*.erl setf erlang' + let l:output = execute('exec "autocmd"') + + let l:map = {} + + for l:line in split(l:output, "\n") + " Parse filetypes, like so: + " + " *.erl setf erlang + " *.md set filetype=markdown + " *.snippet setlocal filetype=snippets + let l:match = matchlist(l:line, '\v^ *\*(\.[^ ]+).*set(f *| *filetype=|local *filetype=)([^ ]+)') + + if !empty(l:match) + let l:map[substitute(l:match[3], '^=', '', '')] = l:match[1] + endif + endfor + + return l:map +endfunction + +let s:cached_map = {} + +function! s:GetCachedExtensionMap() abort + if empty(s:cached_map) + let s:cached_map = ale#filetypes#LoadExtensionMap() + endif + + return s:cached_map +endfunction + +function! ale#filetypes#GuessExtension(filetype) abort + let l:map = s:GetCachedExtensionMap() + let l:ext = get(l:map, a:filetype, '') + + " If we have an exact match, like something for javascript.jsx, use that. + if !empty(l:ext) + return l:ext + endif + + " If we don't have an exact match, use the first filetype in the compound + " filetype. + for l:part in split(a:filetype, '\.') + let l:ext = get(l:map, l:part, '') + + if !empty(l:ext) + return l:ext + endif + endfor + + " Return an empty string if we don't find anything. + return '' +endfunction diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim new file mode 100644 index 00000000..786978d1 --- /dev/null +++ b/autoload/ale/fix.vim @@ -0,0 +1,399 @@ +" Author: w0rp +" Description: Functions for fixing code with programs, or other means. + +let g:ale_fix_on_save_ignore = get(g:, 'ale_fix_on_save_ignore', {}) +let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {}) + +" Apply fixes queued up for buffers which may be hidden. +" Vim doesn't let you modify hidden buffers. +function! ale#fix#ApplyQueuedFixes(buffer) abort + let l:data = get(g:ale_fix_buffer_data, a:buffer, {'done': 0}) + + if !l:data.done || (!ale#util#HasBuflineApi() && a:buffer isnot bufnr('')) + return + endif + + call remove(g:ale_fix_buffer_data, a:buffer) + + try + if l:data.changes_made + let l:new_lines = ale#util#SetBufferContents(a:buffer, l:data.output) + + if l:data.should_save + if a:buffer is bufnr('') + if empty(&buftype) + noautocmd :w! + else + set nomodified + endif + else + call writefile(l:new_lines, expand('#' . a:buffer . ':p')) " no-custom-checks + call setbufvar(a:buffer, '&modified', 0) + endif + endif + endif + catch /E21\|E5555/ + " If we cannot modify the buffer now, try again later. + let g:ale_fix_buffer_data[a:buffer] = l:data + + return + endtry + + if l:data.should_save + let l:should_lint = ale#Var(a:buffer, 'fix_on_save') + \ && ale#Var(a:buffer, 'lint_on_save') + else + let l:should_lint = l:data.changes_made + endif + + silent doautocmd User ALEFixPost + + " If ALE linting is enabled, check for problems with the file again after + " fixing problems. + if g:ale_enabled + \&& l:should_lint + \&& !ale#events#QuitRecently(a:buffer) + call ale#Queue(0, l:data.should_save ? 'lint_file' : '') + endif +endfunction + +function! ale#fix#ApplyFixes(buffer, output) abort + 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 " no-custom-checks + 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, '$') + + if l:data.lines_before != l:lines + call remove(g:ale_fix_buffer_data, a:buffer) + + if !l:data.ignore_file_changed_errors + " no-custom-checks + echoerr 'The file was changed before fixing finished' + endif + + return + endif + endif + + " 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(a:buffer) +endfunction + +function! s:HandleExit(job_info, buffer, job_output, data) abort + let l:buffer_info = get(g:ale_fix_buffer_data, a:buffer, {}) + + if empty(l:buffer_info) + return + endif + + 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: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: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 !empty(split(join(l:output))) + let l:input = l:output + else + let l:input = a:job_info.input + endif + + call s:RunFixer({ + \ 'buffer': a:buffer, + \ 'input': l:input, + \ 'callback_list': a:job_info.callback_list, + \ 'callback_index': a:job_info.callback_index + 1, + \}) +endfunction + +function! s:RunJob(result, options) abort + if ale#command#IsDeferred(a:result) + let a:result.result_callback = {x -> s:RunJob(x, a:options)} + + return + endif + + let l:buffer = a:options.buffer + let l:input = a:options.input + let l:fixer_name = a:options.fixer_name + + 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 + + call s:RunFixer({ + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'callback_index': a:options.callback_index + 1, + \ 'callback_list': a:options.callback_list, + \}) + + return + endif + + let l:command = get(a:result, 'command', '') + + if empty(l:command) + " If the command is empty, skip to the next item. + call s:RunFixer({ + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'callback_index': a:options.callback_index, + \ 'callback_list': a:options.callback_list, + \}) + + return + endif + + let l:read_temporary_file = get(a:result, 'read_temporary_file', 0) + let l:read_buffer = get(a:result, 'read_buffer', 1) + let l:output_stream = get(a:result, 'output_stream', 'stdout') + let l:cwd = get(a:result, 'cwd', v:null) + + if l:read_temporary_file + let l:output_stream = 'none' + endif + + let l:Callback = function('s:HandleExit', [{ + \ 'input': l:input, + \ 'callback_index': a:options.callback_index, + \ 'callback_list': a:options.callback_list, + \ '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, + \ 'cwd': l:cwd, + \ 'filename_mappings': ale#GetFilenameMappings(l:buffer, l:fixer_name), + \}) + + 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 +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:fixer_name, l:Function] = a:options.callback_list[l:index] + + " Record new jobs started as fixer jobs. + call setbufvar(l:buffer, 'ale_job_type', 'fixer') + + " 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)]) + + call s:RunJob(l:result, { + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'callback_list': a:options.callback_list, + \ 'callback_index': l:index, + \ 'fixer_name': l:fixer_name, + \}) +endfunction + +function! s:AddSubCallbacks(full_list, callbacks) abort + if type(a:callbacks) is v:t_string + call add(a:full_list, a:callbacks) + elseif type(a:callbacks) is v:t_list + call extend(a:full_list, a:callbacks) + else + return 0 + endif + + return 1 +endfunction + +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 + " Lists can be used for buffer-local variables only + let l:callback_list = b:ale_fixers + else + " buffer and global options can use dictionaries mapping filetypes to + " callbacks to run. + let l:fixers = ale#Var(a:buffer, 'fixers') + let l:callback_list = [] + let l:matched = 0 + + for l:sub_type in split(&filetype, '\.') + if s:AddSubCallbacks(l:callback_list, get(l:fixers, l:sub_type)) + let l:matched = 1 + endif + endfor + + " If we couldn't find fixers for a filetype, default to '*' fixers. + if !l:matched + call s:AddSubCallbacks(l:callback_list, get(l:fixers, '*')) + endif + endif + + if 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 = [] + + " Variables with capital characters are needed, or Vim will complain about + " funcref variables. + for l:Item in l:callback_list + " Try to capture the names of registered fixer names, so we can use + " them for filename mapping or other purposes later. + let l:fixer_name = v:null + + if type(l:Item) is v:t_string + let l:Func = ale#fix#registry#GetFunc(l:Item) + + if !empty(l:Func) + let l:fixer_name = l:Item + let l:Item = l:Func + endif + endif + + try + call add(l:corrected_list, [ + \ l:fixer_name, + \ ale#util#GetFunction(l:Item) + \]) + catch /E475/ + " Rethrow exceptions for failing to get a function so we can print + " a friendly message about it. + throw 'BADNAME ' . v:exception + endtry + endfor + + return l:corrected_list +endfunction + +function! ale#fix#InitBufferData(buffer, fixing_flag) abort + " The 'done' flag tells the function for applying changes when fixing + " is complete. + let g:ale_fix_buffer_data[a:buffer] = { + \ 'lines_before': getbufline(a:buffer, 1, '$'), + \ 'done': 0, + \ 'should_save': a:fixing_flag is# 'save_file', + \ 'ignore_file_changed_errors': a:fixing_flag is# '!', + \ 'temporary_directory_list': [], + \} +endfunction + +" Accepts an optional argument for what to do when fixing. +" +" Returns 0 if no fixes can be applied, and 1 if fixing can be done. +function! ale#fix#Fix(buffer, fixing_flag, ...) abort + if a:fixing_flag isnot# '' + \&& a:fixing_flag isnot# '!' + \&& a:fixing_flag isnot# 'save_file' + throw "fixing_flag must be '', '!', or 'save_file'" + endif + + try + let l:callback_list = s:GetCallbacks(a:buffer, a:fixing_flag, a:000) + catch /E700\|BADNAME/ + if a:fixing_flag isnot# '!' + let l:function_name = join(split(split(v:exception, ':')[3])) + let l:echo_message = printf( + \ 'There is no fixer named `%s`. Check :ALEFixSuggest', + \ l:function_name, + \) + " no-custom-checks + echom l:echo_message + endif + + return 0 + endtry + + if empty(l:callback_list) + if a:fixing_flag is# '' + " no-custom-checks + echom 'No fixers have been defined. Try :ALEFixSuggest' + endif + + return 0 + endif + + call ale#command#StopJobs(a:buffer, 'fixer') + " Clean up any files we might have left behind from a previous run. + call ale#command#RemoveManagedFiles(a:buffer) + call ale#fix#InitBufferData(a:buffer, a:fixing_flag) + + silent doautocmd User ALEFixPre + + call s:RunFixer({ + \ 'buffer': a:buffer, + \ 'input': g:ale_fix_buffer_data[a:buffer].lines_before, + \ 'callback_index': 0, + \ 'callback_list': l:callback_list, + \}) + + return 1 +endfunction + +" Set up an autocmd command to try and apply buffer fixes when available. +augroup ALEBufferFixGroup + autocmd! + autocmd BufEnter * call ale#fix#ApplyQueuedFixes(str2nr(expand(''))) +augroup END diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim new file mode 100644 index 00000000..efa1fe75 --- /dev/null +++ b/autoload/ale/fix/registry.vim @@ -0,0 +1,931 @@ +" Author: w0rp +" Description: A registry of functions for fixing things. + +let s:default_registry = { +\ 'add_blank_lines_for_python_control_statements': { +\ 'function': 'ale#fixers#generic_python#AddLinesBeforeControlStatements', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Add blank lines before control statements.', +\ }, +\ 'alejandra': { +\ 'function': 'ale#fixers#alejandra#Fix', +\ 'suggested_filetypes': ['nix'], +\ 'description': 'The Uncompromising Nix Code Formatter', +\ }, +\ 'align_help_tags': { +\ 'function': 'ale#fixers#help#AlignTags', +\ 'suggested_filetypes': ['help'], +\ 'description': 'Align help tags to the right margin', +\ }, +\ 'apkbuild-fixer': { +\ 'function': 'ale#fixers#apkbuild_fixer#Fix', +\ 'suggested_filetypes': ['apkbuild'], +\ 'description': 'Fix policy violations found by apkbuild-lint in APKBUILDs', +\ }, +\ 'autoimport': { +\ 'function': 'ale#fixers#autoimport#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix import issues with autoimport.', +\ }, +\ 'autoflake': { +\ 'function': 'ale#fixers#autoflake#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix flake issues with autoflake.', +\ }, +\ 'autopep8': { +\ 'function': 'ale#fixers#autopep8#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix PEP8 issues with autopep8.', +\ }, +\ 'bibclean': { +\ 'function': 'ale#fixers#bibclean#Fix', +\ 'suggested_filetypes': ['bib'], +\ 'description': 'Format bib files using bibclean.', +\ }, +\ 'biome': { +\ 'function': 'ale#fixers#biome#Fix', +\ 'suggested_filetypes': ['javascript', 'typescript', 'json', 'jsonc', 'css', 'graphql'], +\ 'description': 'Fix JavaScript and TypeScript using biome.', +\ }, +\ 'black': { +\ 'function': 'ale#fixers#black#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix PEP8 issues with black.', +\ }, +\ 'buf-format': { +\ 'function': 'ale#fixers#buf_format#Fix', +\ 'suggested_filetypes': ['proto'], +\ 'description': 'Fix .proto files with buf format.', +\ }, +\ 'buildifier': { +\ 'function': 'ale#fixers#buildifier#Fix', +\ 'suggested_filetypes': ['bzl'], +\ 'description': 'Format BUILD and .bzl files with buildifier.', +\ }, +\ 'css-beautify': { +\ 'function': 'ale#fixers#css_beautify#Fix', +\ 'suggested_filetypes': ['css'], +\ 'description': 'Format CSS using css-beautify from js-beautify.', +\ }, +\ 'deno': { +\ 'function': 'ale#fixers#deno#Fix', +\ 'suggested_filetypes': ['typescript'], +\ 'description': 'Fix TypeScript using deno fmt.', +\ }, +\ 'dfmt': { +\ 'function': 'ale#fixers#dfmt#Fix', +\ 'suggested_filetypes': ['d'], +\ 'description': 'Fix D files with dfmt.', +\ }, +\ 'dhall': { +\ 'function': 'ale#fixers#dhall#Fix', +\ 'suggested_filetypes': ['dhall'], +\ 'description': 'Fix Dhall files with dhall-format.', +\ }, +\ 'dhall-format': { +\ 'function': 'ale#fixers#dhall_format#Fix', +\ 'suggested_filetypes': ['dhall'], +\ 'description': 'Standard code formatter for the Dhall language', +\ 'aliases': ['dhall'], +\ }, +\ 'dhall-freeze': { +\ 'function': 'ale#fixers#dhall_freeze#Freeze', +\ 'suggested_filetypes': ['dhall'], +\ 'description': 'Add integrity checks to remote import statements of an expression for the Dhall language', +\ }, +\ 'dhall-lint': { +\ 'function': 'ale#fixers#dhall_lint#Fix', +\ 'suggested_filetypes': ['dhall'], +\ 'description': 'Standard code formatter for the Dhall language and removing dead code', +\ }, +\ 'djlint': { +\ 'function': 'ale#fixers#djlint#Fix', +\ 'suggested_filetypes': ['html', 'htmldjango', 'htmlangular', 'jinja', 'handlebars', 'nunjucks', 'gohtmltmpl'], +\ 'description': 'Fix HTML templates with `djlint --reformat`.', +\ }, +\ 'dune': { +\ 'function': 'ale#fixers#dune#Fix', +\ 'suggested_filetypes': ['dune'], +\ 'description': 'Fix dune files with dune format', +\ }, +\ 'erlang_mode': { +\ 'function': 'ale#fixers#erlang_mode#Fix', +\ 'suggested_filetypes': ['erlang'], +\ 'description': 'Indent with the Erlang mode for Emacs', +\ 'aliases': ['erlang-mode'], +\ }, +\ 'erlfmt': { +\ 'function': 'ale#fixers#erlfmt#Fix', +\ 'suggested_filetypes': ['erlang'], +\ 'description': 'Format Erlang code with erlfmt', +\ }, +\ 'fecs': { +\ 'function': 'ale#fixers#fecs#Fix', +\ 'suggested_filetypes': ['javascript', 'css', 'html'], +\ 'description': 'Apply fecs format to a file.', +\ }, +\ 'hurlfmt': { +\ 'function': 'ale#fixers#hurlfmt#Fix', +\ 'suggested_filetypes': ['hurl'], +\ 'description': 'Fix hurl files with hurlfmt.', +\ }, +\ 'kulala_fmt': { +\ 'function': 'ale#fixers#kulala_fmt#Fix', +\ 'suggested_filetypes': ['http', 'rest'], +\ 'description': 'Fix http and rest files with kulala_fmt.', +\ }, +\ 'tidy': { +\ 'function': 'ale#fixers#tidy#Fix', +\ 'suggested_filetypes': ['html'], +\ 'description': 'Fix HTML files with tidy.', +\ }, +\ 'prettier_standard': { +\ 'function': 'ale#fixers#prettier_standard#Fix', +\ 'suggested_filetypes': ['javascript'], +\ 'description': 'Apply prettier-standard to a file.', +\ 'aliases': ['prettier-standard'], +\ }, +\ 'elm-format': { +\ 'function': 'ale#fixers#elm_format#Fix', +\ 'suggested_filetypes': ['elm'], +\ 'description': 'Apply elm-format to a file.', +\ 'aliases': ['format'], +\ }, +\ 'nimpretty': { +\ 'function': 'ale#fixers#nimpretty#Fix', +\ 'suggested_filetypes': ['nim'], +\ 'description': 'Apply nimpretty to a file.', +\ }, +\ 'erblint': { +\ 'function': 'ale#fixers#erblint#Fix', +\ 'suggested_filetypes': ['eruby'], +\ 'description': 'Apply erblint --autocorrect to a file.', +\ }, +\ 'eslint': { +\ 'function': 'ale#fixers#eslint#Fix', +\ 'suggested_filetypes': ['javascript', 'typescript', 'astro'], +\ 'description': 'Apply eslint --fix to a file.', +\ }, +\ 'mix_format': { +\ 'function': 'ale#fixers#mix_format#Fix', +\ 'suggested_filetypes': ['elixir'], +\ 'description': 'Apply mix format to a file.', +\ }, +\ 'isort': { +\ 'function': 'ale#fixers#isort#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Sort Python imports with isort.', +\ }, +\ 'prettier': { +\ 'function': 'ale#fixers#prettier#Fix', +\ 'suggested_filetypes': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue', 'svelte', 'html', 'yaml', 'openapi', 'ruby', 'astro'], +\ 'description': 'Apply prettier to a file.', +\ }, +\ 'prettier_eslint': { +\ 'function': 'ale#fixers#prettier_eslint#Fix', +\ 'suggested_filetypes': ['javascript'], +\ 'description': 'Apply prettier-eslint to a file.', +\ 'aliases': ['prettier-eslint'], +\ }, +\ 'pyflyby': { +\ 'function': 'ale#fixers#pyflyby#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Tidy Python imports with pyflyby.', +\ }, +\ 'importjs': { +\ 'function': 'ale#fixers#importjs#Fix', +\ 'suggested_filetypes': ['javascript'], +\ 'description': 'automatic imports for javascript', +\ }, +\ 'puppetlint': { +\ 'function': 'ale#fixers#puppetlint#Fix', +\ 'suggested_filetypes': ['puppet'], +\ 'description': 'Run puppet-lint -f on a file.', +\ }, +\ 'remove_trailing_lines': { +\ 'function': 'ale#fixers#generic#RemoveTrailingBlankLines', +\ 'suggested_filetypes': [], +\ 'description': 'Remove all blank lines at the end of a file.', +\ }, +\ 'trim_whitespace': { +\ 'function': 'ale#fixers#generic#TrimWhitespace', +\ 'suggested_filetypes': [], +\ 'description': 'Remove all trailing whitespace characters at the end of every line.', +\ }, +\ 'yamlfix': { +\ 'function': 'ale#fixers#yamlfix#Fix', +\ 'suggested_filetypes': ['yaml'], +\ 'description': 'Fix YAML files with yamlfix.', +\ }, +\ 'yamlfmt': { +\ 'function': 'ale#fixers#yamlfmt#Fix', +\ 'suggested_filetypes': ['yaml'], +\ 'description': 'Format YAML files with yamlfmt.', +\ }, +\ 'yapf': { +\ 'function': 'ale#fixers#yapf#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix Python files with yapf.', +\ }, +\ 'yq': { +\ 'function': 'ale#fixers#yq#Fix', +\ 'suggested_filetypes': ['yaml'], +\ 'description': 'Fix YAML files with yq.', +\ }, +\ 'rubocop': { +\ 'function': 'ale#fixers#rubocop#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with rubocop --auto-correct.', +\ }, +\ 'rufo': { +\ 'function': 'ale#fixers#rufo#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with rufo', +\ }, +\ 'scalafmt': { +\ 'function': 'ale#fixers#scalafmt#Fix', +\ 'suggested_filetypes': ['sbt', 'scala'], +\ 'description': 'Fix Scala files using scalafmt', +\ }, +\ 'sorbet': { +\ 'function': 'ale#fixers#sorbet#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with srb tc --autocorrect.', +\ }, +\ 'standard': { +\ 'function': 'ale#fixers#standard#Fix', +\ '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', +\ }, +\ 'statix': { +\ 'function': 'ale#fixers#statix#Fix', +\ 'suggested_filetypes': ['nix'], +\ 'description': 'Fix common Nix antipatterns with statix fix', +\ }, +\ 'stylelint': { +\ 'function': 'ale#fixers#stylelint#Fix', +\ 'suggested_filetypes': ['css', 'sass', 'scss', 'sugarss', 'stylus'], +\ 'description': 'Fix stylesheet files using stylelint --fix.', +\ }, +\ 'swiftformat': { +\ 'function': 'ale#fixers#swiftformat#Fix', +\ 'suggested_filetypes': ['swift'], +\ 'description': 'Apply SwiftFormat to a file.', +\ }, +\ 'syntax_tree': { +\ 'function': 'ale#fixers#syntax_tree#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with stree write', +\ }, +\ 'apple-swift-format': { +\ 'function': 'ale#fixers#appleswiftformat#Fix', +\ 'suggested_filetypes': ['swift'], +\ 'description': 'Apply apple/swift-format to a file.', +\ }, +\ 'phpcbf': { +\ 'function': 'ale#fixers#phpcbf#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix PHP files with phpcbf.', +\ }, +\ 'php_cs_fixer': { +\ 'function': 'ale#fixers#php_cs_fixer#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix PHP files with php-cs-fixer.', +\ }, +\ 'pint': { +\ 'function': 'ale#fixers#pint#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix PHP files with Laravel Pint.', +\ }, +\ 'astyle': { +\ 'function': 'ale#fixers#astyle#Fix', +\ 'suggested_filetypes': ['c', 'cpp'], +\ 'description': 'Fix C/C++ with astyle.', +\ }, +\ 'clangtidy': { +\ 'function': 'ale#fixers#clangtidy#Fix', +\ 'suggested_filetypes': ['c', 'cpp', 'objc'], +\ 'description': 'Fix C/C++ and ObjectiveC files with clang-tidy.', +\ }, +\ 'clang-format': { +\ 'function': 'ale#fixers#clangformat#Fix', +\ 'suggested_filetypes': ['c', 'cpp', 'cs', 'cuda', 'java', 'javascript', 'json', 'objc', 'proto'], +\ 'description': 'Fix C, C++, C#, CUDA, Java, JavaScript, JSON, ObjectiveC and Protobuf files with clang-format.', +\ }, +\ 'cmakeformat': { +\ 'function': 'ale#fixers#cmakeformat#Fix', +\ 'suggested_filetypes': ['cmake'], +\ 'description': 'Fix CMake files with cmake-format.', +\ }, +\ 'fish_indent': { +\ 'function': 'ale#fixers#fish_indent#Fix', +\ 'suggested_filetypes': ['fish'], +\ 'description': 'Format fish scripts using fish_indent.', +\ }, +\ 'forge': { +\ 'function': 'ale#fixers#forge#Fix', +\ 'suggested_filetypes': ['solidity'], +\ 'description': 'Fix Solidity files with forge fmt.', +\ }, +\ 'gleam_format': { +\ 'function': 'ale#fixers#gleam_format#Fix', +\ 'suggested_filetypes': ['gleam'], +\ 'description': 'Fix Gleam files with gleam format.', +\ }, +\ 'gofmt': { +\ 'function': 'ale#fixers#gofmt#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go files with go fmt.', +\ }, +\ 'gofumpt': { +\ 'function': 'ale#fixers#gofumpt#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go files with gofumpt, a stricter go fmt.', +\ }, +\ 'goimports': { +\ 'function': 'ale#fixers#goimports#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go files imports with goimports.', +\ }, +\ 'golangci_lint': { +\ 'function': 'ale#fixers#golangci_lint#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go files with golangci-lint.', +\ }, +\ 'golines': { +\ 'function': 'ale#fixers#golines#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go file long lines with golines', +\ }, +\ 'gomod': { +\ 'function': 'ale#fixers#gomod#Fix', +\ 'suggested_filetypes': ['gomod'], +\ 'description': 'Fix Go module files with go mod edit -fmt.', +\ }, +\ 'gopls': { +\ 'function': 'ale#fixers#gopls#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go files with gopls.', +\ }, +\ 'tslint': { +\ 'function': 'ale#fixers#tslint#Fix', +\ 'suggested_filetypes': ['typescript'], +\ 'description': 'Fix typescript files with tslint --fix.', +\ }, +\ 'rustfmt': { +\ 'function': 'ale#fixers#rustfmt#Fix', +\ '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'], +\ 'description': 'Fix Haskell files with hfmt.', +\ }, +\ 'brittany': { +\ 'function': 'ale#fixers#brittany#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'Fix Haskell files with brittany.', +\ }, +\ 'hindent': { +\ 'function': 'ale#fixers#hindent#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'Fix Haskell files with hindent.', +\ }, +\ 'hlint': { +\ 'function': 'ale#fixers#hlint#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'Refactor Haskell files with hlint.', +\ }, +\ 'stylish-haskell': { +\ 'function': 'ale#fixers#stylish_haskell#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'Refactor Haskell files with stylish-haskell.', +\ }, +\ 'purs-tidy': { +\ 'function': 'ale#fixers#purs_tidy#Fix', +\ 'suggested_filetypes': ['purescript'], +\ 'description': 'Format PureScript files with purs-tidy.', +\ }, +\ 'purty': { +\ 'function': 'ale#fixers#purty#Fix', +\ 'suggested_filetypes': ['purescript'], +\ 'description': 'Format PureScript files with purty.', +\ }, +\ 'ocamlformat': { +\ 'function': 'ale#fixers#ocamlformat#Fix', +\ 'suggested_filetypes': ['ocaml', 'ocamlinterface'], +\ 'description': 'Fix OCaml files with ocamlformat.', +\ }, +\ 'ocp-indent': { +\ 'function': 'ale#fixers#ocp_indent#Fix', +\ 'suggested_filetypes': ['ocaml', 'ocamlinterface'], +\ 'description': 'Fix OCaml files with ocp-indent.', +\ }, +\ 'refmt': { +\ 'function': 'ale#fixers#refmt#Fix', +\ 'suggested_filetypes': ['reason'], +\ 'description': 'Fix ReasonML files with refmt.', +\ }, +\ 'pandoc': { +\ 'function': 'ale#fixers#pandoc#Fix', +\ 'suggested_filetypes': ['markdown'], +\ 'description': 'Fix markdown files with pandoc.', +\ }, +\ 'shfmt': { +\ 'function': 'ale#fixers#shfmt#Fix', +\ 'suggested_filetypes': ['sh'], +\ 'description': 'Fix sh files with shfmt.', +\ }, +\ 'sqlfluff': { +\ 'function': 'ale#fixers#sqlfluff#Fix', +\ 'suggested_filetypes': ['sql'], +\ 'description': 'Fix SQL files with sqlfluff.', +\ }, +\ 'sqlfmt': { +\ 'function': 'ale#fixers#sqlfmt#Fix', +\ 'suggested_filetypes': ['sql'], +\ 'description': 'Fix SQL files with sqlfmt.', +\ }, +\ 'sqlformat': { +\ 'function': 'ale#fixers#sqlformat#Fix', +\ 'suggested_filetypes': ['sql'], +\ 'description': 'Fix SQL files with sqlformat.', +\ }, +\ 'google_java_format': { +\ 'function': 'ale#fixers#google_java_format#Fix', +\ 'suggested_filetypes': ['java'], +\ 'description': 'Fix Java files with google-java-format.', +\ }, +\ 'fixjson': { +\ 'function': 'ale#fixers#fixjson#Fix', +\ 'suggested_filetypes': ['json'], +\ 'description': 'Fix JSON files with fixjson.', +\ }, +\ 'jq': { +\ 'function': 'ale#fixers#jq#Fix', +\ 'suggested_filetypes': ['json'], +\ 'description': 'Fix JSON files with jq.', +\ }, +\ 'json_pytool': { +\ 'function': 'ale#fixers#json_pytool#Fix', +\ 'suggested_filetypes': ['json'], +\ 'description': "Fix JSON files with python's built-in json.tool module.", +\ }, +\ 'protolint': { +\ 'function': 'ale#fixers#protolint#Fix', +\ 'suggested_filetypes': ['proto'], +\ 'description': 'Fix Protocol Buffer files with protolint.', +\ }, +\ 'perltidy': { +\ 'function': 'ale#fixers#perltidy#Fix', +\ 'suggested_filetypes': ['perl'], +\ 'description': 'Fix Perl files with perltidy.', +\ }, +\ 'xo': { +\ 'function': 'ale#fixers#xo#Fix', +\ 'suggested_filetypes': ['javascript', 'typescript'], +\ 'description': 'Fix JavaScript/TypeScript files using xo --fix.', +\ }, +\ 'qmlfmt': { +\ 'function': 'ale#fixers#qmlfmt#Fix', +\ 'suggested_filetypes': ['qml'], +\ 'description': 'Fix QML files with qmlfmt.', +\ }, +\ 'dartfmt': { +\ 'function': 'ale#fixers#dartfmt#Fix', +\ 'suggested_filetypes': ['dart'], +\ 'description': 'Fix Dart files with dartfmt.', +\ }, +\ 'dart-format': { +\ 'function': 'ale#fixers#dart_format#Fix', +\ 'suggested_filetypes': ['dart'], +\ 'description': 'Fix Dart files with dart format.', +\ }, +\ 'dotnet-format': { +\ 'function': 'ale#fixers#dotnet_format#Fix', +\ 'suggested_filetypes': ['cs'], +\ 'description': 'Fix C# files with dotnet format.', +\ }, +\ 'xmllint': { +\ 'function': 'ale#fixers#xmllint#Fix', +\ 'suggested_filetypes': ['xml'], +\ 'description': 'Fix XML files with xmllint.', +\ }, +\ 'uncrustify': { +\ 'function': 'ale#fixers#uncrustify#Fix', +\ 'suggested_filetypes': ['c', 'cpp', 'cs', 'objc', 'objcpp', 'd', 'java', 'p', 'vala' ], +\ 'description': 'Fix C, C++, C#, ObjectiveC, ObjectiveC++, D, Java, Pawn, and VALA files with uncrustify.', +\ }, +\ 'terraform': { +\ 'function': 'ale#fixers#terraform#Fix', +\ 'suggested_filetypes': ['hcl', 'terraform'], +\ 'description': 'Fix tf and hcl files with terraform fmt.', +\ }, +\ 'packer': { +\ 'function': 'ale#fixers#packer#Fix', +\ 'suggested_filetypes': ['hcl', 'packer'], +\ 'description': 'Fix Packer HCL files with packer fmt.', +\ }, +\ 'crystal': { +\ 'function': 'ale#fixers#crystal#Fix', +\ 'suggested_filetypes': ['cr'], +\ 'description': 'Fix cr (crystal).', +\ }, +\ 'ktlint': { +\ 'function': 'ale#fixers#ktlint#Fix', +\ 'suggested_filetypes': ['kt', 'kotlin'], +\ 'description': 'Fix Kotlin files with ktlint.', +\ }, +\ 'styler': { +\ 'function': 'ale#fixers#styler#Fix', +\ 'suggested_filetypes': ['r', 'rmarkdown', 'rmd'], +\ '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.', +\ }, +\ 'pgformatter': { +\ 'function': 'ale#fixers#pgformatter#Fix', +\ 'suggested_filetypes': ['sql'], +\ 'description': 'A PostgreSQL SQL syntax beautifier', +\ }, +\ 'reorder-python-imports': { +\ 'function': 'ale#fixers#reorder_python_imports#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Sort Python imports with reorder-python-imports.', +\ }, +\ 'gnatpp': { +\ 'function': 'ale#fixers#gnatpp#Fix', +\ 'suggested_filetypes': ['ada'], +\ 'description': 'Format Ada files with gnatpp.', +\ }, +\ 'nixfmt': { +\ 'function': 'ale#fixers#nixfmt#Fix', +\ 'suggested_filetypes': ['nix'], +\ 'description': 'A nix formatter written in Haskell.', +\ }, +\ 'nixpkgs-fmt': { +\ 'function': 'ale#fixers#nixpkgsfmt#Fix', +\ 'suggested_filetypes': ['nix'], +\ 'description': 'A formatter for Nix code', +\ }, +\ 'remark-lint': { +\ 'function': 'ale#fixers#remark_lint#Fix', +\ 'suggested_filetypes': ['markdown'], +\ 'description': 'Fix markdown files with remark-lint', +\ }, +\ 'html-beautify': { +\ 'function': 'ale#fixers#html_beautify#Fix', +\ 'suggested_filetypes': ['html', 'htmldjango'], +\ 'description': 'Fix HTML files with html-beautify from js-beautify.', +\ }, +\ 'htmlbeautifier': { +\ 'function': 'ale#fixers#htmlbeautifier#Fix', +\ 'suggested_filetypes': ['eruby'], +\ 'description': 'Fix ERB files with htmlbeautifier gem.', +\ }, +\ 'lua-format': { +\ 'function': 'ale#fixers#lua_format#Fix', +\ 'suggested_filetypes': ['lua'], +\ 'description': 'Fix Lua files with lua-format.', +\ }, +\ 'luafmt': { +\ 'function': 'ale#fixers#luafmt#Fix', +\ 'suggested_filetypes': ['lua'], +\ 'description': 'Fix Lua files with luafmt.', +\ }, +\ 'dprint': { +\ 'function': 'ale#fixers#dprint#Fix', +\ 'suggested_filetypes': ['dockerfile', 'javascript', 'json', 'markdown', 'toml', 'typescript'], +\ 'description': 'Pluggable and configurable code formatting platform', +\ }, +\ 'stylua': { +\ 'function': 'ale#fixers#stylua#Fix', +\ 'suggested_filetypes': ['lua'], +\ 'description': 'Fix Lua files with stylua.', +\ }, +\ 'ormolu': { +\ 'function': 'ale#fixers#ormolu#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'A formatter for Haskell source code.', +\ }, +\ 'fourmolu': { +\ 'function': 'ale#fixers#fourmolu#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'A formatter for Haskell source code.', +\ }, +\ 'jsonnetfmt': { +\ 'function': 'ale#fixers#jsonnetfmt#Fix', +\ 'suggested_filetypes': ['jsonnet'], +\ 'description': 'Fix jsonnet files with jsonnetfmt', +\ }, +\ 'ptop': { +\ 'function': 'ale#fixers#ptop#Fix', +\ 'suggested_filetypes': ['pascal'], +\ 'description': 'Fix Pascal files with ptop.', +\ }, +\ 'opafmt': { +\ 'function': 'ale#fixers#opafmt#Fix', +\ 'suggested_filetypes': ['rego'], +\ 'description': 'Fix rego files with opa fmt.', +\ }, +\ 'vfmt': { +\ 'function': 'ale#fixers#vfmt#Fix', +\ 'suggested_filetypes': ['v'], +\ 'description': 'A formatter for V source code.', +\ }, +\ 'zigfmt': { +\ 'function': 'ale#fixers#zigfmt#Fix', +\ 'suggested_filetypes': ['zig'], +\ 'description': 'Official formatter for Zig', +\ }, +\ 'raco_fmt': { +\ 'function': 'ale#fixers#raco_fmt#Fix', +\ 'suggested_filetypes': ['racket'], +\ 'description': 'Fix Racket files with raco fmt.', +\ }, +\ 'ruff': { +\ 'function': 'ale#fixers#ruff#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix python files with ruff.', +\ }, +\ 'ruff_format': { +\ 'function': 'ale#fixers#ruff_format#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix python files with the ruff formatter.', +\ }, +\ 'pycln': { +\ 'function': 'ale#fixers#pycln#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'remove unused python import statements', +\ }, +\ 'rustywind': { +\ 'function': 'ale#fixers#rustywind#Fix', +\ 'suggested_filetypes': ['html'], +\ 'description': 'Sort Tailwind CSS classes', +\ }, +\ 'npm-groovy-lint': { +\ 'function': 'ale#fixers#npmgroovylint#Fix', +\ 'suggested_filetypes': ['groovy'], +\ 'description': 'Fix Groovy files with npm-groovy-fix.', +\ }, +\ 'erb-formatter': { +\ 'function': 'ale#fixers#erbformatter#Fix', +\ 'suggested_filetypes': ['eruby'], +\ 'description': 'Apply erb-formatter -w to eruby/erb files.', +\ }, +\ 'nickel_format': { +\ 'function': 'ale#fixers#nickel_format#Fix', +\ 'suggested_filetypes': ['nickel'], +\ 'description': 'Fix nickel files with nickel format', +\ }, +\ 'rubyfmt': { +\ 'function': 'ale#fixers#rubyfmt#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'A formatter for Ruby source code', +\ }, +\ 'scadformat': { +\ 'function': 'ale#fixers#scadformat#Fix', +\ 'suggested_filetypes': ['openscad'], +\ 'description': 'Formatter for scad files', +\ }, +\ 'cljfmt': { +\ 'function': 'ale#fixers#cljfmt#Fix', +\ 'suggested_filetypes': ['clojure'], +\ 'description': 'formatter and linter for clojure files', +\ }, +\ 'typstyle': { +\ 'function': 'ale#fixers#typstyle#Fix', +\ 'suggested_filetypes': ['typst'], +\ 'description': 'A formatter for Typst files', +\ }, +\} + +" Reset the function registry to the default entries. +function! ale#fix#registry#ResetToDefaults() abort + let s:entries = deepcopy(s:default_registry) + let s:aliases = {} + + " Set up aliases for fixers too. + for [l:key, l:entry] in items(s:entries) + for l:alias in get(l:entry, 'aliases', []) + let s:aliases[l:alias] = l:key + endfor + endfor +endfunction + +" Set up entries now. +call ale#fix#registry#ResetToDefaults() + +" Remove everything from the registry, useful for tests. +function! ale#fix#registry#Clear() abort + let s:entries = {} + let s:aliases = {} +endfunction + +" Add a function for fixing problems to the registry. +" (name, func, filetypes, desc, aliases) +function! ale#fix#registry#Add(name, func, filetypes, desc, ...) abort + " This command will throw from the sandbox. + let &l:equalprg=&l:equalprg + + if type(a:name) isnot v:t_string + throw '''name'' must be a String' + endif + + if type(a:func) isnot v:t_string + throw '''func'' must be a String' + endif + + if type(a:filetypes) isnot v:t_list + throw '''filetypes'' must be a List' + endif + + for l:type in a:filetypes + if type(l:type) isnot v:t_string + throw 'Each entry of ''filetypes'' must be a String' + endif + endfor + + if type(a:desc) isnot v:t_string + throw '''desc'' must be a String' + endif + + let l:aliases = get(a:000, 0, []) + + if type(l:aliases) isnot v:t_list + \|| !empty(filter(copy(l:aliases), 'type(v:val) isnot v:t_string')) + throw '''aliases'' must be a List of String values' + endif + + let s:entries[a:name] = { + \ 'function': a:func, + \ 'suggested_filetypes': a:filetypes, + \ 'description': a:desc, + \} + + " Set up aliases for the fixer. + if !empty(l:aliases) + let s:entries[a:name].aliases = l:aliases + + for l:alias in l:aliases + let s:aliases[l:alias] = a:name + endfor + endif +endfunction + +" Get a function from the registry by its short name. +function! ale#fix#registry#GetFunc(name) abort + " Use the exact name, or an alias. + let l:resolved_name = !has_key(s:entries, a:name) + \ ? get(s:aliases, a:name, a:name) + \ : a:name + + return get(s:entries, l:resolved_name, {'function': ''}).function +endfunction + +function! s:ShouldSuggestForType(suggested_filetypes, type_list) abort + for l:type in a:type_list + if index(a:suggested_filetypes, l:type) >= 0 + return 1 + endif + endfor + + return 0 +endfunction + +function! s:IsGenericFixer(suggested_filetypes) abort + if empty(a:suggested_filetypes) + return 1 + endif + + return 0 +endfunction + +function! s:FormatEntry(key, entry) abort + let l:aliases_str = '' + + " Show aliases in :ALEFixSuggest if they are there. + if !empty(get(a:entry, 'aliases', [])) + let l:aliases_str = ', ' . join( + \ map(copy(a:entry.aliases), 'string(v:val)'), + \ ',' + \) + endif + + return printf( + \ '%s%s - %s', + \ string(a:key), + \ l:aliases_str, + \ a:entry.description, + \) +endfunction + +" Get list of applicable fixers for filetype, including generic fixers +function! ale#fix#registry#GetApplicableFixers(filetype) abort + let l:type_list = split(a:filetype, '\.') + let l:fixer_name_list = [] + + for l:key in sort(keys(s:entries)) + let l:suggested_filetypes = s:entries[l:key].suggested_filetypes + + if s:IsGenericFixer(l:suggested_filetypes) || s:ShouldSuggestForType(l:suggested_filetypes, l:type_list) + call add(l:fixer_name_list, l:key) + endif + endfor + + return l:fixer_name_list +endfunction + +" Function that returns autocomplete candidates for ALEFix command +function! ale#fix#registry#CompleteFixers(ArgLead, CmdLine, CursorPos) abort + return filter(ale#fix#registry#GetApplicableFixers(&filetype), 'v:val =~? a:ArgLead') +endfunction + +function! ale#fix#registry#SuggestedFixers(filetype) abort + let l:type_list = split(a:filetype, '\.') + let l:filetype_fixer_list = [] + + for l:key in sort(keys(s:entries)) + let l:suggested_filetypes = s:entries[l:key].suggested_filetypes + + if s:ShouldSuggestForType(l:suggested_filetypes, l:type_list) + call add( + \ l:filetype_fixer_list, + \ s:FormatEntry(l:key, s:entries[l:key]), + \) + endif + endfor + + let l:generic_fixer_list = [] + + for l:key in sort(keys(s:entries)) + if s:IsGenericFixer(s:entries[l:key].suggested_filetypes) + call add( + \ l:generic_fixer_list, + \ s:FormatEntry(l:key, s:entries[l:key]), + \) + endif + endfor + + return [l:filetype_fixer_list, l:generic_fixer_list] +endfunction + +" Suggest functions to use from the registry. +function! ale#fix#registry#Suggest(filetype) abort + let l:suggested = ale#fix#registry#SuggestedFixers(a:filetype) + let l:filetype_fixer_list = l:suggested[0] + let l:generic_fixer_list = l:suggested[1] + + let l:filetype_fixer_header = !empty(l:filetype_fixer_list) + \ ? ['Try the following fixers appropriate for the filetype:', ''] + \ : [] + let l:generic_fixer_header = !empty(l:generic_fixer_list) + \ ? ['Try the following generic fixers:', ''] + \ : [] + + let l:has_both_lists = !empty(l:filetype_fixer_list) && !empty(l:generic_fixer_list) + + let l:lines = + \ l:filetype_fixer_header + \ + l:filetype_fixer_list + \ + (l:has_both_lists ? [''] : []) + \ + l:generic_fixer_header + \ + l:generic_fixer_list + + if empty(l:lines) + let l:lines = ['There is nothing in the registry to suggest.'] + else + let l:lines += ['', 'See :help ale-fix-configuration'] + endif + + let l:lines += ['', 'Press q to close this window'] + + new +set\ filetype=ale-fix-suggest + call setline(1, l:lines) + setlocal nomodified + setlocal nomodifiable +endfunction diff --git a/autoload/ale/fixers/alejandra.vim b/autoload/ale/fixers/alejandra.vim new file mode 100644 index 00000000..3844e8c0 --- /dev/null +++ b/autoload/ale/fixers/alejandra.vim @@ -0,0 +1,13 @@ +call ale#Set('nix_alejandra_executable', 'alejandra') +call ale#Set('nix_alejandra_options', '') + +function! ale#fixers#alejandra#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nix_alejandra_executable') + let l:options = ale#Var(a:buffer, 'nix_alejandra_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' -- -' + \} +endfunction diff --git a/autoload/ale/fixers/apkbuild_fixer.vim b/autoload/ale/fixers/apkbuild_fixer.vim new file mode 100644 index 00000000..b297fc61 --- /dev/null +++ b/autoload/ale/fixers/apkbuild_fixer.vim @@ -0,0 +1,19 @@ +" Author: Leo +" Description: Fix policy violations found by apkbuild-lint + +call ale#Set('apkbuild_apkbuild_fixer_executable', 'apkbuild-fixer') +call ale#Set('apkbuild_apkbuild_fixer_lint_executable', get(g:, 'ale_apkbuild_apkbuild_lint_executable')) +call ale#Set('apkbuild_apkbuild_fixer_options', '') + +function! ale#fixers#apkbuild_fixer#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'apkbuild_apkbuild_fixer_executable') + let l:options = ale#Var(a:buffer, 'apkbuild_apkbuild_fixer_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -p ' . ale#Var(a:buffer, 'apkbuild_apkbuild_fixer_lint_executable') + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/appleswiftformat.vim b/autoload/ale/fixers/appleswiftformat.vim new file mode 100644 index 00000000..ca27e82c --- /dev/null +++ b/autoload/ale/fixers/appleswiftformat.vim @@ -0,0 +1,16 @@ +" Author: (bosr) +" Description: Integration of apple/swift-format formatter with ALE. + +function! ale#fixers#appleswiftformat#Fix(buffer) abort + let l:command_args = ale#swift#GetAppleSwiftFormatCommand(a:buffer) . ' format --in-place %t' + let l:config_args = ale#swift#GetAppleSwiftFormatConfigArgs(a:buffer) + + if l:config_args isnot# '' + let l:command_args = l:command_args . ' ' . l:config_args + endif + + return { + \ 'read_temporary_file': 1, + \ 'command': l:command_args, + \} +endfunction diff --git a/autoload/ale/fixers/astyle.vim b/autoload/ale/fixers/astyle.vim new file mode 100644 index 00000000..3a5a70a1 --- /dev/null +++ b/autoload/ale/fixers/astyle.vim @@ -0,0 +1,59 @@ +" Author: James Kim +" Description: Fix C/C++ files with astyle. + +function! s:set_variables() abort + for l:ft in ['c', 'cpp'] + call ale#Set(l:ft . '_astyle_executable', 'astyle') + call ale#Set(l:ft . '_astyle_project_options', '') + endfor +endfunction + +call s:set_variables() + + +function! ale#fixers#astyle#Var(buffer, name) abort + let l:ft = getbufvar(str2nr(a:buffer), '&filetype') + let l:ft = l:ft =~# 'cpp' ? 'cpp' : 'c' + + return ale#Var(a:buffer, l:ft . '_astyle_' . a:name) +endfunction + +" Try to find a project options file. +function! ale#fixers#astyle#FindProjectOptions(buffer) abort + let l:proj_options = ale#fixers#astyle#Var(a:buffer, 'project_options') + + " If user has set project options variable then use it and skip any searching. + " This would allow users to use project files named differently than .astylerc. + if !empty(l:proj_options) + return l:proj_options + endif + + " Try to find nearest .astylerc file. + let l:proj_options = fnamemodify(ale#path#FindNearestFile(a:buffer, '.astylerc'), ':t') + + if !empty(l:proj_options) + return l:proj_options + endif + + " Try to find nearest _astylerc file. + let l:proj_options = fnamemodify(ale#path#FindNearestFile(a:buffer, '_astylerc'), ':t') + + if !empty(l:proj_options) + return l:proj_options + endif + + " If no project options file is found return an empty string. + return '' +endfunction + +function! ale#fixers#astyle#Fix(buffer) abort + let l:executable = ale#fixers#astyle#Var(a:buffer, 'executable') + let l:proj_options = ale#fixers#astyle#FindProjectOptions(a:buffer) + let l:command = ' --stdin=' . ale#Escape(expand('#' . a:buffer)) + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:proj_options) ? '' : ' --project=' . l:proj_options) + \ . l:command + \} +endfunction diff --git a/autoload/ale/fixers/autoflake.vim b/autoload/ale/fixers/autoflake.vim new file mode 100644 index 00000000..9f8b04fc --- /dev/null +++ b/autoload/ale/fixers/autoflake.vim @@ -0,0 +1,46 @@ +" Author: circld +" Description: Fixing files with autoflake. + +call ale#Set('python_autoflake_executable', 'autoflake') +call ale#Set('python_autoflake_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_autoflake_options', '') +call ale#Set('python_autoflake_auto_pipenv', 0) +call ale#Set('python_autoflake_auto_poetry', 0) +call ale#Set('python_autoflake_auto_uv', 0) + +function! ale#fixers#autoflake#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_autoflake_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_autoflake_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_autoflake_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_autoflake', ['autoflake']) +endfunction + +function! ale#fixers#autoflake#Fix(buffer) abort + let l:executable = ale#fixers#autoflake#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run autoflake' + \ : '' + + let l:options = ale#Var(a:buffer, 'python_autoflake_options') + + return { + \ 'command': ale#Escape(l:executable) . l:exec_args + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --in-place ' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/autoimport.vim b/autoload/ale/fixers/autoimport.vim new file mode 100644 index 00000000..8d35419e --- /dev/null +++ b/autoload/ale/fixers/autoimport.vim @@ -0,0 +1,45 @@ +" Author: lyz-code +" Description: Fixing Python imports with autoimport. + +call ale#Set('python_autoimport_executable', 'autoimport') +call ale#Set('python_autoimport_options', '') +call ale#Set('python_autoimport_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_autoimport_auto_pipenv', 0) +call ale#Set('python_autoimport_auto_poetry', 0) +call ale#Set('python_autoimport_auto_uv', 0) + +function! ale#fixers#autoimport#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_autoimport_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_autoimport_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_autoimport_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_autoimport', ['autoimport']) +endfunction + +function! ale#fixers#autoimport#Fix(buffer) abort + let l:executable = ale#fixers#autoimport#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run autoimport' + \ : '' + + let l:options = ale#Var(a:buffer, 'python_autoimport_options') + + return { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(l:executable) . l:exec_args + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/autopep8.vim b/autoload/ale/fixers/autopep8.vim new file mode 100644 index 00000000..f9af46f8 --- /dev/null +++ b/autoload/ale/fixers/autopep8.vim @@ -0,0 +1,44 @@ +" Author: w0rp +" Description: Fixing files with autopep8. + +call ale#Set('python_autopep8_executable', 'autopep8') +call ale#Set('python_autopep8_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_autopep8_options', '') +call ale#Set('python_autopep8_auto_pipenv', 0) +call ale#Set('python_autopep8_auto_poetry', 0) +call ale#Set('python_autopep8_auto_uv', 0) + +function! ale#fixers#autopep8#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_autopep8_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_autopep8_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_autopep8_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_autopep8', ['autopep8']) +endfunction + +function! ale#fixers#autopep8#Fix(buffer) abort + let l:executable = ale#fixers#autopep8#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run autopep8' + \ : '' + + let l:options = ale#Var(a:buffer, 'python_autopep8_options') + + return { + \ 'command': ale#Escape(l:executable) . l:exec_args + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/bibclean.vim b/autoload/ale/fixers/bibclean.vim new file mode 100644 index 00000000..89cb97ab --- /dev/null +++ b/autoload/ale/fixers/bibclean.vim @@ -0,0 +1,15 @@ +" Author: Horacio Sanson - https://github.com/hsanson +" Description: Support for bibclean fixer for BibTeX files. + +call ale#Set('bib_bibclean_executable', 'bibclean') +call ale#Set('bib_bibclean_options', '-align-equals') + +function! ale#fixers#bibclean#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'bib_bibclean_options') + let l:executable = ale#Var(a:buffer, 'bib_bibclean_executable') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' ' . (empty(l:options) ? '' : l:options), + \} +endfunction diff --git a/autoload/ale/fixers/biome.vim b/autoload/ale/fixers/biome.vim new file mode 100644 index 00000000..5d33bb2a --- /dev/null +++ b/autoload/ale/fixers/biome.vim @@ -0,0 +1,12 @@ +function! ale#fixers#biome#Fix(buffer) abort + let l:executable = ale#handlers#biome#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'biome_options') + let l:apply = ale#Var(a:buffer, 'biome_fixer_apply_unsafe') ? '--write --unsafe' : '--write' + + return { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(l:executable) . ' check ' . l:apply + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' + \} +endfunction diff --git a/autoload/ale/fixers/black.vim b/autoload/ale/fixers/black.vim new file mode 100644 index 00000000..6f263f13 --- /dev/null +++ b/autoload/ale/fixers/black.vim @@ -0,0 +1,61 @@ +" Author: w0rp +" Description: Fixing Python files with black. +" +call ale#Set('python_black_executable', 'black') +call ale#Set('python_black_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_black_options', '') +call ale#Set('python_black_auto_pipenv', 0) +call ale#Set('python_black_auto_poetry', 0) +call ale#Set('python_black_auto_uv', 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 + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_black_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_black_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_black', ['black']) +endfunction + +function! ale#fixers#black#Fix(buffer) abort + let l:executable = ale#fixers#black#GetExecutable(a:buffer) + let l:cmd = [ale#Escape(l:executable)] + + if l:executable =~? '\(pipenv\|poetry\|uv\)$' + call extend(l:cmd, ['run', 'black']) + endif + + let l:options = ale#Var(a:buffer, 'python_black_options') + + if !empty(l:options) + call add(l:cmd, l:options) + endif + + let l:fname = expand('#' . a:buffer . '...') + call add(l:cmd, '--stdin-filename '.ale#Escape(ale#path#Simplify(l:fname))) + + if expand('#' . a:buffer . ':e') is? 'pyi' + call add(l:cmd, '--pyi') + endif + + call add(l:cmd, '-') + + let l:result = {'command': join(l:cmd, ' ')} + + if ale#Var(a:buffer, 'python_black_change_directory') + let l:result.cwd = '%s:h' + endif + + return l:result +endfunction diff --git a/autoload/ale/fixers/brittany.vim b/autoload/ale/fixers/brittany.vim new file mode 100644 index 00000000..c2448348 --- /dev/null +++ b/autoload/ale/fixers/brittany.vim @@ -0,0 +1,22 @@ +" Author: eborden , ifyouseewendy , aspidiets +" Description: Integration of brittany with ALE. + +call ale#Set('haskell_brittany_executable', 'brittany') + +function! ale#fixers#brittany#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_brittany_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'brittany') +endfunction + +function! ale#fixers#brittany#Fix(buffer) abort + let l:executable = ale#fixers#brittany#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' --write-mode inplace' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/buf_format.vim b/autoload/ale/fixers/buf_format.vim new file mode 100644 index 00000000..c2c156b7 --- /dev/null +++ b/autoload/ale/fixers/buf_format.vim @@ -0,0 +1,12 @@ +" Author: Alex McKinney +" Description: Run buf format. + +call ale#Set('proto_buf_format_executable', 'buf') + +function! ale#fixers#buf_format#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'proto_buf_format_executable') + + return { + \ 'command': ale#Escape(l:executable) . ' format %t', + \} +endfunction diff --git a/autoload/ale/fixers/buildifier.vim b/autoload/ale/fixers/buildifier.vim new file mode 100644 index 00000000..48103b2e --- /dev/null +++ b/autoload/ale/fixers/buildifier.vim @@ -0,0 +1,26 @@ +" Author: Jon Parise +" Description: Format Bazel BUILD and .bzl files with buildifier. +" +call ale#Set('bazel_buildifier_executable', 'buildifier') +call ale#Set('bazel_buildifier_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('bazel_buildifier_options', '') + +function! ale#fixers#buildifier#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'bazel_buildifier', [ + \ 'buildifier', + \]) +endfunction + +function! ale#fixers#buildifier#Fix(buffer) abort + let l:executable = ale#Escape(ale#fixers#buildifier#GetExecutable(a:buffer)) + let l:options = ale#Var(a:buffer, 'bazel_buildifier_options') + let l:filename = ale#Escape(bufname(a:buffer)) + + let l:command = l:executable . ' -mode fix -lint fix -path ' . l:filename + + if l:options isnot# '' + let l:command .= ' ' . l:options + endif + + return {'command': l:command . ' -'} +endfunction diff --git a/autoload/ale/fixers/clangformat.vim b/autoload/ale/fixers/clangformat.vim new file mode 100644 index 00000000..81498ebd --- /dev/null +++ b/autoload/ale/fixers/clangformat.vim @@ -0,0 +1,47 @@ +scriptencoding utf-8 +" Author: Peter Renström +" Description: Fixing C/C++ files with clang-format. + +call ale#Set('c_clangformat_executable', 'clang-format') +call ale#Set('c_clangformat_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('c_clangformat_options', '') +call ale#Set('c_clangformat_style_option', '') +call ale#Set('c_clangformat_use_local_file', 0) + +function! ale#fixers#clangformat#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'c_clangformat', [ + \ 'clang-format', + \]) +endfunction + +function! ale#fixers#clangformat#Fix(buffer) abort + let l:executable = ale#Escape(ale#fixers#clangformat#GetExecutable(a:buffer)) + let l:filename = ale#Escape(bufname(a:buffer)) + let l:options = ale#Var(a:buffer, 'c_clangformat_options') + let l:style_option = ale#Var(a:buffer, 'c_clangformat_style_option') + let l:use_local_file = ale#Var(a:buffer, 'c_clangformat_use_local_file') + + if l:style_option isnot# '' + let l:style_option = '-style=' . "'" . l:style_option . "'" + endif + + if l:use_local_file + let l:config = ale#path#FindNearestFile(a:buffer, '.clang-format') + + if !empty(l:config) + let l:style_option = '-style=file' + endif + endif + + if l:style_option isnot# '' + let l:options .= ' ' . l:style_option + endif + + let l:command = l:executable . ' --assume-filename=' . l:filename + + if l:options isnot# '' + let l:command .= ' ' . l:options + endif + + return {'command': l:command} +endfunction diff --git a/autoload/ale/fixers/clangtidy.vim b/autoload/ale/fixers/clangtidy.vim new file mode 100644 index 00000000..b37360a7 --- /dev/null +++ b/autoload/ale/fixers/clangtidy.vim @@ -0,0 +1,52 @@ +scriptencoding utf-8 +" Author: ObserverOfTime +" Description: Fixing C/C++ files with clang-tidy. + +function! s:set_variables() abort + let l:use_global = get(g:, 'ale_use_global_executables', 0) + + for l:ft in ['c', 'cpp'] + call ale#Set(l:ft . '_clangtidy_executable', 'clang-tidy') + call ale#Set(l:ft . '_clangtidy_use_global', l:use_global) + call ale#Set(l:ft . '_clangtidy_checks', []) + call ale#Set(l:ft . '_clangtidy_options', '') + call ale#Set(l:ft . '_clangtidy_extra_options', '') + call ale#Set(l:ft . '_clangtidy_fix_errors', 1) + endfor + + call ale#Set('c_build_dir', '') +endfunction + +call s:set_variables() + +function! ale#fixers#clangtidy#Var(buffer, name) abort + let l:ft = getbufvar(str2nr(a:buffer), '&filetype') + let l:ft = l:ft =~# 'cpp' ? 'cpp' : 'c' + + return ale#Var(a:buffer, l:ft . '_clangtidy_' . a:name) +endfunction + +function! ale#fixers#clangtidy#GetCommand(buffer) abort + let l:checks = join(ale#fixers#clangtidy#Var(a:buffer, 'checks'), ',') + let l:extra_options = ale#fixers#clangtidy#Var(a:buffer, 'extra_options') + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + let l:options = empty(l:build_dir) + \ ? ale#fixers#clangtidy#Var(a:buffer, 'options') : '' + let l:fix_errors = ale#fixers#clangtidy#Var(a:buffer, 'fix_errors') + + return ' -fix' . (l:fix_errors ? ' -fix-errors' : '') + \ . (empty(l:checks) ? '' : ' -checks=' . ale#Escape(l:checks)) + \ . (empty(l:extra_options) ? '' : ' ' . l:extra_options) + \ . (empty(l:build_dir) ? '' : ' -p ' . ale#Escape(l:build_dir)) + \ . ' %t' . (empty(l:options) ? '' : ' -- ' . l:options) +endfunction + +function! ale#fixers#clangtidy#Fix(buffer) abort + let l:executable = ale#fixers#clangtidy#Var(a:buffer, 'executable') + let l:command = ale#fixers#clangtidy#GetCommand(a:buffer) + + return { + \ 'command': ale#Escape(l:executable) . l:command, + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/cljfmt.vim b/autoload/ale/fixers/cljfmt.vim new file mode 100644 index 00000000..dbcf10d8 --- /dev/null +++ b/autoload/ale/fixers/cljfmt.vim @@ -0,0 +1,14 @@ +" Author: rudolf ordoyne +" Description: Support for cljfmt https://github.com/weavejester/cljfmt + +call ale#Set('clojure_cljfmt_executable', 'cljfmt') + +function! ale#fixers#cljfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'clojure_cljfmt_executable') + + return { + \ 'command': ale#Escape(l:executable) . ' fix %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/cmakeformat.vim b/autoload/ale/fixers/cmakeformat.vim new file mode 100644 index 00000000..dcc29cf3 --- /dev/null +++ b/autoload/ale/fixers/cmakeformat.vim @@ -0,0 +1,16 @@ +" Author: Attila Maczak +" 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) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/crystal.vim b/autoload/ale/fixers/crystal.vim new file mode 100644 index 00000000..4ba702ba --- /dev/null +++ b/autoload/ale/fixers/crystal.vim @@ -0,0 +1,14 @@ +call ale#Set('crystal_format_executable', 'crystal') +call ale#Set('crystal_format_options', '') + +function! ale#fixers#crystal#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'crystal_format_executable') + let l:options = ale#Var(a:buffer, 'crystal_format_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' tool format' + \ . ale#Pad(l:options) + \ . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/css_beautify.vim b/autoload/ale/fixers/css_beautify.vim new file mode 100644 index 00000000..14a837c4 --- /dev/null +++ b/autoload/ale/fixers/css_beautify.vim @@ -0,0 +1,20 @@ +" Author: https://github.com/Spixmaster +" Description: Format CSS using css-beautify from js-beautify. + +call ale#Set('css_css_beautify_executable', 'css-beautify') +call ale#Set('css_css_beautify_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('css_css_beautify_options', '') + +function! ale#fixers#css_beautify#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'css_css_beautify', + \ ['css-beautify'] + \) + + let l:options = ale#Var(a:buffer, 'css_css_beautify_options') + + return { + \ 'command': ale#Escape(l:executable) . ' ' . l:options . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/dart_format.vim b/autoload/ale/fixers/dart_format.vim new file mode 100644 index 00000000..4b8f730b --- /dev/null +++ b/autoload/ale/fixers/dart_format.vim @@ -0,0 +1,18 @@ +" Author: ghsang +" Description: Integration of dart format with ALE. + +call ale#Set('dart_format_executable', 'dart') +call ale#Set('dart_format_options', '') + +function! ale#fixers#dart_format#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'dart_format_executable') + let l:options = ale#Var(a:buffer, 'dart_format_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' format' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/dartfmt.vim b/autoload/ale/fixers/dartfmt.vim new file mode 100644 index 00000000..0687d6d1 --- /dev/null +++ b/autoload/ale/fixers/dartfmt.vim @@ -0,0 +1,18 @@ +" Author: reisub0 +" Description: Integration of dartfmt with ALE. + +call ale#Set('dart_dartfmt_executable', 'dartfmt') +call ale#Set('dart_dartfmt_options', '') + +function! ale#fixers#dartfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'dart_dartfmt_executable') + let l:options = ale#Var(a:buffer, 'dart_dartfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -w' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/deno.vim b/autoload/ale/fixers/deno.vim new file mode 100644 index 00000000..7154c6ee --- /dev/null +++ b/autoload/ale/fixers/deno.vim @@ -0,0 +1,17 @@ +function! ale#fixers#deno#Fix(buffer) abort + let l:executable = ale#handlers#deno#GetExecutable(a:buffer) + + if !executable(l:executable) + return 0 + endif + + let l:options = ' fmt -' + + if ale#Var(a:buffer, 'deno_unstable') + let l:options = l:options . ' --unstable' + endif + + return { + \ 'command': ale#Escape(l:executable) . l:options + \} +endfunction diff --git a/autoload/ale/fixers/dfmt.vim b/autoload/ale/fixers/dfmt.vim new file mode 100644 index 00000000..0072e045 --- /dev/null +++ b/autoload/ale/fixers/dfmt.vim @@ -0,0 +1,18 @@ +" Author: theoldmoon0602 +" Description: Integration of dfmt with ALE. + +call ale#Set('d_dfmt_executable', 'dfmt') +call ale#Set('d_dfmt_options', '') + +function! ale#fixers#dfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'd_dfmt_executable') + let l:options = ale#Var(a:buffer, 'd_dfmt_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/dhall_format.vim b/autoload/ale/fixers/dhall_format.vim new file mode 100644 index 00000000..4f12abc8 --- /dev/null +++ b/autoload/ale/fixers/dhall_format.vim @@ -0,0 +1,11 @@ +" Author: toastal +" Description: Dhall’s built-in formatter +" +function! ale#fixers#dhall_format#Fix(buffer) abort + let l:executable = ale#dhall#GetExecutableWithOptions(a:buffer) + + return { + \ 'command': l:executable + \ . ' format' + \} +endfunction diff --git a/autoload/ale/fixers/dhall_freeze.vim b/autoload/ale/fixers/dhall_freeze.vim new file mode 100644 index 00000000..ff54482d --- /dev/null +++ b/autoload/ale/fixers/dhall_freeze.vim @@ -0,0 +1,14 @@ +" Author: toastal +" Description: Dhall’s package freezing + +call ale#Set('dhall_freeze_options', '') + +function! ale#fixers#dhall_freeze#Freeze(buffer) abort + let l:executable = ale#dhall#GetExecutableWithOptions(a:buffer) + + return { + \ 'command': l:executable + \ . ' freeze' + \ . ale#Pad(ale#Var(a:buffer, 'dhall_freeze_options')) + \} +endfunction diff --git a/autoload/ale/fixers/dhall_lint.vim b/autoload/ale/fixers/dhall_lint.vim new file mode 100644 index 00000000..149a6581 --- /dev/null +++ b/autoload/ale/fixers/dhall_lint.vim @@ -0,0 +1,11 @@ +" Author: toastal +" Description: Dhall’s built-in linter/formatter + +function! ale#fixers#dhall_lint#Fix(buffer) abort + let l:executable = ale#dhall#GetExecutableWithOptions(a:buffer) + + return { + \ 'command': l:executable + \ . ' lint' + \} +endfunction diff --git a/autoload/ale/fixers/djlint.vim b/autoload/ale/fixers/djlint.vim new file mode 100644 index 00000000..74c6c261 --- /dev/null +++ b/autoload/ale/fixers/djlint.vim @@ -0,0 +1,48 @@ +" Author: Adrian Vollmer (computerfluesterer@protonmail.com) +" Description: HTML template formatter using `djlint --reformat` + +call ale#Set('html_djlint_executable', 'djlint') +call ale#Set('html_djlint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('html_djlint_options', '') + +function! ale#fixers#djlint#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'html_djlint', + \ ['djlint'] + \) + + let l:options = ale#Var(a:buffer, 'html_djlint_options') + + let l:profile = '' + let l:filetypes = split(getbufvar(a:buffer, '&filetype'), '\.') + + " Append the --profile flag depending on the current filetype (unless it's + " already set in g:html_djlint_options). + if match(l:options, '--profile') == -1 + let l:djlint_profiles = { + \ 'html': 'html', + \ 'htmldjango': 'django', + \ 'jinja': 'jinja', + \ 'nunjucks': 'nunjucks', + \ 'handlebars': 'handlebars', + \ 'gohtmltmpl': 'golang', + \ 'htmlangular': 'angular', + \} + + for l:filetype in l:filetypes + if has_key(l:djlint_profiles, l:filetype) + let l:profile = l:djlint_profiles[l:filetype] + break + endif + endfor + endif + + if !empty(l:profile) + let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--profile ' . l:profile + endif + + return { + \ 'command': ale#Escape(l:executable) . ' --reformat ' . l:options . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/dotnet_format.vim b/autoload/ale/fixers/dotnet_format.vim new file mode 100644 index 00000000..b44a28bd --- /dev/null +++ b/autoload/ale/fixers/dotnet_format.vim @@ -0,0 +1,18 @@ +" Author: ghsang +" Description: Integration of dotnet format with ALE. + +call ale#Set('cs_dotnet_format_executable', 'dotnet') +call ale#Set('cs_dotnet_format_options', '') + +function! ale#fixers#dotnet_format#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'cs_dotnet_format_executable') + let l:options = ale#Var(a:buffer, 'cs_dotnet_format_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' format' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' --folder --include %t "$(dirname %t)"', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/dprint.vim b/autoload/ale/fixers/dprint.vim new file mode 100644 index 00000000..99e590df --- /dev/null +++ b/autoload/ale/fixers/dprint.vim @@ -0,0 +1,29 @@ +call ale#Set('dprint_executable', 'dprint') +call ale#Set('dprint_executable_override', 0) +call ale#Set('dprint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('dprint_options', '') +call ale#Set('dprint_config', 'dprint.json') + +function! ale#fixers#dprint#Fix(buffer) abort + let l:executable = ale#path#FindExecutable(a:buffer, 'dprint', ['dprint']) + let l:executable_override = ale#Var(a:buffer, 'dprint_executable_override') + + if !executable(l:executable) && !l:executable_override + return 0 + endif + + let l:options = ale#Var(a:buffer, 'dprint_options') + let l:config = ale#path#FindNearestFile(a:buffer, ale#Var(a:buffer, 'dprint_config')) + + if !empty(l:config) + let l:options = l:options . ' -c ' . ale#Escape(l:config) + endif + + let l:options = l:options . ' --stdin %s' + + return { + \ 'command': ale#Escape(l:executable) + \ . ' fmt ' + \ . l:options + \} +endfunction diff --git a/autoload/ale/fixers/dune.vim b/autoload/ale/fixers/dune.vim new file mode 100644 index 00000000..6ef7ec9f --- /dev/null +++ b/autoload/ale/fixers/dune.vim @@ -0,0 +1,16 @@ +" Author: Albert Peschar +" Description: Fix files with dune format. + +call ale#Set('ocaml_dune_executable', 'dune') +call ale#Set('ocaml_dune_options', '') + +function! ale#fixers#dune#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'ocaml_dune_executable') + let l:options = ale#Var(a:buffer, 'ocaml_dune_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' format' + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/elm_format.vim b/autoload/ale/fixers/elm_format.vim new file mode 100644 index 00000000..a4740db4 --- /dev/null +++ b/autoload/ale/fixers/elm_format.vim @@ -0,0 +1,23 @@ +" Author: soywod +" Description: Integration of elm-format with ALE. + +call ale#Set('elm_format_executable', 'elm-format') +call ale#Set('elm_format_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('elm_format_options', '--yes') + +function! ale#fixers#elm_format#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'elm_format', [ + \ 'node_modules/.bin/elm-format', + \]) +endfunction + +function! ale#fixers#elm_format#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'elm_format_options') + + return { + \ 'command': ale#Escape(ale#fixers#elm_format#GetExecutable(a:buffer)) + \ . ' %t' + \ . (empty(l:options) ? '' : ' ' . l:options), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/erbformatter.vim b/autoload/ale/fixers/erbformatter.vim new file mode 100644 index 00000000..7bb43e0c --- /dev/null +++ b/autoload/ale/fixers/erbformatter.vim @@ -0,0 +1,13 @@ +" Author: Arash Mousavi +" Description: Support for ERB::Formetter https://github.com/nebulab/erb-formatter + +call ale#Set('eruby_erbformatter_executable', 'erb-formatter') + +function! ale#fixers#erbformatter#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'eruby_erbformatter_executable') + + return { + \ 'command': ale#Escape(l:executable) . ' -w %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/erblint.vim b/autoload/ale/fixers/erblint.vim new file mode 100644 index 00000000..41aca0c8 --- /dev/null +++ b/autoload/ale/fixers/erblint.vim @@ -0,0 +1,40 @@ +" Author: Roeland Moors - https://github.com/roelandmoors +" Description: ERB Lint, support for https://github.com/Shopify/erb-lint + +call ale#Set('eruby_erblint_executable', 'erblint') +call ale#Set('eruby_erblint_options', '') + + +" Erblint fixer outputs diagnostics first and then the fixed +" output. These are delimited by something like this: +" ================ /path/to/demo.html.erb ================== +" We only need the output after this +function! ale#fixers#erblint#PostProcess(buffer, output) abort + let l:line = 0 + + for l:output in a:output + let l:line = l:line + 1 + + if l:output =~# "^=\\+.*=\\+$" + break + endif + endfor + + return a:output[l:line :] +endfunction + +function! ale#fixers#erblint#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'eruby_erblint_executable') + let l:options = ale#Var(a:buffer, 'eruby_erblint_options') + + return ale#ruby#EscapeExecutable(l:executable, 'erblint') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --autocorrect --stdin %s' +endfunction + +function! ale#fixers#erblint#Fix(buffer) abort + return { + \ 'command': ale#fixers#erblint#GetCommand(a:buffer), + \ 'process_with': 'ale#fixers#erblint#PostProcess' + \} +endfunction diff --git a/autoload/ale/fixers/erlang_mode.vim b/autoload/ale/fixers/erlang_mode.vim new file mode 100644 index 00000000..0e7d46e8 --- /dev/null +++ b/autoload/ale/fixers/erlang_mode.vim @@ -0,0 +1,50 @@ +" Author: Dmitri Vereshchagin +" Description: Indent with the Erlang mode for Emacs + +call ale#Set('erlang_erlang_mode_emacs_executable', 'emacs') +call ale#Set('erlang_erlang_mode_indent_level', 4) +call ale#Set('erlang_erlang_mode_icr_indent', 'nil') +call ale#Set('erlang_erlang_mode_indent_guard', 2) +call ale#Set('erlang_erlang_mode_argument_indent', 2) +call ale#Set('erlang_erlang_mode_indent_tabs_mode', 'nil') + +let s:variables = { +\ 'erlang-indent-level': 'erlang_erlang_mode_indent_level', +\ 'erlang-icr-indent': 'erlang_erlang_mode_icr_indent', +\ 'erlang-indent-guard': 'erlang_erlang_mode_indent_guard', +\ 'erlang-argument-indent': 'erlang_erlang_mode_argument_indent', +\ 'indent-tabs-mode': 'erlang_erlang_mode_indent_tabs_mode', +\} + +function! ale#fixers#erlang_mode#Fix(buffer) abort + let l:emacs_executable = + \ ale#Var(a:buffer, 'erlang_erlang_mode_emacs_executable') + + let l:exprs = [ + \ '(setq enable-local-variables :safe)', + \ s:SetqDefault(a:buffer, s:variables), + \ '(erlang-mode)', + \ '(font-lock-fontify-region (point-min) (point-max))', + \ '(indent-region (point-min) (point-max))', + \ '(funcall (if indent-tabs-mode ''tabify ''untabify)' + \ . ' (point-min) (point-max))', + \ '(save-buffer 0)', + \] + + let l:command = ale#Escape(l:emacs_executable) + \ . ' --batch' + \ . ' --find-file=%t' + \ . join(map(l:exprs, '" --eval=" . ale#Escape(v:val)'), '') + + return {'command': l:command, 'read_temporary_file': 1} +endfunction + +function! s:SetqDefault(buffer, variables) abort + let l:args = [] + + for [l:emacs_name, l:ale_name] in items(a:variables) + let l:args += [l:emacs_name, ale#Var(a:buffer, l:ale_name)] + endfor + + return '(setq-default ' . join(l:args) . ')' +endfunction diff --git a/autoload/ale/fixers/erlfmt.vim b/autoload/ale/fixers/erlfmt.vim new file mode 100644 index 00000000..06cb1704 --- /dev/null +++ b/autoload/ale/fixers/erlfmt.vim @@ -0,0 +1,19 @@ +" Author: AntoineGagne - https://github.com/AntoineGagne +" Description: Integration of erlfmt with ALE. + +call ale#Set('erlang_erlfmt_executable', 'erlfmt') +call ale#Set('erlang_erlfmt_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('erlang_erlfmt_options', '') + +function! ale#fixers#erlfmt#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'erlang_erlfmt', ['erlfmt']) +endfunction + +function! ale#fixers#erlfmt#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'erlang_erlfmt_options') + let l:executable = ale#fixers#erlfmt#GetExecutable(a:buffer) + + let l:command = ale#Escape(l:executable) . ale#Pad(l:options) . ' -' + + return {'command': l:command} +endfunction diff --git a/autoload/ale/fixers/eslint.vim b/autoload/ale/fixers/eslint.vim new file mode 100644 index 00000000..c9535cb0 --- /dev/null +++ b/autoload/ale/fixers/eslint.vim @@ -0,0 +1,83 @@ +" Author: w0rp +" Description: Fixing files with eslint. + +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' + + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ l:command, + \ function('ale#fixers#eslint#ApplyFixForVersion'), + \) +endfunction + +function! ale#fixers#eslint#ProcessFixDryRunOutput(buffer, output) abort + for l:item in ale#util#FuzzyJSONDecode(a:output, []) + return split(get(l:item, 'output', ''), "\n") + endfor + + return [] +endfunction + +function! ale#fixers#eslint#ProcessEslintDOutput(buffer, output) abort + " If the output is an error message, don't use it. + for l:line in a:output[:10] + if l:line =~# '\v^Error:|^Could not connect' + return [] + endif + endfor + + return a:output +endfunction + +function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort + let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'javascript_eslint_options') + + " Use the configuration file from the options, if configured. + if l:options =~# '\v(^| )-c|(^| )--config' + let l:config = '' + let l:has_config = 1 + else + let l:config = ale#handlers#eslint#FindConfig(a:buffer) + let l:has_config = !empty(l:config) + endif + + if !l:has_config + return 0 + endif + + " Use --fix-to-stdout with eslint_d + if l:executable =~# 'eslint_d$' && ale#semver#GTE(a:version, [3, 19, 0]) + return { + \ 'cwd': ale#handlers#eslint#GetCwd(a:buffer), + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ale#Pad(l:options) + \ . ' --stdin-filename %s --stdin --fix-to-stdout', + \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', + \} + endif + + " 4.9.0 is the first version with --fix-dry-run + if ale#semver#GTE(a:version, [4, 9, 0]) + return { + \ 'cwd': ale#handlers#eslint#GetCwd(a:buffer), + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ale#Pad(l:options) + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \} + endif + + return { + \ 'cwd': ale#handlers#eslint#GetCwd(a:buffer), + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ale#Pad(l:options) + \ . (!empty(l:config) ? ' -c ' . ale#Escape(l:config) : '') + \ . ' --fix %t', + \ 'read_temporary_file': 1, + \} +endfunction 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 +" 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/fish_indent.vim b/autoload/ale/fixers/fish_indent.vim new file mode 100644 index 00000000..ebf17c5a --- /dev/null +++ b/autoload/ale/fixers/fish_indent.vim @@ -0,0 +1,19 @@ +" Author: Chen YuanYuan +" Description: Integration of fish_indent with ALE. + +call ale#Set('fish_fish_indent_executable', 'fish_indent') +call ale#Set('fish_fish_indent_options', '') + +function! ale#fixers#fish_indent#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'fish_fish_indent_executable') + let l:options = ale#Var(a:buffer, 'fish_fish_indent_options') + let l:filename = ale#Escape(bufname(a:buffer)) + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -w ' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/fixjson.vim b/autoload/ale/fixers/fixjson.vim new file mode 100644 index 00000000..4bad8f9b --- /dev/null +++ b/autoload/ale/fixers/fixjson.vim @@ -0,0 +1,28 @@ +" Author: rhysd +" Description: Integration of fixjson with ALE. + +call ale#Set('json_fixjson_executable', 'fixjson') +call ale#Set('json_fixjson_options', '') +call ale#Set('json_fixjson_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#fixers#fixjson#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'json_fixjson', [ + \ 'node_modules/.bin/fixjson', + \]) +endfunction + +function! ale#fixers#fixjson#Fix(buffer) abort + let l:executable = ale#Escape(ale#fixers#fixjson#GetExecutable(a:buffer)) + let l:filename = ale#Escape(bufname(a:buffer)) + let l:command = l:executable . ' --stdin-filename ' . l:filename + + let l:options = ale#Var(a:buffer, 'json_fixjson_options') + + if l:options isnot# '' + let l:command .= ' ' . l:options + endif + + return { + \ 'command': l:command + \} +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 +" 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/forge.vim b/autoload/ale/fixers/forge.vim new file mode 100644 index 00000000..2efbb7da --- /dev/null +++ b/autoload/ale/fixers/forge.vim @@ -0,0 +1,11 @@ +call ale#Set('solidity_forge_executable', 'forge') + +function! ale#fixers#forge#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'solidity_forge_executable') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' fmt %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/fourmolu.vim b/autoload/ale/fixers/fourmolu.vim new file mode 100644 index 00000000..399ec0f4 --- /dev/null +++ b/autoload/ale/fixers/fourmolu.vim @@ -0,0 +1,20 @@ +call ale#Set('haskell_fourmolu_executable', 'fourmolu') +call ale#Set('haskell_fourmolu_options', '') + +function! ale#fixers#fourmolu#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_fourmolu_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'fourmolu') +endfunction + +function! ale#fixers#fourmolu#Fix(buffer) abort + let l:executable = ale#fixers#fourmolu#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'haskell_fourmolu_options') + + return { + \ 'command': l:executable + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' --stdin-input-file ' + \ . ale#Escape(@%), + \} +endfunction diff --git a/autoload/ale/fixers/generic.vim b/autoload/ale/fixers/generic.vim new file mode 100644 index 00000000..cb8865b4 --- /dev/null +++ b/autoload/ale/fixers/generic.vim @@ -0,0 +1,25 @@ +" Author: w0rp +" Description: Generic functions for fixing files with. + +function! ale#fixers#generic#RemoveTrailingBlankLines(buffer, lines) abort + let l:end_index = len(a:lines) - 1 + + while l:end_index > 0 && empty(a:lines[l:end_index]) + let l:end_index -= 1 + endwhile + + return a:lines[:l:end_index] +endfunction + +" Remove all whitespaces at the end of lines +function! ale#fixers#generic#TrimWhitespace(buffer, lines) abort + let l:index = 0 + let l:lines_new = range(len(a:lines)) + + for l:line in a:lines + let l:lines_new[l:index] = substitute(l:line, '\s\+$', '', 'g') + let l:index = l:index + 1 + endfor + + return l:lines_new +endfunction diff --git a/autoload/ale/fixers/generic_python.vim b/autoload/ale/fixers/generic_python.vim new file mode 100644 index 00000000..d55a23c3 --- /dev/null +++ b/autoload/ale/fixers/generic_python.vim @@ -0,0 +1,75 @@ +" Author: w0rp +" Description: Generic fixer functions for Python. + +" Add blank lines before control statements. +function! ale#fixers#generic_python#AddLinesBeforeControlStatements(buffer, lines) abort + let l:new_lines = [] + let l:last_indent_size = 0 + let l:last_line_is_blank = 0 + let l:in_docstring = 0 + + for l:line in a:lines + let l:indent_size = len(matchstr(l:line, '^ *')) + + if !l:in_docstring + " Make sure it is not just a single line docstring and then verify + " it's starting a new docstring + if match(l:line, '\v^ *("""|'''''').*("""|'''''')') == -1 + \&& match(l:line, '\v^ *("""|'''''')') >= 0 + let l:in_docstring = 1 + endif + else + if match(l:line, '\v^ *.*("""|'''''')') >= 0 + let l:in_docstring = 0 + endif + endif + + if !l:last_line_is_blank + \&& !l:in_docstring + \&& l:indent_size <= l:last_indent_size + \&& match(l:line, '\v^ *(return|if|for|while|break|continue)(\(| |$)') >= 0 + call add(l:new_lines, '') + endif + + call add(l:new_lines, l:line) + let l:last_indent_size = l:indent_size + let l:last_line_is_blank = empty(split(l:line)) + endfor + + return l:new_lines +endfunction + +" This function breaks up long lines so that autopep8 or other tools can +" fix the badly-indented code which is produced as a result. +function! ale#fixers#generic_python#BreakUpLongLines(buffer, lines) abort + " Default to a maximum line length of 79 + let l:max_line_length = 79 + let l:conf = ale#path#FindNearestFile(a:buffer, 'setup.cfg') + + " Read the maximum line length from setup.cfg + if !empty(l:conf) + for l:match in ale#util#GetMatches( + \ readfile(l:conf), + \ '\v^ *max-line-length *\= *(\d+)', + \) + let l:max_line_length = str2nr(l:match[1]) + endfor + endif + + let l:new_list = [] + + for l:line in a:lines + if len(l:line) > l:max_line_length && l:line !~# '# *noqa' + let l:line = substitute(l:line, '\v([(,])([^)])', '\1\n\2', 'g') + let l:line = substitute(l:line, '\v([^(])([)])', '\1,\n\2', 'g') + + for l:split_line in split(l:line, "\n") + call add(l:new_list, l:split_line) + endfor + else + call add(l:new_list, l:line) + endif + endfor + + return l:new_list +endfunction diff --git a/autoload/ale/fixers/gleam_format.vim b/autoload/ale/fixers/gleam_format.vim new file mode 100644 index 00000000..00c366f5 --- /dev/null +++ b/autoload/ale/fixers/gleam_format.vim @@ -0,0 +1,19 @@ +" Author: Jonathan Palardt https://github.com/jpalardy +" Description: Integration of 'gleam format' with ALE. + +call ale#Set('gleam_format_executable', 'gleam') + +function! ale#fixers#gleam_format#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'gleam_format_executable') + + return ale#Escape(l:executable) +endfunction + +function! ale#fixers#gleam_format#Fix(buffer) abort + let l:executable = ale#fixers#gleam_format#GetExecutable(a:buffer) + + return { + \ 'command': l:executable . ' format %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/gnatpp.vim b/autoload/ale/fixers/gnatpp.vim new file mode 100644 index 00000000..bf3d484e --- /dev/null +++ b/autoload/ale/fixers/gnatpp.vim @@ -0,0 +1,17 @@ +" Author: tim +" Description: Fix files with gnatpp. + +call ale#Set('ada_gnatpp_executable', 'gnatpp') +call ale#Set('ada_gnatpp_options', '') + +function! ale#fixers#gnatpp#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'ada_gnatpp_executable') + let l:options = ale#Var(a:buffer, 'ada_gnatpp_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/gofmt.vim b/autoload/ale/fixers/gofmt.vim new file mode 100644 index 00000000..b9cfbb58 --- /dev/null +++ b/autoload/ale/fixers/gofmt.vim @@ -0,0 +1,16 @@ +" Author: aliou +" Description: Integration of gofmt with ALE. + +call ale#Set('go_gofmt_executable', 'gofmt') +call ale#Set('go_gofmt_options', '') + +function! ale#fixers#gofmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'go_gofmt_executable') + let l:options = ale#Var(a:buffer, 'go_gofmt_options') + let l:env = ale#go#EnvString(a:buffer) + + return { + \ 'command': l:env . ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \} +endfunction diff --git a/autoload/ale/fixers/gofumpt.vim b/autoload/ale/fixers/gofumpt.vim new file mode 100644 index 00000000..99753209 --- /dev/null +++ b/autoload/ale/fixers/gofumpt.vim @@ -0,0 +1,17 @@ +" Author: David Houston +" Description: A stricter gofmt implementation. + +call ale#Set('go_gofumpt_executable', 'gofumpt') +call ale#Set('go_gofumpt_options', '') + +function! ale#fixers#gofumpt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'go_gofumpt_executable') + let l:options = ale#Var(a:buffer, 'go_gofumpt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ale#Pad(l:options) + \ . ' -w -- %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/goimports.vim b/autoload/ale/fixers/goimports.vim new file mode 100644 index 00000000..65f0fd98 --- /dev/null +++ b/autoload/ale/fixers/goimports.vim @@ -0,0 +1,23 @@ +" Author: Jeff Willette +" Description: Integration of goimports with ALE. + +call ale#Set('go_goimports_executable', 'goimports') +call ale#Set('go_goimports_options', '') + +function! ale#fixers#goimports#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'go_goimports_executable') + let l:options = ale#Var(a:buffer, 'go_goimports_options') + let l:env = ale#go#EnvString(a:buffer) + + if !executable(l:executable) + return 0 + endif + + return { + \ 'command': l:env . ale#Escape(l:executable) + \ . ' -l -w -srcdir %s' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/golangci_lint.vim b/autoload/ale/fixers/golangci_lint.vim new file mode 100644 index 00000000..596f1590 --- /dev/null +++ b/autoload/ale/fixers/golangci_lint.vim @@ -0,0 +1,32 @@ +" Author: Ian Stapleton Cordasco +" Description: Run golangci-lint with the --fix flag to autofix some issues + +call ale#Set('go_golangci_lint_options', '') +call ale#Set('go_golangci_lint_executable', 'golangci-lint') +call ale#Set('go_golangci_lint_package', 1) + +function! ale#fixers#golangci_lint#GetCommand(buffer) abort + let l:filename = expand('#' . a:buffer . ':t') + let l:executable = ale#Var(a:buffer, 'go_golangci_lint_executable') + let l:options = ale#Var(a:buffer, 'go_golangci_lint_options') . ' --fix' + let l:package_mode = ale#Var(a:buffer, 'go_golangci_lint_package') + let l:env = ale#go#EnvString(a:buffer) + + + if l:package_mode + return l:env . ale#Escape(l:executable) + \ . ' run ' + \ . l:options + endif + + return l:env . ale#Escape(l:executable) + \ . ' run ' + \ . l:options + \ . ' ' . ale#Escape(l:filename) +endfunction + +function! ale#fixers#golangci_lint#Fix(buffer) abort + return { + \ 'command': ale#fixers#golangci_lint#GetCommand(a:buffer), + \} +endfunction diff --git a/autoload/ale/fixers/golines.vim b/autoload/ale/fixers/golines.vim new file mode 100644 index 00000000..9326f482 --- /dev/null +++ b/autoload/ale/fixers/golines.vim @@ -0,0 +1,21 @@ +" Author Pig Frown +" Description: Fix Go files long lines with golines" + +call ale#Set('go_golines_executable', 'golines') + +call ale#Set('go_golines_options', '') + +function! ale#fixers#golines#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'go_golines_executable') + let l:options = ale#Var(a:buffer, 'go_golines_options') + let l:env = ale#go#EnvString(a:buffer) + + if !executable(l:executable) + return 0 + endif + + return { + \ 'command': l:env . ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \} +endfunction diff --git a/autoload/ale/fixers/gomod.vim b/autoload/ale/fixers/gomod.vim new file mode 100644 index 00000000..ee8c46c9 --- /dev/null +++ b/autoload/ale/fixers/gomod.vim @@ -0,0 +1,11 @@ +call ale#Set('go_go_executable', 'go') + +function! ale#fixers#gomod#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'go_go_executable') + let l:env = ale#go#EnvString(a:buffer) + + return { + \ 'command': l:env . ale#Escape(l:executable) . ' mod edit -fmt %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/google_java_format.vim b/autoload/ale/fixers/google_java_format.vim new file mode 100644 index 00000000..20086c73 --- /dev/null +++ b/autoload/ale/fixers/google_java_format.vim @@ -0,0 +1,23 @@ +" Author: butlerx +" Description: Integration of Google-java-format with ALE. + +call ale#Set('java_google_java_format_executable', 'google-java-format') +call ale#Set('java_google_java_format_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('java_google_java_format_options', '') + +function! ale#fixers#google_java_format#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'java_google_java_format_options') + let l:executable = ale#Var(a:buffer, 'java_google_java_format_executable') + + if !executable(l:executable) + return 0 + endif + + return { + \ 'command': ale#Escape(l:executable) + \ . ' ' . (empty(l:options) ? '' : ' ' . l:options) + \ . ' --replace' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/gopls.vim b/autoload/ale/fixers/gopls.vim new file mode 100644 index 00000000..98f553c1 --- /dev/null +++ b/autoload/ale/fixers/gopls.vim @@ -0,0 +1,23 @@ +" Author: Sean Enck +" Description: Integration of gopls format with ALE. + +call ale#Set('go_gopls_fix_executable', 'gopls') +call ale#Set('go_gopls_fix_options', '') + +function! ale#fixers#gopls#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'go_gopls_fix_executable') + let l:options = ale#Var(a:buffer, 'go_gopls_fix_options') + let l:env = ale#go#EnvString(a:buffer) + + if !executable(l:executable) + return 0 + endif + + return { + \ 'command': l:env . ale#Escape(l:executable) + \ . ' format' + \ . ale#Pad(l:options) + \ . ' -l -w %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/hackfmt.vim b/autoload/ale/fixers/hackfmt.vim new file mode 100644 index 00000000..bf2d4f71 --- /dev/null +++ b/autoload/ale/fixers/hackfmt.vim @@ -0,0 +1,18 @@ +" Author: Sam Howie +" Description: Integration of hackfmt with ALE. + +call ale#Set('hack_hackfmt_executable', 'hackfmt') +call ale#Set('hack_hackfmt_options', '') + +function! ale#fixers#hackfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'hack_hackfmt_executable') + let l:options = ale#Var(a:buffer, 'hack_hackfmt_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/help.vim b/autoload/ale/fixers/help.vim new file mode 100644 index 00000000..b20740fe --- /dev/null +++ b/autoload/ale/fixers/help.vim @@ -0,0 +1,24 @@ +" Author: w0rp +" Description: Generic fixer functions for Vim help documents. + +function! ale#fixers#help#AlignTags(buffer, lines) abort + let l:new_lines = [] + + for l:line in a:lines + if len(l:line) != 79 + let l:match = matchlist(l:line, '\v +(\*[^*]+\*)$') + + if !empty(l:match) + let l:start = l:line[:-len(l:match[0]) - 1] + let l:tag = l:match[1] + let l:spaces = repeat(' ', 79 - len(l:start) - len(l:tag)) + + let l:line = l:start . l:spaces . l:tag + endif + endif + + call add(l:new_lines, l:line) + endfor + + return l:new_lines +endfunction diff --git a/autoload/ale/fixers/hfmt.vim b/autoload/ale/fixers/hfmt.vim new file mode 100644 index 00000000..0407b713 --- /dev/null +++ b/autoload/ale/fixers/hfmt.vim @@ -0,0 +1,16 @@ +" Author: zack +" Description: Integration of hfmt with ALE. + +call ale#Set('haskell_hfmt_executable', 'hfmt') + +function! ale#fixers#hfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_hfmt_executable') + + return { + \ 'command': ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hfmt') + \ . ' -w' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/hindent.vim b/autoload/ale/fixers/hindent.vim new file mode 100644 index 00000000..b6009a2c --- /dev/null +++ b/autoload/ale/fixers/hindent.vim @@ -0,0 +1,20 @@ +" Author: AlexeiDrake +" Description: Integration of hindent formatting with ALE. +" +call ale#Set('haskell_hindent_executable', 'hindent') + +function! ale#fixers#hindent#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_hindent_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hindent') +endfunction + +function! ale#fixers#hindent#Fix(buffer) abort + let l:executable = ale#fixers#hindent#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/hlint.vim b/autoload/ale/fixers/hlint.vim new file mode 100644 index 00000000..88779a55 --- /dev/null +++ b/autoload/ale/fixers/hlint.vim @@ -0,0 +1,13 @@ +" Author: eborden +" Description: Integration of hlint refactor with ALE. +" + +function! ale#fixers#hlint#Fix(buffer) abort + return { + \ 'command': ale#handlers#hlint#GetExecutable(a:buffer) + \ . ' --refactor' + \ . ' --refactor-options="--inplace"' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/html_beautify.vim b/autoload/ale/fixers/html_beautify.vim new file mode 100644 index 00000000..9817563e --- /dev/null +++ b/autoload/ale/fixers/html_beautify.vim @@ -0,0 +1,20 @@ +" Author: WhyNotHugo +" Description: Format HTML files with html-beautify. + +call ale#Set('html_beautify_executable', 'html-beautify') +call ale#Set('html_beautify_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('html_beautify_options', '') + +function! ale#fixers#html_beautify#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'html_beautify', + \ ['html-beautify'] + \) + + let l:options = ale#Var(a:buffer, 'html_beautify_options') + + return { + \ 'command': ale#Escape(l:executable) . ' ' . l:options . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/htmlbeautifier.vim b/autoload/ale/fixers/htmlbeautifier.vim new file mode 100644 index 00000000..756d4a05 --- /dev/null +++ b/autoload/ale/fixers/htmlbeautifier.vim @@ -0,0 +1,13 @@ +" Author: Arash Mousavi +" Description: Support for HTML Beautifier https://github.com/threedaymonk/htmlbeautifier + +call ale#Set('eruby_htmlbeautifier_executable', 'htmlbeautifier') + +function! ale#fixers#htmlbeautifier#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'eruby_htmlbeautifier_executable') + + return { + \ 'command': ale#Escape(l:executable) . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/hurlfmt.vim b/autoload/ale/fixers/hurlfmt.vim new file mode 100644 index 00000000..fc19fa83 --- /dev/null +++ b/autoload/ale/fixers/hurlfmt.vim @@ -0,0 +1,15 @@ +call ale#Set('hurl_hurlfmt_executable', 'hurlfmt') + +function! ale#fixers#hurlfmt#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'hurl_hurlfmt_executable') + + return ale#Escape(l:executable) + \ . ' --out hurl' +endfunction + +function! ale#fixers#hurlfmt#Fix(buffer) abort + return { + \ 'command': ale#fixers#hurlfmt#GetCommand(a:buffer) + \} +endfunction + diff --git a/autoload/ale/fixers/importjs.vim b/autoload/ale/fixers/importjs.vim new file mode 100644 index 00000000..b5487b2c --- /dev/null +++ b/autoload/ale/fixers/importjs.vim @@ -0,0 +1,25 @@ +" Author: Jeff Willette +" Description: Integration of importjs with ALE. + +call ale#Set('javascript_importjs_executable', 'importjs') + +function! ale#fixers#importjs#ProcessOutput(buffer, output) abort + let l:result = ale#util#FuzzyJSONDecode(a:output, []) + + return split(get(l:result, 'fileContent', ''), "\n") +endfunction + +function! ale#fixers#importjs#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'javascript_importjs_executable') + + if !executable(l:executable) + return 0 + endif + + return { + \ 'command': ale#Escape(l:executable) + \ . ' fix' + \ . ' %s', + \ 'process_with': 'ale#fixers#importjs#ProcessOutput', + \} +endfunction diff --git a/autoload/ale/fixers/isort.vim b/autoload/ale/fixers/isort.vim new file mode 100644 index 00000000..45083ee8 --- /dev/null +++ b/autoload/ale/fixers/isort.vim @@ -0,0 +1,77 @@ +" Author: w0rp +" Description: Fixing Python imports with isort. + +call ale#Set('python_isort_executable', 'isort') +call ale#Set('python_isort_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_isort_options', '') +call ale#Set('python_isort_auto_pipenv', 0) +call ale#Set('python_isort_auto_poetry', 0) +call ale#Set('python_isort_auto_uv', 0) + +function! ale#fixers#isort#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_isort_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_isort_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_isort_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_isort', ['isort']) +endfunction + +function! ale#fixers#isort#GetCmd(buffer) abort + let l:executable = ale#fixers#isort#GetExecutable(a:buffer) + let l:cmd = [ale#Escape(l:executable)] + + if l:executable =~? '\(pipenv\|poetry\|uv\)$' + call extend(l:cmd, ['run', 'isort']) + endif + + return join(l:cmd, ' ') +endfunction + +function! ale#fixers#isort#FixForVersion(buffer, version) abort + let l:executable = ale#fixers#isort#GetExecutable(a:buffer) + let l:cmd = [ale#Escape(l:executable)] + + if l:executable =~? '\(pipenv\|poetry\|uv\)$' + call extend(l:cmd, ['run', 'isort']) + endif + + if ale#semver#GTE(a:version, [5, 7, 0]) + call add(l:cmd, '--filename %s') + endif + + let l:options = ale#Var(a:buffer, 'python_isort_options') + + if !empty(l:options) + call add(l:cmd, l:options) + endif + + call add(l:cmd, '-') + + return { + \ 'cwd': '%s:h', + \ 'command': join(l:cmd, ' '), + \} +endfunction + +function! ale#fixers#isort#Fix(buffer) abort + let l:executable = ale#fixers#isort#GetExecutable(a:buffer) + let l:command = ale#fixers#isort#GetCmd(a:buffer) . ale#Pad('--version') + + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ l:command, + \ function('ale#fixers#isort#FixForVersion'), + \) +endfunction diff --git a/autoload/ale/fixers/jq.vim b/autoload/ale/fixers/jq.vim new file mode 100644 index 00000000..cd9b9138 --- /dev/null +++ b/autoload/ale/fixers/jq.vim @@ -0,0 +1,22 @@ +call ale#Set('json_jq_executable', 'jq') +call ale#Set('json_jq_options', '') +call ale#Set('json_jq_filters', '.') + +function! ale#fixers#jq#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'json_jq_executable') +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') + + if empty(l:filters) + return 0 + endif + + return { + \ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer)) + \ . ' ' . l:filters . ' ' + \ . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/json_pytool.vim b/autoload/ale/fixers/json_pytool.vim new file mode 100644 index 00000000..17aeeea1 --- /dev/null +++ b/autoload/ale/fixers/json_pytool.vim @@ -0,0 +1,20 @@ +" Author: idbrii +" Description: json formatter as ALE fixer using python's json.tool + +call ale#Set('json_pytool_executable', 'python') +call ale#Set('json_pytool_options', '') +call ale#Set('json_pytool_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#fixers#json_pytool#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'json_pytool', ['python']) +endfunction + +function! ale#fixers#json_pytool#Fix(buffer) abort + let l:executable = ale#Escape(ale#fixers#json_pytool#GetExecutable(a:buffer)) + let l:opts = ale#Var(a:buffer, 'json_pytool_options') + let l:command = printf('%s -m json.tool %s -', l:executable, l:opts) + + return { + \ 'command': l:command + \ } +endfunction diff --git a/autoload/ale/fixers/jsonnetfmt.vim b/autoload/ale/fixers/jsonnetfmt.vim new file mode 100644 index 00000000..f1e41cd5 --- /dev/null +++ b/autoload/ale/fixers/jsonnetfmt.vim @@ -0,0 +1,18 @@ +" Authors: Trevor Whitney and Takuya Kosugiyama +" Description: Integration of jsonnetfmt with ALE. + +call ale#Set('jsonnet_jsonnetfmt_executable', 'jsonnetfmt') +call ale#Set('jsonnet_jsonnetfmt_options', '') + +function! ale#fixers#jsonnetfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'jsonnet_jsonnetfmt_executable') + let l:options = ale#Var(a:buffer, 'jsonnet_jsonnetfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -i' + \ . ale#Pad(l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/ktlint.vim b/autoload/ale/fixers/ktlint.vim new file mode 100644 index 00000000..64d1340d --- /dev/null +++ b/autoload/ale/fixers/ktlint.vim @@ -0,0 +1,8 @@ +" Author: Michael Phillips +" Description: Fix Kotlin files with ktlint. + +function! ale#fixers#ktlint#Fix(buffer) abort + return { + \ 'command': ale#handlers#ktlint#GetCommand(a:buffer) . ' --format' + \} +endfunction diff --git a/autoload/ale/fixers/kulala_fmt.vim b/autoload/ale/fixers/kulala_fmt.vim new file mode 100644 index 00000000..10e81450 --- /dev/null +++ b/autoload/ale/fixers/kulala_fmt.vim @@ -0,0 +1,11 @@ +" Author: hsanson +" Description: kulala_fmt fixer for http and rest files. + +call ale#Set('http_kulala_fmt_executable', 'kulala-fmt') + +function! ale#fixers#kulala_fmt#Fix(buffer) abort + return { + \ 'command': ale#Escape(ale#Var(a:buffer, 'http_kulala_fmt_executable')) . ' format %t > /dev/null', + \ '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..54f1231e --- /dev/null +++ b/autoload/ale/fixers/latexindent.vim @@ -0,0 +1,16 @@ +" Author: riley-martine +" 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' + \ . (empty(l:options) ? '' : ' ' . l:options) + \} +endfunction diff --git a/autoload/ale/fixers/lua_format.vim b/autoload/ale/fixers/lua_format.vim new file mode 100644 index 00000000..98b155c0 --- /dev/null +++ b/autoload/ale/fixers/lua_format.vim @@ -0,0 +1,16 @@ +" Author: Mathias Jean Johansen +" Description: Integration of LuaFormatter with ALE. + +call ale#Set('lua_lua_format_executable', 'lua-format') +call ale#Set('lua_lua_format_options', '') + +function! ale#fixers#lua_format#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'lua_lua_format_executable') + let l:options = ale#Var(a:buffer, 'lua_lua_format_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ale#Pad(l:options) + \ . ' -i', + \} +endfunction diff --git a/autoload/ale/fixers/luafmt.vim b/autoload/ale/fixers/luafmt.vim new file mode 100644 index 00000000..6cb9ef4a --- /dev/null +++ b/autoload/ale/fixers/luafmt.vim @@ -0,0 +1,13 @@ +call ale#Set('lua_luafmt_executable', 'luafmt') +call ale#Set('lua_luafmt_options', '') + +function! ale#fixers#luafmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'lua_luafmt_executable') + let l:options = ale#Var(a:buffer, 'lua_luafmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' --stdin', + \} +endfunction diff --git a/autoload/ale/fixers/mix_format.vim b/autoload/ale/fixers/mix_format.vim new file mode 100644 index 00000000..7a091701 --- /dev/null +++ b/autoload/ale/fixers/mix_format.vim @@ -0,0 +1,25 @@ +" Author: carakan , Fernando Mendes +" Description: Fixing files with elixir formatter 'mix format'. + +call ale#Set('elixir_mix_executable', 'mix') +call ale#Set('elixir_mix_format_options', '') + +function! ale#fixers#mix_format#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'elixir_mix_executable') +endfunction + +function! ale#fixers#mix_format#GetCommand(buffer) abort + let l:executable = ale#Escape(ale#fixers#mix_format#GetExecutable(a:buffer)) + let l:options = ale#Var(a:buffer, 'elixir_mix_format_options') + + return l:executable . ' format' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + +function! ale#fixers#mix_format#Fix(buffer) abort + return { + \ 'command': ale#fixers#mix_format#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/nickel_format.vim b/autoload/ale/fixers/nickel_format.vim new file mode 100644 index 00000000..07eed8f9 --- /dev/null +++ b/autoload/ale/fixers/nickel_format.vim @@ -0,0 +1,16 @@ +" Author: Yining +" Description: nickel format as ALE fixer for Nickel files + +call ale#Set('nickel_nickel_format_executable', 'nickel') +call ale#Set('nickel_nickel_format_options', '') + +function! ale#fixers#nickel_format#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nickel_nickel_format_executable') + let l:options = ale#Var(a:buffer, 'nickel_nickel_format_options') + + return { + \ 'command': ale#Escape(l:executable) . ' format' + \ . (empty(l:options) ? '' : ' ' . l:options) + \} +endfunction + diff --git a/autoload/ale/fixers/nimpretty.vim b/autoload/ale/fixers/nimpretty.vim new file mode 100644 index 00000000..fe2e7136 --- /dev/null +++ b/autoload/ale/fixers/nimpretty.vim @@ -0,0 +1,15 @@ +" Author: Nhan +" Description: Integration of nimpretty with ALE. + +call ale#Set('nim_nimpretty_executable', 'nimpretty') +call ale#Set('nim_nimpretty_options', '--maxLineLen:80') + +function! ale#fixers#nimpretty#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nim_nimpretty_executable') + let l:options = ale#Var(a:buffer, 'nim_nimpretty_options') + + return { + \ 'command': ale#Escape(l:executable) . ' %t' . ale#Pad(l:options), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/nixfmt.vim b/autoload/ale/fixers/nixfmt.vim new file mode 100644 index 00000000..4a548b23 --- /dev/null +++ b/autoload/ale/fixers/nixfmt.vim @@ -0,0 +1,15 @@ +scriptencoding utf-8 +" Author: houstdav000 +" Description: Fix files with nixfmt + +call ale#Set('nix_nixfmt_executable', 'nixfmt') +call ale#Set('nix_nixfmt_options', '') + +function! ale#fixers#nixfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nix_nixfmt_executable') + let l:options = ale#Var(a:buffer, 'nix_nixfmt_options') + + return { + \ 'command': ale#Escape(l:executable) . ale#Pad(l:options), + \} +endfunction diff --git a/autoload/ale/fixers/nixpkgsfmt.vim b/autoload/ale/fixers/nixpkgsfmt.vim new file mode 100644 index 00000000..403ce798 --- /dev/null +++ b/autoload/ale/fixers/nixpkgsfmt.vim @@ -0,0 +1,12 @@ +call ale#Set('nix_nixpkgsfmt_executable', 'nixpkgs-fmt') +call ale#Set('nix_nixpkgsfmt_options', '') + +function! ale#fixers#nixpkgsfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nix_nixpkgsfmt_executable') + let l:options = ale#Var(a:buffer, 'nix_nixpkgsfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/npmgroovylint.vim b/autoload/ale/fixers/npmgroovylint.vim new file mode 100644 index 00000000..39e43cf6 --- /dev/null +++ b/autoload/ale/fixers/npmgroovylint.vim @@ -0,0 +1,16 @@ +" Author: lucas-str +" Description: Integration of npm-groovy-lint for Groovy files. + +call ale#Set('groovy_npmgroovylint_fix_options', '--fix') + +function! ale#fixers#npmgroovylint#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'groovy_npmgroovylint_executable') + let l:options = ale#Var(a:buffer, 'groovy_npmgroovylint_fix_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/ocamlformat.vim b/autoload/ale/fixers/ocamlformat.vim new file mode 100644 index 00000000..b12d2eb9 --- /dev/null +++ b/autoload/ale/fixers/ocamlformat.vim @@ -0,0 +1,17 @@ +" Author: Stephen Lumenta <@sbl> +" Description: Integration of ocamlformat with ALE. + +call ale#Set('ocaml_ocamlformat_executable', 'ocamlformat') +call ale#Set('ocaml_ocamlformat_options', '') + +function! ale#fixers#ocamlformat#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'ocaml_ocamlformat_executable') + let l:options = ale#Var(a:buffer, 'ocaml_ocamlformat_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' --name=%s' + \ . ' -' + \} +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/opafmt.vim b/autoload/ale/fixers/opafmt.vim new file mode 100644 index 00000000..a0999b70 --- /dev/null +++ b/autoload/ale/fixers/opafmt.vim @@ -0,0 +1,15 @@ +" Description: Fixer for rego files + +call ale#Set('opa_fmt_executable', 'opa') +call ale#Set('opa_fmt_options', '') + +function! ale#fixers#opafmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'opa_fmt_executable') + let l:options = ale#Var(a:buffer, 'opa_fmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' fmt' + \ . (empty(l:options) ? '' : ' ' . l:options) + \} +endfunction diff --git a/autoload/ale/fixers/ormolu.vim b/autoload/ale/fixers/ormolu.vim new file mode 100644 index 00000000..69b55c1f --- /dev/null +++ b/autoload/ale/fixers/ormolu.vim @@ -0,0 +1,12 @@ +call ale#Set('haskell_ormolu_executable', 'ormolu') +call ale#Set('haskell_ormolu_options', '') + +function! ale#fixers#ormolu#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_ormolu_executable') + let l:options = ale#Var(a:buffer, 'haskell_ormolu_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/packer.vim b/autoload/ale/fixers/packer.vim new file mode 100644 index 00000000..8770550d --- /dev/null +++ b/autoload/ale/fixers/packer.vim @@ -0,0 +1,17 @@ +" Author: Zhuoyun Wei +" Description: Fixer for Packer HCL files + +call ale#Set('packer_fmt_executable', 'packer') +call ale#Set('packer_fmt_options', '') + +function! ale#fixers#packer#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'packer_fmt_executable') + let l:options = ale#Var(a:buffer, 'packer_fmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' fmt' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/pandoc.vim b/autoload/ale/fixers/pandoc.vim new file mode 100644 index 00000000..d704c8a2 --- /dev/null +++ b/autoload/ale/fixers/pandoc.vim @@ -0,0 +1,16 @@ +scriptencoding utf-8 +" Author: Jesse Hathaway +" Description: Fix markdown files with pandoc. + +call ale#Set('markdown_pandoc_executable', 'pandoc') +call ale#Set('markdown_pandoc_options', '-f gfm -t gfm -s -') + +function! ale#fixers#pandoc#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'markdown_pandoc_executable') + let l:options = ale#Var(a:buffer, 'markdown_pandoc_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/perltidy.vim b/autoload/ale/fixers/perltidy.vim new file mode 100644 index 00000000..a55a572b --- /dev/null +++ b/autoload/ale/fixers/perltidy.vim @@ -0,0 +1,18 @@ +" Author: kfly8 +" Description: Integration of perltidy with ALE. + +call ale#Set('perl_perltidy_executable', 'perltidy') +call ale#Set('perl_perltidy_options', '') + +function! ale#fixers#perltidy#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'perl_perltidy_executable') + let l:options = ale#Var(a:buffer, 'perl_perltidy_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -b' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/pgformatter.vim b/autoload/ale/fixers/pgformatter.vim new file mode 100644 index 00000000..9ea08ec6 --- /dev/null +++ b/autoload/ale/fixers/pgformatter.vim @@ -0,0 +1,12 @@ +call ale#Set('sql_pgformatter_executable', 'pg_format') +call ale#Set('sql_pgformatter_options', '') + +function! ale#fixers#pgformatter#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'sql_pgformatter_executable') + let l:options = ale#Var(a:buffer, 'sql_pgformatter_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/php_cs_fixer.vim b/autoload/ale/fixers/php_cs_fixer.vim new file mode 100644 index 00000000..96c6445c --- /dev/null +++ b/autoload/ale/fixers/php_cs_fixer.vim @@ -0,0 +1,26 @@ +" Author: Julien Deniau +" Description: Fixing files with php-cs-fixer. + +call ale#Set('php_cs_fixer_executable', 'php-cs-fixer') +call ale#Set('php_cs_fixer_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('php_cs_fixer_options', '') +call ale#Set('php_cs_fixer_fix_options', '') + +function! ale#fixers#php_cs_fixer#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'php_cs_fixer', [ + \ 'vendor/bin/php-cs-fixer', + \ 'php-cs-fixer' + \]) +endfunction + +function! ale#fixers#php_cs_fixer#Fix(buffer) abort + let l:executable = ale#fixers#php_cs_fixer#GetExecutable(a:buffer) + + return { + \ 'command': ale#Escape(l:executable) + \ . ' ' . ale#Var(a:buffer, 'php_cs_fixer_options') + \ . ' fix ' . ale#Var(a:buffer, 'php_cs_fixer_fix_options') + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/phpcbf.vim b/autoload/ale/fixers/phpcbf.vim new file mode 100644 index 00000000..494bf346 --- /dev/null +++ b/autoload/ale/fixers/phpcbf.vim @@ -0,0 +1,26 @@ +" Author: notomo +" Description: Fixing files with phpcbf. + +call ale#Set('php_phpcbf_standard', '') +call ale#Set('php_phpcbf_options', '') +call ale#Set('php_phpcbf_executable', 'phpcbf') +call ale#Set('php_phpcbf_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#fixers#phpcbf#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'php_phpcbf', [ + \ 'vendor/bin/phpcbf', + \ 'phpcbf' + \]) +endfunction + +function! ale#fixers#phpcbf#Fix(buffer) abort + let l:executable = ale#fixers#phpcbf#GetExecutable(a:buffer) + let l:standard = ale#Var(a:buffer, 'php_phpcbf_standard') + let l:standard_option = !empty(l:standard) + \ ? '--standard=' . l:standard + \ : '' + + return { + \ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ale#Pad(ale#Var(a:buffer, 'php_phpcbf_options')) . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/pint.vim b/autoload/ale/fixers/pint.vim new file mode 100644 index 00000000..274ddd9e --- /dev/null +++ b/autoload/ale/fixers/pint.vim @@ -0,0 +1,25 @@ +" Author: Michael Dyrynda +" Description: Fixing files with Laravel Pint. + +call ale#Set('php_pint_executable', 'pint') +call ale#Set('php_pint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('php_pint_options', '') + +function! ale#fixers#pint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'php_pint', [ + \ 'vendor/bin/pint', + \ 'pint' + \]) +endfunction + +function! ale#fixers#pint#Fix(buffer) abort + let l:executable = ale#fixers#pint#GetExecutable(a:buffer) + + return { + \ 'command': ale#Escape(l:executable) + \ . ' ' . ale#Var(a:buffer, 'php_pint_options') + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim new file mode 100644 index 00000000..c9210e63 --- /dev/null +++ b/autoload/ale/fixers/prettier.vim @@ -0,0 +1,125 @@ +" Author: tunnckoCore (Charlike Mike Reagent) , +" w0rp , morhetz (Pavel Pertsev) +" Description: Integration of Prettier with ALE. + +call ale#Set('javascript_prettier_executable', 'prettier') +call ale#Set('javascript_prettier_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_prettier_options', '') + +function! ale#fixers#prettier#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'javascript_prettier', [ + \ 'node_modules/.bin/prettier_d', + \ 'node_modules/prettier-cli/index.js', + \ 'node_modules/.bin/prettier', + \]) +endfunction + +function! ale#fixers#prettier#Fix(buffer) abort + 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 + " If the output is an error message, don't use it. + for l:line in a:output[:10] + if l:line =~# '^\w*Error:' + return [] + endif + endfor + + return a:output +endfunction + +function! ale#fixers#prettier#GetCwd(buffer) abort + let l:config = ale#path#FindNearestFile(a:buffer, '.prettierignore') + + " Fall back to the directory of the buffer + return !empty(l:config) ? fnamemodify(l:config, ':h') : '%s:h' +endfunction + +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:parser = '' + + let l:filetypes = split(getbufvar(a:buffer, '&filetype'), '\.') + + if index(l:filetypes, 'handlebars') > -1 + let l:parser = 'glimmer' + endif + + " 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')) && l:parser is# '' && 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', + \ 'less': 'less', + \ 'scss': 'scss', + \ 'json': 'json', + \ 'json5': 'json5', + \ 'graphql': 'graphql', + \ 'markdown': 'markdown', + \ 'vue': 'vue', + \ 'svelte': 'svelte', + \ 'yaml': 'yaml', + \ 'openapi': 'yaml', + \ 'html': 'html', + \ 'ruby': 'ruby', + \ 'astro': 'astro', + \} + + for l:filetype in l:filetypes + if has_key(l:prettier_parsers, l:filetype) + let l:parser = l:prettier_parsers[l:filetype] + break + endif + endfor + endif + + if !empty(l:parser) + let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--parser ' . l:parser + endif + + " Special error handling needed for prettier_d + if l:executable =~# 'prettier_d$' + return { + \ 'cwd': '%s:h', + \ 'command':ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin-filepath %s --stdin', + \ 'process_with': 'ale#fixers#prettier#ProcessPrettierDOutput', + \} + endif + + " 1.4.0 is the first version with --stdin-filepath + if ale#semver#GTE(a:version, [1, 4, 0]) + return { + \ 'cwd': ale#fixers#prettier#GetCwd(a:buffer), + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin-filepath %s --stdin', + \} + endif + + return { + \ 'command': ale#Escape(l:executable) + \ . ' %t' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --write', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/prettier_eslint.vim b/autoload/ale/fixers/prettier_eslint.vim new file mode 100644 index 00000000..0b9c88b7 --- /dev/null +++ b/autoload/ale/fixers/prettier_eslint.vim @@ -0,0 +1,56 @@ +" Author: tunnckoCore (Charlike Mike Reagent) , +" w0rp , morhetz (Pavel Pertsev) +" Description: Integration between Prettier and ESLint. + +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#path#FindExecutable(a:buffer, 'javascript_prettier_eslint', [ + \ 'node_modules/prettier-eslint-cli/dist/index.js', + \ 'node_modules/.bin/prettier-eslint', + \]) +endfunction + +function! ale#fixers#prettier_eslint#Fix(buffer) abort + 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) abort + let l:options = ale#Var(a:buffer, 'javascript_prettier_eslint_options') + let l:executable = ale#fixers#prettier_eslint#GetExecutable(a:buffer) + + " 4.2.0 is the first version with --eslint-config-path + 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) + \ ? ' --eslint-config-path ' . ale#Escape(l:config) + \ : '' + + " 4.4.0 is the first version with --stdin-filepath + if ale#semver#GTE(a:version, [4, 4, 0]) + return { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(l:executable) + \ . l:eslint_config_option + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin-filepath %s --stdin', + \} + endif + + return { + \ 'command': ale#Escape(l:executable) + \ . ' %t' + \ . l:eslint_config_option + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --write', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/prettier_standard.vim b/autoload/ale/fixers/prettier_standard.vim new file mode 100644 index 00000000..c8c09e31 --- /dev/null +++ b/autoload/ale/fixers/prettier_standard.vim @@ -0,0 +1,24 @@ +" Author: sheerun (Adam Stankiewicz) +" Description: Integration of Prettier Standard with ALE. + +call ale#Set('javascript_prettier_standard_executable', 'prettier-standard') +call ale#Set('javascript_prettier_standard_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_prettier_standard_options', '') + +function! ale#fixers#prettier_standard#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'javascript_prettier_standard', [ + \ 'node_modules/prettier-standard/lib/index.js', + \ 'node_modules/.bin/prettier-standard', + \]) +endfunction + +function! ale#fixers#prettier_standard#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'javascript_prettier_standard_options') + + return { + \ 'command': ale#Escape(ale#fixers#prettier_standard#GetExecutable(a:buffer)) + \ . ' --stdin' + \ . ' --stdin-filepath=%s' + \ . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/protolint.vim b/autoload/ale/fixers/protolint.vim new file mode 100644 index 00000000..9b8e72f1 --- /dev/null +++ b/autoload/ale/fixers/protolint.vim @@ -0,0 +1,26 @@ +" Author: Yohei Yoshimuta +" Description: Integration of protolint with ALE. + +call ale#Set('proto_protolint_executable', 'protolint') +call ale#Set('proto_protolint_config', '') + +function! ale#fixers#protolint#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'proto_protolint_executable') + + return ale#Escape(l:executable) +endfunction + +function! ale#fixers#protolint#Fix(buffer) abort + let l:executable = ale#fixers#protolint#GetExecutable(a:buffer) + let l:config = ale#Var(a:buffer, 'proto_protolint_config') + + return { + \ 'command': l:executable + \ . (!empty(l:config) ? ' -config_path=' . ale#Escape(l:config) : '') + \ . ' -fix' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + + diff --git a/autoload/ale/fixers/ptop.vim b/autoload/ale/fixers/ptop.vim new file mode 100644 index 00000000..98345226 --- /dev/null +++ b/autoload/ale/fixers/ptop.vim @@ -0,0 +1,17 @@ +" Author: BarrOff https://github.com/BarrOff +" Description: Integration of ptop with ALE. + +call ale#Set('pascal_ptop_executable', 'ptop') +call ale#Set('pascal_ptop_options', '') + +function! ale#fixers#ptop#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'pascal_ptop_executable') + let l:options = ale#Var(a:buffer, 'pascal_ptop_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %s %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/puppetlint.vim b/autoload/ale/fixers/puppetlint.vim new file mode 100644 index 00000000..bf36e486 --- /dev/null +++ b/autoload/ale/fixers/puppetlint.vim @@ -0,0 +1,22 @@ +" Author: Alexander Olofsson +" Description: puppet-lint fixer + +if !exists('g:ale_puppet_puppetlint_executable') + let g:ale_puppet_puppetlint_executable = 'puppet-lint' +endif + +if !exists('g:ale_puppet_puppetlint_options') + let g:ale_puppet_puppetlint_options = '' +endif + +function! ale#fixers#puppetlint#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'puppet_puppetlint_executable') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' ' . ale#Var(a:buffer, 'puppet_puppetlint_options') + \ . ' --fix' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/purs_tidy.vim b/autoload/ale/fixers/purs_tidy.vim new file mode 100644 index 00000000..09fa631b --- /dev/null +++ b/autoload/ale/fixers/purs_tidy.vim @@ -0,0 +1,24 @@ +" Author: toastal +" Description: Integration of purs-tidy with ALE. + +call ale#Set('purescript_tidy_executable', 'purs-tidy') +call ale#Set('purescript_tidy_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('purescript_tidy_options', '') + +function! ale#fixers#purs_tidy#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'purescript_tidy', [ + \ 'node_modules/purescript-tidy/bin/index.js', + \ 'node_modules/.bin/purs-tidy', + \]) +endfunction + +function! ale#fixers#purs_tidy#Fix(buffer) abort + let l:executable = ale#fixers#purs_tidy#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'purescript_tidy_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' format' + \ . ale#Pad(l:options) + \} +endfunction diff --git a/autoload/ale/fixers/purty.vim b/autoload/ale/fixers/purty.vim new file mode 100644 index 00000000..46d2cacd --- /dev/null +++ b/autoload/ale/fixers/purty.vim @@ -0,0 +1,22 @@ +" Author: iclanzan +" Description: Integration of purty with ALE. + +call ale#Set('purescript_purty_executable', 'purty') + +function! ale#fixers#purty#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'purescript_purty_executable') + + return ale#Escape(l:executable) +endfunction + +function! ale#fixers#purty#Fix(buffer) abort + let l:executable = ale#fixers#purty#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' --write' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/pycln.vim b/autoload/ale/fixers/pycln.vim new file mode 100644 index 00000000..ca15cc4e --- /dev/null +++ b/autoload/ale/fixers/pycln.vim @@ -0,0 +1,96 @@ +" Author: Yining +" Description: pycln as ALE fixer for python files + +call ale#Set('python_pycln_executable', 'pycln') +call ale#Set('python_pycln_options', '') +call ale#Set('python_pycln_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pycln_change_directory', 1) +call ale#Set('python_pycln_auto_pipenv', 0) +call ale#Set('python_pycln_auto_poetry', 0) +call ale#Set('python_pycln_auto_uv', 0) +call ale#Set('python_pycln_config_file', '') + +function! ale#fixers#pycln#GetCwd(buffer) abort + if ale#Var(a:buffer, 'python_pycln_change_directory') + " Run from project root if found, else from buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '%s:h' +endfunction + +function! ale#fixers#pycln#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pycln_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pycln_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pycln_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pycln', ['pycln']) +endfunction + +function! ale#fixers#pycln#GetCommand(buffer) abort + let l:executable = ale#fixers#pycln#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run pycln' + \ : '' + + return ale#Escape(l:executable) . l:exec_args +endfunction + +function! ale#fixers#pycln#FixForVersion(buffer, version) abort + let l:executable = ale#fixers#pycln#GetExecutable(a:buffer) + let l:cmd = [ale#Escape(l:executable)] + + if l:executable =~? '\(pipenv\|poetry\|uv\)$' + call extend(l:cmd, ['run', 'pycln']) + endif + + let l:options = ale#Var(a:buffer, 'python_pycln_options') + + if !empty(l:options) + call add(l:cmd, l:options) + endif + + let l:config_file = ale#Var(a:buffer, 'python_pycln_config_file') + let l:config_file = l:options !~# '\v(^| )--config ' && !empty(l:config_file) + \ ? ale#Escape(ale#path#Simplify(l:config_file)) + \ : '' + + if !empty(l:config_file) + call add(l:cmd, '--config ' . l:config_file) + endif + + call add(l:cmd, '--silence') + + " NOTE: pycln version `1.3.0` support reading from stdin + call add(l:cmd, ale#semver#GTE(a:version, [1, 3, 0]) ? '-' : '%s') + + return { + \ 'cwd': ale#fixers#pycln#GetCwd(a:buffer), + \ 'command': join(l:cmd, ' '), + \} +endfunction + +function! ale#fixers#pycln#Fix(buffer) abort + let l:executable = ale#fixers#pycln#GetExecutable(a:buffer) + let l:command = ale#fixers#pycln#GetCommand(a:buffer) . ale#Pad('--version') + + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ l:command, + \ function('ale#fixers#pycln#FixForVersion'), + \) +endfunction diff --git a/autoload/ale/fixers/pyflyby.vim b/autoload/ale/fixers/pyflyby.vim new file mode 100644 index 00000000..d5c2d235 --- /dev/null +++ b/autoload/ale/fixers/pyflyby.vim @@ -0,0 +1,47 @@ +" Author: infokiller +" Description: Tidy imports using pyflyby's tidy-import script +" https://github.com/deshaw/pyflyby + +call ale#Set('python_pyflyby_executable', 'tidy-imports') +call ale#Set('python_pyflyby_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_pyflyby_options', '') +call ale#Set('python_pyflyby_auto_pipenv', 0) +call ale#Set('python_pyflyby_auto_poetry', 0) +call ale#Set('python_pyflyby_auto_uv', 0) + +function! ale#fixers#pyflyby#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_pyflyby_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_pyflyby_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_pyflyby_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_pyflyby', ['tidy-imports']) +endfunction + +function! ale#fixers#pyflyby#Fix(buffer) abort + " let l:executable = ale#fixers#pyflyby#GetExecutable(a:buffer) + let l:executable = ale#fixers#pyflyby#GetExecutable(a:buffer) + let l:cmd = [ale#Escape(l:executable)] + + if l:executable =~? '\(pipenv\|poetry\|uv\)$' + call extend(l:cmd, ['run', 'tidy-imports']) + endif + + let l:options = ale#Var(a:buffer, 'python_pyflyby_options') + + if !empty(l:options) + call add(l:cmd, l:options) + endif + + return {'command': join(l:cmd, ' ')} +endfunction diff --git a/autoload/ale/fixers/qmlfmt.vim b/autoload/ale/fixers/qmlfmt.vim new file mode 100644 index 00000000..90b25679 --- /dev/null +++ b/autoload/ale/fixers/qmlfmt.vim @@ -0,0 +1,11 @@ +call ale#Set('qml_qmlfmt_executable', 'qmlfmt') + +function! ale#fixers#qmlfmt#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'qml_qmlfmt_executable') +endfunction + +function! ale#fixers#qmlfmt#Fix(buffer) abort + return { + \ 'command': ale#Escape(ale#fixers#qmlfmt#GetExecutable(a:buffer)), + \} +endfunction diff --git a/autoload/ale/fixers/raco_fmt.vim b/autoload/ale/fixers/raco_fmt.vim new file mode 100644 index 00000000..16cf4468 --- /dev/null +++ b/autoload/ale/fixers/raco_fmt.vim @@ -0,0 +1,15 @@ +" Author: Jeremy Cantrell +" Description: Integration of raco fmt with ALE. + +call ale#Set('racket_raco_fmt_executable', 'raco') +call ale#Set('racket_raco_fmt_options', '') + +function! ale#fixers#raco_fmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'racket_raco_fmt_executable') + let l:options = ale#Var(a:buffer, 'racket_raco_fmt_options') + + return { + \ 'command': ale#Escape(l:executable) . ' fmt' + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/refmt.vim b/autoload/ale/fixers/refmt.vim new file mode 100644 index 00000000..514f950a --- /dev/null +++ b/autoload/ale/fixers/refmt.vim @@ -0,0 +1,18 @@ +" Author: Ahmed El Gabri <@ahmedelgabri> +" Description: Integration of refmt with ALE. + +call ale#Set('reasonml_refmt_executable', 'refmt') +call ale#Set('reasonml_refmt_options', '') + +function! ale#fixers#refmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'reasonml_refmt_executable') + let l:options = ale#Var(a:buffer, 'reasonml_refmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' --in-place' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/remark_lint.vim b/autoload/ale/fixers/remark_lint.vim new file mode 100644 index 00000000..85593b44 --- /dev/null +++ b/autoload/ale/fixers/remark_lint.vim @@ -0,0 +1,24 @@ +" Author: blyoa +" Description: Fixing files with remark-lint. + +call ale#Set('markdown_remark_lint_executable', 'remark') +call ale#Set('markdown_remark_lint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('markdown_remark_lint_options', '') + +function! ale#fixers#remark_lint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'markdown_remark_lint', [ + \ 'node_modules/remark-cli/cli.js', + \ 'node_modules/.bin/remark', + \]) +endfunction + +function! ale#fixers#remark_lint#Fix(buffer) abort + let l:executable = ale#fixers#remark_lint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'markdown_remark_lint_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : ''), + \} +endfunction + diff --git a/autoload/ale/fixers/reorder_python_imports.vim b/autoload/ale/fixers/reorder_python_imports.vim new file mode 100644 index 00000000..6e10c1d6 --- /dev/null +++ b/autoload/ale/fixers/reorder_python_imports.vim @@ -0,0 +1,43 @@ +" Author: jake +" Description: Fixing Python imports with reorder-python-imports. + +call ale#Set('python_reorder_python_imports_executable', 'reorder-python-imports') +call ale#Set('python_reorder_python_imports_options', '') +call ale#Set('python_reorder_python_imports_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_reorder_python_imports_auto_pipenv', 0) +call ale#Set('python_reorder_python_imports_auto_poetry', 0) +call ale#Set('python_reorder_python_imports_auto_uv', 0) + +function! ale#fixers#reorder_python_imports#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_reorder_python_imports_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_reorder_python_imports_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_reorder_python_imports_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_reorder_python_imports', ['reorder-python-imports']) +endfunction + +function! ale#fixers#reorder_python_imports#Fix(buffer) abort + let l:executable = ale#fixers#reorder_python_imports#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run reorder-python-imports' + \ : '' + + let l:options = ale#Var(a:buffer, 'python_reorder_python_imports_options') + + return { + \ 'command': ale#Escape(l:executable) . l:exec_args + \ . (!empty(l:options) ? ' ' . l:options : '') . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/rubocop.vim b/autoload/ale/fixers/rubocop.vim new file mode 100644 index 00000000..5a1b7959 --- /dev/null +++ b/autoload/ale/fixers/rubocop.vim @@ -0,0 +1,38 @@ +call ale#Set('ruby_rubocop_options', '') +call ale#Set('ruby_rubocop_auto_correct_all', 0) +call ale#Set('ruby_rubocop_executable', 'rubocop') + +" Rubocop fixer outputs diagnostics first and then the fixed +" output. These are delimited by a "=======" string that we +" look for to remove everything before it. +function! ale#fixers#rubocop#PostProcess(buffer, output) abort + let l:line = 0 + + for l:output in a:output + let l:line = l:line + 1 + + if l:output =~# "^=\\+$" + break + endif + endfor + + return a:output[l:line :] +endfunction + +function! ale#fixers#rubocop#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_rubocop_executable') + let l:options = ale#Var(a:buffer, 'ruby_rubocop_options') + let l:auto_correct_all = ale#Var(a:buffer, 'ruby_rubocop_auto_correct_all') + + return ale#ruby#EscapeExecutable(l:executable, 'rubocop') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (l:auto_correct_all ? ' --auto-correct-all' : ' --auto-correct') + \ . ' --force-exclusion --stdin %s' +endfunction + +function! ale#fixers#rubocop#Fix(buffer) abort + return { + \ 'command': ale#fixers#rubocop#GetCommand(a:buffer), + \ 'process_with': 'ale#fixers#rubocop#PostProcess' + \} +endfunction diff --git a/autoload/ale/fixers/rubyfmt.vim b/autoload/ale/fixers/rubyfmt.vim new file mode 100644 index 00000000..64b3c2c4 --- /dev/null +++ b/autoload/ale/fixers/rubyfmt.vim @@ -0,0 +1,16 @@ +" Author: Yining +" Description: support rubyfmt as ALE fixer for Ruby files + +call ale#Set('ruby_rubyfmt_executable', 'rubyfmt') +call ale#Set('ruby_rubyfmt_options', '') + +function! ale#fixers#rubyfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_rubyfmt_executable') + let l:options = ale#Var(a:buffer, 'ruby_rubyfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \} +endfunction + diff --git a/autoload/ale/fixers/ruff.vim b/autoload/ale/fixers/ruff.vim new file mode 100644 index 00000000..c0c3cd62 --- /dev/null +++ b/autoload/ale/fixers/ruff.vim @@ -0,0 +1,100 @@ +" Author: Yining +" Description: ruff as ALE fixer for python files + +call ale#Set('python_ruff_executable', 'ruff') +call ale#Set('python_ruff_options', '') +call ale#Set('python_ruff_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_ruff_change_directory', 1) +call ale#Set('python_ruff_auto_pipenv', 0) +call ale#Set('python_ruff_auto_poetry', 0) +call ale#Set('python_ruff_auto_uv', 0) + +function! ale#fixers#ruff#GetCwd(buffer) abort + if ale#Var(a:buffer, 'python_ruff_change_directory') + " Run from project root if found, else from buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '%s:h' +endfunction + +function! ale#fixers#ruff#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_ruff_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_ruff_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_ruff_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_ruff', ['ruff']) +endfunction + +function! ale#fixers#ruff#GetCommand(buffer) abort + let l:executable = ale#fixers#ruff#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run ruff' + \ : '' + + return ale#Escape(l:executable) . l:exec_args +endfunction + +function! ale#fixers#ruff#FixForVersion(buffer, version) abort + let l:executable = ale#fixers#ruff#GetExecutable(a:buffer) + let l:cmd = [ale#Escape(l:executable)] + + if l:executable =~? '\(pipenv\|poetry\|uv\)$' + call extend(l:cmd, ['run', 'ruff']) + endif + + " NOTE: ruff 0.5.0 removes `ruff ` in favor of `ruff check ` + if ale#semver#GTE(a:version, [0, 5, 0]) + call extend(l:cmd, ['check']) + endif + + let l:options = ale#Var(a:buffer, 'python_ruff_options') + + if !empty(l:options) + call add(l:cmd, l:options) + endif + + " when --stdin-filename present, ruff will use it for proj root resolution + " https://github.com/charliermarsh/ruff/pull/1281 + let l:fname = expand('#' . a:buffer . '...') + call add(l:cmd, '--stdin-filename '.ale#Escape(ale#path#Simplify(l:fname))) + + call add(l:cmd, '--fix') + + " NOTE: ruff version `0.0.72` implements `--fix` with stdin + if ale#semver#GTE(a:version, [0, 0, 72]) + call add(l:cmd, '-') + else + call add(l:cmd, '%s') + endif + + return { + \ 'cwd': ale#fixers#ruff#GetCwd(a:buffer), + \ 'command': join(l:cmd, ' '), + \} +endfunction + +function! ale#fixers#ruff#Fix(buffer) abort + let l:executable = ale#fixers#ruff#GetExecutable(a:buffer) + let l:command = ale#fixers#ruff#GetCommand(a:buffer) . ale#Pad('--version') + + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ l:command, + \ function('ale#fixers#ruff#FixForVersion'), + \) +endfunction diff --git a/autoload/ale/fixers/ruff_format.vim b/autoload/ale/fixers/ruff_format.vim new file mode 100644 index 00000000..cfa7b76d --- /dev/null +++ b/autoload/ale/fixers/ruff_format.vim @@ -0,0 +1,78 @@ +" Author: Yining , Joseph Henrich +" Description: ruff formatter as ALE fixer for python files + +call ale#Set('python_ruff_format_executable', 'ruff') +call ale#Set('python_ruff_format_options', '') +call ale#Set('python_ruff_format_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_ruff_format_change_directory', 1) +call ale#Set('python_ruff_format_auto_pipenv', 0) +call ale#Set('python_ruff_format_auto_poetry', 0) +call ale#Set('python_ruff_format_auto_uv', 0) + +function! ale#fixers#ruff_format#GetCwd(buffer) abort + if ale#Var(a:buffer, 'python_ruff_format_change_directory') + " Run from project root if found, else from buffer dir. + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? l:project_root : '%s:h' + endif + + return '%s:h' +endfunction + +function! ale#fixers#ruff_format#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_ruff_format_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_ruff_format_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_ruff_format_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_ruff_format', ['ruff']) +endfunction + +function! ale#fixers#ruff_format#GetCommand(buffer) abort + let l:executable = ale#fixers#ruff_format#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run ruff' + \ : '' + + return ale#Escape(l:executable) . l:exec_args +endfunction + +function! ale#fixers#ruff_format#Fix(buffer) abort + let l:executable = ale#fixers#ruff_format#GetExecutable(a:buffer) + let l:cmd = [ale#Escape(l:executable)] + + if l:executable =~? '\(pipenv\|poetry\|uv\)$' + call extend(l:cmd, ['run', 'ruff']) + endif + + let l:options = ale#Var(a:buffer, 'python_ruff_format_options') + + " when --stdin-filename present, ruff will use it for proj root resolution + " https://github.com/charliermarsh/ruff/pull/1281 + let l:fname = expand('#' . a:buffer . '...') + call add(l:cmd, 'format') + + if !empty(l:options) + call add(l:cmd, l:options) + endif + + call add(l:cmd, '--stdin-filename '.ale#Escape(ale#path#Simplify(l:fname))) + + call add(l:cmd, '-') + + return { + \ 'cwd': ale#fixers#ruff_format#GetCwd(a:buffer), + \ 'command': join(l:cmd, ' '), + \} +endfunction diff --git a/autoload/ale/fixers/rufo.vim b/autoload/ale/fixers/rufo.vim new file mode 100644 index 00000000..01d537a9 --- /dev/null +++ b/autoload/ale/fixers/rufo.vim @@ -0,0 +1,20 @@ +" Author: Fohte (Hayato Kawai) https://github.com/fohte +" Description: Integration of Rufo with ALE. + +call ale#Set('ruby_rufo_executable', 'rufo') + +function! ale#fixers#rufo#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_rufo_executable') + let l:exec_args = l:executable =~? 'bundle$' + \ ? ' exec rufo' + \ : '' + + return ale#Escape(l:executable) . l:exec_args . ' %t' +endfunction + +function! ale#fixers#rufo#Fix(buffer) abort + return { + \ 'command': ale#fixers#rufo#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/rustfmt.vim b/autoload/ale/fixers/rustfmt.vim new file mode 100644 index 00000000..38882fbf --- /dev/null +++ b/autoload/ale/fixers/rustfmt.vim @@ -0,0 +1,15 @@ +" Author: Kelly Fox +" Description: Integration of rustfmt with ALE. + +call ale#Set('rust_rustfmt_executable', 'rustfmt') +call ale#Set('rust_rustfmt_options', '') + +function! ale#fixers#rustfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'rust_rustfmt_executable') + let l:options = ale#Var(a:buffer, 'rust_rustfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/rustywind.vim b/autoload/ale/fixers/rustywind.vim new file mode 100644 index 00000000..5e9bb3c5 --- /dev/null +++ b/autoload/ale/fixers/rustywind.vim @@ -0,0 +1,17 @@ +scriptencoding utf-8 +" Author: Guillermo Roig +" Description: Sort TailwindCSS classes with rustywind + +call ale#Set('html_rustywind_executable', 'rustywind') +call ale#Set('html_rustywind_options', '') + +function! ale#fixers#rustywind#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'html_rustywind_executable') + let l:options = ale#Var(a:buffer, 'html_rustywind_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' --stdin' + \} +endfunction diff --git a/autoload/ale/fixers/scadformat.vim b/autoload/ale/fixers/scadformat.vim new file mode 100644 index 00000000..f95f2963 --- /dev/null +++ b/autoload/ale/fixers/scadformat.vim @@ -0,0 +1,15 @@ +" Author: tony o'dell +" Description: Fix scad files with scadformat + +call ale#Set('openscad_scadformat_executable', 'scadformat') +call ale#Set('openscad_scadformat_options', '') + +function! ale#fixers#scadformat#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'openscad_scadformat_executable') + let l:options = ale#Var(a:buffer, 'openscad_scadformat_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/scalafmt.vim b/autoload/ale/fixers/scalafmt.vim new file mode 100644 index 00000000..dd0e7745 --- /dev/null +++ b/autoload/ale/fixers/scalafmt.vim @@ -0,0 +1,25 @@ +" Author: Jeffrey Lau https://github.com/zoonfafer +" Description: Integration of Scalafmt with ALE. + +call ale#Set('scala_scalafmt_executable', 'scalafmt') +call ale#Set('scala_scalafmt_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('scala_scalafmt_options', '') + +function! ale#fixers#scalafmt#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'scala_scalafmt_executable') + let l:options = ale#Var(a:buffer, 'scala_scalafmt_options') + let l:exec_args = l:executable =~? 'ng$' + \ ? ' scalafmt' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t' +endfunction + +function! ale#fixers#scalafmt#Fix(buffer) abort + return { + \ 'command': ale#fixers#scalafmt#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/shfmt.vim b/autoload/ale/fixers/shfmt.vim new file mode 100644 index 00000000..0eefc985 --- /dev/null +++ b/autoload/ale/fixers/shfmt.vim @@ -0,0 +1,17 @@ +scriptencoding utf-8 +" Author: Simon Bugert +" Description: Fix sh files with shfmt. + +call ale#Set('sh_shfmt_executable', 'shfmt') +call ale#Set('sh_shfmt_options', '') + +function! ale#fixers#shfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'sh_shfmt_executable') + let l:options = ale#Var(a:buffer, 'sh_shfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -filename=%s' + \ . (empty(l:options) ? '' : ' ' . l:options) + \} +endfunction diff --git a/autoload/ale/fixers/sorbet.vim b/autoload/ale/fixers/sorbet.vim new file mode 100644 index 00000000..7c12fa1e --- /dev/null +++ b/autoload/ale/fixers/sorbet.vim @@ -0,0 +1,19 @@ +call ale#Set('ruby_sorbet_executable', 'srb') +call ale#Set('ruby_sorbet_options', '') + +function! ale#fixers#sorbet#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_sorbet_executable') + let l:options = ale#Var(a:buffer, 'ruby_sorbet_options') + + return ale#ruby#EscapeExecutable(l:executable, 'srb') + \ . ' tc' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --autocorrect --file %t' +endfunction + +function! ale#fixers#sorbet#Fix(buffer) abort + return { + \ 'command': ale#fixers#sorbet#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/sqlfluff.vim b/autoload/ale/fixers/sqlfluff.vim new file mode 100644 index 00000000..1dc9f5c1 --- /dev/null +++ b/autoload/ale/fixers/sqlfluff.vim @@ -0,0 +1,25 @@ +" Author: Carl Smedstad +" Description: Fixing SQL files with sqlfluff + +call ale#Set('sql_sqlfluff_executable', 'sqlfluff') + +function! ale#fixers#sqlfluff#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'sql_sqlfluff_executable') + + let l:cmd = + \ ale#Escape(l:executable) + \ . ' fix --force' + + let l:config_file = ale#path#FindNearestFile(a:buffer, '.sqlfluff') + + if !empty(l:config_file) + let l:cmd .= ' --config ' . ale#Escape(l:config_file) + else + let l:cmd .= ' --dialect ansi' + endif + + return { + \ 'command': l:cmd . ' %t > /dev/null', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/sqlfmt.vim b/autoload/ale/fixers/sqlfmt.vim new file mode 100644 index 00000000..c88a8ec2 --- /dev/null +++ b/autoload/ale/fixers/sqlfmt.vim @@ -0,0 +1,13 @@ +call ale#Set('sql_sqlfmt_executable', 'sqlfmt') +call ale#Set('sql_sqlfmt_options', '') + +function! ale#fixers#sqlfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'sql_sqlfmt_executable') + let l:options = ale#Var(a:buffer, 'sql_sqlfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -w' + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/sqlformat.vim b/autoload/ale/fixers/sqlformat.vim new file mode 100644 index 00000000..6319c1ac --- /dev/null +++ b/autoload/ale/fixers/sqlformat.vim @@ -0,0 +1,16 @@ +" Author: Cluas +" Description: Fixing files with sqlformat. + +call ale#Set('sql_sqlformat_executable', 'sqlformat') +call ale#Set('sql_sqlformat_options', '') + +function! ale#fixers#sqlformat#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'sql_sqlformat_executable') + let l:options = ale#Var(a:buffer, 'sql_sqlformat_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/standard.vim b/autoload/ale/fixers/standard.vim new file mode 100644 index 00000000..b9d60ebb --- /dev/null +++ b/autoload/ale/fixers/standard.vim @@ -0,0 +1,33 @@ +" Author: Sumner Evans +" Description: Fixing files with Standard. + +call ale#Set('javascript_standard_executable', 'standard') +call ale#Set('javascript_standard_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_standard_options', '') + +function! ale#fixers#standard#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'javascript_standard', [ + \ 'node_modules/standardx/bin/cmd.js', + \ 'node_modules/standard/bin/cmd.js', + \ 'node_modules/.bin/standard', + \]) +endfunction + +function! ale#fixers#standard#Fix(buffer) abort + let l:executable = ale#fixers#standard#GetExecutable(a:buffer) + let l:filetype = getbufvar(a:buffer, '&filetype') + let l:options_type = 'javascript_standard_options' + + if l:filetype =~# 'typescript' + let l:options_type = 'typescript_standard_options' + endif + + let l:options = ale#Var(a:buffer, l:options_type) + + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --fix --stdin < %s > %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/standardrb.vim b/autoload/ale/fixers/standardrb.vim new file mode 100644 index 00000000..acb310c6 --- /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#ruby#EscapeExecutable(l:executable, 'standardrb') + \ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --fix --force-exclusion --stdin %s' +endfunction + +function! ale#fixers#standardrb#Fix(buffer) abort + return { + \ 'command': ale#fixers#standardrb#GetCommand(a:buffer), + \ 'process_with': 'ale#fixers#rubocop#PostProcess' + \} +endfunction diff --git a/autoload/ale/fixers/statix.vim b/autoload/ale/fixers/statix.vim new file mode 100644 index 00000000..5991c925 --- /dev/null +++ b/autoload/ale/fixers/statix.vim @@ -0,0 +1,17 @@ +" Author: David Houston +" Description: Provide statix fix as a fixer for simple Nix antipatterns. + +call ale#Set('nix_statix_fix_executable', 'statix') +call ale#Set('nix_statix_fix_options', '') + +function! ale#fixers#statix#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nix_statix_fix_executable') + let l:options = ale#Var(a:buffer, 'nix_statix_fix_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ale#Pad('fix') + \ . ale#Pad('--stdin') + \ . ale#Pad(l:options), + \} +endfunction diff --git a/autoload/ale/fixers/stylelint.vim b/autoload/ale/fixers/stylelint.vim new file mode 100644 index 00000000..650b9c4a --- /dev/null +++ b/autoload/ale/fixers/stylelint.vim @@ -0,0 +1,26 @@ +" Author: Mahmoud Mostafa +" Description: Fixing files with stylelint. + +call ale#Set('stylelint_executable', 'stylelint') +call ale#Set('stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('stylelint_options', '') + +function! ale#fixers#stylelint#GetExecutable(buffer) abort + return ale#path#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) + let l:options = ale#Var(a:buffer, 'stylelint_options') + + return { + \ 'cwd': '%s:h', + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ale#Pad(l:options) + \ . ' --fix --stdin --stdin-filename %s', + \ 'read_temporary_file': 0, + \} +endfunction diff --git a/autoload/ale/fixers/styler.vim b/autoload/ale/fixers/styler.vim new file mode 100644 index 00000000..1c7607bd --- /dev/null +++ b/autoload/ale/fixers/styler.vim @@ -0,0 +1,16 @@ +" Author: tvatter +" 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), transformers = ' + \ . ale#Var(a:buffer, 'r_styler_options') . ')"' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/stylish_haskell.vim b/autoload/ale/fixers/stylish_haskell.vim new file mode 100644 index 00000000..ce71c1ce --- /dev/null +++ b/autoload/ale/fixers/stylish_haskell.vim @@ -0,0 +1,21 @@ +" Author: eborden +" Description: Integration of stylish-haskell formatting with ALE. +" +call ale#Set('haskell_stylish_haskell_executable', 'stylish-haskell') + +function! ale#fixers#stylish_haskell#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_stylish_haskell_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'stylish-haskell') +endfunction + +function! ale#fixers#stylish_haskell#Fix(buffer) abort + let l:executable = ale#fixers#stylish_haskell#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' --inplace' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/stylua.vim b/autoload/ale/fixers/stylua.vim new file mode 100644 index 00000000..6c3ba054 --- /dev/null +++ b/autoload/ale/fixers/stylua.vim @@ -0,0 +1,27 @@ +" Author: Robert Liebowitz +" Description: https://github.com/johnnymorganz/stylua + +call ale#Set('lua_stylua_executable', 'stylua') +call ale#Set('lua_stylua_options', '') + +function! ale#fixers#stylua#GetCwd(buffer) abort + for l:possible_configfile in ['stylua.toml', '.stylua.toml'] + let l:config = ale#path#FindNearestFile(a:buffer, l:possible_configfile) + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + endfor + + return '%s:h' +endfunction + +function! ale#fixers#stylua#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'lua_stylua_executable') + let l:options = ale#Var(a:buffer, 'lua_stylua_options') + + return { + \ 'cwd': ale#fixers#stylua#GetCwd(a:buffer), + \ 'command': ale#Escape(l:executable) . ale#Pad(l:options) . ' --stdin-filepath %s -', + \} +endfunction diff --git a/autoload/ale/fixers/swiftformat.vim b/autoload/ale/fixers/swiftformat.vim new file mode 100644 index 00000000..cc553b7d --- /dev/null +++ b/autoload/ale/fixers/swiftformat.vim @@ -0,0 +1,25 @@ +" Author: gfontenot (Gordon Fontenot) +" Description: Integration of SwiftFormat with ALE. + +call ale#Set('swift_swiftformat_executable', 'swiftformat') +call ale#Set('swift_swiftformat_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('swift_swiftformat_options', '') + +function! ale#fixers#swiftformat#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'swift_swiftformat', [ + \ 'Pods/SwiftFormat/CommandLineTool/swiftformat', + \ 'ios/Pods/SwiftFormat/CommandLineTool/swiftformat', + \ 'swiftformat', + \]) +endfunction + +function! ale#fixers#swiftformat#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'swift_swiftformat_options') + + return { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(ale#fixers#swiftformat#GetExecutable(a:buffer)) + \ . ' %t' + \ . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/syntax_tree.vim b/autoload/ale/fixers/syntax_tree.vim new file mode 100644 index 00000000..08823a88 --- /dev/null +++ b/autoload/ale/fixers/syntax_tree.vim @@ -0,0 +1,18 @@ +call ale#Set('ruby_syntax_tree_options', '') +call ale#Set('ruby_syntax_tree_executable', 'stree') + +function! ale#fixers#syntax_tree#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_syntax_tree_executable') + let l:options = ale#Var(a:buffer, 'ruby_syntax_tree_options') + + return ale#ruby#EscapeExecutable(l:executable, 'stree') + \ . ' format' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + +function! ale#fixers#syntax_tree#Fix(buffer) abort + return { + \ 'command': ale#fixers#syntax_tree#GetCommand(a:buffer), + \} +endfunction diff --git a/autoload/ale/fixers/terraform.vim b/autoload/ale/fixers/terraform.vim new file mode 100644 index 00000000..bc05380a --- /dev/null +++ b/autoload/ale/fixers/terraform.vim @@ -0,0 +1,17 @@ +" Author: dsifford +" Description: Fixer for terraform and .hcl files + +call ale#Set('terraform_fmt_executable', 'terraform') +call ale#Set('terraform_fmt_options', '') + +function! ale#fixers#terraform#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'terraform_fmt_executable') + let l:options = ale#Var(a:buffer, 'terraform_fmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' fmt' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' -' + \} +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 +" 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/fixers/tidy.vim b/autoload/ale/fixers/tidy.vim new file mode 100644 index 00000000..2c79e73a --- /dev/null +++ b/autoload/ale/fixers/tidy.vim @@ -0,0 +1,26 @@ +" Author: meain +" Description: Fixing HTML files with tidy. + +call ale#Set('html_tidy_executable', 'tidy') +call ale#Set('html_tidy_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#fixers#tidy#Fix(buffer) abort + let l:executable = ale#path#FindExecutable( + \ a:buffer, + \ 'html_tidy', + \ ['tidy'], + \) + + if !executable(l:executable) + return 0 + endif + + let l:config = ale#path#FindNearestFile(a:buffer, '.tidyrc') + let l:config_options = !empty(l:config) + \ ? ' -q --tidy-mark no --show-errors 0 --show-warnings 0 -config ' . ale#Escape(l:config) + \ : ' -q --tidy-mark no --show-errors 0 --show-warnings 0' + + return { + \ 'command': ale#Escape(l:executable) . l:config_options, + \} +endfunction diff --git a/autoload/ale/fixers/tslint.vim b/autoload/ale/fixers/tslint.vim new file mode 100644 index 00000000..15768fd5 --- /dev/null +++ b/autoload/ale/fixers/tslint.vim @@ -0,0 +1,22 @@ +" Author: carakan +" Description: Fixing files with tslint. + +function! ale#fixers#tslint#Fix(buffer) abort + let l:executable = ale#handlers#tslint#GetExecutable(a:buffer) + + let l:tslint_config_path = ale#path#ResolveLocalPath( + \ a:buffer, + \ 'tslint.json', + \ ale#Var(a:buffer, 'typescript_tslint_config_path') + \) + let l:tslint_config_option = !empty(l:tslint_config_path) + \ ? ' -c ' . ale#Escape(l:tslint_config_path) + \ : '' + + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . l:tslint_config_option + \ . ' --outputAbsolutePaths --fix %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/typstyle.vim b/autoload/ale/fixers/typstyle.vim new file mode 100644 index 00000000..19c9399f --- /dev/null +++ b/autoload/ale/fixers/typstyle.vim @@ -0,0 +1,20 @@ +" Author: Adrian Vollmer (computerfluesterer@protonmail.com) +" Description: Typst formatter using typstyle + +call ale#Set('typst_typstyle_executable', 'typstyle') +call ale#Set('typst_typstyle_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('typst_typstyle_options', '') + +function! ale#fixers#typstyle#Fix(buffer) abort + let l:executable = ale#path#FindExecutable( + \ a:buffer, + \ 'typst_typstyle', + \ ['typstyle'] + \) + + let l:options = ale#Var(a:buffer, 'typst_typstyle_options') + + return { + \ 'command': ale#Escape(l:executable) . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/uncrustify.vim b/autoload/ale/fixers/uncrustify.vim new file mode 100644 index 00000000..0e8271ec --- /dev/null +++ b/autoload/ale/fixers/uncrustify.vim @@ -0,0 +1,33 @@ +" Author: Derek P Sifford +" Description: Fixer for C, C++, C#, ObjectiveC, D, Java, Pawn, and VALA. + +call ale#Set('c_uncrustify_executable', 'uncrustify') +call ale#Set('c_uncrustify_options', '') + +let s:languages = { +\ 'c': 'C', +\ 'cpp': 'CPP', +\ 'cs': 'CS', +\ 'objc': 'OC', +\ 'objcpp': 'OC+', +\ 'd': 'D', +\ 'java': 'JAVA', +\ 'vala': 'VALA', +\ 'p': 'PAWN', +\} + +function! ale#fixers#uncrustify#Language(buffer) abort + return get(s:languages, &filetype, 'C') +endfunction + +function! ale#fixers#uncrustify#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'c_uncrustify_executable') + let l:options = ale#Var(a:buffer, 'c_uncrustify_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' --no-backup ' + \ . '-l' . ale#Pad(ale#fixers#uncrustify#Language(a:buffer)) + \ . ale#Pad(l:options) + \} +endfunction diff --git a/autoload/ale/fixers/vfmt.vim b/autoload/ale/fixers/vfmt.vim new file mode 100644 index 00000000..2e780318 --- /dev/null +++ b/autoload/ale/fixers/vfmt.vim @@ -0,0 +1,13 @@ +" Author: fiatjaf +" Description: Integration of `v fmt` with ALE. + +call ale#Set('v_vfmt_options', '') + +function! ale#fixers#vfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'v_v_executable') + let l:options = ale#Var(a:buffer, 'v_vfmt_options') + + return { + \ 'command': ale#Escape(l:executable) . ' fmt' . ale#Pad(l:options) + \} +endfunction diff --git a/autoload/ale/fixers/xmllint.vim b/autoload/ale/fixers/xmllint.vim new file mode 100644 index 00000000..4c74508b --- /dev/null +++ b/autoload/ale/fixers/xmllint.vim @@ -0,0 +1,29 @@ +" Author: Cyril Roelandt , jiz4oh +" Description: Integration of xmllint with ALE. + +call ale#Set('xml_xmllint_executable', 'xmllint') +call ale#Set('xml_xmllint_options', '') +call ale#Set('xml_xmllint_indentsize', 2) + +function! ale#fixers#xmllint#Fix(buffer) abort + let l:executable = ale#Escape(ale#Var(a:buffer, 'xml_xmllint_executable')) + + let l:command = l:executable . ' --format' + + let l:indent = ale#Var(a:buffer, 'xml_xmllint_indentsize') + + if l:indent isnot# '' + let l:env = ale#Env('XMLLINT_INDENT', repeat(' ', l:indent)) + let l:command = l:env . l:command + endif + + let l:options = ale#Var(a:buffer, 'xml_xmllint_options') + + if l:options isnot# '' + let l:command .= ' ' . l:options + endif + + return { + \ 'command': l:command . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/xo.vim b/autoload/ale/fixers/xo.vim new file mode 100644 index 00000000..dcf4c737 --- /dev/null +++ b/autoload/ale/fixers/xo.vim @@ -0,0 +1,36 @@ +" Author: Albert Marquez - https://github.com/a-marquez +" Description: Fixing files with XO. + +function! ale#fixers#xo#Fix(buffer) abort + let l:executable = ale#handlers#xo#GetExecutable(a:buffer) + let l:options = ale#handlers#xo#GetOptions(a:buffer) + + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ l:executable, + \ '%e --version', + \ {b, v -> ale#fixers#xo#ApplyFixForVersion(b, v, l:executable, l:options)} + \) +endfunction + +function! ale#fixers#xo#ApplyFixForVersion(buffer, version, executable, options) abort + let l:executable = ale#node#Executable(a:buffer, a:executable) + let l:options = ale#Pad(a:options) + + " 0.30.0 is the first version with a working --stdin --fix + if ale#semver#GTE(a:version, [0, 30, 0]) + return { + \ 'command': l:executable + \ . ' --stdin --stdin-filename %s' + \ . ' --fix' + \ . l:options, + \} + endif + + return { + \ 'command': l:executable + \ . ' --fix %t' + \ . l:options, + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/yamlfix.vim b/autoload/ale/fixers/yamlfix.vim new file mode 100644 index 00000000..6654a25c --- /dev/null +++ b/autoload/ale/fixers/yamlfix.vim @@ -0,0 +1,25 @@ +" Author: lyz-code +" Description: Fixing yaml files with yamlfix. + +call ale#Set('yaml_yamlfix_executable', 'yamlfix') +call ale#Set('yaml_yamlfix_options', '') +call ale#Set('yaml_yamlfix_use_global', get(g:, 'ale_use_global_executables', 0)) + +function! ale#fixers#yamlfix#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'yaml_yamlfix_options') + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'yaml_yamlfix', + \ ['yamlfix'], + \) + + if !executable(l:executable) + return 0 + endif + + return { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/yamlfmt.vim b/autoload/ale/fixers/yamlfmt.vim new file mode 100644 index 00000000..e730832a --- /dev/null +++ b/autoload/ale/fixers/yamlfmt.vim @@ -0,0 +1,20 @@ +" Author: https://github.com/Spixmaster +" Description: Format YAML files with yamlfmt. + +call ale#Set('yaml_yamlfmt_executable', 'yamlfmt') +call ale#Set('yaml_yamlfmt_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('yaml_yamlfmt_options', '') + +function! ale#fixers#yamlfmt#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'yaml_yamlfmt', + \ ['yamlfmt'] + \) + + let l:options = ale#Var(a:buffer, 'yaml_yamlfmt_options') + + return { + \ 'command': ale#Escape(l:executable) . ' ' . l:options . ' -in', + \} +endfunction diff --git a/autoload/ale/fixers/yapf.vim b/autoload/ale/fixers/yapf.vim new file mode 100644 index 00000000..cbc3d46d --- /dev/null +++ b/autoload/ale/fixers/yapf.vim @@ -0,0 +1,44 @@ +" Author: w0rp +" Description: Fixing Python files with yapf. + +call ale#Set('python_yapf_executable', 'yapf') +call ale#Set('python_yapf_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_yapf_auto_pipenv', 0) +call ale#Set('python_yapf_auto_poetry', 0) +call ale#Set('python_yapf_auto_uv', 0) + +function! ale#fixers#yapf#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_yapf_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_yapf_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_yapf_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_yapf', ['yapf']) +endfunction + +function! ale#fixers#yapf#Fix(buffer) abort + let l:executable = ale#fixers#yapf#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? '\(pipenv\|poetry\|uv\)$' + \ ? ' run yapf' + \ : '' + + let l:config = ale#path#FindNearestFile(a:buffer, '.style.yapf') + let l:config_options = !empty(l:config) + \ ? ' --no-local-style --style ' . ale#Escape(l:config) + \ : '' + + return { + \ 'command': ale#Escape(l:executable) . l:exec_args . l:config_options, + \} +endfunction diff --git a/autoload/ale/fixers/yq.vim b/autoload/ale/fixers/yq.vim new file mode 100644 index 00000000..b9bf7007 --- /dev/null +++ b/autoload/ale/fixers/yq.vim @@ -0,0 +1,22 @@ +call ale#Set('yaml_yq_executable', 'yq') +call ale#Set('yaml_yq_options', '') +call ale#Set('yaml_yq_filters', '.') + +function! ale#fixers#yq#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'yaml_yq_executable') +endfunction + +function! ale#fixers#yq#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'yaml_yq_options') + let l:filters = ale#Var(a:buffer, 'yaml_yq_filters') + + if empty(l:filters) + return 0 + endif + + return { + \ 'command': ale#Escape(ale#fixers#yq#GetExecutable(a:buffer)) + \ . ' ' . l:filters . ' ' + \ . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/zigfmt.vim b/autoload/ale/fixers/zigfmt.vim new file mode 100644 index 00000000..b22e5b83 --- /dev/null +++ b/autoload/ale/fixers/zigfmt.vim @@ -0,0 +1,14 @@ +scriptencoding utf-8 +" Author: Arash Mousavi +" Description: Official formatter for Zig. + +call ale#Set('zig_zigfmt_executable', 'zig') + +function! ale#fixers#zigfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'zig_zigfmt_executable') + + return { + \ 'command': ale#Escape(l:executable) . ' fmt %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/floating_preview.vim b/autoload/ale/floating_preview.vim new file mode 100644 index 00000000..3e1fabb8 --- /dev/null +++ b/autoload/ale/floating_preview.vim @@ -0,0 +1,221 @@ +" Author: Jan-Grimo Sobez +" Author: Kevin Clark +" Author: D. Ben Knoble +" Author: Shaun Duncan +" Description: Floating preview window for showing whatever information in. + +" Precondition: exists('*nvim_open_win') || has('popupwin') + +function! ale#floating_preview#Show(lines, ...) abort + if !exists('*nvim_open_win') && !has('popupwin') + " no-custom-checks + echom 'Floating windows not supported in this vim instance.' + + return + endif + + let l:options = get(a:000, 0, {}) + + if has('nvim') + call s:NvimShow(a:lines, l:options) + else + call s:VimShow(a:lines, l:options) + endif + + return w:preview.id +endfunction + +function! s:NvimShow(lines, options) abort + " Remove the close autocmd so it doesn't happen mid update + augroup ale_floating_preview_window + autocmd! + augroup END + + " Only create a new window if we need it + if !exists('w:preview') || index(nvim_list_wins(), w:preview['id']) is# -1 + call s:NvimCreate(a:options) + else + call nvim_buf_set_option(w:preview['buffer'], 'modifiable', v:true) + endif + + " Execute commands in window context + if exists('*win_execute') + for l:command in get(a:options, 'commands', []) + call win_execute(w:preview['id'], l:command) + endfor + else + let l:parent_window = nvim_get_current_win() + + call nvim_set_current_win(w:preview['id']) + + for l:command in get(a:options, 'commands', []) + call execute(l:command) + endfor + + call nvim_set_current_win(l:parent_window) + endif + + " Return to parent context on move + augroup ale_floating_preview_window + autocmd! + + if g:ale_close_preview_on_insert + autocmd CursorMoved,TabLeave,WinLeave,BufWinLeave,WinScrolled,InsertEnter ++once call s:NvimClose() + else + autocmd CursorMoved,TabLeave,WinLeave,BufWinLeave,WinScrolled ++once call s:NvimClose() + endif + augroup END + + let [l:lines, l:width, l:height] = s:NvimPrepareWindowContent(a:lines) + + call nvim_win_set_width(w:preview['id'], l:width) + call nvim_win_set_height(w:preview['id'], l:height) + call nvim_buf_set_lines(w:preview['buffer'], 0, -1, v:false, l:lines) + call nvim_buf_set_option(w:preview['buffer'], 'modified', v:false) + call nvim_buf_set_option(w:preview['buffer'], 'modifiable', v:false) +endfunction + +function! s:VimShow(lines, options) abort + if g:ale_close_preview_on_insert + " Remove the close autocmd so it doesn't happen mid update + silent! autocmd! ale_floating_preview_window + endif + + " Only create a new window if we need it + if !exists('w:preview') || index(popup_list(), w:preview['id']) is# -1 + call s:VimCreate(a:options) + endif + + " Execute commands in window context + for l:command in get(a:options, 'commands', []) + call win_execute(w:preview['id'], l:command) + endfor + + call popup_settext(w:preview['id'], a:lines) + + if g:ale_close_preview_on_insert + augroup ale_floating_preview_window + autocmd! + autocmd InsertEnter * ++once call s:VimClose() + augroup END + endif +endfunction + +function! s:NvimPrepareWindowContent(lines) abort + let l:max_height = 10 + + let l:width = max(map(copy(a:lines), 'strdisplaywidth(v:val)')) + let l:height = min([len(a:lines), l:max_height]) + + return [a:lines[0:l:height-1], l:width, l:height] +endfunction + +function! s:NvimCreate(options) abort + let l:left = get(g:ale_floating_window_border, 0, '|') + let l:top = get(g:ale_floating_window_border, 1, '-') + + let l:popup_opts = extend({ + \ 'relative': 'cursor', + \ 'row': 1, + \ 'col': 0, + \ 'width': 42, + \ 'height': 4, + \ 'style': 'minimal', + \ 'border': empty(g:ale_floating_window_border) ? 'none' : [ + \ get(g:ale_floating_window_border, 2, '+'), + \ l:top, + \ get(g:ale_floating_window_border, 3, '+'), + \ get(g:ale_floating_window_border, 6, l:left), + \ get(g:ale_floating_window_border, 4, '+'), + \ get(g:ale_floating_window_border, 7, l:top), + \ get(g:ale_floating_window_border, 5, '+'), + \ l:left, + \ ], + \ }, s:GetPopupOpts()) + + let l:buffer = nvim_create_buf(v:false, v:false) + let l:winid = nvim_open_win(l:buffer, v:false, l:popup_opts) + + call nvim_buf_set_option(l:buffer, 'buftype', 'acwrite') + call nvim_buf_set_option(l:buffer, 'bufhidden', 'delete') + call nvim_buf_set_option(l:buffer, 'swapfile', v:false) + call nvim_buf_set_option(l:buffer, 'filetype', get(a:options, 'filetype', 'ale-preview')) + + let w:preview = {'id': l:winid, 'buffer': l:buffer} +endfunction + +function! s:VimCreate(options) abort + " default options + let l:popup_opts = extend({ + \ 'line': 'cursor+1', + \ 'col': 'cursor', + \ 'drag': v:true, + \ 'resize': v:true, + \ 'close': 'button', + \ 'padding': [0, 1, 0, 1], + \ 'border': [], + \ 'borderchars': empty(g:ale_floating_window_border) ? [' '] : [ + \ get(g:ale_floating_window_border, 1, '-'), + \ get(g:ale_floating_window_border, 6, '|'), + \ get(g:ale_floating_window_border, 7, '-'), + \ get(g:ale_floating_window_border, 0, '|'), + \ get(g:ale_floating_window_border, 2, '+'), + \ get(g:ale_floating_window_border, 3, '+'), + \ get(g:ale_floating_window_border, 4, '+'), + \ get(g:ale_floating_window_border, 5, '+'), + \ ], + \ 'moved': 'any', + \ }, s:GetPopupOpts()) + + let l:popup_id = popup_create([], l:popup_opts) + call setbufvar(winbufnr(l:popup_id), '&filetype', get(a:options, 'filetype', 'ale-preview')) + let w:preview = {'id': l:popup_id} +endfunction + +function! s:NvimClose() abort + let l:mode = mode() + let l:restore_visual = l:mode is# 'v' || l:mode is# 'V' || l:mode is# "\" + + if !exists('w:preview') + return + endif + + call setbufvar(w:preview['buffer'], '&modified', 0) + + if win_id2win(w:preview['id']) > 0 + execute win_id2win(w:preview['id']).'wincmd c' + endif + + unlet w:preview + + if l:restore_visual + normal! gv + endif +endfunction + +function! s:VimClose() abort + if !exists('w:preview') + return + endif + + call popup_close(w:preview['id']) + unlet w:preview +endfunction + +" get either the results of a function callback or dictionary for popup overrides +function! s:GetPopupOpts() abort + if exists('g:ale_floating_preview_popup_opts') + let l:ref = g:ale_floating_preview_popup_opts + + if type(l:ref) is# v:t_dict + return l:ref + elseif type(l:ref) is# v:t_string + try + return function(l:ref)() + catch /E700/ + endtry + endif + endif + + return {} +endfunction diff --git a/autoload/ale/go.vim b/autoload/ale/go.vim new file mode 100644 index 00000000..bce85a87 --- /dev/null +++ b/autoload/ale/go.vim @@ -0,0 +1,58 @@ +" Author: Horacio Sanson https://github.com/hsanson +" Description: Functions for integrating with Go tools + +" Find the nearest dir listed in GOPATH and assume it the root of the go +" project. +function! ale#go#FindProjectRoot(buffer) abort + let l:sep = has('win32') ? ';' : ':' + + 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) + + " 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 + endif + + return '' +endfunction + + +call ale#Set('go_go111module', '') + +" Return a string setting Go-specific environment variables +function! ale#go#EnvString(buffer) abort + let l:env = '' + + " GO111MODULE - turn go modules behavior on/off + let l:go111module = ale#Var(a:buffer, 'go_go111module') + + if !empty(l:go111module) + let l:env = ale#Env('GO111MODULE', l:go111module) . l:env + endif + + return l:env +endfunction + +function! ale#go#GetGoPathExecutable(suffix) abort + let l:prefix = $GOPATH + + if !empty($GOPATH) + let l:prefix = $GOPATH + elseif has('win32') + let l:prefix = $USERPROFILE . '/go' + else + let l:prefix = $HOME . '/go' + endif + + return ale#path#Simplify(l:prefix . '/' . a:suffix) +endfunction diff --git a/autoload/ale/gradle.vim b/autoload/ale/gradle.vim new file mode 100644 index 00000000..ba1add4d --- /dev/null +++ b/autoload/ale/gradle.vim @@ -0,0 +1,74 @@ +" Author: Michael Pardo +" Description: Functions for working with Gradle projects. + +let s:script_path = fnamemodify(resolve(expand(':p')), ':h') +let s:init_path = has('win32') +\ ? s:script_path . '\gradle\init.gradle' +\ : s:script_path . '/gradle/init.gradle' + +function! ale#gradle#GetInitPath() abort + return s:init_path +endfunction + +" Given a buffer number, find a Gradle project root. +function! ale#gradle#FindProjectRoot(buffer) abort + let l:gradlew_path = ale#path#FindNearestFile(a:buffer, 'gradlew') + + if !empty(l:gradlew_path) + return fnamemodify(l:gradlew_path, ':h') + endif + + let l:settings_path = ale#path#FindNearestFile(a:buffer, 'settings.gradle') + + if !empty(l:settings_path) + return fnamemodify(l:settings_path, ':h') + endif + + let l:build_path = ale#path#FindNearestFile(a:buffer, 'build.gradle') + + if !empty(l:build_path) + return fnamemodify(l:build_path, ':h') + endif + + return '' +endfunction + +" Given a buffer number, find the path to the executable. +" First search on the path for 'gradlew', if nothing is found, try the global +" command. Returns an empty string if cannot find the executable. +function! ale#gradle#FindExecutable(buffer) abort + let l:gradlew_path = ale#path#FindNearestFile(a:buffer, 'gradlew') + + if !empty(l:gradlew_path) + return l:gradlew_path + endif + + if executable('gradle') + return 'gradle' + endif + + return '' +endfunction + +" Given a buffer number, get a working directory and command to print the +" classpath of the root project. +" +" Returns an empty string for the command if Gradle is not detected. +function! ale#gradle#BuildClasspathCommand(buffer) abort + let l:executable = ale#gradle#FindExecutable(a:buffer) + + if !empty(l:executable) + let l:project_root = ale#gradle#FindProjectRoot(a:buffer) + + if !empty(l:project_root) + return [ + \ l:project_root, + \ ale#Escape(l:executable) + \ . ' -I ' . ale#Escape(s:init_path) + \ . ' -q printClasspath' + \] + endif + endif + + return ['', ''] +endfunction diff --git a/autoload/ale/gradle/init.gradle b/autoload/ale/gradle/init.gradle new file mode 100644 index 00000000..fb1db9ee --- /dev/null +++ b/autoload/ale/gradle/init.gradle @@ -0,0 +1,23 @@ +class ClasspathPlugin implements Plugin { + void apply(Project project) { + project.task('printClasspath') { + doLast { + project + .rootProject + .allprojects + .configurations + .flatten() + .findAll { it.name.endsWith('Classpath') } + .collect { it.resolve() } + .flatten() + .unique() + .findAll { it.exists() } + .each { println it } + } + } + } +} + +rootProject { + apply plugin: ClasspathPlugin +} diff --git a/autoload/ale/handlers/alex.vim b/autoload/ale/handlers/alex.vim new file mode 100644 index 00000000..1a92bd14 --- /dev/null +++ b/autoload/ale/handlers/alex.vim @@ -0,0 +1,55 @@ +scriptencoding utf-8 +" Author: Johannes Wienke +" Description: Error handling for errors in alex output format + +function! ale#handlers#alex#GetExecutable(buffer) abort + return ale#path#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)) + \ . ' --stdin ' + \ . 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 = '\v^ *(\d+):(\d+)-(\d+):(\d+) +warning +(.{-}) +(.{-}) +(.{-})$' + 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, + \ 'end_lnum': l:match[3] + 0, + \ 'end_col': l:match[4] - 1, + \ 'text': l:match[5] . ' (' . (l:match[7]) . ')', + \ 'type': 'W', + \}) + endfor + + 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 +" --mdx treat input as MDX +" --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', + \}) +endfunction diff --git a/autoload/ale/handlers/atools.vim b/autoload/ale/handlers/atools.vim new file mode 100644 index 00000000..c273fc40 --- /dev/null +++ b/autoload/ale/handlers/atools.vim @@ -0,0 +1,41 @@ +" Author: Leo +" Description: Handlers for output expected from atools + +function! ale#handlers#atools#Handle(buffer, lines) abort + " Format: SEVERITY:[TAG]:PATH:LINENUM:MSG + " Example: MC:[AL5]:./APKBUILD:12:variable set to empty string: install= + let l:pattern = '\([^:]\+\):\([^:]\+\):\([^:]\+\):\(\d\+\):\(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " We are expected to receive 2 characters, the first character + " can be 'S', 'I', 'M' 'T', which are respectively: + " Serious (Error) + " Important (Error) + " Minor (Warning) + " Style (Warning) + " + " The second character can be either 'C' or 'P', which are respectively: + " Certain (Error) + " Possible (Warning) + let l:severity = matchstr(l:match[1], '^.') + let l:certainty = matchstr(l:match[1], '.$') + + let l:type = 'E' + " If the tag returns 'Minor' or 'Style' or is 'Possible' + " then return a warning + + if l:severity is# 'M' || l:severity is# 'T' || l:certainty is# 'P' + let l:type = 'W' + endif + + call add(l:output, { + \ 'lnum': l:match[4] + 0, + \ 'text': l:match[5], + \ 'type': l:type, + \ 'code': matchstr(l:match[2], 'AL[0-9]*'), + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/biome.vim b/autoload/ale/handlers/biome.vim new file mode 100644 index 00000000..b22c1c46 --- /dev/null +++ b/autoload/ale/handlers/biome.vim @@ -0,0 +1,58 @@ +" Author: Filip Gospodinov +" Description: Functions for working with biome, for checking or fixing files. + +call ale#Set('biome_executable', 'biome') +call ale#Set('biome_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('biome_options', '') +call ale#Set('biome_fixer_apply_unsafe', 0) +call ale#Set('biome_lsp_project_root', '') + +function! ale#handlers#biome#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'biome', [ + \ 'node_modules/@biomejs/cli-linux-x64/biome', + \ 'node_modules/@biomejs/cli-linux-arm64/biome', + \ 'node_modules/@biomejs/cli-win32-x64/biome.exe', + \ 'node_modules/@biomejs/cli-win32-arm64/biome.exe', + \ 'node_modules/@biomejs/cli-darwin-x64/biome', + \ 'node_modules/@biomejs/cli-darwin-arm64/biome', + \ 'node_modules/.bin/biome', + \]) +endfunction + +function! ale#handlers#biome#GetLanguage(buffer) abort + return getbufvar(a:buffer, '&filetype') +endfunction + +function! ale#handlers#biome#GetProjectRoot(buffer) abort + let l:project_root = ale#Var(a:buffer, 'biome_lsp_project_root') + + if !empty(l:project_root) + return l:project_root + endif + + let l:possible_project_roots = [ + \ 'biome.json', + \ 'biome.jsonc', + \ 'package.json', + \ '.git', + \ bufname(a:buffer), + \] + + for l:possible_root in l:possible_project_roots + let l:project_root = ale#path#FindNearestFile(a:buffer, l:possible_root) + + if empty(l:project_root) + let l:project_root = ale#path#FindNearestDirectory(a:buffer, l:possible_root) + endif + + if !empty(l:project_root) + " dir:p expands to /full/path/to/dir/ whereas + " file:p expands to /full/path/to/file (no trailing slash) + " Appending '/' ensures that :h:h removes the path's last segment + " regardless of whether it is a directory or not. + return fnamemodify(l:project_root . '/', ':p:h:h') + endif + endfor + + return '' +endfunction diff --git a/autoload/ale/handlers/c3lsp.vim b/autoload/ale/handlers/c3lsp.vim new file mode 100644 index 00000000..2763f3db --- /dev/null +++ b/autoload/ale/handlers/c3lsp.vim @@ -0,0 +1,19 @@ +scriptencoding utf-8 +" Author: Koni Marti +" Description: Utilities for c3lsp + +function! ale#handlers#c3lsp#GetProjectRoot(buffer) abort + let l:config = ale#path#FindNearestFile(a:buffer, 'project.json') + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + return expand('#' . a:buffer . ':p:h') +endfunction + +function! ale#handlers#c3lsp#GetInitOpts(buffer, init_options_var) abort + let l:init_options = {} + + return extend(l:init_options, ale#Var(a:buffer, a:init_options_var)) +endfunction diff --git a/autoload/ale/handlers/cairo.vim b/autoload/ale/handlers/cairo.vim new file mode 100644 index 00000000..41029c8d --- /dev/null +++ b/autoload/ale/handlers/cairo.vim @@ -0,0 +1,41 @@ +" Author: 0xhyoga <0xhyoga@gmx.com>, +" Description: This file implements handlers specific to Cairo +" +function! ale#handlers#cairo#HandleCairoErrors(buffer, lines) abort + " Matches patterns like the following: + " Error: Expected ';' but got '(' + " --> /path/to/file/file.cairo:1:10:) + let l:pattern = '\v(error|warning): (.*)$' + let l:line_and_column_pattern = '\v\.cairo:(\d+):(\d+)' + let l:exclude_pattern = '\vcould not compile.*' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + let l:match = matchlist(l:line, l:line_and_column_pattern) + + if len(l:match) > 0 + let l:index = len(l:output) - 1 + let l:output[l:index]['lnum'] = l:match[1] + 0 + let l:output[l:index]['col'] = l:match[2] + 0 + endif + else + let l:text = l:match[2] + + if l:text !~# l:exclude_pattern + let l:isError = l:match[1] is? 'Error' + + call add(l:output, { + \ 'lnum': 0, + \ 'col': 0, + \ 'text': l:text, + \ 'type': l:isError ? 'E' : 'W', + \}) + endif + endif + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/ccls.vim b/autoload/ale/handlers/ccls.vim new file mode 100644 index 00000000..290f5852 --- /dev/null +++ b/autoload/ale/handlers/ccls.vim @@ -0,0 +1,26 @@ +scriptencoding utf-8 +" Author: Ye Jingchen +" Description: Utilities for ccls + +function! ale#handlers#ccls#GetProjectRoot(buffer) abort + " Try to find ccls configuration files first. + let l:config = ale#path#FindNearestFile(a:buffer, '.ccls-root') + + if empty(l:config) + let l:config = ale#path#FindNearestFile(a:buffer, '.ccls') + endif + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + " Fall back on default project root detection. + return ale#c#FindProjectRoot(a:buffer) +endfunction + +function! ale#handlers#ccls#GetInitOpts(buffer, init_options_var) abort + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + let l:init_options = empty(l:build_dir) ? {} : {'compilationDatabaseDirectory': l:build_dir} + + return extend(l:init_options, ale#Var(a:buffer, a:init_options_var)) +endfunction diff --git a/autoload/ale/handlers/cppcheck.vim b/autoload/ale/handlers/cppcheck.vim new file mode 100644 index 00000000..150bb007 --- /dev/null +++ b/autoload/ale/handlers/cppcheck.vim @@ -0,0 +1,89 @@ +" Description: Handle errors for cppcheck. + +function! ale#handlers#cppcheck#GetCwd(buffer) abort + let [l:dir, l:json_path] = ale#c#FindCompileCommands(a:buffer) + + return !empty(l:dir) ? l:dir : '' +endfunction + +function! ale#handlers#cppcheck#GetBufferPathIncludeOptions(buffer) abort + let l:buffer_path_include = '' + + " Get path to this buffer so we can include it into cppcheck with -I + " This could be expanded to get more -I directives from the compile + " command in compile_commands.json, if it's found. + let l:buffer_path = fnamemodify(bufname(a:buffer), ':p:h') + let l:buffer_path_include = ' -I' . ale#Escape(l:buffer_path) + + return l:buffer_path_include +endfunction + +function! ale#handlers#cppcheck#GetCompileCommandsOptions(buffer) abort + " The compile_commands.json doesn't apply to headers and cppheck will + " bail out if it cannot find a file matching the filter, below. Skip out + " now, for headers. Also, suppress FPs; cppcheck is not meant to + " process lone header files. + let b:buffer_name = bufname(a:buffer) + let b:file_extension = fnamemodify(b:buffer_name, ':e') + + if b:file_extension is# 'h' || b:file_extension is# 'hpp' + return ale#handlers#cppcheck#GetBufferPathIncludeOptions(a:buffer) + \ . ' --suppress=unusedStructMember' + endif + + " If the current buffer is modified, using compile_commands.json does no + " good, so include the file's directory instead. It's not quite as good as + " using --project, but is at least equivalent to running cppcheck on this + " file manually from the file's directory. + let l:modified = getbufvar(a:buffer, '&modified') + + if l:modified + return '' + endif + + " Search upwards from the file for compile_commands.json. + " + " If we find it, we'll `cd` to where the compile_commands.json file is, + " then use the file to set up import paths, etc. + let [l:dir, l:json_path] = ale#c#FindCompileCommands(a:buffer) + + " By default, cppcheck processes every config in compile_commands.json. + " Use --file-filter to limit to just the buffer file. + return !empty(l:json_path) + \ ? '--project=' . ale#Escape(l:json_path[len(l:dir) + 1: ]) . ' --file-filter=' . ale#Escape(bufname(a:buffer)) + \ : '' +endfunction + +function! ale#handlers#cppcheck#HandleCppCheckFormat(buffer, lines) abort + " Look for lines like the following. + " + "test.cpp:974:6: error:inconclusive Array 'n[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\ + " n[3]=3; + " ^ + "" OR if cppcheck doesn't support {column} or {inconclusive:text}: + "test.cpp:974:{column}: error:{inconclusive:inconclusive} Array 'n[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\ + " n[3]=3; + " ^ + " + "" OR if using the misra addon: + "test.c:1:16: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.7]\' + "void test( int parm ) {} + " ^ + let l:pattern = '\v(\f+):(\d+):(\d+|\{column\}): (\w+):(\{inconclusive:inconclusive\})? ?(.*) \[(%(\w[-.]?)+)\]\' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if ale#path#IsBufferPath(a:buffer, l:match[1]) + call add(l:output, { + \ 'lnum': str2nr(l:match[2]), + \ 'col': match(l:match[3],'{column}') >= 0 ? 1 : str2nr(l:match[3]), + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'sub_type': l:match[4] is# 'style' ? 'style' : '', + \ 'text': l:match[6], + \ 'code': l:match[7] + \}) + endif + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/cpplint.vim b/autoload/ale/handlers/cpplint.vim new file mode 100644 index 00000000..5c475a5c --- /dev/null +++ b/autoload/ale/handlers/cpplint.vim @@ -0,0 +1,21 @@ +" Author: Dawid Kurek https://github.com/dawikur +" Description: Handle errors for cpplint. + +function! ale#handlers#cpplint#HandleCppLintFormat(buffer, lines) abort + " Look for lines like the following. + " test.cpp:5: Estra space after ( in function call [whitespace/parents] [4] + let l:pattern = '^.\{-}:\(\d\+\): *\(.\+\) *\[\(.*/.*\)\] ' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': 0, + \ 'text': join(split(l:match[2])), + \ 'code': l:match[3], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/cspell.vim b/autoload/ale/handlers/cspell.vim new file mode 100644 index 00000000..a59002bb --- /dev/null +++ b/autoload/ale/handlers/cspell.vim @@ -0,0 +1,78 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: Define a handler function for cspell's output + +function! ale#handlers#cspell#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, + \ 'cspell', [ + \ 'node_modules/.bin/cspell', + \ 'node_modules/cspell/bin.js', + \ ] + \) +endfunction + +function! ale#handlers#cspell#GetLanguageId(buffer) abort + let l:filetype = getbufvar(a:buffer, '&filetype') + + if l:filetype is# 'tex' + " Vim's tex corresponds to latex language-id in cspell + return 'latex' + elseif l:filetype is# 'plaintex' + " Vim's plaintex corresponds to tex language-id in cspell + return 'tex' + else + " Fallback to filetype for everything else. + return l:filetype + endif +endfunction + +function! ale#handlers#cspell#GetCommand(buffer) abort + let l:executable = ale#handlers#cspell#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'cspell_options') + let l:language_id = ale#handlers#cspell#GetLanguageId(a:buffer) + + let l:language_id_option = empty(l:language_id) ? '' : '--language-id="' . l:language_id . '"' + + return ale#node#Executable(a:buffer, l:executable) + \ . ' lint --no-color --no-progress --no-summary' + \ . ale#Pad(l:language_id_option) + \ . ale#Pad(l:options) + \ . ' -- stdin' +endfunction + +function! ale#handlers#cspell#Handle(buffer, lines) abort + " Look for lines like the following: + " + " /home/user/repos/ale/README.md:3:128 - Unknown word (Neovim) + " match1: 3 + " match2: 128 + " match3: Unknown word (Neovim) + " match4: Neovim + let l:pattern = '\v^.*:(\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, + \ 'end_col': l:match[2] + len(l:match[4]) - 1, + \ 'text': l:match[3], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +function! ale#handlers#cspell#DefineLinter(filetype) abort + call ale#Set('cspell_executable', 'cspell') + call ale#Set('cspell_options', '') + call ale#Set('cspell_use_global', get(g:, 'ale_use_global_executables', 0)) + + call ale#linter#Define(a:filetype, { + \ 'name': 'cspell', + \ 'executable': function('ale#handlers#cspell#GetExecutable'), + \ 'command': function('ale#handlers#cspell#GetCommand'), + \ 'callback': 'ale#handlers#cspell#Handle', + \}) +endfunction diff --git a/autoload/ale/handlers/css.vim b/autoload/ale/handlers/css.vim new file mode 100644 index 00000000..de9eadcc --- /dev/null +++ b/autoload/ale/handlers/css.vim @@ -0,0 +1,70 @@ +scriptencoding utf-8 +" Author: w0rp +" Description: Error handling for CSS linters. + +function! ale#handlers#css#HandleCSSLintFormat(buffer, lines) abort + " Matches patterns line the following: + " + " something.css: line 2, col 1, Error - Expected RBRACE at line 2, col 1. (errors) + " something.css: line 2, col 5, Warning - Expected (inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | grid | inline-grid | run-in | ruby | ruby-base | ruby-text | ruby-base-container | ruby-text-container | contents | none | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker | -webkit-box | -webkit-inline-box | -ms-flexbox | -ms-inline-flexbox | flex | -webkit-flex | inline-flex | -webkit-inline-flex) but found 'wat'. (known-properties) + " + " These errors can be very massive, so the type will be moved to the front + " so you can actually read the error type. + let l:pattern = '\v^.*: line (\d+), col (\d+), (Error|Warning) - (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:match[3] is# 'Warning' ? 'W' : 'E', + \ 'text': l:match[4], + \} + + let l:code_match = matchlist(l:match[4], '\v(.+) \(([^(]+)\)$') + + " Split up the error code and the text if we find one. + if !empty(l:code_match) + let l:item.text = l:code_match[1] + let l:item.code = l:code_match[2] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +function! ale#handlers#css#HandleStyleLintFormat(buffer, lines) abort + let l:exception_pattern = '\v^Error:' + + for l:line in a:lines[:10] + if len(matchlist(l:line, l:exception_pattern)) > 0 + return [{ + \ 'lnum': 1, + \ 'text': 'stylelint exception thrown (type :ALEDetail for more information)', + \ 'detail': join(a:lines, "\n"), + \}] + endif + endfor + + " Matches patterns line the following: + " + " src/main.css + " 108:10 ✖ Unexpected leading zero number-leading-zero + " 116:20 ✖ Expected a trailing semicolon declaration-block-trailing-semicolon + let l:pattern = '\v^.* (\d+):(\d+) \s+(\S+)\s+ (.*[^ ])\s+([^ ]+)\s*$' + 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, + \ 'type': l:match[3] is# '✖' ? 'E' : 'W', + \ 'text': l:match[4], + \ 'code': l:match[5], + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/deadnix.vim b/autoload/ale/handlers/deadnix.vim new file mode 100644 index 00000000..8f03f38e --- /dev/null +++ b/autoload/ale/handlers/deadnix.vim @@ -0,0 +1,33 @@ +function! ale#handlers#deadnix#Handle(buffer, lines) abort + let l:output = [] + + for l:line in a:lines + try + let l:file = ale#util#FuzzyJSONDecode(l:line, v:null) + catch + continue + endtry + + if type(l:file) isnot v:t_dict + continue + endif + + for l:error in l:file['results'] + try + let l:ale_error = { + \ 'lnum': l:error['line'], + \ 'col': l:error['column'], + \ 'end_col': l:error['endColumn'], + \ 'text': l:error['message'], + \ 'type': 'W', + \} + catch + continue + endtry + + call add(l:output, l:ale_error) + endfor + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/deno.vim b/autoload/ale/handlers/deno.vim new file mode 100644 index 00000000..1770559d --- /dev/null +++ b/autoload/ale/handlers/deno.vim @@ -0,0 +1,87 @@ +" Author: Mohammed Chelouti - https://github.com/motato1 +" Arnold Chand +" Description: Handler functions for Deno. + +call ale#Set('deno_executable', 'deno') +call ale#Set('deno_unstable', 0) +call ale#Set('deno_import_map', 'import_map.json') +call ale#Set('deno_lsp_project_root', '') + +function! ale#handlers#deno#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'deno_executable') +endfunction + +" Find project root for Deno's language server. +" +" Deno projects do not require a project or configuration file at the project root. +" This means the root directory has to be guessed, +" unless it is explicitly specified by the user. +" +" The project root is determined by ... +" 1. using a user-specified value from deno_lsp_project_root +" 2. looking for common top-level files/dirs +" 3. using the buffer's directory +function! ale#handlers#deno#GetProjectRoot(buffer) abort + let l:project_root = ale#Var(a:buffer, 'deno_lsp_project_root') + + if !empty(l:project_root) + return l:project_root + endif + + let l:possible_project_roots = [ + \ 'deno.json', + \ 'deno.jsonc', + \ 'tsconfig.json', + \ '.git', + \ bufname(a:buffer), + \] + + for l:possible_root in l:possible_project_roots + let l:project_root = ale#path#FindNearestFile(a:buffer, l:possible_root) + + if empty(l:project_root) + let l:project_root = ale#path#FindNearestDirectory(a:buffer, l:possible_root) + endif + + if !empty(l:project_root) + " dir:p expands to /full/path/to/dir/ whereas + " file:p expands to /full/path/to/file (no trailing slash) + " Appending '/' ensures that :h:h removes the path's last segment + " regardless of whether it is a directory or not. + return fnamemodify(l:project_root . '/', ':p:h:h') + endif + endfor + + return '' +endfunction + +" Initialization Options for deno, for javascript and typescript +function! ale#handlers#deno#GetInitializationOptions(buffer) abort + let l:options = { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:false, + \ 'importMap': ale#path#FindNearestFile(a:buffer, 'import_map.json'), + \ } + + if ale#Var(a:buffer, 'deno_unstable') + let l:options.unstable = v:true + endif + + " Look for a value set using the historical option name. + let l:import_map = getbufvar( + \ a:buffer, + \ 'ale_deno_importMap', + \ get(g:, 'ale_deno_importMap', '') + \) + + if empty(l:import_map) + let l:import_map = ale#Var(a:buffer, 'deno_import_map') + endif + + if !empty(l:import_map) + let l:options.importMap = ale#path#FindNearestFile(a:buffer, l:import_map) + endif + + return l:options +endfunction diff --git a/autoload/ale/handlers/djlint.vim b/autoload/ale/handlers/djlint.vim new file mode 100644 index 00000000..57015248 --- /dev/null +++ b/autoload/ale/handlers/djlint.vim @@ -0,0 +1,64 @@ +" Author: Vivian De Smedt , Adrian Vollmer +" Description: Adds support for djlint +" +function! ale#handlers#djlint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'html_djlint_executable') +endfunction + +function! ale#handlers#djlint#GetCommand(buffer) abort + let l:executable = ale#handlers#djlint#GetExecutable(a:buffer) + + let l:options = ale#Var(a:buffer, 'html_djlint_options') + + let l:profile = '' + let l:filetypes = split(getbufvar(a:buffer, '&filetype'), '\.') + + " Append the --profile flag depending on the current filetype (unless it's + " already set in g:html_djlint_options). + if match(l:options, '--profile') == -1 + let l:djlint_profiles = { + \ 'html': 'html', + \ 'htmldjango': 'django', + \ 'jinja': 'jinja', + \ 'nunjucks': 'nunjucks', + \ 'handlebars': 'handlebars', + \ 'gohtmltmpl': 'golang', + \ 'htmlangular': 'angular', + \} + + for l:filetype in l:filetypes + if has_key(l:djlint_profiles, l:filetype) + let l:profile = l:djlint_profiles[l:filetype] + break + endif + endfor + endif + + if !empty(l:profile) + let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--profile ' . l:profile + endif + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') . ' %s' +endfunction + +function! ale#handlers#djlint#Handle(buffer, lines) abort + let l:output = [] + let l:pattern = '\v^([A-Z]\d+) (\d+):(\d+) (.*)$' + let l:i = 0 + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:i += 1 + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'vcol': 1, + \ 'text': l:match[4], + \ 'code': l:match[1], + \ 'type': 'W', + \} + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/elixir.vim b/autoload/ale/handlers/elixir.vim new file mode 100644 index 00000000..2fddf8e7 --- /dev/null +++ b/autoload/ale/handlers/elixir.vim @@ -0,0 +1,28 @@ +" Author: Matteo Centenaro (bugant) - https://github.com/bugant +" Author: Jon Parise +" Description: Functions for working with Elixir projects + +" Find the root directory for an elixir project that uses mix. +function! ale#handlers#elixir#FindMixProjectRoot(buffer) abort + let l:mix_file = ale#path#FindNearestFile(a:buffer, 'mix.exs') + + if !empty(l:mix_file) + return fnamemodify(l:mix_file, ':p:h') + endif + + return '.' +endfunction + +" Similar to ale#handlers#elixir#FindMixProjectRoot but also continue the +" search upward for a potential umbrella project root. If an umbrella root +" does not exist, the initial project root will be returned. +function! ale#handlers#elixir#FindMixUmbrellaRoot(buffer) abort + let l:app_root = ale#handlers#elixir#FindMixProjectRoot(a:buffer) + let l:umbrella_root = fnamemodify(l:app_root, ':h:h') + + if filereadable(l:umbrella_root . '/mix.exs') + return l:umbrella_root + endif + + return l:app_root +endfunction diff --git a/autoload/ale/handlers/embertemplatelint.vim b/autoload/ale/handlers/embertemplatelint.vim new file mode 100644 index 00000000..d2e83400 --- /dev/null +++ b/autoload/ale/handlers/embertemplatelint.vim @@ -0,0 +1,66 @@ +" Author: Adrian Zalewski +" Description: Ember-template-lint for checking Handlebars files + +function! ale#handlers#embertemplatelint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'handlebars_embertemplatelint', [ + \ 'node_modules/.bin/ember-template-lint', + \]) +endfunction + +function! ale#handlers#embertemplatelint#GetCommand(buffer, version) abort + if ale#semver#GTE(a:version, [4, 0, 0]) + " --json was removed in favor of --format=json in ember-template-lint@4.0.0 + return '%e --format=json --filename %s' + endif + + return '%e --json --filename %s' +endfunction + +function! ale#handlers#embertemplatelint#GetCommandWithVersionCheck(buffer) abort + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ ale#handlers#embertemplatelint#GetExecutable(a:buffer), + \ '%e --version', + \ function('ale#handlers#embertemplatelint#GetCommand'), + \) +endfunction + +function! ale#handlers#embertemplatelint#Handle(buffer, lines) abort + let l:output = [] + let l:json = ale#util#FuzzyJSONDecode(a:lines, {}) + + for l:error in get(values(l:json), 0, []) + if has_key(l:error, 'fatal') + call add(l:output, { + \ 'lnum': get(l:error, 'line', 1), + \ 'col': get(l:error, 'column', 1), + \ 'text': l:error.message, + \ 'type': l:error.severity == 1 ? 'W' : 'E', + \}) + else + call add(l:output, { + \ 'lnum': l:error.line, + \ 'col': l:error.column, + \ 'text': l:error.rule . ': ' . l:error.message, + \ 'type': l:error.severity == 1 ? 'W' : 'E', + \}) + endif + endfor + + return l:output +endfunction + +function! ale#handlers#embertemplatelint#DefineLinter(filetype) abort + call ale#Set('handlebars_embertemplatelint_executable', 'ember-template-lint') + call ale#Set('handlebars_embertemplatelint_use_global', get(g:, 'ale_use_global_executables', 0)) + + call ale#linter#Define(a:filetype, { + \ 'name': 'embertemplatelint', + \ 'aliases': ['ember-template-lint'], + \ 'executable': function('ale#handlers#embertemplatelint#GetExecutable'), + \ 'command': function('ale#handlers#embertemplatelint#GetCommandWithVersionCheck'), + \ 'callback': 'ale#handlers#embertemplatelint#Handle', + \}) +endfunction + + diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim new file mode 100644 index 00000000..eea06f51 --- /dev/null +++ b/autoload/ale/handlers/eslint.vim @@ -0,0 +1,260 @@ +" Author: w0rp +" Description: Functions for working with eslint, for checking or fixing files. + +let s:executables = [ +\ '.yarn/sdks/eslint/bin/eslint.js', +\ 'node_modules/.bin/eslint_d', +\ 'node_modules/eslint/bin/eslint.js', +\ 'node_modules/.bin/eslint', +\] +let s:sep = has('win32') ? '\' : '/' + +call ale#Set('javascript_eslint_options', '') +call ale#Set('javascript_eslint_executable', 'eslint') +call ale#Set('javascript_eslint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_eslint_suppress_eslintignore', 0) +call ale#Set('javascript_eslint_suppress_missing_config', 0) + +function! ale#handlers#eslint#FindConfig(buffer) abort + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + for l:basename in [ + \ 'eslint.config.js', + \ 'eslint.config.mjs', + \ 'eslint.config.cjs', + \ '.eslintrc.js', + \ '.eslintrc.cjs', + \ '.eslintrc.yaml', + \ '.eslintrc.yml', + \ '.eslintrc.json', + \ '.eslintrc', + \] + let l:config = ale#path#Simplify(join([l:path, l:basename], s:sep)) + + if filereadable(l:config) + return l:config + endif + endfor + endfor + + return ale#path#FindNearestFile(a:buffer, 'package.json') +endfunction + +function! ale#handlers#eslint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'javascript_eslint', s:executables) +endfunction + +" Given a buffer, return an appropriate working directory for ESLint. +function! ale#handlers#eslint#GetCwd(buffer) abort + return ale#path#Dirname(ale#handlers#eslint#FindConfig(a:buffer)) +endfunction + +function! ale#handlers#eslint#GetCommand(buffer) abort + let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) + + let l:options = ale#Var(a:buffer, 'javascript_eslint_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -f json --stdin --stdin-filename %s' +endfunction + +function! s:AddHintsForTypeScriptParsingErrors(output) abort + for l:item in a:output + let l:item.text = substitute( + \ l:item.text, + \ '^\(Parsing error\)', + \ '\1 (You may need configure typescript-eslint-parser)', + \ '', + \) + endfor +endfunction + +function! s:CheckForBadConfig(buffer, lines) abort + let l:config_error_pattern = '\v^ESLint couldn''t find a configuration file' + \ . '|^Cannot read config file' + \ . '|^.*Configuration for rule .* is invalid' + \ . '|^ImportDeclaration should appear' + + " Look for a message in the first few lines which indicates that + " a configuration file couldn't be found. + for l:line in a:lines[:10] + let l:match = matchlist(l:line, l:config_error_pattern) + + if len(l:match) > 0 + " Don't show the missing config error if we've disabled it. + if ale#Var(a:buffer, 'javascript_eslint_suppress_missing_config') + \&& l:match[0] is# 'ESLint couldn''t find a configuration file' + return 0 + endif + + return 1 + endif + endfor + + return 0 +endfunction + +function! s:parseJSON(buffer, lines) abort + let l:parsed = [] + + for l:line in a:lines + try + let l:parsed = extend(l:parsed, json_decode(l:line)) + catch + endtry + endfor + + if type(l:parsed) != v:t_list || empty(l:parsed) + return [] + endif + + let l:errors = l:parsed[0]['messages'] + + if empty(l:errors) + return [] + endif + + let l:output = [] + + for l:error in l:errors + let l:obj = ({ + \ 'lnum': get(l:error, 'line', 0), + \ 'text': get(l:error, 'message', ''), + \ 'type': 'E', + \}) + + if get(l:error, 'severity', 0) is# 1 + let l:obj.type = 'W' + endif + + if has_key(l:error, 'ruleId') + let l:code = l:error['ruleId'] + + " Sometimes ESLint returns null here + if !empty(l:code) + let l:obj.code = l:code + endif + endif + + if has_key(l:error, 'column') + let l:obj.col = l:error['column'] + endif + + if has_key(l:error, 'endColumn') + let l:obj.end_col = l:error['endColumn'] - 1 + endif + + if has_key(l:error, 'endLine') + let l:obj.end_lnum = l:error['endLine'] + endif + + call add(l:output, l:obj) + endfor + + return l:output +endfunction + +let s:col_end_patterns = [ +\ '\vParsing error: Unexpected token (.+) ?', +\ '\v''(.+)'' is not defined.', +\ '\v%(Unexpected|Redundant use of) [''`](.+)[''`]', +\ '\vUnexpected (console) statement', +\] + +function! s:parseLines(buffer, lines) abort + " Matches patterns line the following: + " + " /path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle] + " /path/to/some-filename.js:56:41: Missing semicolon. [Error/semi] + let l:pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)\]$' + " This second pattern matches lines like the following: + " + " /path/to/some-filename.js:13:3: Parsing error: Unexpected token + let l:parsing_pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, [l:pattern, l:parsing_pattern]) + let l:text = l:match[3] + + let l:obj = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:text, + \ 'type': 'E', + \} + + " Take the error type from the output if available. + let l:split_code = split(l:match[4], '/') + + if get(l:split_code, 0, '') is# 'Warning' + let l:obj.type = 'W' + endif + + " The code can be something like 'Error/foo/bar', or just 'Error' + if !empty(get(l:split_code, 1)) + let l:obj.code = join(l:split_code[1:], '/') + endif + + for l:col_match in ale#util#GetMatches(l:text, s:col_end_patterns) + let l:obj.end_col = l:obj.col + len(l:col_match[1]) - 1 + endfor + + call add(l:output, l:obj) + endfor + + return l:output +endfunction + +function! s:FilterResult(buffer, obj) abort + if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore') + if a:obj.text =~# '^File ignored' + return 0 + endif + endif + + if has_key(a:obj, 'code') && a:obj.code is# 'no-trailing-spaces' + \&& !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + return 0 + endif + + return 1 +endfunction + +function! s:HandleESLintOutput(buffer, lines, type) abort + if s:CheckForBadConfig(a:buffer, a:lines) + return [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(a:lines, "\n"), + \}] + endif + + if a:lines == ['Could not connect'] + return [{ + \ 'lnum': 1, + \ 'text': 'Could not connect to eslint_d. Try updating eslint_d or killing it.', + \}] + endif + + if a:type is# 'json' + let l:output = s:parseJSON(a:buffer, a:lines) + else + let l:output = s:parseLines(a:buffer, a:lines) + endif + + call filter(l:output, {idx, obj -> s:FilterResult(a:buffer, obj)}) + + if expand('#' . a:buffer . ':t') =~? '\.tsx\?$' + call s:AddHintsForTypeScriptParsingErrors(l:output) + endif + + return l:output +endfunction + +function! ale#handlers#eslint#HandleJSON(buffer, lines) abort + return s:HandleESLintOutput(a:buffer, a:lines, 'json') +endfunction + +function! ale#handlers#eslint#Handle(buffer, lines) abort + return s:HandleESLintOutput(a:buffer, a:lines, 'lines') +endfunction diff --git a/autoload/ale/handlers/fecs.vim b/autoload/ale/handlers/fecs.vim new file mode 100644 index 00000000..064b927e --- /dev/null +++ b/autoload/ale/handlers/fecs.vim @@ -0,0 +1,52 @@ +" Author: harttle +" 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#path#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 new file mode 100644 index 00000000..b7d2bec3 --- /dev/null +++ b/autoload/ale/handlers/flawfinder.vim @@ -0,0 +1,48 @@ +scriptencoding utf-8 +" Author: Christian Gibbons +" Description: This file defines a handler function that should work for the +" flawfinder format with the -CDQS flags. + +" Swiped this function from the GCC handler. Not sure if needed, but doesn't +" hurt to have it. +function! s:RemoveUnicodeQuotes(text) abort + let l:text = a:text + let l:text = substitute(l:text, '[`´‘’]', '''', 'g') + let l:text = substitute(l:text, '\v\\u2018([^\\]+)\\u2019', '''\1''', 'g') + let l:text = substitute(l:text, '[“”]', '"', 'g') + + return l:text +endfunction + +function! ale#handlers#flawfinder#HandleFlawfinderFormat(buffer, lines) abort + " Look for lines like the following. + " + " :12:4: [2] (buffer) char:Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. + " :31:4: [1] (buffer) strncpy:Easily used incorrectly; doesn't always \0-terminate or check for invalid pointers [MS-banned] (CWE-120). + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ( \[[0-5]\] [^:]+):(.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " Use severity level to determine if it should be considered a warning + " or error. + let l:severity = str2nr(matchstr(split(l:match[4])[0], '[0-5]')) + + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'type': (l:severity < ale#Var(a:buffer, 'c_flawfinder_error_severity')) + \ ? 'W' : 'E', + \ 'text': s:RemoveUnicodeQuotes(join(split(l:match[4])[1:]) . ': ' . l:match[5]), + \} + + " If the filename is something like , or -, then + " this is an error for the file we checked. + if l:match[1] isnot# '-' && l:match[1][0] isnot# '<' + let l:item['filename'] = l:match[1] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/gawk.vim b/autoload/ale/handlers/gawk.vim new file mode 100644 index 00000000..50bc4c45 --- /dev/null +++ b/autoload/ale/handlers/gawk.vim @@ -0,0 +1,27 @@ +" Author: Anthony DeDominic +" Description: Handle output from gawk's --lint option + +function! ale#handlers#gawk#HandleGawkFormat(buffer, lines) abort + " Look for lines like the following: + " gawk: /tmp/v0fddXz/1/something.awk:1: ^ invalid char ''' in expression + let l:pattern = '^.\{-}:\(\d\+\):\s\+\(warning:\|\^\)\s*\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:ecode = 'E' + + if l:match[2] is? 'warning:' + let l:ecode = 'W' + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': 0, + \ 'text': l:match[3], + \ 'code': 0, + \ 'type': l:ecode, + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/gcc.vim b/autoload/ale/handlers/gcc.vim new file mode 100644 index 00000000..0b37c98a --- /dev/null +++ b/autoload/ale/handlers/gcc.vim @@ -0,0 +1,176 @@ +scriptencoding utf-8 +" Author: w0rp +" Description: This file defines a handler function which ought to work for +" any program which outputs errors in the format that GCC uses. + +let s:pragma_error = '#pragma once in main file' + +" Look for lines like the following. +" +" :8:5: warning: conversion lacks type at end of format [-Wformat=] +" :10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’) +" -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004] +let s:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+)?:?(\d+)?:? ([^:]+): (.+)$' +let s:inline_pattern = '\v inlined from .* at \:(\d+):(\d+):$' + +function! s:IsHeaderFile(filename) abort + return a:filename =~? '\v\.(h|hpp)$' +endfunction + +function! s:RemoveUnicodeQuotes(text) abort + let l:text = a:text + let l:text = substitute(l:text, '[`´‘’]', '''', 'g') + let l:text = substitute(l:text, '\v\\u2018([^\\]+)\\u2019', '''\1''', 'g') + let l:text = substitute(l:text, '[“”]', '"', 'g') + + return l:text +endfunction + +function! s:ParseInlinedFunctionProblems(buffer, lines) abort + let l:output = [] + let l:pos_match = [] + + for l:line in a:lines + let l:match = matchlist(l:line, s:pattern) + + if !empty(l:match) && !empty(l:pos_match) + call add(l:output, { + \ 'lnum': str2nr(l:pos_match[1]), + \ 'col': str2nr(l:pos_match[2]), + \ 'type': (l:match[4] is# 'error' || l:match[4] is# 'fatal error') ? 'E' : 'W', + \ 'text': s:RemoveUnicodeQuotes(l:match[5]), + \}) + endif + + let l:pos_match = matchlist(l:line, s:inline_pattern) + endfor + + return l:output +endfunction + +" Report problems inside of header files just for gcc and clang +function! s:ParseProblemsInHeaders(buffer, lines) abort + let l:output = [] + let l:include_item = {} + + for l:line in a:lines[: -2] + let l:include_match = matchlist(l:line, '\v^In file included from') + + if !empty(l:include_item) + let l:pattern_match = matchlist(l:line, s:pattern) + + if !empty(l:pattern_match) && l:pattern_match[1] is# '' + if has_key(l:include_item, 'lnum') + call add(l:output, l:include_item) + endif + + let l:include_item = {} + + continue + endif + + let l:include_item.detail .= "\n" . l:line + endif + + if !empty(l:include_match) + if empty(l:include_item) + let l:include_item = { + \ 'text': 'Error found in header. See :ALEDetail', + \ 'detail': l:line, + \} + endif + endif + + if !empty(l:include_item) + let l:stdin_match = matchlist(l:line, '\vfrom \:(\d+):(\d*):?$') + + if !empty(l:stdin_match) + let l:include_item.lnum = str2nr(l:stdin_match[1]) + + if str2nr(l:stdin_match[2]) + let l:include_item.col = str2nr(l:stdin_match[2]) + endif + endif + endif + endfor + + if !empty(l:include_item) && has_key(l:include_item, 'lnum') + call add(l:output, l:include_item) + endif + + return l:output +endfunction + +function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, s:pattern) + " Filter out the pragma errors + if s:IsHeaderFile(bufname(bufnr(''))) + \&& l:match[5][:len(s:pragma_error) - 1] is# s:pragma_error + continue + endif + + " If the 'error type' is a note, make it detail related to + " the previous error parsed in output + if l:match[4] is# 'note' + if !empty(l:output) + if !has_key(l:output[-1], 'detail') + let l:output[-1].detail = l:output[-1].text + + " handle macro expansion errors/notes + if l:match[5] =~? '^in expansion of macro ‘\w*\w’$' + " if the macro expansion is in the file we're in, add + " the lnum and col keys to the previous error + if l:match[1] is# '' + \ && !has_key(l:output[-1], 'col') + let l:output[-1].lnum = str2nr(l:match[2]) + let l:output[-1].col = str2nr(l:match[3]) + else + " the error is not in the current file, and since + " macro expansion errors don't show the full path to + " the error from the current file, we have to just + " give out a generic error message + let l:output[-1].text = 'Error found in macro expansion. See :ALEDetail' + endif + endif + endif + + let l:output[-1].detail = l:output[-1].detail . "\n" + \ . s:RemoveUnicodeQuotes(l:match[0]) + endif + + continue + endif + + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'type': (l:match[4] is# 'error' || l:match[4] is# 'fatal error') ? 'E' : 'W', + \ 'text': s:RemoveUnicodeQuotes(l:match[5]), + \} + + if !empty(l:match[3]) + let l:item.col = str2nr(l:match[3]) + endif + + " If the filename is something like , or -, then + " this is an error for the file we checked. + if l:match[1] isnot# '-' && l:match[1][0] isnot# '<' + let l:item['filename'] = l:match[1] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +" Handle problems with the GCC format, but report problems inside of headers. +function! ale#handlers#gcc#HandleGCCFormatWithIncludes(buffer, lines) abort + let l:output = ale#handlers#gcc#HandleGCCFormat(a:buffer, a:lines) + + call extend(l:output, s:ParseInlinedFunctionProblems(a:buffer, a:lines)) + call extend(l:output, s:ParseProblemsInHeaders(a:buffer, a:lines)) + + return l:output +endfunction diff --git a/autoload/ale/handlers/go.vim b/autoload/ale/handlers/go.vim new file mode 100644 index 00000000..c969669d --- /dev/null +++ b/autoload/ale/handlers/go.vim @@ -0,0 +1,29 @@ +" Author: neersighted +" Description: go vet for Go files +" +" Author: John Eikenberry +" Description: updated to work with go1.10 +" +" Author: Ben Paxton +" Description: moved to generic Golang file from govet +" +" Author: mostfunkyduck +" Description: updated to work with go 1.14 + +function! ale#handlers#go#Handler(buffer, lines) abort + let l:pattern = '\v^%(vet: )?([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? ?(.+)$' + let l:output = [] + let l:dir = expand('#' . a:buffer . ':p:h') + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/haskell.vim b/autoload/ale/handlers/haskell.vim new file mode 100644 index 00000000..70a3a7ea --- /dev/null +++ b/autoload/ale/handlers/haskell.vim @@ -0,0 +1,119 @@ +" Author: w0rp +" Description: Error handling for the format GHC outputs. +" +function! ale#handlers#haskell#GetStackExecutable(bufnr) abort + if ale#path#FindNearestFile(a:bufnr, 'stack.yaml') isnot# '' + return 'stack' + endif + + " if there is no stack.yaml file, we don't use stack even if it exists, + " so we return '', because executable('') apparently always fails + return '' +endfunction + +" Remember the directory used for temporary files for Vim. +let s:temp_dir = fnamemodify(ale#util#Tempname(), ':h') +" Build part of a regular expression for matching ALE temporary filenames. +let s:temp_regex_prefix = +\ '\M' +\ . substitute(s:temp_dir, '\\', '\\\\', 'g') +\ . '\.\{-}' + +function! s:PanicOutput(lines) abort + return [{ + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'ghc panic!', + \ 'type': 'E', + \ 'detail' : join(a:lines, "\n"), + \}] +endfunction + +function! ale#handlers#haskell#HandleGHCFormat(buffer, lines) abort + " Look for lines like the following. + " + "Appoint/Lib.hs:8:1: warning: + "Appoint/Lib.hs:8:1: + let l:basename = expand('#' . a:buffer . ':t') + " Build a complete regular expression for replacing temporary filenames + " in Haskell error messages with the basename for this file. + let l:temp_filename_regex = s:temp_regex_prefix . l:basename + + let l:pattern = '\v^\s*([a-zA-Z]?:?[^:]+):(\d+):(\d+):(.*)?$' + let l:output = [] + + let l:corrected_lines = [] + + " If ghc panic error, put the whole message in details and exit. + let l:panic_position = match(a:lines,'ghc: panic!') + let l:panic_end = match(a:lines,'Please report this as a GHC bug:') + + if l:panic_position >= 0 + return s:PanicOutput(a:lines[l:panic_position : l:panic_end]) + endif + + " Group the lines into smaller lists. + for l:line in a:lines + if len(matchlist(l:line, l:pattern)) > 0 + call add(l:corrected_lines, [l:line]) + elseif l:line is# '' + call add(l:corrected_lines, [l:line]) + elseif len(l:corrected_lines) > 0 + call add(l:corrected_lines[-1], l:line) + endif + endfor + + for l:line_list in l:corrected_lines + " Join the smaller lists into one large line to parse. + let l:line = l:line_list[0] + + for l:extra_line in l:line_list[1:] + let l:line .= substitute(l:extra_line, '\v^\s+', ' ', '') + endfor + + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + if !ale#path#IsBufferPath(a:buffer, l:match[1]) + continue + endif + + let l:errors = matchlist(l:match[4], '\v([wW]arning|[eE]rror): ?(.*)') + + if len(l:errors) > 0 + let l:ghc_type = l:errors[1] + let l:text = l:errors[2] + else + let l:ghc_type = '' + let l:text = l:match[4][:0] is# ' ' ? l:match[4][1:] : l:match[4] + endif + + if l:ghc_type is? 'Warning' + let l:type = 'W' + else + let l:type = 'E' + endif + + " Replace temporary filenames in problem messages with the basename + let l:text = substitute(l:text, l:temp_filename_regex, l:basename, 'g') + + let l:item = { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:text, + \ 'type': l:type, + \} + + " Include extra lines as details if they are there. + if len(l:line_list) > 1 + let l:item.detail = join(l:line_list[1:], "\n") + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/haskell_stack.vim b/autoload/ale/handlers/haskell_stack.vim new file mode 100644 index 00000000..108301a9 --- /dev/null +++ b/autoload/ale/handlers/haskell_stack.vim @@ -0,0 +1,7 @@ +function! ale#handlers#haskell_stack#EscapeExecutable(executable, stack_exec) abort + let l:exec_args = a:executable =~? 'stack$' + \ ? ' exec ' . ale#Escape(a:stack_exec) . ' --' + \ : '' + + return ale#Escape(a:executable) . l:exec_args +endfunction diff --git a/autoload/ale/handlers/hdl_checker.vim b/autoload/ale/handlers/hdl_checker.vim new file mode 100644 index 00000000..d45f86e1 --- /dev/null +++ b/autoload/ale/handlers/hdl_checker.vim @@ -0,0 +1,73 @@ +" Author: suoto +" Description: Adds support for HDL Code Checker, which wraps vcom/vlog, ghdl +" or xvhdl. More info on https://github.com/suoto/hdl_checker + +call ale#Set('hdl_checker_executable', 'hdl_checker') +call ale#Set('hdl_checker_config_file', has('unix') ? '.hdl_checker.config' : '_hdl_checker.config') +call ale#Set('hdl_checker_options', '') + +" Use this as a function so we can mock it on testing. Need to do this because +" test files are inside /testplugin (which refers to the ale repo), which will +" always have a .git folder +function! ale#handlers#hdl_checker#IsDotGit(path) abort + return ! empty(a:path) && isdirectory(a:path) +endfunction + +" Should return (in order of preference) +" 1. Nearest config file +" 2. Nearest .git directory +" 3. The current path +function! ale#handlers#hdl_checker#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile( + \ a:buffer, + \ ale#Var(a:buffer, 'hdl_checker_config_file')) + + if !empty(l:project_root) + return fnamemodify(l:project_root, ':h') + endif + + " Search for .git to use as root + let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git') + + if ale#handlers#hdl_checker#IsDotGit(l:project_root) + return fnamemodify(l:project_root, ':h:h') + endif + + return '' +endfunction + +function! ale#handlers#hdl_checker#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'hdl_checker_executable') +endfunction + +function! ale#handlers#hdl_checker#GetCommand(buffer) abort + let l:command = ale#Escape(ale#handlers#hdl_checker#GetExecutable(a:buffer)) . ' --lsp' + + " Add extra parameters only if config has been set + let l:options = ale#Var(a:buffer, 'hdl_checker_options') + + if ! empty(l:options) + let l:command = l:command . ' ' . l:options + endif + + return l:command +endfunction + +" To allow testing +function! ale#handlers#hdl_checker#GetInitOptions(buffer) abort + return {'project_file': ale#Var(a:buffer, 'hdl_checker_config_file')} +endfunction + +" Define the hdl_checker linter for a given filetype. +function! ale#handlers#hdl_checker#DefineLinter(filetype) abort + call ale#linter#Define(a:filetype, { + \ 'name': 'hdl_checker', + \ 'aliases': ['hdl-checker'], + \ 'lsp': 'stdio', + \ 'language': a:filetype, + \ 'executable': function('ale#handlers#hdl_checker#GetExecutable'), + \ 'command': function('ale#handlers#hdl_checker#GetCommand'), + \ 'project_root': function('ale#handlers#hdl_checker#GetProjectRoot'), + \ 'initialization_options': function('ale#handlers#hdl_checker#GetInitOptions'), + \ }) +endfunction diff --git a/autoload/ale/handlers/hlint.vim b/autoload/ale/handlers/hlint.vim new file mode 100644 index 00000000..b9a8c322 --- /dev/null +++ b/autoload/ale/handlers/hlint.vim @@ -0,0 +1,8 @@ +call ale#Set('haskell_hlint_executable', 'hlint') +call ale#Set('haskell_hlint_options', get(g:, 'hlint_options', '')) + +function! ale#handlers#hlint#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_hlint_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hlint') +endfunction diff --git a/autoload/ale/handlers/inko.vim b/autoload/ale/handlers/inko.vim new file mode 100644 index 00000000..73f06871 --- /dev/null +++ b/autoload/ale/handlers/inko.vim @@ -0,0 +1,37 @@ +" Author: Yorick Peterse +" Description: output handlers for the Inko JSON format + +function! ale#handlers#inko#GetType(severity) abort + if a:severity is? 'warning' + return 'W' + endif + + return 'E' +endfunction + +function! ale#handlers#inko#Handle(buffer, lines) abort + try + let l:errors = json_decode(join(a:lines, '')) + catch + return [] + endtry + + if empty(l:errors) + return [] + endif + + let l:output = [] + let l:dir = expand('#' . a:buffer . ':p:h') + + for l:error in l:errors + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:error['file']), + \ 'lnum': l:error['line'], + \ 'col': l:error['column'], + \ 'text': l:error['message'], + \ 'type': ale#handlers#inko#GetType(l:error['level']), + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/ktlint.vim b/autoload/ale/handlers/ktlint.vim new file mode 100644 index 00000000..77e7ab66 --- /dev/null +++ b/autoload/ale/handlers/ktlint.vim @@ -0,0 +1,45 @@ +" Author: Michael Phillips +" 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) + \ . ' --stdin' +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..73974ceb --- /dev/null +++ b/autoload/ale/handlers/languagetool.vim @@ -0,0 +1,77 @@ +" Author: Vincent (wahrwolf [at] wolfpit.net) +" Description: languagetool for markdown files +" +call ale#Set('languagetool_executable', 'languagetool') +call ale#Set('languagetool_options', '--autoDetect') + +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) + let l:options = ale#Var(a:buffer, 'languagetool_options') + + return ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) . ' %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 new file mode 100644 index 00000000..6c273bd0 --- /dev/null +++ b/autoload/ale/handlers/markdownlint.vim @@ -0,0 +1,24 @@ +" Author: Ty-Lucas Kelley +" Description: Adds support for markdownlint + +function! ale#handlers#markdownlint#Handle(buffer, lines) abort + let l:pattern=': \?\(\d\+\)\(:\(\d\+\)\?\)\? \(MD\d\{3}/[A-Za-z0-9-/]\+\) \(.*\)$' + let l:output=[] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:result = ({ + \ 'lnum': l:match[1] + 0, + \ 'code': l:match[4], + \ 'text': l:match[5], + \ 'type': 'W', + \}) + + if len(l:match[3]) > 0 + let l:result.col = (l:match[3] + 0) + endif + + call add(l:output, l:result) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/naga.vim b/autoload/ale/handlers/naga.vim new file mode 100644 index 00000000..6480aba6 --- /dev/null +++ b/autoload/ale/handlers/naga.vim @@ -0,0 +1,30 @@ +" Author: rhysd +" Description: Handle errors for naga-cli. + +function! ale#handlers#naga#Handle(buffer, lines) abort + let l:errors = [] + let l:current_error = v:null + + for l:line in a:lines + if l:line =~# '^error: ' + let l:text = l:line[7:] + let l:current_error = { 'text': l:text, 'type': 'E' } + continue + endif + + if l:current_error isnot v:null + let l:matches = matchlist(l:line, '\v:(\d+):(\d+)$') + + if !empty(l:matches) + let l:current_error.lnum = str2nr(l:matches[1]) + let l:current_error.col = str2nr(l:matches[2]) + call add(l:errors, l:current_error) + let l:current_error = v:null + continue + endif + endif + endfor + + return l:errors +endfunction + diff --git a/autoload/ale/handlers/ocamllsp.vim b/autoload/ale/handlers/ocamllsp.vim new file mode 100644 index 00000000..2738ea2b --- /dev/null +++ b/autoload/ale/handlers/ocamllsp.vim @@ -0,0 +1,30 @@ +" Author: Risto Stevcev +" Description: Handlers for the official OCaml language server + +let s:language_id_of_filetype = { +\ 'menhir': 'ocaml.menhir', +\ 'ocaml': 'ocaml', +\ 'ocamlinterface': 'ocaml.interface', +\ 'ocamllex': 'ocaml.lex' +\} + +function! ale#handlers#ocamllsp#GetExecutable(buffer) abort + return 'ocamllsp' +endfunction + +function! ale#handlers#ocamllsp#GetCommand(buffer) abort + let l:executable = ale#handlers#ocamllsp#GetExecutable(a:buffer) + let l:ocaml_ocamllsp_use_opam = ale#Var(a:buffer, 'ocaml_ocamllsp_use_opam') + + return l:ocaml_ocamllsp_use_opam ? 'opam config exec -- ' . l:executable : l:executable +endfunction + +function! ale#handlers#ocamllsp#GetLanguage(buffer) abort + return s:language_id_of_filetype[getbufvar(a:buffer, '&filetype')] +endfunction + +function! ale#handlers#ocamllsp#GetProjectRoot(buffer) abort + let l:dune_project_file = ale#path#FindNearestFile(a:buffer, 'dune-project') + + return !empty(l:dune_project_file) ? fnamemodify(l:dune_project_file, ':h') : '' +endfunction diff --git a/autoload/ale/handlers/ols.vim b/autoload/ale/handlers/ols.vim new file mode 100644 index 00000000..c292c6d9 --- /dev/null +++ b/autoload/ale/handlers/ols.vim @@ -0,0 +1,26 @@ +" Author: Michael Jungo +" Description: Handlers for the OCaml language server + +function! ale#handlers#ols#GetExecutable(buffer) abort + let l:ols_setting = ale#handlers#ols#GetLanguage(a:buffer) . '_ols' + + return ale#path#FindExecutable(a:buffer, l:ols_setting, [ + \ 'node_modules/.bin/ocaml-language-server', + \]) +endfunction + +function! ale#handlers#ols#GetCommand(buffer) abort + let l:executable = ale#handlers#ols#GetExecutable(a:buffer) + + return ale#node#Executable(a:buffer, l:executable) . ' --stdio' +endfunction + +function! ale#handlers#ols#GetLanguage(buffer) abort + return getbufvar(a:buffer, '&filetype') +endfunction + +function! ale#handlers#ols#GetProjectRoot(buffer) abort + let l:merlin_file = ale#path#FindNearestFile(a:buffer, '.merlin') + + return !empty(l:merlin_file) ? fnamemodify(l:merlin_file, ':h') : '' +endfunction diff --git a/autoload/ale/handlers/openscad.vim b/autoload/ale/handlers/openscad.vim new file mode 100644 index 00000000..33eee31c --- /dev/null +++ b/autoload/ale/handlers/openscad.vim @@ -0,0 +1,73 @@ +scriptencoding utf-8LE +" Description: This file defines a handler function for linting OpenSCAD files +" with SCA2D + +function! ale#handlers#openscad#SCA2D_callback(buffer, lines) abort + " Example output:: + " foo.scad:3:1: W2001: Variable `unused` overwritten within scope. + " foo.scad:1:1: F0001: Cannot read file due to syntax error: + " - No terminal matches '}' in the current parser context, at line 1 col 36 + let l:filename_re = '^\([^:]*\):' + let l:linenum_re = '\([0-9]*\):' + let l:colnum_re = '\([0-9]*\):' + let l:err_id = '\([IWEFU][0-9]\+\):' + let l:err_msg = '\(.*\)' + let l:pattern = filename_re . + \ linenum_re . + \ colnum_re . + \ ' ' . + \ err_id . + \ ' ' . + \ err_msg + + let l:result = [] + let l:idx = 0 + + for l:line in a:lines + let l:matches = matchlist(line, pattern) + + if len(matches) > 0 + " option: Info, Warning, Error, Fatal, Unknown + if index(['I', 'W'], matches[4][0]) >= 0 + let l:type = 'W' + else + let l:type = 'E' + endif + + let l:lnum = matches[2] + let l:col = matches[3] + let l:text = matches[5] + + " Better locations for some syntax errors + if matches[4][0] is# 'F' + let l:syntax_error_re = '^\(.*\), at line \([0-9]\+\) col \([0-9]\+\)$' + let l:next_line = a:lines[idx+1] + let l:syn_err_matches = matchlist(l:next_line, l:syntax_error_re) + + if len(syn_err_matches) > 0 + let l:text = l:text . l:syn_err_matches[1] + let l:lnum = l:syn_err_matches[2] + let l:col = l:syn_err_matches[3] + else + let l:text = l:next_line + endif + endif + + let l:element = { + \ 'lnum': str2nr(l:lnum), + \ 'col': str2nr(l:col), + \ 'text': l:text, + \ 'detail': l:matches[4] . ': ' . l:text, + \ 'filename': fnamemodify(matches[1], ':p'), + \ 'type': l:type + \ } + + call add(l:result, l:element) + endif + + let l:idx += 1 + endfor + + return result + +endfun diff --git a/autoload/ale/handlers/pony.vim b/autoload/ale/handlers/pony.vim new file mode 100644 index 00000000..ea84ac4b --- /dev/null +++ b/autoload/ale/handlers/pony.vim @@ -0,0 +1,33 @@ +scriptencoding utf-8 +" Description: This file defines a handler function which ought to work for +" any program which outputs errors in the format that ponyc uses. + +function! s:RemoveUnicodeQuotes(text) abort + let l:text = a:text + let l:text = substitute(l:text, '[`´‘’]', '''', 'g') + let l:text = substitute(l:text, '\v\\u2018([^\\]+)\\u2019', '''\1''', 'g') + let l:text = substitute(l:text, '[“”]', '"', 'g') + + return l:text +endfunction + +function! ale#handlers#pony#HandlePonycFormat(buffer, lines) abort + " Look for lines like the following. + " /home/code/pony/classes/Wombat.pony:22:30: can't lookup private fields from outside the type + let l:pattern = '\v^([^:]+):(\d+):(\d+)?:? (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'filename': l:match[1], + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'type': 'E', + \ 'text': s:RemoveUnicodeQuotes(l:match[4]), + \} + + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/redpen.vim b/autoload/ale/handlers/redpen.vim new file mode 100644 index 00000000..195057ca --- /dev/null +++ b/autoload/ale/handlers/redpen.vim @@ -0,0 +1,65 @@ +" Author: rhysd https://rhysd.github.io +" Description: Redpen, a proofreading tool (http://redpen.cc) + +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 = get(ale#util#FuzzyJSONDecode(a:lines, []), 0, {}) + let l:output = [] + + for l:err in get(l:res, 'errors', []) + let l:item = { + \ 'text': l:err.message, + \ 'type': 'W', + \ 'code': l:err.validator, + \} + + if has_key(l:err, 'startPosition') + let l:item.lnum = l:err.startPosition.lineNum + let l:item.col = l:err.startPosition.offset + 1 + + if has_key(l:err, 'endPosition') + let l:item.end_lnum = l:err.endPosition.lineNum + let l:item.end_col = l:err.endPosition.offset + endif + else + " Fallback to a whole sentence region when a region is not + " specified by the error. + let l:item.lnum = l:err.lineNum + let l:item.col = l:err.sentenceStartColumnNum + 1 + endif + + " Adjust column number for multibyte string + let l:line = getline(l:item.lnum) + + if l:line is# '' + let l:line = l:err.sentence + endif + + let l:line = split(l:line, '\zs') + + if l:item.col >= 2 + let l:col = 0 + + for l:strlen in map(l:line[0:(l:item.col - 2)], 'strlen(v:val)') + let l:col = l:col + l:strlen + endfor + + let l:item.col = l:col + 1 + endif + + if has_key(l:item, 'end_col') + let l:col = 0 + + for l:strlen in map(l:line[0:(l:item.end_col - 1)], 'strlen(v:val)') + let l:col = l:col + l:strlen + endfor + + let l:item.end_col = l:col + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/ruby.vim b/autoload/ale/handlers/ruby.vim new file mode 100644 index 00000000..7a1c5765 --- /dev/null +++ b/autoload/ale/handlers/ruby.vim @@ -0,0 +1,38 @@ +" Author: Brandon Roehl - https://github.com/BrandonRoehl, Matthias Guenther https://wikimatze.de +" +" Description: This file implements handlers specific to Ruby. + +function! s:HandleSyntaxError(buffer, lines) abort + " Matches patterns line the following: + " + " test.rb:3: warning: parentheses after method name is interpreted as an argument list, not a decomposed argument + " test.rb:8: syntax error, unexpected keyword_end, expecting end-of-input + let l:pattern = '\v^.+:(\d+): (warning: )?(.+)$' + let l:column = '\v^(\s+)\^$' + let l:output = [] + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + let l:match = matchlist(l:line, l:column) + + if len(l:match) != 0 + let l:output[len(l:output) - 1]['col'] = len(l:match[1]) + endif + else + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': 0, + \ 'text': l:match[2] . l:match[3], + \ 'type': empty(l:match[2]) ? 'E' : 'W', + \}) + endif + endfor + + return l:output +endfunction + +function! ale#handlers#ruby#HandleSyntaxErrors(buffer, lines) abort + return s:HandleSyntaxError(a:buffer, a:lines) +endfunction diff --git a/autoload/ale/handlers/rust.vim b/autoload/ale/handlers/rust.vim new file mode 100644 index 00000000..a7fac464 --- /dev/null +++ b/autoload/ale/handlers/rust.vim @@ -0,0 +1,78 @@ +" Author: Daniel Schemala , +" w0rp +" +" Description: This file implements handlers specific to Rust. + +if !exists('g:ale_rust_ignore_error_codes') + let g:ale_rust_ignore_error_codes = [] +endif + +if !exists('g:ale_rust_ignore_secondary_spans') + let g:ale_rust_ignore_secondary_spans = 0 +endif + +function! s:FindSpan(buffer, span) abort + if ale#path#IsBufferPath(a:buffer, a:span.file_name) || a:span.file_name is# '' + return a:span + endif + + " Search inside the expansion of an error, as the problem for this buffer + " could lie inside a nested object. + if !empty(get(a:span, 'expansion', v:null)) + return s:FindSpan(a:buffer, a:span.expansion.span) + endif + + return {} +endfunction + +function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort + let l:output = [] + + for l:errorline in a:lines + " ignore everything that is not JSON + if l:errorline !~# '^{' + continue + endif + + let l:error = json_decode(l:errorline) + + if has_key(l:error, 'message') && type(l:error.message) is v:t_dict + let l:error = l:error.message + endif + + if !has_key(l:error, 'code') + continue + endif + + if !empty(l:error.code) && index(g:ale_rust_ignore_error_codes, l:error.code.code) > -1 + continue + endif + + for l:root_span in l:error.spans + let l:span = s:FindSpan(a:buffer, l:root_span) + + if ale#Var(a:buffer, 'rust_ignore_secondary_spans') && !get(l:span, 'is_primary', 1) + continue + endif + + if !empty(l:span) + let l:output_line = { + \ 'lnum': l:span.line_start, + \ 'end_lnum': l:span.line_end, + \ 'col': l:span.column_start, + \ '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]), + \} + + if has_key(l:error, 'rendered') && !empty(l:error.rendered) + let l:output_line.detail = l:error.rendered + endif + + call add(l:output, l:output_line) + endif + endfor + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/scala.vim b/autoload/ale/handlers/scala.vim new file mode 100644 index 00000000..3fe7a12e --- /dev/null +++ b/autoload/ale/handlers/scala.vim @@ -0,0 +1,37 @@ +" Author: Nils Leuzinger - https://github.com/PawkyPenguin +" Description: Scala linting handlers for scalac-like compilers. + +function! ale#handlers#scala#HandleScalacLintFormat(buffer, lines) abort + " Matches patterns line the following: + " + " /var/folders/5q/20rgxx3x1s34g3m14n5bq0x80000gn/T/vv6pSsy/0:26: error: expected class or object definition + let l:pattern = '^.\+:\(\d\+\): \(\w\+\): \(.\+\)' + let l:output = [] + let l:ln = 0 + + for l:line in a:lines + let l:ln = l:ln + 1 + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + let l:text = l:match[3] + let l:type = l:match[2] is# 'error' ? 'E' : 'W' + let l:col = 0 + + if l:ln + 1 < len(a:lines) + let l:col = stridx(a:lines[l:ln + 1], '^') + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:col + 1, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim new file mode 100644 index 00000000..6ed9fea3 --- /dev/null +++ b/autoload/ale/handlers/sh.vim @@ -0,0 +1,37 @@ +" Author: w0rp + +function! ale#handlers#sh#GetShellType(buffer) abort + let l:shebang = get(getbufline(a:buffer, 1), 0, '') + + let l:command = '' + + " Take the shell executable from the shebang, if we can. + if l:shebang[:1] is# '#!' + " Remove options like -e, etc. + let l:command = substitute(l:shebang, ' --\?[a-zA-Z0-9]\+', '', 'g') + endif + + " With no shebang line, attempt to use Vim's buffer-local variables. + if l:command is# '' + if getbufvar(a:buffer, 'is_bash', 0) + let l:command = 'bash' + elseif getbufvar(a:buffer, 'is_sh', 0) + let l:command = 'sh' + elseif getbufvar(a:buffer, 'is_kornshell', 0) + let l:command = 'ksh' + endif + endif + + " If we couldn't find a shebang, try the filetype + if l:command is# '' + let l:command = &filetype + endif + + 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 + endfor + + return '' +endfunction diff --git a/autoload/ale/handlers/shellcheck.vim b/autoload/ale/handlers/shellcheck.vim new file mode 100644 index 00000000..002c4651 --- /dev/null +++ b/autoload/ale/handlers/shellcheck.vim @@ -0,0 +1,198 @@ +" Author: w0rp +" Description: This file adds support for using the shellcheck linter + +" Shellcheck supports shell directives to define the shell dialect for scripts +" that do not have a shebang for some reason. +" https://github.com/koalaman/shellcheck/wiki/Directive#shell +function! ale#handlers#shellcheck#GetShellcheckDialectDirective(buffer) abort + let l:linenr = 0 + let l:pattern = '\s\{-}#\s\{-}shellcheck\s\{-}shell=\(.*\)' + let l:possible_shell = ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh'] + + while l:linenr < min([50, line('$')]) + let l:linenr += 1 + let l:match = matchlist(getline(l:linenr), l:pattern) + + if len(l:match) > 1 && index(l:possible_shell, l:match[1]) >= 0 + return l:match[1] + endif + endwhile + + return '' +endfunction + +function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort + let l:shell_type = ale#handlers#shellcheck#GetShellcheckDialectDirective(a:buffer) + + if empty(l:shell_type) + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) + endif + + if !empty(l:shell_type) + " Use the dash dialect for /bin/ash, etc. + if l:shell_type is# 'ash' + return 'dash' + endif + + return l:shell_type + endif + + return '' +endfunction + +function! ale#handlers#shellcheck#GetCwd(buffer) abort + return ale#Var(a:buffer, 'sh_shellcheck_change_directory') ? '%s:h' : '' +endfunction + +function! ale#handlers#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#Var(a:buffer, 'sh_shellcheck_dialect') + let l:external_option = ale#semver#GTE(a:version, [0, 4, 0]) ? ' -x' : '' + let l:format = ale#semver#GTE(a:version, [0, 7, 0]) ? 'json1' : 'gcc' + + if l:dialect is# 'auto' + let l:dialect = ale#handlers#shellcheck#GetDialectArgument(a:buffer) + endif + + return '%e' + \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '') + \ . l:external_option + \ . ' -f ' . l:format . ' -' +endfunction + +function! s:HandleShellcheckJSON(buffer, lines) abort + try + let l:errors = json_decode(a:lines[0]) + catch + return [] + endtry + + if !has_key(l:errors, 'comments') + return [] + endif + + let l:output = [] + + for l:error in l:errors['comments'] + if l:error['level'] is# 'error' + let l:type = 'E' + elseif l:error['level'] is# 'info' + let l:type = 'I' + elseif l:error['level'] is# 'style' + let l:type = 'I' + else + let l:type = 'W' + endif + + let l:item = { + \ 'lnum': l:error['line'], + \ 'type': l:type, + \ 'text': l:error['message'], + \ 'code': 'SC' . l:error['code'], + \ 'detail': l:error['message'] . "\n\nFor more information:\n https://www.shellcheck.net/wiki/SC" . l:error['code'], + \} + + if has_key(l:error, 'column') + let l:item.col = l:error['column'] + endif + + if has_key(l:error, 'endColumn') + let l:item.end_col = l:error['endColumn'] - 1 + endif + + if has_key(l:error, 'endLine') + let l:item.end_lnum = l:error['endLine'] + endif + + + " If the filename is something like , or -, then + " this is an error for the file we checked. + if has_key(l:error, 'file') + if l:error['file'] isnot# '-' && l:error['file'][0] isnot# '<' + let l:item['filename'] = l:error['file'] + endif + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +function! s:HandleShellcheckGCC(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) + if l:match[4] is# 'error' + let l:type = 'E' + elseif l:match[4] is# 'note' + let l:type = 'I' + else + let l:type = 'W' + endif + + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'type': l:type, + \ 'text': l:match[5], + \ 'code': l:match[6], + \ 'detail': l:match[5] . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . l:match[6], + \} + + if !empty(l:match[3]) + let l:item.col = str2nr(l:match[3]) + endif + + " If the filename is something like , or -, then + " this is an error for the file we checked. + if l:match[1] isnot# '-' && l:match[1][0] isnot# '<' + let l:item['filename'] = l:match[1] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +function! ale#handlers#shellcheck#Handle(buffer, version, lines) abort + return ale#semver#GTE(a:version, [0, 7, 0]) + \ ? s:HandleShellcheckJSON(a:buffer, a:lines) + \ : s:HandleShellcheckGCC(a:buffer, a:lines) +endfunction + +function! ale#handlers#shellcheck#DefineLinter(filetype) abort + " This global variable can be set with a string of comma-separated error + " codes to exclude from shellcheck. For example: + " let g:ale_sh_shellcheck_exclusions = 'SC2002,SC2004' + call ale#Set('sh_shellcheck_exclusions', '') + call ale#Set('sh_shellcheck_executable', 'shellcheck') + call ale#Set('sh_shellcheck_dialect', 'auto') + call ale#Set('sh_shellcheck_options', '') + call ale#Set('sh_shellcheck_change_directory', 1) + + call ale#linter#Define(a:filetype, { + \ 'name': 'shellcheck', + \ 'executable': {buffer -> ale#Var(buffer, 'sh_shellcheck_executable')}, + \ 'cwd': function('ale#handlers#shellcheck#GetCwd'), + \ 'command': {buffer -> ale#semver#RunWithVersionCheck( + \ buffer, + \ ale#Var(buffer, 'sh_shellcheck_executable'), + \ '%e --version', + \ function('ale#handlers#shellcheck#GetCommand'), + \ )}, + \ 'callback': {buffer, lines -> ale#semver#RunWithVersionCheck( + \ buffer, + \ ale#Var(buffer, 'sh_shellcheck_executable'), + \ '%e --version', + \ {buffer, version -> ale#handlers#shellcheck#Handle( + \ buffer, + \ l:version, + \ lines)}, + \ )}, + \}) +endfunction diff --git a/autoload/ale/handlers/sml.vim b/autoload/ale/handlers/sml.vim new file mode 100644 index 00000000..403b25fa --- /dev/null +++ b/autoload/ale/handlers/sml.vim @@ -0,0 +1,102 @@ +" Author: Jake Zimmerman +" Description: Shared functions for SML linters + +" The glob to use for finding the .cm file. +" +" See :help ale-sml-smlnj for more information. +call ale#Set('sml_smlnj_cm_file', '*.cm') + +function! ale#handlers#sml#GetCmFile(buffer) abort + let l:pattern = ale#Var(a:buffer, 'sml_smlnj_cm_file') + let l:as_list = 1 + + let l:cmfile = '' + + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + let l:results = glob(l:path . '/' . l:pattern, 0, l:as_list) + + if len(l:results) > 0 + " If there is more than one CM file, we take the first one + " See :help ale-sml-smlnj for how to configure this. + let l:cmfile = l:results[0] + endif + endfor + + return l:cmfile +endfunction + +" Only one of smlnj or smlnj-cm can be enabled at a time. +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 + if a:source is# 'smlnj-file' + return 'sml' + elseif a:source is# 'smlnj-cm' + return '' + endif + else + " Found a CM file; only allow cm-file mode to be enabled + if a:source is# 'smlnj-file' + return '' + elseif a:source is# 'smlnj-cm' + return 'sml' + endif + endif +endfunction + +function! ale#handlers#sml#GetExecutableSmlnjCm(buffer) abort + return s:GetExecutable(a:buffer, 'smlnj-cm') +endfunction + +function! ale#handlers#sml#GetExecutableSmlnjFile(buffer) abort + return s:GetExecutable(a:buffer, 'smlnj-file') +endfunction + +function! ale#handlers#sml#Handle(buffer, lines) abort + " Try to match basic sml errors + " TODO(jez) We can get better errorfmt strings from Syntastic + let l:out = [] + let l:pattern = '^\(.*\)\:\([0-9\.]\+\)\ \(\w\+\)\:\ \(.*\)' + let l:pattern2 = '^\(.*\)\:\([0-9]\+\)\.\?\([0-9]\+\).* \(\(Warning\|Error\): .*\)' + + for l:line in a:lines + let l:match2 = matchlist(l:line, l:pattern2) + + if len(l:match2) != 0 + if l:match2[1] =~# 'stdIn$' + let l:loc = {'bufnr': a:buffer} + else + let l:loc = {'filename': l:match2[1]} + endif + + call add(l:out, extend(l:loc, { + \ 'lnum': l:match2[2] + 0, + \ 'col' : l:match2[3] - 1, + \ 'text': l:match2[4], + \ 'type': l:match2[4] =~# '^Warning' ? 'W' : 'E', + \})) + continue + endif + + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) != 0 + if l:match[1] =~# 'stdIn$' + let l:loc = {'bufnr': a:buffer} + else + let l:loc = {'filename': l:match[1]} + endif + + call add(l:out, extend(l:loc, { + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[3] . ': ' . l:match[4], + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \})) + continue + endif + endfor + + return l:out +endfunction + +" vim:ts=4:sts=4:sw=4 diff --git a/autoload/ale/handlers/spectral.vim b/autoload/ale/handlers/spectral.vim new file mode 100644 index 00000000..1eb4a5de --- /dev/null +++ b/autoload/ale/handlers/spectral.vim @@ -0,0 +1,31 @@ +" Author: t2h5 +" Description: Integration of Stoplight Spectral CLI with ALE. + +function! ale#handlers#spectral#HandleSpectralOutput(buffer, lines) abort + " Matches patterns like the following: + " openapi.yml:1:1 error oas3-schema "Object should have required property `info`." + " openapi.yml:1:1 warning oas3-api-servers "OpenAPI `servers` must be present and non-empty array." + let l:pattern = '\v^.*:(\d+):(\d+) (error|warning) (.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:obj = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \ 'text': l:match[4], + \} + + let l:code_match = matchlist(l:obj.text, '\v^(.+) "(.+)"$') + + if !empty(l:code_match) + let l:obj.code = l:code_match[1] + let l:obj.text = l:code_match[2] + endif + + call add(l:output, l:obj) + endfor + + return l:output +endfunction + diff --git a/autoload/ale/handlers/statix.vim b/autoload/ale/handlers/statix.vim new file mode 100644 index 00000000..eeef4107 --- /dev/null +++ b/autoload/ale/handlers/statix.vim @@ -0,0 +1,24 @@ +scriptencoding utf-8 +" Author: David Houston +" Description: This file defines a handler function for statix's errorformat +" output. + +function! ale#handlers#statix#Handle(buffer, lines) abort + " Look for lines like the following. + " + " flake.nix>46:13:W:3:This assignment is better written with `inherit` + let l:pattern = '\v^.*\>(\d+):(\d+):([A-Z]):(\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, + \ 'type': l:match[3], + \ 'code': l:match[4], + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/textlint.vim b/autoload/ale/handlers/textlint.vim new file mode 100644 index 00000000..7a648617 --- /dev/null +++ b/autoload/ale/handlers/textlint.vim @@ -0,0 +1,39 @@ +" Author: tokida https://rouger.info, Yasuhiro Kiyota +" Description: textlint, a proofreading tool (https://textlint.github.io/) + +call ale#Set('textlint_executable', 'textlint') +call ale#Set('textlint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('textlint_options', '') + +function! ale#handlers#textlint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'textlint', [ + \ 'node_modules/.bin/textlint', + \ 'node_modules/textlint/bin/textlint.js', + \]) +endfunction + +function! ale#handlers#textlint#GetCommand(buffer) abort + let l:executable = ale#handlers#textlint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'textlint_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -f json --stdin --stdin-filename %s' +endfunction + +function! ale#handlers#textlint#HandleTextlintOutput(buffer, lines) abort + let l:res = get(ale#util#FuzzyJSONDecode(a:lines, []), 0, {'messages': []}) + let l:output = [] + + for l:err in l:res.messages + call add(l:output, { + \ 'text': l:err.message, + \ 'type': 'W', + \ 'code': l:err.ruleId, + \ 'lnum': l:err.line, + \ 'col' : l:err.column + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/tslint.vim b/autoload/ale/handlers/tslint.vim new file mode 100644 index 00000000..ee091d24 --- /dev/null +++ b/autoload/ale/handlers/tslint.vim @@ -0,0 +1,13 @@ +function! ale#handlers#tslint#InitVariables() abort + call ale#Set('typescript_tslint_executable', 'tslint') + call ale#Set('typescript_tslint_config_path', '') + call ale#Set('typescript_tslint_rules_dir', '') + call ale#Set('typescript_tslint_use_global', get(g:, 'ale_use_global_executables', 0)) + call ale#Set('typescript_tslint_ignore_empty_files', 0) +endfunction + +function! ale#handlers#tslint#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'typescript_tslint', [ + \ 'node_modules/.bin/tslint', + \]) +endfunction 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 +" 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/unix.vim b/autoload/ale/handlers/unix.vim new file mode 100644 index 00000000..f90fd591 --- /dev/null +++ b/autoload/ale/handlers/unix.vim @@ -0,0 +1,26 @@ +" Author: w0rp +" Description: Error handling for errors in a Unix format. + +function! s:HandleUnixFormat(buffer, lines, type) 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[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3], + \ 'type': a:type, + \}) + endfor + + return l:output +endfunction + +function! ale#handlers#unix#HandleAsError(buffer, lines) abort + return s:HandleUnixFormat(a:buffer, a:lines, 'E') +endfunction + +function! ale#handlers#unix#HandleAsWarning(buffer, lines) abort + return s:HandleUnixFormat(a:buffer, a:lines, 'W') +endfunction diff --git a/autoload/ale/handlers/vale.vim b/autoload/ale/handlers/vale.vim new file mode 100644 index 00000000..2da72fc7 --- /dev/null +++ b/autoload/ale/handlers/vale.vim @@ -0,0 +1,39 @@ +" Author: Johannes Wienke +" Description: output handler for the vale JSON format + +function! ale#handlers#vale#GetType(severity) abort + if a:severity is? 'warning' + return 'W' + elseif a:severity is? 'suggestion' + return 'I' + endif + + return 'E' +endfunction + +function! ale#handlers#vale#Handle(buffer, lines) abort + try + let l:errors = json_decode(join(a:lines, '')) + catch + return [] + endtry + + if empty(l:errors) + return [] + endif + + let l:output = [] + + for l:error in l:errors[keys(l:errors)[0]] + call add(l:output, { + \ 'lnum': l:error['Line'], + \ 'col': l:error['Span'][0], + \ 'end_col': l:error['Span'][1], + \ 'code': l:error['Check'], + \ 'text': l:error['Message'], + \ 'type': ale#handlers#vale#GetType(l:error['Severity']), + \}) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/writegood.vim b/autoload/ale/handlers/writegood.vim new file mode 100644 index 00000000..b5b91b3f --- /dev/null +++ b/autoload/ale/handlers/writegood.vim @@ -0,0 +1,72 @@ +" Author: Sumner Evans +" Description: Error handling for errors in the write-good format. + +function! ale#handlers#writegood#ResetOptions() abort + call ale#Set('writegood_options', '') + call ale#Set('writegood_executable', 'write-good') + call ale#Set('writegood_use_global', get(g:, 'ale_use_global_executables', 0)) +endfunction + +" Reset the options so the tests can test how they are set. +call ale#handlers#writegood#ResetOptions() + +function! ale#handlers#writegood#GetExecutable(buffer) abort + return ale#path#FindExecutable(a:buffer, 'writegood', [ + \ 'node_modules/.bin/write-good', + \ 'node_modules/write-good/bin/write-good.js', + \]) +endfunction + +function! ale#handlers#writegood#GetCommand(buffer) abort + let l:executable = ale#handlers#writegood#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'writegood_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + +function! ale#handlers#writegood#Handle(buffer, lines) abort + " Look for lines like the following. + " + " "it is" is wordy or unneeded on line 20 at column 53 + " "easily" can weaken meaning on line 154 at column 29 + let l:marks_pattern = '\v^ *(\^+) *$' + let l:pattern = '\v^(".*"\s.*)\son\sline\s(\d+)\sat\scolumn\s(\d+)$' + let l:output = [] + let l:last_len = 0 + + for l:match in ale#util#GetMatches(a:lines, [l:marks_pattern, l:pattern]) + if empty(l:match[2]) + let l:last_len = len(l:match[1]) + else + let l:col = l:match[3] + 1 + + " Add the linter error. Note that we need to add 1 to the col because + " write-good reports the column corresponding to the space before the + " offending word or phrase. + call add(l:output, { + \ 'text': l:match[1], + \ 'lnum': l:match[2] + 0, + \ 'col': l:col, + \ 'end_col': l:last_len ? (l:col + l:last_len - 1) : l:col, + \ 'type': 'W', + \}) + + let l:last_len = 0 + endif + endfor + + return l:output +endfunction + +" Define the writegood linter for a given filetype. +function! ale#handlers#writegood#DefineLinter(filetype) abort + call ale#linter#Define(a:filetype, { + \ 'name': 'writegood', + \ 'aliases': ['write-good'], + \ 'executable': function('ale#handlers#writegood#GetExecutable'), + \ 'command': function('ale#handlers#writegood#GetCommand'), + \ 'callback': 'ale#handlers#writegood#Handle', + \}) +endfunction diff --git a/autoload/ale/handlers/xo.vim b/autoload/ale/handlers/xo.vim new file mode 100644 index 00000000..a87c6d8f --- /dev/null +++ b/autoload/ale/handlers/xo.vim @@ -0,0 +1,44 @@ +call ale#Set('javascript_xo_executable', 'xo') +call ale#Set('javascript_xo_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('javascript_xo_options', '') + +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#handlers#xo#GetExecutable(buffer) abort + let l:type = ale#handlers#xo#GetType(a:buffer) + + return ale#path#FindExecutable(a:buffer, l:type . '_xo', [ + \ 'node_modules/xo/cli.js', + \ 'node_modules/.bin/xo', + \]) +endfunction + +function! ale#handlers#xo#GetLintCommand(buffer) abort + return ale#Escape(ale#handlers#xo#GetExecutable(a:buffer)) + \ . ale#Pad(ale#handlers#xo#GetOptions(a:buffer)) + \ . ' --reporter json --stdin --stdin-filename %s' +endfunction + +function! ale#handlers#xo#GetOptions(buffer) abort + let l:type = ale#handlers#xo#GetType(a:buffer) + + return ale#Var(a:buffer, l:type . '_xo_options') +endfunction + +" xo uses eslint and the output format is the same +function! ale#handlers#xo#HandleJSON(buffer, lines) abort + return ale#handlers#eslint#HandleJSON(a:buffer, a:lines) +endfunction + +function! ale#handlers#xo#GetType(buffer) abort + let l:filetype = getbufvar(a:buffer, '&filetype') + let l:type = 'javascript' + + if l:filetype =~# 'typescript' + let l:type = 'typescript' + endif + + return l:type +endfunction diff --git a/autoload/ale/handlers/yamllint.vim b/autoload/ale/handlers/yamllint.vim new file mode 100644 index 00000000..5e04577d --- /dev/null +++ b/autoload/ale/handlers/yamllint.vim @@ -0,0 +1,39 @@ +function! ale#handlers#yamllint#GetCommand(buffer) abort + return '%e' . ale#Pad(ale#Var(a:buffer, 'yaml_yamllint_options')) + \ . ' -f parsable %t' +endfunction + +function! ale#handlers#yamllint#Handle(buffer, lines) abort + " Matches patterns line the following: + " something.yaml:1:1: [warning] missing document start "---" (document-start) + " something.yml:2:1: [error] syntax error: expected the node content, but found '' + let l:pattern = '\v^.*:(\d+):(\d+): \[(error|warning)\] (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4], + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \} + + let l:code_match = matchlist(l:item.text, '\v^(.+) \(([^)]+)\)$') + + if !empty(l:code_match) + if l:code_match[2] is# 'trailing-spaces' + \&& !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + " Skip warnings for trailing whitespace if the option is off. + continue + endif + + let l:item.text = l:code_match[1] + let l:item.code = l:code_match[2] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim new file mode 100644 index 00000000..473ad354 --- /dev/null +++ b/autoload/ale/highlight.vim @@ -0,0 +1,222 @@ +scriptencoding utf8 +" Author: w0rp +" Description: This module implements error/warning highlighting. + +if !hlexists('ALEError') + highlight link ALEError SpellBad +endif + +if !hlexists('ALEStyleError') + highlight link ALEStyleError ALEError +endif + +if !hlexists('ALEWarning') + highlight link ALEWarning SpellCap +endif + +if !hlexists('ALEStyleWarning') + highlight link ALEStyleWarning ALEWarning +endif + +if !hlexists('ALEInfo') + highlight link ALEInfo ALEWarning +endif + +" The maximum number of items for the second argument of matchaddpos() +let s:MAX_POS_VALUES = 8 +let s:MAX_COL_SIZE = 1073741824 " pow(2, 30) + +let s:has_nvim_highlight = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') + +if s:has_nvim_highlight + let s:ns_id = nvim_create_namespace('ale_highlight') +endif + +" Wrappers are necessary to test this functionality by faking the calls in tests. +function! ale#highlight#nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) abort + " Ignore all errors for adding highlights. + try + call nvim_buf_add_highlight(a:buffer, a:ns_id, a:hl_group, a:line, a:col_start, a:col_end) + catch + endtry +endfunction + +function! ale#highlight#nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end) abort + call nvim_buf_clear_namespace(a:buffer, a:ns_id, a:line_start, a:line_end) +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. + return [[[a:line, a:col, a:end_col - a:col + 1]]] + endif + + " Get positions from the first line at the first column, up to a large + " integer for highlighting up to the end of the line, followed by + " the lines in-between, for highlighting entire lines, and + " a highlight for the last line, up to the end column. + let l:all_positions = + \ [[a:line, a:col, s:MAX_COL_SIZE]] + \ + range(a:line + 1, a:end_line - 1) + \ + [[a:end_line, 1, a:end_col]] + + return map( + \ range(0, len(l:all_positions) - 1, s:MAX_POS_VALUES), + \ 'l:all_positions[v:val : v:val + s:MAX_POS_VALUES - 1]', + \) +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 + if s:has_nvim_highlight + call ale#highlight#nvim_buf_clear_namespace(bufnr(''), s:ns_id, 0, -1) + else + for l:match in getmatches() + if l:match.group =~? '\v^ALE(Style)?(Error|Warning|Info)(Line)?$' + call matchdelete(l:match.id) + endif + endfor + endif +endfunction + +" Same semantics of matchaddpos but will use nvim_buf_add_highlight if +" available. This involves iterating over the position list, switching from +" 1-based indexing to 0-based indexing, and translating the multiple ways +" that position can be specified for matchaddpos into line + col_start + +" col_end. +function! s:matchaddpos(group, pos_list) abort + if s:has_nvim_highlight + for l:pos in a:pos_list + let l:line = type(l:pos) == v:t_number + \ ? l:pos - 1 + \ : l:pos[0] - 1 + + if type(l:pos) == v:t_number || len(l:pos) == 1 + let l:col_start = 0 + let l:col_end = s:MAX_COL_SIZE + else + let l:col_start = l:pos[1] - 1 + let l:col_end = l:col_start + get(l:pos, 2, 1) + endif + + call ale#highlight#nvim_buf_add_highlight( + \ bufnr(''), + \ s:ns_id, + \ a:group, + \ l:line, + \ l:col_start, + \ l:col_end, + \) + endfor + else + call matchaddpos(a:group, a:pos_list) + endif +endfunction + +function! s:highlight_line(bufnr, lnum, group) abort + call s:matchaddpos(a:group, [a:lnum]) +endfunction + +function! s:highlight_range(bufnr, range, group) abort + " 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 + \ ), + \ 's:matchaddpos(a:group, v:val)' + \) +endfunction + +function! ale#highlight#UpdateHighlights() abort + let l:item_list = get(b:, 'ale_enabled', 1) && g:ale_enabled + \ ? get(b:, 'ale_highlight_items', []) + \ : [] + + call ale#highlight#RemoveHighlights() + + for l:item in l:item_list + if l:item.type is# 'W' + if get(l:item, 'sub_type', '') is# 'style' + let l:group = 'ALEStyleWarning' + else + let l:group = 'ALEWarning' + endif + elseif l:item.type is# 'I' + let l:group = 'ALEInfo' + elseif get(l:item, 'sub_type', '') is# 'style' + let l:group = 'ALEStyleError' + else + let l:group = 'ALEError' + endif + + 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) + \} + + call s:highlight_range(l:item.bufnr, l:range, l:group) + endfor + + " If highlights are enabled and signs are not enabled, we should still + " offer line highlights by adding a separate set of highlights. + if !g:ale_set_signs + let l:available_groups = { + \ 'ALEWarningLine': hlexists('ALEWarningLine'), + \ 'ALEInfoLine': hlexists('ALEInfoLine'), + \ 'ALEErrorLine': hlexists('ALEErrorLine'), + \} + + for l:item in l:item_list + if l:item.type is# 'W' + let l:group = 'ALEWarningLine' + elseif l:item.type is# 'I' + let l:group = 'ALEInfoLine' + else + let l:group = 'ALEErrorLine' + endif + + if l:available_groups[l:group] + call s:highlight_line(l:item.bufnr, l:item.lnum, l:group) + endif + endfor + endif +endfunction + +function! ale#highlight#BufferHidden(buffer) abort + " Remove highlights right away when buffers are hidden. + " They will be restored later when buffers are entered. + call ale#highlight#RemoveHighlights() +endfunction + +augroup ALEHighlightBufferGroup + autocmd! + autocmd BufEnter * call ale#highlight#UpdateHighlights() + autocmd BufHidden * call ale#highlight#BufferHidden(expand('')) +augroup END + +function! ale#highlight#SetHighlights(buffer, loclist) abort + let l:new_list = getbufvar(a:buffer, 'ale_enabled', 1) && g:ale_enabled + \ ? filter(copy(a:loclist), 'v:val.bufnr == a:buffer && v:val.col > 0') + \ : [] + + " Set the list in the buffer variable. + call setbufvar(str2nr(a:buffer), 'ale_highlight_items', l:new_list) + + let l:exclude_list = ale#Var(a:buffer, 'exclude_highlights') + + if !empty(l:exclude_list) + call filter(l:new_list, 'empty(ale#util#GetMatches(v:val.text, l:exclude_list))') + endif + + " Update highlights for the current buffer, which may or may not + " be the buffer we just set highlights for. + call ale#highlight#UpdateHighlights() +endfunction diff --git a/autoload/ale/history.vim b/autoload/ale/history.vim new file mode 100644 index 00000000..27ae74c2 --- /dev/null +++ b/autoload/ale/history.vim @@ -0,0 +1,62 @@ +" Author: w0rp +" Description: Tools for managing command history + +" A flag for controlling the maximum size of the command history to store. +let g:ale_max_buffer_history_size = get(g:, 'ale_max_buffer_history_size', 20) + +" Return a shallow copy of the command history for a given buffer number. +function! ale#history#Get(buffer) abort + return copy(getbufvar(a:buffer, 'ale_history', [])) +endfunction + +function! ale#history#Add(buffer, status, job_id, command) abort + if g:ale_max_buffer_history_size <= 0 + " Don't save anything if the history isn't a positive number. + call setbufvar(a:buffer, 'ale_history', []) + + return + endif + + let l:history = getbufvar(a:buffer, 'ale_history', []) + + " Remove the first item if we hit the max history size. + if len(l:history) >= g:ale_max_buffer_history_size + let l:history = l:history[1:] + endif + + call add(l:history, { + \ 'status': a:status, + \ 'job_id': a:job_id, + \ 'command': a:command, + \}) + + call setbufvar(a:buffer, 'ale_history', l:history) +endfunction + +function! s:FindHistoryItem(buffer, job_id) abort + " Search backwards to find a matching job ID. IDs might be recycled, + " so finding the last one should be good enough. + for l:obj in reverse(ale#history#Get(a:buffer)) + if l:obj.job_id == a:job_id + return l:obj + endif + endfor + + return {} +endfunction + +" Set an exit code for a command which finished. +function! ale#history#SetExitCode(buffer, job_id, exit_code) abort + let l:obj = s:FindHistoryItem(a:buffer, a:job_id) + + " If we find a match, then set the code and status. + let l:obj.exit_code = a:exit_code + let l:obj.status = 'finished' +endfunction + +" Set the output for a command which finished. +function! ale#history#RememberOutput(buffer, job_id, output) abort + let l:obj = s:FindHistoryItem(a:buffer, a:job_id) + + let l:obj.output = a:output +endfunction diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim new file mode 100644 index 00000000..a42766eb --- /dev/null +++ b/autoload/ale/hover.vim @@ -0,0 +1,380 @@ +" Author: w0rp +" Description: Hover support for LSP linters. + +let s:hover_map = {} + +" Used to get the hover map in tests. +function! ale#hover#GetMap() abort + return deepcopy(s:hover_map) +endfunction + +" Used to set the hover map in tests. +function! ale#hover#SetMap(map) abort + let s:hover_map = a:map +endfunction + +function! ale#hover#ClearLSPData() abort + let s:hover_map = {} +endfunction + +function! ale#hover#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') is# 'quickinfo' + \&& has_key(s:hover_map, a:response.request_seq) + let l:options = remove(s:hover_map, a:response.request_seq) + + if get(a:response, 'success', v:false) is v:true + \&& get(a:response, 'body', v:null) isnot v:null + let l:set_balloons = ale#Var(l:options.buffer, 'set_balloons') + + " 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') + \&& (l:set_balloons is 1 || l:set_balloons is# 'hover') + call balloon_show(a:response.body.displayString) + elseif get(l:options, 'truncated_echo', 0) + if !empty(a:response.body.displayString) + call ale#cursor#TruncatedEcho(a:response.body.displayString) + endif + elseif g:ale_hover_to_floating_preview || g:ale_floating_preview + call ale#floating_preview#Show(split(a:response.body.displayString, "\n"), { + \ 'filetype': 'ale-preview.message', + \}) + elseif g:ale_hover_to_preview + call ale#preview#Show(split(a:response.body.displayString, "\n"), { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \}) + else + call ale#util#ShowMessage(a:response.body.displayString) + endif + endif + endif +endfunction + +" Convert a language name to another one. +" The language name could be an empty string or v:null +function! s:ConvertLanguageName(language) abort + return a:language +endfunction + +" Cache syntax file (non-)existence to avoid calling globpath repeatedly. +let s:syntax_file_exists_cache = {} + +function! s:SyntaxFileExists(syntax_file) abort + if !has_key(s:syntax_file_exists_cache, a:syntax_file) + let s:syntax_file_exists_cache[a:syntax_file] = + \ !empty(globpath(&runtimepath, a:syntax_file)) + endif + + return s:syntax_file_exists_cache[a:syntax_file] +endfunction + +function! ale#hover#ParseLSPResult(contents) abort + let l:includes = {} + let l:highlights = [] + let l:lines = [] + let l:list = type(a:contents) is v:t_list ? a:contents : [a:contents] + let l:region_index = 0 + + for l:item in l:list + if !empty(l:lines) + call add(l:lines, '') + endif + + if type(l:item) is v:t_dict && has_key(l:item, 'kind') + if l:item.kind is# 'markdown' + " Handle markdown values as we handle strings below. + let l:item = get(l:item, 'value', '') + elseif l:item.kind is# 'plaintext' + " We shouldn't try to parse plaintext as markdown. + " Pass the lines on and skip parsing them. + call extend(l:lines, split(get(l:item, 'value', ''), "\n")) + + continue + endif + endif + + let l:marked_list = [] + + " If the item is a string, then we should parse it as Markdown text. + if type(l:item) is v:t_string + let l:fence_language = v:null + let l:fence_lines = [] + + for l:line in split(l:item, "\n") + if l:fence_language is v:null + " Look for the start of a code fence. (```python, etc.) + let l:match = matchlist(l:line, '^``` *\([^ ]\+\)\? *$') + + if !empty(l:match) + let l:fence_language = len(l:match) > 1 ? l:match[1] : 'text' + + if !empty(l:marked_list) + call add(l:fence_lines, '') + endif + else + if !empty(l:marked_list) + \&& l:marked_list[-1][0] isnot v:null + call add(l:marked_list, [v:null, ['']]) + endif + + call add(l:marked_list, [v:null, [l:line]]) + endif + elseif l:line =~# '^```$' + " When we hit the end of a code fence, pass the fenced + " lines on to the next steps below. + call add(l:marked_list, [l:fence_language, l:fence_lines]) + let l:fence_language = v:null + let l:fence_lines = [] + else + " Gather lines inside of a code fence. + call add(l:fence_lines, l:line) + endif + endfor + " If the result from the LSP server is a {language: ..., value: ...} + " Dictionary, then that should be interpreted as if it was: + " + " ```${language} + " ${value} + " ``` + elseif type(l:item) is v:t_dict + \&& has_key(l:item, 'language') + \&& type(l:item.language) is v:t_string + \&& has_key(l:item, 'value') + \&& type(l:item.value) is v:t_string + call add( + \ l:marked_list, + \ [l:item.language, split(l:item.value, "\n")], + \) + endif + + for [l:language, l:marked_lines] in l:marked_list + if l:language is v:null + " NOTE: We could handle other Markdown formatting here. + call map( + \ l:marked_lines, + \ 'substitute(v:val, ''\\_'', ''_'', ''g'')', + \) + else + let l:language = s:ConvertLanguageName(l:language) + + if !empty(l:language) + let l:syntax_file = printf('syntax/%s.vim', l:language) + + if s:SyntaxFileExists(l:syntax_file) + let l:includes[l:language] = l:syntax_file + endif + + let l:start = len(l:lines) + 1 + let l:end = l:start + len(l:marked_lines) + let l:region_index += 1 + + call add(l:highlights, 'syntax region' + \ . ' ALE_hover_' . l:region_index + \ . ' start=/\%' . l:start . 'l/' + \ . ' end=/\%' . l:end . 'l/' + \ . ' contains=@ALE_hover_' . l:language + \) + endif + endif + + call extend(l:lines, l:marked_lines) + endfor + endfor + + let l:include_commands = [] + + for [l:language, l:lang_path] in sort(items(l:includes)) + call add(l:include_commands, 'unlet! b:current_syntax') + call add( + \ l:include_commands, + \ printf('syntax include @ALE_hover_%s %s', l:language, l:lang_path), + \) + endfor + + return [l:include_commands + l:highlights, l:lines] +endfunction + +function! ale#hover#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:hover_map, a:response.id) + let l:options = remove(s:hover_map, a:response.id) + + " If the call did __not__ come from balloonexpr... + if !get(l:options, 'hover_from_balloonexpr', 0) + let l:buffer = bufnr('') + let [l:line, l:column] = getpos('.')[1:2] + let l:end = len(getline(l:line)) + + if l:buffer isnot l:options.buffer + \|| l:line isnot l:options.line + \|| min([l:column, l:end]) isnot min([l:options.column, l:end]) + " ... Cancel display the message if the cursor has moved. + return + endif + endif + + " The result can be a Dictionary item, a List of the same, or null. + let l:result = get(a:response, 'result', v:null) + + if l:result is v:null + return + endif + + let [l:commands, l:lines] = ale#hover#ParseLSPResult(l:result.contents) + + if !empty(l:lines) + let l:set_balloons = ale#Var(l:options.buffer, 'set_balloons') + + if get(l:options, 'hover_from_balloonexpr', 0) + \&& exists('*balloon_show') + \&& (l:set_balloons is 1 || l:set_balloons is# 'hover') + call balloon_show(join(l:lines, "\n")) + elseif get(l:options, 'truncated_echo', 0) + if type(l:lines[0]) is# v:t_list + call ale#cursor#TruncatedEcho(join(l:lines[0], '\n')) + else + call ale#cursor#TruncatedEcho(l:lines[0]) + endif + elseif g:ale_hover_to_floating_preview || g:ale_floating_preview + call ale#floating_preview#Show(l:lines, { + \ 'filetype': 'ale-preview.message', + \ 'commands': l:commands, + \}) + elseif g:ale_hover_to_preview + call ale#preview#Show(l:lines, { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \ 'commands': l:commands, + \}) + else + call ale#util#ShowMessage(join(l:lines, "\n"), { + \ 'commands': l:commands, + \}) + endif + endif + endif +endfunction + +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') + call ale#lsp#RegisterCallback(l:id, l:Callback) + + if a:linter.lsp is# 'tsserver' + let l:column = a:column + + let l:message = ale#lsp#tsserver_message#Quickinfo( + \ l:buffer, + \ a:line, + \ l:column + \) + else + " Send a message saying the buffer has changed first, or the + " hover position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:buffer) + + let l:column = max([ + \ min([a:column, len(getbufline(l:buffer, a:line)[0])]), + \ 1, + \]) + + let l:message = ale#lsp#message#Hover(l:buffer, a:line, l:column) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message) + + let s:hover_map[l:request_id] = { + \ 'buffer': l:buffer, + \ 'line': a:line, + \ 'column': l:column, + \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0), + \ 'show_documentation': get(a:opt, 'show_documentation', 0), + \ 'truncated_echo': get(a:opt, 'truncated_echo', 0), + \} +endfunction + +" Obtain Hover information for the specified position +" Pass optional arguments in the dictionary opt. +" Currently, only one key/value is useful: +" - called_from_balloonexpr, this flag marks if we want the result from this +" ale#hover#Show to display in a balloon if possible +" +" Currently, the callbacks displays the info from hover : +" - in the balloon if opt.called_from_balloonexpr and balloon_show is detected +" - as status message otherwise +function! ale#hover#Show(buffer, line, col, opt) abort + 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#lsp_linter#GetEnabled(a:buffer) + " Only tsserver supports documentation requests at the moment. + if !l:show_documentation || l:linter.lsp is# 'tsserver' + call ale#lsp_linter#StartLSP(a:buffer, l:linter, l:Callback) + endif + endfor +endfunction + +let s:last_pos = [0, 0, 0] + +" 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 + +function! ale#hover#ShowTruncatedMessageAtCursor() abort + let l:buffer = bufnr('') + let l:pos = getpos('.')[0:2] + + if !getbufvar(l:buffer, 'ale_enabled', 1) + return + endif + + if l:pos != s:last_pos + let s:last_pos = l:pos + let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer) + + if empty(l:loc) + call ale#hover#Show( + \ l:buffer, + \ l:pos[1], + \ l:pos[2], + \ {'truncated_echo': 1}, + \) + endif + endif +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/java.vim b/autoload/ale/java.vim new file mode 100644 index 00000000..859d938d --- /dev/null +++ b/autoload/ale/java.vim @@ -0,0 +1,26 @@ +" Author: Horacio Sanson https://github.com/hsanson +" Description: Functions for integrating with Java tools + +" Find the nearest dir contining a gradle or pom file and assume it +" the root of a java app. +function! ale#java#FindProjectRoot(buffer) abort + let l:gradle_root = ale#gradle#FindProjectRoot(a:buffer) + + if !empty(l:gradle_root) + return l:gradle_root + endif + + let l:maven_pom_file = ale#path#FindNearestFile(a:buffer, 'pom.xml') + + if !empty(l:maven_pom_file) + return fnamemodify(l:maven_pom_file, ':h') + endif + + let l:ant_root = ale#ant#FindProjectRoot(a:buffer) + + if !empty(l:ant_root) + return l:ant_root + endif + + return '' +endfunction diff --git a/autoload/ale/job.vim b/autoload/ale/job.vim new file mode 100644 index 00000000..0fc43a8c --- /dev/null +++ b/autoload/ale/job.vim @@ -0,0 +1,385 @@ +" Author: w0rp +" Description: APIs for working with Asynchronous jobs, with an API normalised +" between Vim 8 and NeoVim. +" +" Important functions are described below. They are: +" +" ale#job#Start(command, options) -> job_id +" ale#job#IsRunning(job_id) -> 1 if running, 0 otherwise. +" ale#job#Stop(job_id) + +" A setting for wrapping commands. +let g:ale_command_wrapper = get(g:, 'ale_command_wrapper', '') + +if !has_key(s:, 'job_map') + let s:job_map = {} +endif + +" A map from timer IDs to jobs, for tracking jobs that need to be killed +" with SIGKILL if they don't terminate right away. +if !has_key(s:, 'job_kill_timers') + let s:job_kill_timers = {} +endif + +function! s:KillHandler(timer) abort + let l:job = remove(s:job_kill_timers, a:timer) + call job_stop(l:job, 'kill') +endfunction + +function! s:NeoVimCallback(job, data, event) abort + let l:info = s:job_map[a:job] + + if a:event is# 'stdout' + let l:info.out_cb_line = ale#util#JoinNeovimOutput( + \ a:job, + \ l:info.out_cb_line, + \ a:data, + \ l:info.mode, + \ ale#util#GetFunction(l:info.out_cb), + \) + elseif a:event is# 'stderr' + let l:info.err_cb_line = ale#util#JoinNeovimOutput( + \ a:job, + \ l:info.err_cb_line, + \ a:data, + \ l:info.mode, + \ ale#util#GetFunction(l:info.err_cb), + \) + else + if has_key(l:info, 'out_cb') && !empty(l:info.out_cb_line) + call ale#util#GetFunction(l:info.out_cb)(a:job, l:info.out_cb_line) + endif + + if has_key(l:info, 'err_cb') && !empty(l:info.err_cb_line) + call ale#util#GetFunction(l:info.err_cb)(a:job, l:info.err_cb_line) + endif + + try + call ale#util#GetFunction(l:info.exit_cb)(a:job, a:data) + finally + " Automatically forget about the job after it's done. + if has_key(s:job_map, a:job) + call remove(s:job_map, a:job) + endif + endtry + endif +endfunction + +function! s:VimOutputCallback(channel, data) abort + let l:job = ch_getjob(a:channel) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job)) + + " Only call the callbacks for jobs which are valid. + if l:job_id > 0 && has_key(s:job_map, l:job_id) + call ale#util#GetFunction(s:job_map[l:job_id].out_cb)(l:job_id, a:data) + endif +endfunction + +function! s:VimErrorCallback(channel, data) abort + let l:job = ch_getjob(a:channel) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job)) + + " Only call the callbacks for jobs which are valid. + if l:job_id > 0 && has_key(s:job_map, l:job_id) + call ale#util#GetFunction(s:job_map[l:job_id].err_cb)(l:job_id, a:data) + endif +endfunction + +function! s:VimCloseCallback(channel) abort + let l:job = ch_getjob(a:channel) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job)) + let l:info = get(s:job_map, l:job_id, {}) + + if empty(l:info) + return + endif + + " job_status() can trigger the exit handler. + " The channel can close before the job has exited. + if job_status(l:job) is# 'dead' + try + if !empty(l:info) && has_key(l:info, 'exit_cb') + " 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. + if has_key(s:job_map, l:job_id) + call remove(s:job_map, l:job_id) + endif + endtry + endif +endfunction + +function! s:VimExitCallback(job, exit_code) abort + let l:job_id = ale#job#ParseVim8ProcessID(string(a:job)) + let l:info = get(s:job_map, l:job_id, {}) + + if empty(l:info) + return + endif + + let l:info.exit_code = a:exit_code + + " The program can exit before the data has finished being read. + if ch_status(job_getchannel(a:job)) is# 'closed' + try + if !empty(l:info) && has_key(l:info, 'exit_cb') + " 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. + if has_key(s:job_map, l:job_id) + call remove(s:job_map, l:job_id) + endif + endtry + endif +endfunction + +function! ale#job#ParseVim8ProcessID(job_string) abort + return matchstr(a:job_string, '\d\+') + 0 +endfunction + +function! ale#job#ValidateArguments(command, options) abort + if a:options.mode isnot# 'nl' && a:options.mode isnot# 'raw' + throw 'Invalid mode: ' . a:options.mode + endif +endfunction + +function! s:PrepareWrappedCommand(original_wrapper, command) abort + let l:match = matchlist(a:command, '\v^(.*(\&\&|;)) *(.*)$') + let l:prefix = '' + let l:command = a:command + + if !empty(l:match) + let l:prefix = l:match[1] . ' ' + let l:command = l:match[3] + endif + + let l:format = a:original_wrapper + + if l:format =~# '%@' + let l:wrapped = substitute(l:format, '%@', ale#Escape(l:command), '') + else + if l:format !~# '%\*' + let l:format .= ' %*' + endif + + let l:wrapped = substitute(l:format, '%\*', l:command, '') + endif + + return l:prefix . l:wrapped +endfunction + +function! ale#job#PrepareCommand(buffer, command) abort + let l:wrapper = ale#Var(a:buffer, 'command_wrapper') + + " 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. + " + " 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('b:ale_shell') + let l:ale_shell = b:ale_shell + elseif exists('g:ale_shell') + let l:ale_shell = g:ale_shell + endif + + if exists('l:ale_shell') + let l:shell_arguments = get(b:, 'ale_shell_arguments', get(g:, 'ale_shell_arguments', &shellcmdflag)) + + return split(l:ale_shell) + split(l:shell_arguments) + [l:command] + endif + + if has('win32') + return 'cmd /s/c "' . l:command . '"' + endif + + if &shell =~? 'fish$\|pwsh$' + return ['/bin/sh', '-c', l:command] + endif + + return split(&shell) + split(&shellcmdflag) + [l:command] +endfunction + +" Start a job with options which are agnostic to Vim and NeoVim. +" +" The following options are accepted: +" +" out_cb - A callback for receiving stdin. Arguments: (job_id, data) +" err_cb - A callback for receiving stderr. Arguments: (job_id, data) +" exit_cb - A callback for program exit. Arguments: (job_id, status_code) +" mode - A mode for I/O. Can be 'nl' for split lines or 'raw'. +function! ale#job#Start(command, options) abort + call ale#job#ValidateArguments(a:command, a:options) + + let l:job_info = copy(a:options) + let l:job_options = {} + + if has('nvim') + if has_key(a:options, 'out_cb') + let l:job_options.on_stdout = function('s:NeoVimCallback') + let l:job_info.out_cb_line = '' + endif + + if has_key(a:options, 'err_cb') + let l:job_options.on_stderr = function('s:NeoVimCallback') + let l:job_info.err_cb_line = '' + endif + + if has_key(a:options, 'exit_cb') + let l:job_options.on_exit = function('s:NeoVimCallback') + endif + + let l:job_info.job = jobstart(a:command, l:job_options) + let l:job_id = l:job_info.job + else + let l:job_options = { + \ 'in_mode': l:job_info.mode, + \ 'out_mode': l:job_info.mode, + \ 'err_mode': l:job_info.mode, + \} + + if has_key(a:options, 'out_cb') + let l:job_options.out_cb = function('s:VimOutputCallback') + else + " prevent buffering of output and excessive polling in case close_cb is set + let l:job_options.out_cb = {->0} + endif + + if has_key(a:options, 'err_cb') + let l:job_options.err_cb = function('s:VimErrorCallback') + else + " prevent buffering of output and excessive polling in case close_cb is set + let l:job_options.err_cb = {->0} + endif + + if has_key(a:options, 'exit_cb') + " Set a close callback to which simply calls job_status() + " when the channel is closed, which can trigger the exit callback + " earlier on. + let l:job_options.close_cb = function('s:VimCloseCallback') + let l:job_options.exit_cb = function('s:VimExitCallback') + endif + + " Use non-blocking writes for Vim versions that support the option. + if has('patch-8.1.350') + let l:job_options.noblock = 1 + endif + + " Vim 8 will read the stdin from the file's buffer. + let l:job_info.job = job_start(a:command, l:job_options) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job_info.job)) + endif + + if l:job_id > 0 + " Store the job in the map for later only if we can get the ID. + let s:job_map[l:job_id] = l:job_info + endif + + 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 + 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 + +" Given a job ID, return 1 if the job is currently running. +" Invalid job IDs will be ignored. +function! ale#job#IsRunning(job_id) abort + if has('nvim') + try + " In NeoVim, if the job isn't running, jobpid() will throw. + call jobpid(a:job_id) + + return 1 + catch + endtry + elseif has_key(s:job_map, a:job_id) + let l:job = s:job_map[a:job_id].job + + return job_status(l:job) is# 'run' + endif + + 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 + if !has_key(s:job_map, a:job_id) + return + endif + + if has('nvim') + " FIXME: NeoVim kills jobs on a timer, but will not kill any processes + " which are child processes on Unix. Some work needs to be done to + " kill child processes to stop long-running processes like pylint. + silent! call jobstop(a:job_id) + else + let l:job = s:job_map[a:job_id].job + + " We must close the channel for reading the buffer if it is open + " when stopping a job. Otherwise, we will get errors in the status line. + if ch_status(job_getchannel(l:job)) is# 'open' + call ch_close_in(job_getchannel(l:job)) + endif + + " Ask nicely for the job to stop. + call job_stop(l:job) + + if ale#job#IsRunning(l:job) + " Set a 100ms delay for killing the job with SIGKILL. + let s:job_kill_timers[timer_start(100, function('s:KillHandler'))] = l:job + endif + endif +endfunction diff --git a/autoload/ale/julia.vim b/autoload/ale/julia.vim new file mode 100644 index 00000000..18dd9ad7 --- /dev/null +++ b/autoload/ale/julia.vim @@ -0,0 +1,19 @@ +" Author: Bartolomeo Stellato bartolomeo.stellato@gmail.com +" Description: Functions for integrating with Julia tools + +" Find the nearest dir containing a julia project +let s:__ale_julia_project_filenames = ['REQUIRE', 'Manifest.toml', 'Project.toml'] + +function! ale#julia#FindProjectRoot(buffer) abort + for l:project_filename in s:__ale_julia_project_filenames + let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename) + + if !empty(l:full_path) + let l:path = fnamemodify(l:full_path, ':p:h') + + return l:path + endif + endfor + + return '' +endfunction diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim new file mode 100644 index 00000000..021516c7 --- /dev/null +++ b/autoload/ale/linter.vim @@ -0,0 +1,448 @@ +" Author: w0rp +" Description: Linter registration and lazy-loading +" Retrieves linters as requested by the engine, loading them if needed. + +let s:runtime_loaded_map = {} +let s:linters = {} + +" Default filetype aliases. +" The user defined aliases will be merged with this Dictionary. +" +" NOTE: Update the g:ale_linter_aliases documentation when modifying this. +let s:default_ale_linter_aliases = { +\ 'Dockerfile': 'dockerfile', +\ 'csh': 'sh', +\ 'javascriptreact': ['javascript', 'jsx'], +\ 'plaintex': 'tex', +\ 'ps1': 'powershell', +\ 'rmarkdown': 'r', +\ 'rmd': 'r', +\ 'systemverilog': 'verilog', +\ 'typescriptreact': ['typescript', 'tsx'], +\ 'vader': ['vim', 'vader'], +\ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'], +\ 'vimwiki': 'markdown', +\ 'vue': ['vue', 'javascript'], +\ 'xsd': ['xsd', 'xml'], +\ 'xslt': ['xslt', 'xml'], +\ 'zsh': 'sh', +\} + +" Default linters to run for particular filetypes. +" The user defined linter selections will be merged with this Dictionary. +" +" No linters are used for plaintext files by default. +" +" Only cargo and rls are enabled for Rust by default. +" rpmlint is disabled by default because it can result in code execution. +" hhast is disabled by default because it executes code in the project root. +" +" NOTE: Update the g:ale_linters documentation when modifying this. +let s:default_ale_linters = { +\ 'apkbuild': ['apkbuild_lint', 'secfixes_check'], +\ 'astro': ['eslint'], +\ 'csh': ['shell'], +\ 'elixir': ['credo', 'dialyxir', 'dogma'], +\ 'go': ['gofmt', 'golangci-lint', 'gopls', 'govet'], +\ 'groovy': ['npm-groovy-lint'], +\ 'hack': ['hack'], +\ 'help': [], +\ 'inko': ['inko'], +\ 'json': ['biome', 'jsonlint', 'spectral', 'vscodejson'], +\ 'json5': [], +\ 'jsonc': ['biome'], +\ 'perl': ['perlcritic'], +\ 'perl6': [], +\ 'python': ['flake8', 'mypy', 'pylint', 'pyright', 'ruff'], +\ 'rust': ['analyzer', 'cargo'], +\ 'spec': [], +\ 'text': [], +\ 'vader': ['vimls'], +\ 'vue': ['eslint', 'vls'], +\ 'zsh': ['shell'], +\ 'v': ['v'], +\ 'yaml': ['actionlint', 'spectral', 'yaml-language-server', 'yamllint'], +\} + +" Testing/debugging helper to unload all linters. +function! ale#linter#Reset() abort + let s:runtime_loaded_map = {} + let s:linters = {} +endfunction + +" Return a reference to the linters loaded. +" This is only for tests. +" Do not call this function. +function! ale#linter#GetLintersLoaded() abort + " This command will throw from the sandbox. + let &l:equalprg=&l:equalprg + + return s:linters +endfunction + +function! s:IsCallback(value) abort + return type(a:value) is v:t_string || type(a:value) is v:t_func +endfunction + +function! s:IsBoolean(value) abort + return type(a:value) is v:t_number && (a:value == 0 || a:value == 1) +endfunction + +function! ale#linter#PreProcess(filetype, linter) abort + if type(a:linter) isnot v:t_dict + throw 'The linter object must be a Dictionary' + endif + + let l:obj = { + \ 'name': get(a:linter, 'name'), + \ 'lsp': get(a:linter, 'lsp', ''), + \} + + if type(l:obj.name) isnot v:t_string + throw '`name` must be defined to name the linter' + endif + + let l:needs_address = l:obj.lsp is# 'socket' + let l:needs_executable = l:obj.lsp isnot# 'socket' + let l:needs_command = l:obj.lsp isnot# 'socket' + let l:needs_lsp_details = !empty(l:obj.lsp) + + if empty(l:obj.lsp) + let l:obj.callback = get(a:linter, 'callback') + + if !s:IsCallback(l:obj.callback) + throw '`callback` must be defined with a callback to accept output' + endif + endif + + if index(['', 'socket', 'stdio', 'tsserver'], l:obj.lsp) < 0 + throw '`lsp` must be either `''lsp''`, `''stdio''`, `''socket''` or `''tsserver''` if defined' + endif + + if !l:needs_executable + if has_key(a:linter, 'executable') + throw '`executable` cannot be used when lsp == ''socket''' + endif + elseif has_key(a:linter, 'executable') + let l:obj.executable = a:linter.executable + + if type(l:obj.executable) isnot v:t_string + \&& type(l:obj.executable) isnot v:t_func + throw '`executable` must be a String or Function if defined' + endif + else + throw '`executable` must be defined' + endif + + if !l:needs_command + if has_key(a:linter, 'command') + throw '`command` cannot be used when lsp == ''socket''' + endif + elseif has_key(a:linter, 'command') + let l:obj.command = a:linter.command + + if type(l:obj.command) isnot v:t_string + \&& type(l:obj.command) isnot v:t_func + throw '`command` must be a String or Function if defined' + endif + else + throw '`command` must be defined' + endif + + if !l:needs_address + if has_key(a:linter, 'address') + throw '`address` 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 + + if has_key(a:linter, 'cwd') + throw '`cwd` makes no sense for socket LSP connections' + endif + else + throw '`address` must be defined for getting the LSP address' + endif + + if has_key(a:linter, 'cwd') + let l:obj.cwd = a:linter.cwd + + if type(l:obj.cwd) isnot v:t_string + \&& type(l:obj.cwd) isnot v:t_func + throw '`cwd` must be a String or Function if defined' + endif + endif + + if l:needs_lsp_details + " 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 + \&& type(l:obj.language) isnot v:t_func + throw '`language` must be a String or Function if defined' + endif + + if has_key(a:linter, 'project_root') + let l:obj.project_root = a:linter.project_root + + 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' + endif + else + throw '`project_root` must be defined for LSP linters' + endif + + if has_key(a:linter, 'completion_filter') + let l:obj.completion_filter = a:linter.completion_filter + + if !s:IsCallback(l:obj.completion_filter) + throw '`completion_filter` must be a callback' + endif + endif + + if has_key(a:linter, 'initialization_options') + 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 Dictionary or Function if defined' + endif + endif + + if has_key(a:linter, 'lsp_config') + if type(a:linter.lsp_config) isnot v:t_dict + \&& 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 + endif + endif + + let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout') + + if type(l:obj.output_stream) isnot v:t_string + \|| index(['stdout', 'stderr', 'both'], l:obj.output_stream) < 0 + throw "`output_stream` must be 'stdout', 'stderr', or 'both'" + endif + + " An option indicating that this linter should only be run against the + " file on disk. + let l:obj.lint_file = get(a:linter, 'lint_file', 0) + + if !s:IsBoolean(l:obj.lint_file) && type(l:obj.lint_file) isnot v:t_func + throw '`lint_file` must be `0`, `1`, or a Function' + endif + + " An option indicating that the buffer should be read. + let l:obj.read_buffer = get(a:linter, 'read_buffer', 1) + + if !s:IsBoolean(l:obj.read_buffer) + throw '`read_buffer` must be `0` or `1`' + endif + + let l:obj.aliases = get(a:linter, 'aliases', []) + + if type(l:obj.aliases) isnot v:t_list + \|| len(filter(copy(l:obj.aliases), 'type(v:val) isnot v:t_string')) > 0 + throw '`aliases` must be a List of String values' + endif + + return l:obj +endfunction + +function! ale#linter#Define(filetype, linter) abort + " This command will throw from the sandbox. + let &l:equalprg=&l:equalprg + + let l:new_linter = ale#linter#PreProcess(a:filetype, a:linter) + + if !has_key(s:linters, a:filetype) + let s:linters[a:filetype] = [] + endif + + " Remove previously defined linters with the same name. + call filter(s:linters[a:filetype], 'v:val.name isnot# a:linter.name') + call add(s:linters[a:filetype], l:new_linter) +endfunction + +" Prevent any linters from being loaded for a given filetype. +function! ale#linter#PreventLoading(filetype) abort + let s:runtime_loaded_map[a:filetype] = 1 +endfunction + +function! ale#linter#GetAll(filetypes) abort + " Don't return linters in the sandbox. + " Otherwise a sandboxed script could modify them. + if ale#util#InSandbox() + return [] + endif + + let l:combined_linters = [] + + for l:filetype in a:filetypes + " Load linters from runtimepath if we haven't done that yet. + if !has_key(s:runtime_loaded_map, l:filetype) + execute 'silent! runtime! ale_linters/' . l:filetype . '/*.vim' + + let s:runtime_loaded_map[l:filetype] = 1 + endif + + call extend(l:combined_linters, get(s:linters, l:filetype, [])) + endfor + + return l:combined_linters +endfunction + +function! s:GetAliasedFiletype(original_filetype) abort + let l:buffer_aliases = get(b:, 'ale_linter_aliases', {}) + + " b:ale_linter_aliases can be set to a List or String. + if type(l:buffer_aliases) is v:t_list + \|| type(l:buffer_aliases) is v:t_string + return l:buffer_aliases + endif + + " Check for aliased filetypes first in a buffer variable, + " then the global variable, + " then in the default mapping, + " otherwise use the original filetype. + for l:dict in [ + \ l:buffer_aliases, + \ g:ale_linter_aliases, + \ s:default_ale_linter_aliases, + \] + if has_key(l:dict, a:original_filetype) + return l:dict[a:original_filetype] + endif + endfor + + return a:original_filetype +endfunction + +function! ale#linter#ResolveFiletype(original_filetype) abort + let l:filetype = s:GetAliasedFiletype(a:original_filetype) + + if type(l:filetype) isnot v:t_list + return [l:filetype] + endif + + return l:filetype +endfunction + +function! s:GetLinterNames(original_filetype) abort + let l:buffer_ale_linters = get(b:, 'ale_linters', {}) + + " b:ale_linters can be set to 'all' + if l:buffer_ale_linters is# 'all' + return 'all' + endif + + " b:ale_linters can be set to a List. + if type(l:buffer_ale_linters) is v:t_list + return l:buffer_ale_linters + endif + + " Try to get a buffer-local setting for the filetype + if has_key(l:buffer_ale_linters, a:original_filetype) + return l:buffer_ale_linters[a:original_filetype] + endif + + " Try to get a global setting for the filetype + if has_key(g:ale_linters, a:original_filetype) + return g:ale_linters[a:original_filetype] + endif + + " If the user has configured ALE to only enable linters explicitly, then + " don't enable any linters by default. + if g:ale_linters_explicit + return [] + endif + + " Try to get a default setting for the filetype + if has_key(s:default_ale_linters, a:original_filetype) + return s:default_ale_linters[a:original_filetype] + endif + + return 'all' +endfunction + +function! ale#linter#Get(original_filetypes) abort + let l:possibly_duplicated_linters = [] + + " Handle dot-separated filetypes. + for l:original_filetype in split(a:original_filetypes, '\.') + let l:filetype = ale#linter#ResolveFiletype(l:original_filetype) + let l:linter_names = s:GetLinterNames(l:original_filetype) + let l:all_linters = ale#linter#GetAll(l:filetype) + let l:filetype_linters = [] + + if type(l:linter_names) is v:t_string && l:linter_names is# 'all' + let l:filetype_linters = l:all_linters + elseif type(l:linter_names) is v:t_list + " Select only the linters we or the user has specified. + for l:linter in l:all_linters + let l:name_list = [l:linter.name] + l:linter.aliases + + for l:name in l:name_list + if index(l:linter_names, l:name) >= 0 + call add(l:filetype_linters, l:linter) + break + endif + endfor + endfor + endif + + call extend(l:possibly_duplicated_linters, l:filetype_linters) + endfor + + let l:name_list = [] + let l:combined_linters = [] + + " Make sure we override linters so we don't get two with the same name, + " like 'eslint' for both 'javascript' and 'typescript' + " + " Note that the reverse calls here modify the List variables. + for l:linter in reverse(l:possibly_duplicated_linters) + if index(l:name_list, l:linter.name) < 0 + call add(l:name_list, l:linter.name) + call add(l:combined_linters, l:linter) + endif + endfor + + return reverse(l:combined_linters) +endfunction + +" Given a buffer and linter, get the executable String for the linter. +function! ale#linter#GetExecutable(buffer, linter) abort + let l:Executable = a:linter.executable + + return type(l:Executable) is v:t_func + \ ? l:Executable(a:buffer) + \ : l:Executable +endfunction + +function! ale#linter#GetCwd(buffer, linter) abort + let l:Cwd = get(a:linter, 'cwd', v:null) + + return type(l:Cwd) is v:t_func ? l:Cwd(a:buffer) : l:Cwd +endfunction + +" Given a buffer and linter, get the command String for the linter. +function! ale#linter#GetCommand(buffer, linter) abort + let l:Command = 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 + let l:Address = 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 new file mode 100644 index 00000000..18a72655 --- /dev/null +++ b/autoload/ale/list.vim @@ -0,0 +1,274 @@ +" Author: Bjorn Neergaard , modified by Yann fery +" Description: Manages the loclist and quickfix lists + +" This flag dictates if ale open the configured loclist +let g:ale_open_list = get(g:, 'ale_open_list', v:false) +" This flag dictates if ale keeps open loclist even if there is no error in loclist +let g:ale_keep_list_window_open = get(g:, 'ale_keep_list_window_open', 0) +" This flag dictates that quickfix windows should be opened vertically +let g:ale_list_vertical = get(g:, 'ale_list_vertical', v:false) +" The window size to set for the quickfix and loclist windows +let g:ale_list_window_size = get(g:, 'ale_list_window_size', 10) +" A string format for the loclist messages. +let g:ale_loclist_msg_format = get(g:, 'ale_loclist_msg_format', +\ get(g:, 'ale_echo_msg_format', '%code: %%s') +\) + +if !exists('s:timer_args') + let s:timer_args = {} +endif + +" Return 1 if there is a buffer with buftype == 'quickfix' in buffer list +function! ale#list#IsQuickfixOpen() abort + let l:res = getqflist({ 'winid' : winnr() }) + + if has_key(l:res, 'winid') && l:res.winid > 0 + return 1 + endif + + let l:res = getloclist(0, { 'winid' : winnr() }) + + if has_key(l:res, 'winid') && l:res.winid > 0 + return 1 + endif + + return 0 +endfunction + +" Check if we should open the list, based on the save event being fired, and +" that setting being on, or that the error count is at least as high as the +" setting when set to an integer value. +function! s:ShouldOpen(buffer, loclist_len) abort + let l:val = ale#Var(a:buffer, 'open_list') + let l:saved = getbufvar(a:buffer, 'ale_save_event_fired', 0) + + return l:val > 0 ? a:loclist_len >= l:val : l:val is# 'on_save' && l:saved +endfunction + +" Check if we should close the list, based on the save event being fired, and +" that setting being on, or the setting just being set to an integer value. +function! s:ShouldClose(buffer) abort + let l:val = ale#Var(a:buffer, 'open_list') + let l:saved = getbufvar(a:buffer, 'ale_save_event_fired', 0) + + return !((l:val >= 1) || (l:val is# 'on_save' && l:saved)) +endfunction + +function! s:Deduplicate(list) abort + let l:list = a:list + + call sort(l:list, function('ale#util#LocItemCompareWithText')) + call uniq(l:list, function('ale#util#LocItemCompareWithText')) + + return l:list +endfunction + +function! ale#list#GetCombinedList() abort + let l:list = [] + + for l:info in values(g:ale_buffer_info) + call extend(l:list, l:info.loclist) + endfor + + return s:Deduplicate(l:list) +endfunction + +function! s:FixList(buffer, list) abort + let l:format = ale#Var(a:buffer, 'loclist_msg_format') + let l:new_list = [] + + for l:item in a:list + let l:fixed_item = copy(l:item) + + let l:fixed_item.text = ale#GetLocItemMessage(l:item, l:format) + + if l:item.bufnr == -1 + " If the buffer number is invalid, remove it. + call remove(l:fixed_item, 'bufnr') + endif + + call add(l:new_list, l:fixed_item) + endfor + + return l:new_list +endfunction + +function! s:WinFindBuf(buffer) abort + return exists('*win_findbuf') ? win_findbuf(str2nr(a:buffer)) : [0] +endfunction + +function! s:SetListsImpl(timer_id, buffer, loclist) abort + let l:title = expand('#' . a:buffer . ':p') + + if g:ale_set_quickfix + let l:quickfix_list = ale#list#GetCombinedList() + + if has('nvim') + call setqflist(s:FixList(a:buffer, l:quickfix_list), ' ', l:title) + else + call setqflist(s:FixList(a:buffer, l:quickfix_list)) + call setqflist([], 'r', {'title': l:title}) + endif + elseif g:ale_set_loclist + " If windows support is off, win_findbuf() may not exist. + " We'll set result in the current window, which might not be correct, + " but it's better than nothing. + let l:ids = s:WinFindBuf(a:buffer) + + let l:loclist = s:Deduplicate(a:loclist) + + for l:id in l:ids + if has('nvim') + call setloclist(l:id, s:FixList(a:buffer, l:loclist), ' ', l:title) + else + call setloclist(l:id, s:FixList(a:buffer, l:loclist)) + call setloclist(l:id, [], 'r', {'title': l:title}) + endif + endfor + endif + + " Save the current view before opening/closing any window + call setbufvar(a:buffer, 'ale_winview', winsaveview()) + + " Open a window to show the problems if we need to. + " + " ShouldOpen() checks if the current buffer has enough problems to be + " opened. + if s:ShouldOpen(a:buffer, len(a:loclist)) + let l:winnr = winnr() + let l:mode = mode() + + " open windows vertically instead of default horizontally + let l:open_type = '' + + if ale#Var(a:buffer, 'list_vertical') == 1 + let l:open_type = 'vert rightbelow ' + endif + + if g:ale_set_quickfix + if !ale#list#IsQuickfixOpen() + silent! execute l:open_type . 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) + endif + elseif g:ale_set_loclist + silent! execute l:open_type . 'lopen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) + endif + + " If focus changed, restore it (jump to the last window). + if l:winnr isnot# winnr() + wincmd p + endif + + " Return to original mode when applicable + if mode() != l:mode + if l:mode is? 'v' || l:mode is# "\" + " Reset our last visual selection + normal! gv + elseif l:mode is? 's' || l:mode is# "\" + " Reset our last character selection + normal! "\" + endif + endif + + call s:RestoreViewIfNeeded(a:buffer) + endif + + " If ALE isn't currently checking for more problems, close the window if + " needed now. This check happens inside of this timer function, so + " the window can be closed reliably. + if !ale#engine#IsCheckingBuffer(a:buffer) + call s:CloseWindowIfNeeded(a:buffer) + endif +endfunction + +" Try to restore the window view after closing any of the lists to avoid making +" the it moving around, especially useful when on insert mode +function! s:RestoreViewIfNeeded(buffer) abort + let l:saved_view = getbufvar(a:buffer, 'ale_winview', {}) + + " Saved view is empty, can't do anything + if empty(l:saved_view) + return + endif + + " Check whether the cursor has moved since linting was actually requested. If + " the user has indeed moved lines, do nothing + let l:current_view = winsaveview() + + if l:current_view['lnum'] != l:saved_view['lnum'] + return + endif + + " Anchor view by topline if the list is set to open horizontally + if ale#Var(a:buffer, 'list_vertical') == 0 + call winrestview({'topline': l:saved_view['topline']}) + endif +endfunction + +function! ale#list#SetLists(buffer, loclist) abort + if get(g:, 'ale_set_lists_synchronously') == 1 + \|| getbufvar(a:buffer, 'ale_save_event_fired', 0) + " Update lists immediately if running a test synchronously, or if the + " buffer was saved. + " + " The lists need to be updated immediately when saving a buffer so + " that we can reliably close window automatically, if so configured. + call s:SetListsImpl(-1, a:buffer, a:loclist) + else + call ale#util#StartPartialTimer( + \ 0, + \ function('s:SetListsImpl'), + \ [a:buffer, a:loclist], + \) + endif +endfunction + +function! ale#list#ForcePopulateErrorList(populate_quickfix) abort + let l:quickfix_bak = g:ale_set_quickfix + let g:ale_set_quickfix = a:populate_quickfix + let l:loclist_bak = g:ale_set_loclist + let g:ale_set_loclist = !a:populate_quickfix + let l:open_list_bak = g:ale_open_list + let g:ale_open_list = 1 + + let l:buffer = bufnr('') + let l:loclist = get(g:ale_buffer_info, l:buffer, {'loclist': []}).loclist + call s:SetListsImpl(-1, l:buffer, l:loclist) + + let g:ale_open_list = l:open_list_bak + let g:ale_set_loclist = l:loclist_bak + let g:ale_set_quickfix = l:quickfix_bak +endfunction + +function! s:CloseWindowIfNeeded(buffer) abort + if ale#Var(a:buffer, 'keep_list_window_open') || s:ShouldClose(a:buffer) + return + endif + + let l:did_close_any_list = 0 + + try + " Only close windows if the quickfix list or loclist is completely empty, + " including errors set through other means. + if g:ale_set_quickfix + if empty(getqflist()) + cclose + let l:did_close_any_list = 1 + endif + else + let l:win_ids = s:WinFindBuf(a:buffer) + + for l:win_id in l:win_ids + if g:ale_set_loclist && empty(getloclist(l:win_id)) + lclose + let l:did_close_any_list = 1 + endif + endfor + endif + " Ignore 'Cannot close last window' errors. + catch /E444/ + endtry + + if l:did_close_any_list + call s:RestoreViewIfNeeded(a:buffer) + endif +endfunction diff --git a/autoload/ale/loclist_jumping.vim b/autoload/ale/loclist_jumping.vim new file mode 100644 index 00000000..55097d12 --- /dev/null +++ b/autoload/ale/loclist_jumping.vim @@ -0,0 +1,163 @@ +" Author: w0rp +" Description: This file implements functions for jumping around in a file +" based on ALE's internal loclist. + +" Search for the nearest line either before or after the current position +" in the loclist. The argument 'wrap' can be passed to enable wrapping +" around the end of the list. +" +" 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 + let l:buffer = bufnr('') + 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) + endif + + " Look for items before or after the current position. + for l:item in l:loclist + " Compare the cursor with a item where the column number is bounded, + " such that it's possible for the cursor to actually be on the given + " column number, without modifying the cursor number we return. This + " will allow us to move through matches, but still let us move the + " cursor to a line without changing the column, in some cases. + let l:cmp_value = ale#util#LocItemCompare( + \ { + \ 'bufnr': l:buffer, + \ 'lnum': l:item.lnum, + \ 'col': min([ + \ max([l:item.col, 1]), + \ max([len(getline(l:item.lnum)), 1]), + \ ]), + \ }, + \ l:search_item + \) + + 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# '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 + 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, ...) 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` + call cursor([l:nearest[0], max([l:nearest[1], 1])]) + 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': []}) + let l:loclist = filter(copy(l:info.loclist), 'v:val.bufnr == l:buffer') + + if empty(l:loclist) + return + endif + + let l:item = l:loclist[a:index] + + if !empty(l:item) + normal! m` + call cursor([l:item.lnum, l:item.col]) + endif +endfunction diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim new file mode 100644 index 00000000..07b073f8 --- /dev/null +++ b/autoload/ale/lsp.vim @@ -0,0 +1,851 @@ +" Author: w0rp +" Description: Language Server Protocol client code + +" A Dictionary for tracking connections. +let s:connections = get(s:, 'connections', {}) +let g:ale_lsp_next_message_id = 1 + +" Given an id, which can be an executable or address, a project path, +" and a language string or (bufnr) -> string function +" create a new connection if needed. Return a unique ID for the connection. +function! ale#lsp#Register(executable_or_address, project, language, init_options) abort + let l:conn_id = a:executable_or_address . ':' . a:project + + if !has_key(s:connections, l:conn_id) + " is_tsserver: 1 if the connection is for tsserver. + " data: The message data received so far. + " root: The project root. + " open_documents: A Dictionary mapping buffers to b:changedtick, keeping + " track of when documents were opened, and when we last changed them. + " initialized: 0 if the connection is ready, 1 otherwise. + " init_request_id: The ID for the init request. + " 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. + " capabilities_queue: The list of callbacks to call with capabilities. + " capabilities: Features the server supports. + let s:connections[l:conn_id] = { + \ 'id': l:conn_id, + \ 'is_tsserver': 0, + \ 'data': '', + \ 'root': a:project, + \ 'language': a:language, + \ 'open_documents': {}, + \ 'initialized': 0, + \ 'init_request_id': 0, + \ 'init_options': a:init_options, + \ 'config': {}, + \ 'callback_list': [], + \ 'init_queue': [], + \ 'capabilities': { + \ 'hover': 0, + \ 'rename': 0, + \ 'filerename': 0, + \ 'references': 0, + \ 'completion': 0, + \ 'completion_trigger_characters': [], + \ 'definition': 0, + \ 'typeDefinition': 0, + \ 'implementation': 0, + \ 'pull_model': 0, + \ 'symbol_search': 0, + \ 'code_actions': 0, + \ 'did_save': 0, + \ 'includeText': 0, + \ }, + \} + endif + + return l:conn_id +endfunction + +" Remove an LSP connection with a given ID. This is only for tests. +function! ale#lsp#RemoveConnectionWithID(id) abort + if has_key(s:connections, a:id) + call remove(s:connections, a:id) + endif +endfunction + +function! ale#lsp#ResetConnections() abort + let s:connections = {} +endfunction + +" Used only in tests. +function! ale#lsp#GetConnections() abort + " This command will throw from the sandbox. + let &l:equalprg=&l:equalprg + + 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, {}) + + if !empty(l:conn) + let l:conn.open_documents[a:buffer] = -1 + endif +endfunction + +function! ale#lsp#GetNextMessageID() abort + " Use the current ID + let l:id = g:ale_lsp_next_message_id + + " Increment the ID variable. + let g:ale_lsp_next_message_id += 1 + + " When the ID overflows, reset it to 1. By the time we hit the initial ID + " again, the messages will be long gone. + if g:ale_lsp_next_message_id < 1 + let g:ale_lsp_next_message_id = 1 + endif + + return l:id +endfunction + +" TypeScript messages use a different format. +function! s:CreateTSServerMessageData(message) abort + let l:is_notification = a:message[0] + + let l:obj = { + \ 'seq': v:null, + \ 'type': 'request', + \ 'command': a:message[1][3:], + \} + + if !l:is_notification + let l:obj.seq = ale#lsp#GetNextMessageID() + endif + + if len(a:message) > 2 + let l:obj.arguments = a:message[2] + endif + + let l:data = json_encode(l:obj) . "\n" + + return [l:is_notification ? 0 : l:obj.seq, l:data] +endfunction + +" Given a List of one or two items, [method_name] or [method_name, params], +" return a List containing [message_id, message_data] +function! ale#lsp#CreateMessageData(message) abort + if a:message[1][:2] is# 'ts@' + return s:CreateTSServerMessageData(a:message) + endif + + let l:is_notification = a:message[0] + + let l:obj = { + \ 'method': a:message[1], + \ 'jsonrpc': '2.0', + \} + + if !l:is_notification + let l:obj.id = ale#lsp#GetNextMessageID() + endif + + if len(a:message) > 2 + let l:obj.params = a:message[2] + endif + + let l:body = json_encode(l:obj) + let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body + + return [l:is_notification ? 0 : l:obj.id, l:data] +endfunction + +function! ale#lsp#ReadMessageData(data) abort + let l:response_list = [] + let l:remainder = a:data + + while 1 + " Look for the end of the HTTP headers + let l:body_start_index = matchend(l:remainder, "\r\n\r\n") + + if l:body_start_index < 0 + " No header end was found yet. + break + endif + + " Parse the Content-Length header. + let l:header_data = l:remainder[:l:body_start_index - 4] + let l:length_match = matchlist( + \ l:header_data, + \ '\vContent-Length: *(\d+)' + \) + + if empty(l:length_match) + throw "Invalid JSON-RPC header:\n" . l:header_data + endif + + " Split the body and the remainder of the text. + let l:remainder_start_index = l:body_start_index + str2nr(l:length_match[1]) + + if len(l:remainder) < l:remainder_start_index + " We don't have enough data yet. + break + endif + + let l:body = l:remainder[l:body_start_index : l:remainder_start_index - 1] + let l:remainder = l:remainder[l:remainder_start_index :] + + " Parse the JSON object and add it to the list. + call add(l:response_list, json_decode(l:body)) + endwhile + + return [l:remainder, l:response_list] +endfunction + +" Update capabilities from the server, so we know which features the server +" supports. +function! ale#lsp#UpdateCapabilities(conn_id, capabilities) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if empty(l:conn) + return + endif + + if type(a:capabilities) isnot v:t_dict + return + endif + + if get(a:capabilities, 'hoverProvider') is v:true + let l:conn.capabilities.hover = 1 + endif + + if type(get(a:capabilities, 'hoverProvider')) is v:t_dict + let l:conn.capabilities.hover = 1 + endif + + if get(a:capabilities, 'referencesProvider') is v:true + let l:conn.capabilities.references = 1 + endif + + if type(get(a:capabilities, 'referencesProvider')) is v:t_dict + let l:conn.capabilities.references = 1 + endif + + if get(a:capabilities, 'renameProvider') is v:true + let l:conn.capabilities.rename = 1 + endif + + if type(get(a:capabilities, 'renameProvider')) is v:t_dict + let l:conn.capabilities.rename = 1 + endif + + if get(a:capabilities, 'codeActionProvider') is v:true + let l:conn.capabilities.code_actions = 1 + endif + + if type(get(a:capabilities, 'codeActionProvider')) is v:t_dict + let l:conn.capabilities.code_actions = 1 + endif + + if !empty(get(a:capabilities, 'completionProvider')) + let l:conn.capabilities.completion = 1 + endif + + if type(get(a:capabilities, 'completionProvider')) is v:t_dict + let l:chars = get(a:capabilities.completionProvider, 'triggerCharacters') + + if type(l:chars) is v:t_list + let l:conn.capabilities.completion_trigger_characters = l:chars + endif + endif + + if get(a:capabilities, 'definitionProvider') is v:true + let l:conn.capabilities.definition = 1 + endif + + if type(get(a:capabilities, 'definitionProvider')) is v:t_dict + let l:conn.capabilities.definition = 1 + endif + + if get(a:capabilities, 'typeDefinitionProvider') is v:true + let l:conn.capabilities.typeDefinition = 1 + endif + + if type(get(a:capabilities, 'typeDefinitionProvider')) is v:t_dict + let l:conn.capabilities.typeDefinition = 1 + endif + + if get(a:capabilities, 'implementationProvider') is v:true + let l:conn.capabilities.implementation = 1 + endif + + if type(get(a:capabilities, 'implementationProvider')) is v:t_dict + let l:conn.capabilities.implementation = 1 + endif + + " Check if the language server supports pull model diagnostics. + if type(get(a:capabilities, 'diagnosticProvider')) is v:t_dict + if type(get(a:capabilities.diagnosticProvider, 'interFileDependencies')) is v:t_bool + let l:conn.capabilities.pull_model = 1 + endif + endif + + if get(a:capabilities, 'workspaceSymbolProvider') is v:true + let l:conn.capabilities.symbol_search = 1 + endif + + if type(get(a:capabilities, 'workspaceSymbolProvider')) is v:t_dict + let l:conn.capabilities.symbol_search = 1 + endif + + if type(get(a:capabilities, 'textDocumentSync')) is v:t_dict + let l:syncOptions = get(a:capabilities, 'textDocumentSync') + + if get(l:syncOptions, 'save') is v:true + let l:conn.capabilities.did_save = 1 + endif + + if type(get(l:syncOptions, 'save')) is v:t_dict + let l:conn.capabilities.did_save = 1 + + let l:saveOptions = get(l:syncOptions, 'save') + + if get(l:saveOptions, 'includeText') is v:true + let l:conn.capabilities.includeText = 1 + endif + endif + endif +endfunction + +" Update a connection's configuration dictionary and notify LSP servers +" of any changes since the last update. Returns 1 if a configuration +" update was sent; otherwise 0 will be returned. +function! ale#lsp#UpdateConfig(conn_id, buffer, config) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if empty(l:conn) || a:config ==# l:conn.config " no-custom-checks + return 0 + endif + + let l:conn.config = a:config + let l:message = ale#lsp#message#DidChangeConfiguration(a:buffer, a:config) + + call ale#lsp#Send(a:conn_id, l:message) + + return 1 +endfunction + +function! ale#lsp#CallInitCallbacks(conn_id) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if !empty(l:conn) + " Ensure the connection is marked as initialized. + " For integration with Neovim's LSP tooling this ensures immediately + " call OnInit functions in Vim after the `on_init` callback is called. + let l:conn.initialized = 1 + + " Call capabilities callbacks queued for the project. + for l:Callback in l:conn.init_queue + call l:Callback() + endfor + + let l:conn.init_queue = [] + endif +endfunction + +function! ale#lsp#HandleInitResponse(conn, response) abort + if get(a:response, 'method', '') is# 'initialize' + let a:conn.initialized = 1 + elseif type(get(a:response, 'result')) is v:t_dict + \&& has_key(a:response.result, 'capabilities') + call ale#lsp#UpdateCapabilities(a:conn.id, a:response.result.capabilities) + + let a:conn.initialized = 1 + endif + + if !a:conn.initialized + return + endif + + " The initialized message must be sent before everything else. + call ale#lsp#Send(a:conn.id, ale#lsp#message#Initialized()) + + call ale#lsp#CallInitCallbacks(a:conn.id) +endfunction + +function! ale#lsp#HandleMessage(conn_id, message) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if empty(l:conn) + return + endif + + if type(a:message) isnot v:t_string + " Ignore messages that aren't strings. + return + endif + + let l:conn.data .= a:message + + " Parse the objects now if we can, and keep the remaining text. + let [l:conn.data, l:response_list] = ale#lsp#ReadMessageData(l:conn.data) + + " Look for initialize responses first. + if !l:conn.initialized + for l:response in l:response_list + call ale#lsp#HandleInitResponse(l:conn, l:response) + endfor + endif + + " If the connection is marked as initialized, call the callbacks with the + " responses. + if l:conn.initialized + for l:response in l:response_list + " Call all of the registered handlers with the response. + for l:Callback in l:conn.callback_list + call ale#util#GetFunction(l:Callback)(a:conn_id, l:response) + endfor + endfor + endif +endfunction + +" Handle a JSON response from a language server. +" This is called from Lua for integration with Neovim's LSP API. +function! ale#lsp#HandleResponse(conn_id, response) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if empty(l:conn) + return + endif + + for l:Callback in l:conn.callback_list + call ale#util#GetFunction(l:Callback)(a:conn_id, a:response) + endfor +endfunction + +" Given a connection ID, mark it as a tsserver connection, so it will be +" handled that way. +function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort + let l:conn = s:connections[a:conn_id] + let l:conn.is_tsserver = 1 + let l:conn.initialized = 1 + " Set capabilities which are supported by tsserver. + let l:conn.capabilities.hover = 1 + let l:conn.capabilities.references = 1 + let l:conn.capabilities.completion = 1 + let l:conn.capabilities.completion_trigger_characters = ['.'] + let l:conn.capabilities.definition = 1 + let l:conn.capabilities.typeDefinition = 1 + let l:conn.capabilities.implementation = 1 + let l:conn.capabilities.symbol_search = 1 + let l:conn.capabilities.rename = 1 + let l:conn.capabilities.filerename = 1 + let l:conn.capabilities.code_actions = 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, + \ { + \ 'workspace': { + \ 'applyEdit': v:false, + \ 'didChangeConfiguration': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'symbol': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'workspaceFolders': v:false, + \ 'configuration': v:false, + \ }, + \ 'textDocument': { + \ 'synchronization': { + \ 'dynamicRegistration': v:false, + \ 'willSave': v:false, + \ 'willSaveWaitUntil': v:false, + \ 'didSave': v:true, + \ }, + \ 'completion': { + \ 'dynamicRegistration': v:false, + \ 'completionItem': { + \ 'snippetSupport': v:false, + \ 'commitCharactersSupport': v:false, + \ 'documentationFormat': ['plaintext', 'markdown'], + \ 'deprecatedSupport': v:false, + \ 'preselectSupport': v:false, + \ }, + \ 'contextSupport': v:false, + \ }, + \ 'hover': { + \ 'dynamicRegistration': v:false, + \ 'contentFormat': ['plaintext', 'markdown'], + \ }, + \ 'references': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'documentSymbol': { + \ 'dynamicRegistration': v:false, + \ 'hierarchicalDocumentSymbolSupport': v:false, + \ }, + \ 'definition': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport': v:false, + \ }, + \ 'typeDefinition': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'implementation': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport': v:false, + \ }, + \ 'diagnostic': { + \ 'dynamicRegistration': v:true, + \ 'relatedDocumentSupport': v:true, + \ }, + \ 'publishDiagnostics': { + \ 'relatedInformation': v:true, + \ }, + \ 'codeAction': { + \ 'dynamicRegistration': v:false, + \ 'codeActionLiteralSupport': { + \ 'codeActionKind': { + \ 'valueSet': [] + \ } + \ } + \ }, + \ 'rename': { + \ 'dynamicRegistration': v:false, + \ }, + \ }, + \ }, + \ ), + \) + 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 g:ale_use_neovim_lsp_api && !l:conn.is_tsserver + " For Windows from 'cmd /s/c "foo bar"' we need 'foo bar' + let l:lsp_cmd = has('win32') && type(a:command) is v:t_string + \ ? ['cmd', '/s/c/', a:command[10:-2]] + \ : a:command + + " Always call lsp.start, which will either create or re-use a + " connection. We'll set `attach` to `false` so we can later use + " our OpenDocument function to attach the buffer separately. + let l:client_id = luaeval('require("ale.lsp").start(_A)', { + \ 'name': a:conn_id, + \ 'cmd': l:lsp_cmd, + \ 'root_dir': l:conn.root, + \ 'init_options': l:conn.init_options, + \}) + + if l:client_id > 0 + let l:conn.client_id = l:client_id + endif + + return l:client_id > 0 + endif + + 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)}, + \ 'exit_cb': { -> ale#lsp#Stop(a:conn_id) }, + \} + + 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 + + if l:job_id > 0 + let l:conn.job_id = l:job_id + endif + + if l:started && !l:conn.is_tsserver + let l:conn.initialized = 0 + call s:SendInitMessage(l:conn) + endif + + return l:job_id > 0 +endfunction + +" Split an address into [host, port]. +" The port will either be a number or v:null. +function! ale#lsp#SplitAddress(address) abort + let l:port_match = matchlist(a:address, '\v:(\d+)$') + + if !empty(l:port_match) + let l:host = a:address[:-len(l:port_match[1]) - 2] + let l:port = l:port_match[1] + 0 + + return [l:host, l:port ? l:port : v:null] + endif + + return [a:address, v:null] +endfunction + +" Connect to an LSP server via TCP. +" +" 1 will be returned if the connection is running, or 0 if the connection could +" not be opened. +function! ale#lsp#ConnectToAddress(conn_id, address) abort + let l:conn = s:connections[a:conn_id] + let l:started = 0 + + if g:ale_use_neovim_lsp_api && !l:conn.is_tsserver + let [l:host, l:port] = ale#lsp#SplitAddress(a:address) + + let l:client_id = luaeval('require("ale.lsp").start(_A)', { + \ 'name': a:conn_id, + \ 'host': l:host, + \ 'port': l:port, + \ 'root_dir': l:conn.root, + \ 'init_options': l:conn.init_options, + \}) + + if l:client_id > 0 + let l:conn.client_id = l:client_id + endif + + return l:client_id > 0 + elseif !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 + + if l:channel_id >= 0 + let l:conn.channel_id = l:channel_id + endif + + if l:started + call s:SendInitMessage(l:conn) + endif + + return l:channel_id >= 0 +endfunction + +" Given a connection ID and a callback, register that callback for handling +" messages if the connection exists. +function! ale#lsp#RegisterCallback(conn_id, callback) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if !empty(l:conn) + " Add the callback to the List if it's not there already. + call uniq(sort(add(l:conn.callback_list, a:callback))) + endif +endfunction + +" Stop a single LSP connection. +function! ale#lsp#Stop(conn_id) abort + if has_key(s:connections, a:conn_id) + let l:conn = remove(s:connections, a:conn_id) + + if has_key(l:conn, 'channel_id') + call ale#socket#Close(l:conn.channel_id) + elseif has_key(l:conn, 'job_id') + call ale#job#Stop(l:conn.job_id) + endif + endif +endfunction + +function! ale#lsp#CloseDocument(conn_id) abort +endfunction + +" Stop all LSP connections, closing all jobs and channels, and removing any +" queued messages. +function! ale#lsp#StopAll() abort + for l:conn_id in keys(s:connections) + call ale#lsp#Stop(l:conn_id) + endfor +endfunction + +function! s:SendMessageData(conn, data) abort + if has_key(a:conn, 'job_id') + call ale#job#SendRaw(a:conn.job_id, a:data) + elseif has_key(a:conn, 'channel_id') && ale#socket#IsOpen(a:conn.channel_id) + " Send the message to the server + call ale#socket#Send(a:conn.channel_id, a:data) + else + return 0 + endif + + return 1 +endfunction + +" Send a message to an LSP server. +" Notifications do not need to be handled. +" +" Returns -1 when a message is sent, but no response is expected +" 0 when the message is not sent and +" >= 1 with the message ID when a response is expected. +function! ale#lsp#Send(conn_id, message) abort + let l:conn = get(s:connections, a:conn_id, {}) + + if empty(l:conn) + return 0 + endif + + if !l:conn.initialized + throw 'LSP server not initialized yet!' + endif + + if g:ale_use_neovim_lsp_api && !l:conn.is_tsserver + return luaeval('require("ale.lsp").send_message(_A)', { + \ 'client_id': l:conn.client_id, + \ 'is_notification': a:message[0] == 1 ? v:true : v:false, + \ 'method': a:message[1], + \ 'params': get(a:message, 2, v:null) + \}) + endif + + let [l:id, l:data] = ale#lsp#CreateMessageData(a:message) + call s:SendMessageData(l:conn, l:data) + + return l:id == 0 ? -1 : l:id +endfunction + +function! ale#lsp#GetLanguage(conn_id, buffer) abort + let l:conn = get(s:connections, a:conn_id, {}) + let l:Language = get(l:conn, 'language') + + if empty(l:Language) + return getbufvar(a:buffer, '&filetype') + endif + + return type(l:Language) is v:t_func ? l:Language(a:buffer) : l:Language +endfunction + +" Notify LSP servers or tsserver if a document is opened, if needed. +" If a document is opened, 1 will be returned, otherwise 0 will be returned. +function! ale#lsp#OpenDocument(conn_id, buffer) abort + let l:conn = get(s:connections, a:conn_id, {}) + let l:opened = 0 + + if !empty(l:conn) && !has_key(l:conn.open_documents, a:buffer) + if l:conn.is_tsserver + let l:message = ale#lsp#tsserver_message#Open(a:buffer) + call ale#lsp#Send(a:conn_id, l:message) + elseif g:ale_use_neovim_lsp_api + call luaeval('require("ale.lsp").buf_attach(_A)', { + \ 'bufnr': a:buffer, + \ 'client_id': l:conn.client_id, + \}) + else + let l:language_id = ale#lsp#GetLanguage(a:conn_id, a:buffer) + let l:message = ale#lsp#message#DidOpen(a:buffer, l:language_id) + call ale#lsp#Send(a:conn_id, l:message) + endif + + let l:conn.open_documents[a:buffer] = getbufvar(a:buffer, 'changedtick') + let l:opened = 1 + endif + + return l:opened +endfunction + +" Notify LSP servers or tsserver that a document is closed, if opened before. +" If a document is closed, 1 will be returned, otherwise 0 will be returned. +" +" Only the buffer number is required here. A message will be sent to every +" language server that was notified previously of the document being opened. +function! ale#lsp#CloseDocument(buffer) abort + let l:closed = 0 + + " The connection keys are sorted so the messages are easier to test, and + " so messages are sent in a consistent order. + for l:conn_id in sort(keys(s:connections)) + let l:conn = s:connections[l:conn_id] + + if l:conn.initialized && has_key(l:conn.open_documents, a:buffer) + if l:conn.is_tsserver + let l:message = ale#lsp#tsserver_message#Close(a:buffer) + call ale#lsp#Send(l:conn_id, l:message) + elseif g:ale_use_neovim_lsp_api + call luaeval('require("ale.lsp").buf_detach(_A)', { + \ 'bufnr': a:buffer, + \ 'client_id': l:conn.client_id, + \}) + else + let l:message = ale#lsp#message#DidClose(a:buffer) + call ale#lsp#Send(l:conn_id, l:message) + endif + + call remove(l:conn.open_documents, a:buffer) + let l:closed = 1 + endif + endfor + + return l:closed +endfunction + +" Notify LSP servers or tsserver that a document has changed, if needed. +" If a notification is sent, 1 will be returned, otherwise 0 will be returned. +function! ale#lsp#NotifyForChanges(conn_id, buffer) abort + let l:conn = get(s:connections, a:conn_id, {}) + let l:notified = 0 + + if !empty(l:conn) && has_key(l:conn.open_documents, a:buffer) + let l:new_tick = getbufvar(a:buffer, 'changedtick') + + if l:conn.open_documents[a:buffer] < l:new_tick + if l:conn.is_tsserver + let l:message = ale#lsp#tsserver_message#Change(a:buffer) + else + let l:message = ale#lsp#message#DidChange(a:buffer) + endif + + call ale#lsp#Send(a:conn_id, l:message) + let l:conn.open_documents[a:buffer] = l:new_tick + let l:notified = 1 + endif + endif + + return l:notified +endfunction + +" 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 + + return l:conn.capabilities[a:capability] +endfunction diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim new file mode 100644 index 00000000..72ed7d59 --- /dev/null +++ b/autoload/ale/lsp/message.vim @@ -0,0 +1,216 @@ +" Author: w0rp +" Description: Language Server Protocol message implementations +" +" 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 +" undo, with incrementing version numbers, so we'll just use one incrementing +" ID for everything. +function! ale#lsp#message#GetNextVersionID() abort + " Use the current ID + let l:id = g:ale_lsp_next_version_id + + " Increment the ID variable. + let g:ale_lsp_next_version_id += 1 + + " When the ID overflows, reset it to 1. By the time we hit the initial ID + " again, the messages will be long gone. + if g:ale_lsp_next_version_id < 1 + let g:ale_lsp_next_version_id = 1 + endif + + return l:id +endfunction + +function! ale#lsp#message#Initialize(root_path, options, capabilities) abort + " NOTE: rootPath is deprecated in favour of rootUri + return [0, 'initialize', { + \ 'processId': getpid(), + \ 'rootPath': a:root_path, + \ 'capabilities': a:capabilities, + \ 'initializationOptions': a:options, + \ 'rootUri': ale#util#ToURI(a:root_path), + \}] +endfunction + +function! ale#lsp#message#Initialized() abort + return [1, 'initialized', {}] +endfunction + +function! ale#lsp#message#Shutdown() abort + return [0, 'shutdown'] +endfunction + +function! ale#lsp#message#Exit() abort + return [1, 'exit'] +endfunction + +function! ale#lsp#message#DidOpen(buffer, language_id) abort + return [1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ 'languageId': a:language_id, + \ 'version': ale#lsp#message#GetNextVersionID(), + \ 'text': ale#util#GetBufferContents(a:buffer), + \ }, + \}] +endfunction + +function! ale#lsp#message#DidChange(buffer) abort + " For changes, we simply send the full text of the document to the server. + return [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ 'version': ale#lsp#message#GetNextVersionID(), + \ }, + \ 'contentChanges': [{'text': ale#util#GetBufferContents(a:buffer)}] + \}] +endfunction + +function! ale#lsp#message#DidSave(buffer, include_text) abort + let l:response = [1, 'textDocument/didSave', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \}] + + if a:include_text + let l:response[2].textDocument.version = ale#lsp#message#GetNextVersionID() + let l:response[2].text = ale#util#GetBufferContents(a:buffer) + endif + + return l:response +endfunction + +function! ale#lsp#message#DidClose(buffer) abort + return [1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \}] +endfunction + +let s:COMPLETION_TRIGGER_INVOKED = 1 +let s:COMPLETION_TRIGGER_CHARACTER = 2 + +function! ale#lsp#message#Completion(buffer, line, column, trigger_character) abort + let l:message = [0, 'textDocument/completion', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \}] + + if !empty(a:trigger_character) + let l:message[2].context = { + \ 'triggerKind': s:COMPLETION_TRIGGER_CHARACTER, + \ 'triggerCharacter': a:trigger_character, + \} + endif + + return l:message +endfunction + +function! ale#lsp#message#Definition(buffer, line, column) abort + return [0, 'textDocument/definition', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ '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#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \}] +endfunction + +function! ale#lsp#message#Implementation(buffer, line, column) abort + return [0, 'textDocument/implementation', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \}] +endfunction + +function! ale#lsp#message#References(buffer, line, column) abort + return [0, 'textDocument/references', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \ 'context': {'includeDeclaration': v:false}, + \}] +endfunction + +function! ale#lsp#message#Symbol(query) abort + return [0, 'workspace/symbol', { + \ 'query': a:query, + \}] +endfunction + +function! ale#lsp#message#Hover(buffer, line, column) abort + return [0, 'textDocument/hover', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \}] +endfunction + +function! ale#lsp#message#DidChangeConfiguration(buffer, config) abort + return [1, 'workspace/didChangeConfiguration', { + \ 'settings': a:config, + \}] +endfunction + +function! ale#lsp#message#Rename(buffer, line, column, new_name) abort + return [0, 'textDocument/rename', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \ 'newName': a:new_name, + \}] +endfunction + +function! ale#lsp#message#CodeAction(buffer, line, column, end_line, end_column, diagnostics) abort + return [0, 'textDocument/codeAction', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'range': { + \ 'start': {'line': a:line - 1, 'character': a:column - 1}, + \ 'end': {'line': a:end_line - 1, 'character': a:end_column}, + \ }, + \ 'context': { + \ 'diagnostics': a:diagnostics + \ }, + \}] +endfunction + +function! ale#lsp#message#Diagnostic(buffer) abort + return [0, 'textDocument/diagnostic', { + \ 'textDocument': { + \ 'uri': ale#util#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \}] +endfunction + +function! ale#lsp#message#ExecuteCommand(command, arguments) abort + return [0, 'workspace/executeCommand', { + \ 'command': a:command, + \ 'arguments': a:arguments, + \}] +endfunction diff --git a/autoload/ale/lsp/reset.vim b/autoload/ale/lsp/reset.vim new file mode 100644 index 00000000..1801db01 --- /dev/null +++ b/autoload/ale/lsp/reset.vim @@ -0,0 +1,92 @@ +" Author: w0rp +" Description: Functions for resetting LSP servers. + +function! s:Message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +" Stop all LSPs and remove all of the data for them. +function! ale#lsp#reset#StopAllLSPs() abort + call ale#lsp#StopAll() + + if exists('*ale#definition#ClearLSPData') + " Clear the go to definition mapping for everything. + call ale#definition#ClearLSPData() + endif + + if exists('*ale#lsp_linter#ClearLSPData') + " Clear the mapping for connections, etc. + call ale#lsp_linter#ClearLSPData() + + " Remove the problems for all of the LSP linters in every buffer. + for l:buffer_string in keys(g:ale_buffer_info) + let l:buffer = str2nr(l:buffer_string) + + " Non-ignored and disabled linters are included here so we can + " clear results for them after we ignore or disable them. + for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype')) + if !empty(l:linter.lsp) + call ale#engine#HandleLoclist(l:linter.name, l:buffer, [], 0) + endif + endfor + endfor + endif +endfunction + +function! ale#lsp#reset#Complete(arg, line, pos) abort + let l:linter_map = ale#lsp_linter#GetLSPLinterMap() + let l:candidates = map(values(l:linter_map), {_, linter -> linter.name}) + call uniq(sort(l:candidates)) + call filter(l:candidates, {_, name -> name =~? a:arg}) + + return l:candidates +endfunction + +function! ale#lsp#reset#StopLSP(name, bang) abort + let l:linter_map = ale#lsp_linter#GetLSPLinterMap() + let l:matched = filter( + \ items(l:linter_map), + \ {_, item -> item[1].name is# a:name} + \) + + if empty(l:matched) + if a:bang isnot# '!' + call s:Message('No running language server with name: ' . a:name) + endif + + return + endif + + " Stop LSP connections first. + for [l:conn_id, l:linter] in l:matched + call ale#lsp#Stop(l:conn_id) + endfor + + if exists('*ale#definition#ClearLSPData') + " Clear the go to definition mapping for everything. + call ale#definition#ClearLSPData() + endif + + " Remove connections from the lsp_linter map. + for [l:conn_id, l:linter] in l:matched + call remove(l:linter_map, l:conn_id) + endfor + + " Remove the problems for the LSP linters in every buffer. + for [l:buffer_string, l:info] in items(g:ale_buffer_info) + let l:buffer = str2nr(l:buffer_string) + let l:should_clear_buffer = 0 + + for l:item in l:info.loclist + if l:item.linter_name is# a:name + let l:should_clear_buffer = 1 + + break + endif + endfor + + if l:should_clear_buffer + call ale#engine#HandleLoclist(a:name, l:buffer, [], 0) + endif + endfor +endfunction diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim new file mode 100644 index 00000000..85ac9e69 --- /dev/null +++ b/autoload/ale/lsp/response.vim @@ -0,0 +1,152 @@ +" Author: w0rp +" Description: Parsing and transforming of LSP server responses. + +" Constants for error codes. +" Defined by JSON RPC +let s:PARSE_ERROR = -32700 +let s:INVALID_REQUEST = -32600 +let s:METHOD_NOT_FOUND = -32601 +let s:INVALID_PARAMS = -32602 +let s:INTERNAL_ERROR = -32603 +let s:SERVER_ERROR_START = -32099 +let s:SERVER_ERROR_END = -32000 +let s:SERVER_NOT_INITIALIZED = -32002 +let s:UNKNOWN_ERROR_CODE = -32001 +" Defined by the protocol. +let s:REQUEST_CANCELLED = -32800 + +" Constants for message severity codes. +let s:SEVERITY_ERROR = 1 +let s:SEVERITY_WARNING = 2 +let s:SEVERITY_INFORMATION = 3 +let s:SEVERITY_HINT = 4 + +" Convert Diagnostic[] data from a language server to an ALE loclist. +function! ale#lsp#response#ReadDiagnostics(diagnostics) abort + let l:loclist = [] + + for l:diagnostic in a:diagnostics + let l:severity = get(l:diagnostic, 'severity', 0) + let l:loclist_item = { + \ '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, + \} + + if l:severity == s:SEVERITY_WARNING + let l:loclist_item.type = 'W' + elseif l:severity == s:SEVERITY_INFORMATION + " TODO: Use 'I' here in future. + let l:loclist_item.type = 'W' + elseif l:severity == s:SEVERITY_HINT + " TODO: Use 'H' here in future + let l:loclist_item.type = 'W' + endif + + if has_key(l:diagnostic, 'code') + if type(l:diagnostic.code) == v:t_string + let l:loclist_item.code = l:diagnostic.code + elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1 + let l:loclist_item.code = string(l:diagnostic.code) + let l:loclist_item.nr = l:diagnostic.code + endif + endif + + if has_key(l:diagnostic, 'relatedInformation') + \ && l:diagnostic.relatedInformation isnot v:null + let l:related = deepcopy(l:diagnostic.relatedInformation) + call map(l:related, {key, val -> + \ ale#util#ToResource(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 + \) + endif + + call add(l:loclist, l:loclist_item) + endfor + + return l:loclist +endfunction + +function! ale#lsp#response#ReadTSServerDiagnostics(response) abort + let l:loclist = [] + + for l:diagnostic in a:response.body.diagnostics + let l:loclist_item = { + \ 'text': l:diagnostic.text, + \ 'type': 'E', + \ 'lnum': l:diagnostic.start.line, + \ 'col': l:diagnostic.start.offset, + \ 'end_lnum': l:diagnostic.end.line, + \ 'end_col': l:diagnostic.end.offset - 1, + \} + + if has_key(l:diagnostic, 'code') + if type(l:diagnostic.code) == v:t_string + let l:loclist_item.code = l:diagnostic.code + elseif type(l:diagnostic.code) == v:t_number && l:diagnostic.code != -1 + let l:loclist_item.code = string(l:diagnostic.code) + let l:loclist_item.nr = l:diagnostic.code + endif + endif + + if get(l:diagnostic, 'category') is# 'warning' + let l:loclist_item.type = 'W' + endif + + if get(l:diagnostic, 'category') is# 'suggestion' + let l:loclist_item.type = 'I' + endif + + call add(l:loclist, l:loclist_item) + endfor + + return l:loclist +endfunction + +function! ale#lsp#response#GetErrorMessage(response) abort + if type(get(a:response, 'error', 0)) isnot v:t_dict + return '' + endif + + let l:code = get(a:response.error, 'code') + + " Only report things for these error codes. + if l:code isnot s:INVALID_PARAMS && l:code isnot s:INTERNAL_ERROR + return '' + endif + + let l:message = get(a:response.error, 'message', '') + + if empty(l:message) + return '' + endif + + " Include the traceback or error data as details, if present. + let l:error_data = get(a:response.error, 'data', {}) + + if type(l:error_data) is v:t_string + let l:message .= "\n" . l:error_data + elseif type(l:error_data) is v:t_dict + let l:traceback = get(l:error_data, 'traceback', []) + + if type(l:traceback) is v:t_list && !empty(l:traceback) + let l:message .= "\n" . join(l:traceback, "\n") + endif + endif + + return l:message +endfunction diff --git a/autoload/ale/lsp/tsserver_message.vim b/autoload/ale/lsp/tsserver_message.vim new file mode 100644 index 00000000..02e57899 --- /dev/null +++ b/autoload/ale/lsp/tsserver_message.vim @@ -0,0 +1,165 @@ +" Author: w0rp +" Description: tsserver message implementations +" +" Messages in this movie will be returned in the format +" [is_notification, command_name, params?] +" +" Every command must begin with the string 'ts@', which will be used to +" detect the different message format for tsserver, and this string will +" be removed from the actual command name, + +function! ale#lsp#tsserver_message#Open(buffer) abort + return [1, 'ts@open', {'file': expand('#' . a:buffer . ':p')}] +endfunction + +function! ale#lsp#tsserver_message#Close(buffer) abort + return [1, 'ts@close', {'file': expand('#' . a:buffer . ':p')}] +endfunction + +function! ale#lsp#tsserver_message#Change(buffer) abort + let l:lines = getbufline(a:buffer, 1, '$') + + " We will always use a very high endLine number, so we can delete + " lines from files. tsserver will gladly accept line numbers beyond the + " end. + return [1, 'ts@change', { + \ 'file': expand('#' . a:buffer . ':p'), + \ 'line': 1, + \ 'offset': 1, + \ 'endLine': 1073741824, + \ 'endOffset': 1, + \ 'insertString': join(l:lines, "\n") . "\n", + \}] +endfunction + +function! ale#lsp#tsserver_message#Geterr(buffer) abort + return [1, 'ts@geterr', {'files': [expand('#' . a:buffer . ':p')]}] +endfunction + +function! ale#lsp#tsserver_message#Completions( +\ buffer, line, column, prefix, include_external) abort + return [0, 'ts@completions', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \ 'prefix': a:prefix, + \ 'includeExternalModuleExports': a:include_external, + \}] +endfunction + +function! ale#lsp#tsserver_message#CompletionEntryDetails(buffer, line, column, entry_names) abort + return [0, 'ts@completionEntryDetails', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \ 'entryNames': a:entry_names, + \}] +endfunction + +function! ale#lsp#tsserver_message#Definition(buffer, line, column) abort + return [0, 'ts@definition', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction + +function! ale#lsp#tsserver_message#TypeDefinition(buffer, line, column) abort + return [0, 'ts@typeDefinition', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction + +function! ale#lsp#tsserver_message#Implementation(buffer, line, column) abort + return [0, 'ts@implementation', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction + +function! ale#lsp#tsserver_message#References(buffer, line, column) abort + return [0, 'ts@references', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction + +function! ale#lsp#tsserver_message#Quickinfo(buffer, line, column) abort + return [0, 'ts@quickinfo', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction + +function! ale#lsp#tsserver_message#Rename( +\ buffer, line, column, find_in_comments, find_in_strings) abort + return [0, 'ts@rename', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \ 'arguments': { + \ 'findInComments': a:find_in_comments, + \ 'findInStrings': a:find_in_strings, + \ } + \}] +endfunction + +function! ale#lsp#tsserver_message#GetEditsForFileRename( +\ oldFilePath, newFilePath) abort + return [0, 'ts@getEditsForFileRename', { + \ 'oldFilePath': a:oldFilePath, + \ 'newFilePath': a:newFilePath, + \}] +endfunction + +function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort + return [0, 'ts@organizeImports', { + \ 'scope': { + \ 'type': 'file', + \ 'args': { + \ 'file': expand('#' . a:buffer . ':p'), + \ }, + \ }, + \}] +endfunction + +function! ale#lsp#tsserver_message#GetCodeFixes(buffer, line, column, end_line, end_column, error_codes) abort + " The lines and columns are 1-based. + " The errors codes must be a list of tsserver error codes to fix. + return [0, 'ts@getCodeFixes', { + \ 'startLine': a:line, + \ 'startOffset': a:column, + \ 'endLine': a:end_line, + \ 'endOffset': a:end_column + 1, + \ 'file': expand('#' . a:buffer . ':p'), + \ 'errorCodes': a:error_codes, + \}] +endfunction + +function! ale#lsp#tsserver_message#GetApplicableRefactors(buffer, line, column, end_line, end_column) abort + " The arguments for this request can also be just 'line' and 'offset' + return [0, 'ts@getApplicableRefactors', { + \ 'startLine': a:line, + \ 'startOffset': a:column, + \ 'endLine': a:end_line, + \ 'endOffset': a:end_column + 1, + \ 'file': expand('#' . a:buffer . ':p'), + \}] +endfunction + +function! ale#lsp#tsserver_message#GetEditsForRefactor(buffer, line, column, end_line, end_column, refactor, action) abort + return [0, 'ts@getEditsForRefactor', { + \ 'startLine': a:line, + \ 'startOffset': a:column, + \ 'endLine': a:end_line, + \ 'endOffset': a:end_column + 1, + \ 'file': expand('#' . a:buffer . ':p'), + \ 'refactor': a:refactor, + \ 'action': a:action, + \}] +endfunction diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim new file mode 100644 index 00000000..3b3c403c --- /dev/null +++ b/autoload/ale/lsp_linter.vim @@ -0,0 +1,653 @@ +" Author: w0rp +" Description: Integration between linters and LSP/tsserver. + +" This code isn't loaded if a user never users LSP features or linters. + +" Associates LSP connection IDs with linter names. +if !has_key(s:, 'lsp_linter_map') + let s:lsp_linter_map = {} +endif + +" Clear LSP linter data for the linting engine. +function! ale#lsp_linter#ClearLSPData() abort + let s:lsp_linter_map = {} +endfunction + +" Only for internal use. +function! ale#lsp_linter#GetLSPLinterMap() abort + return s:lsp_linter_map +endfunction + +" Just for tests. +function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort + let s:lsp_linter_map = a:replacement_map +endfunction + +" A map for tracking URIs for diagnostic request IDs +if !has_key(s:, 'diagnostic_uri_map') + let s:diagnostic_uri_map = {} +endif + +" For internal use only. +function! ale#lsp_linter#ClearDiagnosticURIMap() abort + let s:diagnostic_uri_map = {} +endfunction + +" For internal use only. +function! ale#lsp_linter#GetDiagnosticURIMap() abort + return s:diagnostic_uri_map +endfunction + +" Just for tests. +function! ale#lsp_linter#SetDiagnosticURIMap(replacement_map) abort + let s:diagnostic_uri_map = a:replacement_map +endfunction + +" Get all enabled LSP linters. +" This list still includes linters ignored with `ale_linters_ignore`. +" +" `ale_linters_ignore` is designed to allow language servers to be used for +" their functionality while ignoring the diagnostics they return. +function! ale#lsp_linter#GetEnabled(buffer) abort + let l:filetype = getbufvar(a:buffer, '&filetype') + " Only LSP linters are included here. + let l:linters = filter(ale#linter#Get(l:filetype), '!empty(v:val.lsp)') + let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') + + " Only load code for ignoring linters if we need it. + if ( + \ l:disable_lsp is 1 + \ || l:disable_lsp is v:true + \ || (l:disable_lsp is# 'auto' && get(g:, 'lspconfig', 0)) + \) + let l:linters = ale#engine#ignore#Exclude( + \ l:filetype, + \ l:linters, + \ [], + \ l:disable_lsp, + \) + endif + + return l:linters +endfunction + +" Check if diagnostics for a particular linter should be ignored. +function! s:ShouldIgnoreDiagnostics(buffer, linter) abort + let l:config = ale#Var(a:buffer, 'linters_ignore') + let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') + + " Only load code for ignoring linters if we need it. + if ( + \ !empty(l:config) + \ || l:disable_lsp is 1 + \ || l:disable_lsp is v:true + \ || (l:disable_lsp is# 'auto' && get(g:, 'lspconfig', 0)) + \) + " Re-use the ignore implementation just for this linter. + return empty( + \ ale#engine#ignore#Exclude( + \ getbufvar(a:buffer, '&filetype'), + \ [a:linter], + \ l:config, + \ l:disable_lsp, + \ ) + \) + endif + + return 0 +endfunction + +" Handle LSP diagnostics for a given URI. +" The special value 'unchanged' can be used for diagnostics to indicate +" that diagnostics haven't changed since we last checked. +function! ale#lsp_linter#HandleLSPDiagnostics(conn_id, uri, diagnostics) abort + let l:linter = get(s:lsp_linter_map, a:conn_id) + + if empty(l:linter) + return + endif + + let l:filename = ale#util#ToResource(a:uri) + let l:escaped_name = escape( + \ fnameescape(l:filename), + \ has('win32') ? '^' : '^,}]' + \) + let l:buffer = bufnr('^' . l:escaped_name . '$') + let l:info = get(g:ale_buffer_info, l:buffer, {}) + + if empty(l:info) + return + endif + + if s:ShouldIgnoreDiagnostics(l:buffer, l:linter) + return + endif + + if a:diagnostics is# 'unchanged' + call ale#engine#MarkLinterInactive(l:info, l:linter) + else + let l:loclist = ale#lsp#response#ReadDiagnostics(a:diagnostics) + call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0) + endif +endfunction + +function! s:HandleTSServerDiagnostics(response, error_type) abort + " Re-create a fake linter object for tsserver. + let l:linter = { + \ 'name': 'tsserver', + \ 'aliases': [], + \ 'lsp': 'tsserver', + \} + let l:escaped_name = escape( + \ fnameescape(a:response.body.file), + \ has('win32') ? '^' : '^,}]' + \) + let l:buffer = bufnr('^' . l:escaped_name . '$') + let l:info = get(g:ale_buffer_info, l:buffer, {}) + + if empty(l:info) + return + endif + + call ale#engine#MarkLinterInactive(l:info, l:linter.name) + + if s:ShouldIgnoreDiagnostics(l:buffer, l:linter) + return + endif + + let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) + let l:no_changes = 0 + + " tsserver sends syntax and semantic errors in separate messages, so we + " have to collect the messages separately for each buffer and join them + " back together again. + if a:error_type is# 'syntax' + if len(l:thislist) is 0 && len(get(l:info, 'syntax_loclist', [])) is 0 + let l:no_changes = 1 + endif + + let l:info.syntax_loclist = l:thislist + elseif a:error_type is# 'semantic' + if len(l:thislist) is 0 && len(get(l:info, 'semantic_loclist', [])) is 0 + let l:no_changes = 1 + endif + + let l:info.semantic_loclist = l:thislist + else + if len(l:thislist) is 0 && len(get(l:info, 'suggestion_loclist', [])) is 0 + let l:no_changes = 1 + endif + + let l:info.suggestion_loclist = l:thislist + endif + + if l:no_changes + return + endif + + let l:loclist = get(l:info, 'semantic_loclist', []) + \ + get(l:info, 'suggestion_loclist', []) + \ + get(l:info, 'syntax_loclist', []) + + call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0) +endfunction + +function! s:HandleLSPErrorMessage(linter, response) abort + if !g:ale_history_enabled || !g:ale_history_log_output + return + endif + + if empty(a:linter) + return + endif + + let l:message = ale#lsp#response#GetErrorMessage(a:response) + + if empty(l:message) + return + endif + + call ale#lsp_linter#AddErrorMessage(a:linter.name, l:message) +endfunction + +function! ale#lsp_linter#AddErrorMessage(linter_name, message) abort + " This global variable is set here so we don't load the debugging.vim file + " until someone uses :ALEInfo. + let g:ale_lsp_error_messages = get(g:, 'ale_lsp_error_messages', {}) + + if !has_key(g:ale_lsp_error_messages, a:linter_name) + let g:ale_lsp_error_messages[a:linter_name] = [] + endif + + call add(g:ale_lsp_error_messages[a:linter_name], a:message) +endfunction + +function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort + let l:method = get(a:response, 'method', '') + + if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error') + let l:linter = get(s:lsp_linter_map, a:conn_id, {}) + + call s:HandleLSPErrorMessage(l:linter, a:response) + elseif l:method is# 'textDocument/publishDiagnostics' + let l:uri = a:response.params.uri + let l:diagnostics = a:response.params.diagnostics + + call ale#lsp_linter#HandleLSPDiagnostics(a:conn_id, l:uri, l:diagnostics) + elseif has_key(s:diagnostic_uri_map, get(a:response, 'id')) + let l:uri = remove(s:diagnostic_uri_map, a:response.id) + let l:diagnostics = a:response.result.kind is# 'unchanged' + \ ? 'unchanged' + \ : a:response.result.items + + call ale#lsp_linter#HandleLSPDiagnostics(a:conn_id, l:uri, l:diagnostics) + elseif l:method is# 'window/showMessage' + call ale#lsp_window#HandleShowMessage( + \ s:lsp_linter_map[a:conn_id].name, + \ g:ale_lsp_show_message_format, + \ a:response.params + \) + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'semanticDiag' + call s:HandleTSServerDiagnostics(a:response, 'semantic') + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'syntaxDiag' + call s:HandleTSServerDiagnostics(a:response, 'syntax') + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'suggestionDiag' + \&& get(g:, 'ale_lsp_suggestions') + call s:HandleTSServerDiagnostics(a:response, 'suggestion') + endif +endfunction + +function! ale#lsp_linter#GetOptions(buffer, linter) abort + if has_key(a:linter, 'initialization_options_callback') + 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 {} +endfunction + +function! ale#lsp_linter#GetConfig(buffer, linter) abort + if has_key(a:linter, 'lsp_config_callback') + 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 {} +endfunction + +function! ale#lsp_linter#FindProjectRoot(buffer, linter) abort + let l:buffer_ale_root = getbufvar(a:buffer, 'ale_root', {}) + + if type(l:buffer_ale_root) is v:t_string + return l:buffer_ale_root + endif + + " 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 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_root, a:linter.name) + let l:Root = g:ale_root[a:linter.name] + + if type(l:Root) is v:t_func + return l:Root(a:buffer) + else + return l:Root + endif + endif + + " Fall back to the linter-specific configuration + if has_key(a:linter, 'project_root') + let l:Root = a:linter.project_root + + return type(l:Root) is v:t_func ? l:Root(a:buffer) : l:Root + endif + + 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) + + call ale#lsp#UpdateConfig(l:conn_id, l:buffer, l:config) + + if ale#lsp#OpenDocument(l:conn_id, l:buffer) + if g:ale_history_enabled && !empty(l:command) + call ale#history#Add(l:buffer, 'started', l:conn_id, l:command) + endif + endif + + " The change message needs to be sent for tsserver before doing anything. + if a:linter.lsp is# 'tsserver' + call ale#lsp#NotifyForChanges(l:conn_id, l:buffer) + endif + + " Tell the relevant buffer that the LSP has started via an autocmd. + if l:buffer > 0 + if l:buffer == bufnr('') + silent doautocmd User ALELSPStarted + else + execute 'augroup ALELSPStartedGroup' . l:buffer + autocmd! + + execute printf( + \ 'autocmd BufEnter ' + \ . ' doautocmd User ALELSPStarted', + \ l:buffer + \) + + " Replicate ++once behavior for backwards compatibility. + execute printf( + \ 'autocmd BufEnter ' + \ . ' autocmd! ALELSPStartedGroup%d', + \ l:buffer, l:buffer + \) + augroup END + endif + 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:linter.language, + \ 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:linter.language, + \ 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:cwd = ale#linter#GetCwd(l:buffer, l:linter) + let l:command = ale#command#FormatCommand( + \ l:buffer, + \ a:executable, + \ a:command, + \ 0, + \ v:false, + \ l:cwd, + \ ale#GetFilenameMappings(l:buffer, l:linter.name), + \)[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 + + 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': l:buffer, + \ 'connection_id': l:conn_id, + \ 'command': l:command, + \ 'project_root': l:root, + \} + + call ale#lsp#OnInit(l:conn_id, {-> + \ ale#lsp_linter#OnInit(l:linter, l:details, l:Callback) + \}) + + 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 + + if empty(a:address) + return 0 + endif + + return s:StartLSP(a:options, a:address, '', '') +endfunction + +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 !ale#engine#IsExecutable(a:options.buffer, a:executable) + return 0 + endif + + 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. + call ale#lsp_linter#AddErrorMessage(a:linter.name, "Failed to find project root, language server won't start.") + + 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') + call ale#lsp#RegisterCallback(l:id, l:Callback) + + " Remember the linter this connection is for. + let s:lsp_linter_map[l:id] = a:linter + + if a:linter.lsp is# 'tsserver' + 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 + elseif !g:ale_use_neovim_lsp_api + let l:notified = ale#lsp#NotifyForChanges(l:id, l:buffer) + + " If this was a file save event, also notify the server of that. + if getbufvar(l:buffer, 'ale_save_event_fired', 0) + \&& ale#lsp#HasCapability(l:id, 'did_save') + let l:include_text = ale#lsp#HasCapability(l:id, 'includeText') + let l:save_message = ale#lsp#message#DidSave(l:buffer, l:include_text) + let l:notified = ale#lsp#Send(l:id, l:save_message) != 0 + endif + + let l:diagnostic_request_id = 0 + + " If the document is updated and we can pull diagnostics, try to. + if ale#lsp#HasCapability(l:id, 'pull_model') + let l:diagnostic_message = ale#lsp#message#Diagnostic(l:buffer) + + let l:diagnostic_request_id = ale#lsp#Send(l:id, l:diagnostic_message) + endif + + " If we are going to pull diagnostics, then mark the linter as active, + " and remember the URI we sent the request for. + if l:diagnostic_request_id + call ale#engine#MarkLinterActive(l:info, a:linter) + let s:diagnostic_uri_map[l:diagnostic_request_id] = + \ l:diagnostic_message[2].textDocument.uri + endif + endif +endfunction + +function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort + return ale#lsp_linter#StartLSP(a:buffer, a:linter, function('s:CheckWithLSP')) +endfunction + +function! s:HandleLSPResponseToCustomRequests(conn_id, response) abort + if has_key(a:response, 'id') + " Get the custom handlers Dictionary from the linter map. + let l:linter = get(s:lsp_linter_map, a:conn_id, {}) + let l:custom_handlers = get(l:linter, 'custom_handlers', {}) + + if has_key(l:custom_handlers, a:response.id) + let l:Handler = remove(l:custom_handlers, a:response.id) + call l:Handler(a:response) + endif + endif +endfunction + +function! s:OnReadyForCustomRequests(args, linter, lsp_details) abort + let l:id = a:lsp_details.connection_id + let l:request_id = ale#lsp#Send(l:id, a:args.message) + + if l:request_id > 0 && has_key(a:args, 'handler') + let l:Callback = function('s:HandleLSPResponseToCustomRequests') + call ale#lsp#RegisterCallback(l:id, l:Callback) + + " Remember the linter this connection is for. + let s:lsp_linter_map[l:id] = a:linter + + " Add custom_handlers to the linter Dictionary. + if !has_key(a:linter, 'custom_handlers') + let a:linter.custom_handlers = {} + endif + + " Put the handler function in the map to call later. + let a:linter.custom_handlers[l:request_id] = a:args.handler + endif +endfunction + +" Send a custom request to an LSP linter. +function! ale#lsp_linter#SendRequest(buffer, linter_name, message, ...) abort + let l:filetype = ale#linter#ResolveFiletype(getbufvar(a:buffer, '&filetype')) + let l:linter_list = ale#linter#GetAll(l:filetype) + let l:linter_list = filter(l:linter_list, {_, v -> v.name is# a:linter_name}) + + if len(l:linter_list) < 1 + throw 'Linter "' . a:linter_name . '" not found!' + endif + + let l:linter = l:linter_list[0] + + if empty(l:linter.lsp) + throw 'Linter "' . a:linter_name . '" does not support LSP!' + endif + + let l:is_notification = a:message[0] + let l:callback_args = {'message': a:message} + + if !l:is_notification && a:0 + let l:callback_args.handler = a:1 + endif + + let l:Callback = function('s:OnReadyForCustomRequests', [l:callback_args]) + + return ale#lsp_linter#StartLSP(a:buffer, l:linter, l:Callback) +endfunction diff --git a/autoload/ale/lsp_window.vim b/autoload/ale/lsp_window.vim new file mode 100644 index 00000000..9a27f2f1 --- /dev/null +++ b/autoload/ale/lsp_window.vim @@ -0,0 +1,58 @@ +" Author: suoto +" Description: Handling of window/* LSP methods, although right now only +" handles window/showMessage + +" Constants for message type codes +let s:LSP_MESSAGE_TYPE_DISABLED = 0 +let s:LSP_MESSAGE_TYPE_ERROR = 1 +let s:LSP_MESSAGE_TYPE_WARNING = 2 +let s:LSP_MESSAGE_TYPE_INFORMATION = 3 +let s:LSP_MESSAGE_TYPE_LOG = 4 + +" Translate strings from the user config to a number so we can check +" severities +let s:CFG_TO_LSP_SEVERITY = { +\ 'disabled': s:LSP_MESSAGE_TYPE_DISABLED, +\ 'error': s:LSP_MESSAGE_TYPE_ERROR, +\ 'warning': s:LSP_MESSAGE_TYPE_WARNING, +\ 'information': s:LSP_MESSAGE_TYPE_INFORMATION, +\ 'info': s:LSP_MESSAGE_TYPE_INFORMATION, +\ 'log': s:LSP_MESSAGE_TYPE_LOG +\} + +" Handle window/showMessage response. +" - details: dict containing linter name and format (g:ale_lsp_show_message_format) +" - params: dict with the params for the call in the form of {type: number, message: string} +function! ale#lsp_window#HandleShowMessage(linter_name, format, params) abort + let l:message = a:params.message + let l:type = a:params.type + + " Get the configured severity level threshold and check if the message + " should be displayed or not + let l:configured_severity = tolower(get(g:, 'ale_lsp_show_message_severity', 'error')) + " If the user has configured with a value we can't find on the conversion + " dict, fall back to warning + let l:cfg_severity_threshold = get(s:CFG_TO_LSP_SEVERITY, l:configured_severity, s:LSP_MESSAGE_TYPE_WARNING) + + if l:type > l:cfg_severity_threshold + return + endif + + " Severity will depend on the message type + if l:type is# s:LSP_MESSAGE_TYPE_ERROR + let l:severity = g:ale_echo_msg_error_str + elseif l:type is# s:LSP_MESSAGE_TYPE_INFORMATION + let l:severity = g:ale_echo_msg_info_str + elseif l:type is# s:LSP_MESSAGE_TYPE_LOG + let l:severity = g:ale_echo_msg_log_str + else + " Default to warning just in case + let l:severity = g:ale_echo_msg_warning_str + endif + + let l:string = substitute(a:format, '\V%severity%', l:severity, 'g') + let l:string = substitute(l:string, '\V%linter%', a:linter_name, 'g') + let l:string = substitute(l:string, '\V%s\>', l:message, 'g') + + call ale#util#ShowMessage(l:string) +endfunction diff --git a/autoload/ale/lua.vim b/autoload/ale/lua.vim new file mode 100644 index 00000000..f4a5d05c --- /dev/null +++ b/autoload/ale/lua.vim @@ -0,0 +1,29 @@ +" Author: w0rp +" Description: Functions for integrating with Lua linters. + +" Find project root for a Lua language server. +function! ale#lua#FindProjectRoot(buffer) abort + let l:possible_project_roots = [ + \ '.luarc.json', + \ '.git', + \ bufname(a:buffer), + \] + + for l:possible_root in l:possible_project_roots + let l:project_root = ale#path#FindNearestFile(a:buffer, l:possible_root) + + if empty(l:project_root) + let l:project_root = ale#path#FindNearestDirectory(a:buffer, l:possible_root) + endif + + if !empty(l:project_root) + " dir:p expands to /full/path/to/dir/ whereas + " file:p expands to /full/path/to/file (no trailing slash) + " Appending '/' ensures that :h:h removes the path's last segment + " regardless of whether it is a directory or not. + return fnamemodify(l:project_root . '/', ':p:h:h') + endif + endfor + + return '' +endfunction diff --git a/autoload/ale/maven.vim b/autoload/ale/maven.vim new file mode 100644 index 00000000..4f87ebb7 --- /dev/null +++ b/autoload/ale/maven.vim @@ -0,0 +1,57 @@ +" Description: Functions for working with Maven projects. +" +" Given a buffer number, find a Maven project root. +function! ale#maven#FindProjectRoot(buffer) abort + let l:wrapper_path = ale#path#FindNearestFile(a:buffer, 'mvnw') + + if !empty(l:wrapper_path) + return fnamemodify(l:wrapper_path, ':h') + endif + + let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') + + if !empty(l:pom_path) + return fnamemodify(l:pom_path, ':h') + endif + + return '' +endfunction + +" Given a buffer number, find the path to the executable. +" First search on the path for 'mvnw' (mvnw.cmd on Windows), if nothing is found, +" try the global command. Returns an empty string if cannot find the executable. +function! ale#maven#FindExecutable(buffer) abort + let l:wrapper_cmd = has('unix') ? 'mvnw' : 'mvnw.cmd' + let l:wrapper_path = ale#path#FindNearestFile(a:buffer, l:wrapper_cmd) + + if !empty(l:wrapper_path) && executable(l:wrapper_path) + return l:wrapper_path + endif + + if executable('mvn') + return 'mvn' + endif + + return '' +endfunction + +" Given a buffer number, get a working directory and command to print the +" classpath of the root project. +" +" Returns an empty string for the command if Maven is not detected. +function! ale#maven#BuildClasspathCommand(buffer) abort + let l:executable = ale#maven#FindExecutable(a:buffer) + + if !empty(l:executable) + let l:project_root = ale#maven#FindProjectRoot(a:buffer) + + if !empty(l:project_root) + return [ + \ l:project_root, + \ ale#Escape(l:executable) . ' dependency:build-classpath' + \] + endif + endif + + return ['', ''] +endfunction diff --git a/autoload/ale/node.vim b/autoload/ale/node.vim new file mode 100644 index 00000000..9e11ca7e --- /dev/null +++ b/autoload/ale/node.vim @@ -0,0 +1,22 @@ +" Author: w0rp +" Description: Functions for working with Node executables. + +call ale#Set('windows_node_executable_path', 'node.exe') + +" Create a executable string which executes a Node.js script command with a +" Node.js executable if needed. +" +" The executable string should not be escaped before passing it to this +" function, the executable string will be escaped when returned by this +" function. +" +" The executable is only prefixed for Windows machines +function! ale#node#Executable(buffer, executable) abort + 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) + endif + + return ale#Escape(a:executable) +endfunction diff --git a/autoload/ale/organize_imports.vim b/autoload/ale/organize_imports.vim new file mode 100644 index 00000000..fb00bc21 --- /dev/null +++ b/autoload/ale/organize_imports.vim @@ -0,0 +1,63 @@ +" Author: Jerko Steiner +" Description: Organize imports support for tsserver + +function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') isnot# 'organizeImports' + return + endif + + if get(a:response, 'success', v:false) isnot v:true + return + endif + + let l:file_code_edits = a:response.body + + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'Organize Imports', + \ 'changes': l:file_code_edits, + \ }, + \ { + \ 'conn_id': a:conn_id, + \ 'should_save': g:ale_save_hidden || !&hidden, + \ }, + \) +endfunction + +function! s:OnReady(linter, lsp_details) abort + let l:id = a:lsp_details.connection_id + + if a:linter.lsp isnot# 'tsserver' + call ale#util#Execute('echom ''OrganizeImports currently only works with tsserver''') + + return + endif + + let l:buffer = a:lsp_details.buffer + + let l:Callback = function('ale#organize_imports#HandleTSServerResponse') + + call ale#lsp#RegisterCallback(l:id, l:Callback) + + let l:message = ale#lsp#tsserver_message#OrganizeImports(l:buffer) + + let l:request_id = ale#lsp#Send(l:id, l:message) +endfunction + +function! s:OrganizeImports(linter) abort + let l:buffer = bufnr('') + let [l:line, l:column] = getpos('.')[1:2] + + if a:linter.lsp isnot# 'tsserver' + let l:column = min([l:column, len(getline(l:line))]) + endif + + let l:Callback = function('s:OnReady') + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) +endfunction + +function! ale#organize_imports#Execute() abort + for l:linter in ale#lsp_linter#GetEnabled(bufnr('')) + call s:OrganizeImports(l:linter) + endfor +endfunction diff --git a/autoload/ale/other_source.vim b/autoload/ale/other_source.vim new file mode 100644 index 00000000..1a092034 --- /dev/null +++ b/autoload/ale/other_source.vim @@ -0,0 +1,21 @@ +" Tell ALE that another source has started checking a buffer. +function! ale#other_source#StartChecking(buffer, linter_name) abort + call ale#engine#InitBufferInfo(a:buffer) + let l:list = g:ale_buffer_info[a:buffer].active_other_sources_list + + call add(l:list, a:linter_name) + call uniq(sort(l:list)) +endfunction + +" Show some results, and stop checking a buffer. +" To clear results or cancel checking a buffer, an empty List can be given. +function! ale#other_source#ShowResults(buffer, linter_name, loclist) abort + call ale#engine#InitBufferInfo(a:buffer) + let l:info = g:ale_buffer_info[a:buffer] + + " Remove this linter name from the active list. + let l:list = l:info.active_other_sources_list + call filter(l:list, 'v:val isnot# a:linter_name') + + call ale#engine#HandleLoclist(a:linter_name, a:buffer, a:loclist, 1) +endfunction diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim new file mode 100644 index 00000000..a4260d39 --- /dev/null +++ b/autoload/ale/path.vim @@ -0,0 +1,257 @@ +" Author: w0rp +" Description: Functions for working with paths in the filesystem. + +" simplify a path, and fix annoying issues with paths on Windows. +" +" Forward slashes are changed to back slashes so path equality works better +" on Windows. Back slashes are changed to forward slashes on Unix. +" +" Unix paths can technically contain back slashes, but in practice no path +" should, and replacing back slashes with forward slashes makes linters work +" in environments like MSYS. +" +" Paths starting with more than one forward slash are changed to only one +" forward slash, to prevent the paths being treated as special MSYS paths. +function! ale#path#Simplify(path) abort + if has('unix') + let l:unix_path = substitute(a:path, '\\', '/', 'g') + + return substitute(simplify(l:unix_path), '^//\+', '/', 'g') " no-custom-checks + endif + + let l:win_path = substitute(a:path, '/', '\\', 'g') + + return substitute(simplify(l:win_path), '^\\\+', '\', 'g') " no-custom-checks +endfunction + +" Simplify a path without a Windows drive letter. +" This function can be used for checking if paths are equal. +function! ale#path#RemoveDriveLetter(path) abort + return has('win32') && a:path[1:2] is# ':\' + \ ? ale#path#Simplify(a:path[2:]) + \ : ale#path#Simplify(a:path) +endfunction + +" Given a buffer and a filename, find the nearest file by searching upwards +" through the paths relative to the given buffer. +function! ale#path#FindNearestFile(buffer, filename) abort + let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p') + let l:buffer_filename = fnameescape(l:buffer_filename) + + let l:relative_path = findfile(a:filename, l:buffer_filename . ';') + + if !empty(l:relative_path) + return fnamemodify(l:relative_path, ':p') + endif + + return '' +endfunction + +" Given a buffer and a directory name, find the nearest directory by searching upwards +" through the paths relative to the given buffer. +function! ale#path#FindNearestDirectory(buffer, directory_name) abort + let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p') + let l:buffer_filename = fnameescape(l:buffer_filename) + + let l:relative_path = finddir(a:directory_name, l:buffer_filename . ';') + + if !empty(l:relative_path) + return fnamemodify(l:relative_path, ':p') + endif + + return '' +endfunction + +" Given a buffer, a string to search for, and a global fallback for when +" the search fails, look for a file in parent paths, and if that fails, +" use the global fallback path instead. +function! ale#path#ResolveLocalPath(buffer, search_string, global_fallback) abort + " Search for a locally installed file first. + let l:path = ale#path#FindNearestFile(a:buffer, a:search_string) + + " If the search fails, try the global executable instead. + if empty(l:path) + let l:path = a:global_fallback + endif + + return l:path +endfunction + +" Given a buffer number, a base variable name, and a list of paths to search +" for in ancestor directories, detect the executable path for a program. +function! ale#path#FindNearestExecutable(buffer, path_list) abort + for l:path in a:path_list + if ale#path#IsAbsolute(l:path) + let l:executable = filereadable(l:path) ? l:path : '' + else + let l:executable = ale#path#FindNearestFile(a:buffer, l:path) + endif + + if !empty(l:executable) + return l:executable + endif + endfor + + return '' +endfunction + +" Given a buffer number, a base variable name, and a list of paths to search +" for in ancestor directories, detect the executable path for a program. +" +" The use_global and executable options for the relevant program will be used. +function! ale#path#FindExecutable(buffer, base_var_name, path_list) abort + if ale#Var(a:buffer, a:base_var_name . '_use_global') + return ale#Var(a:buffer, a:base_var_name . '_executable') + endif + + let l:nearest = ale#path#FindNearestExecutable(a:buffer, a:path_list) + + if !empty(l:nearest) + return l:nearest + endif + + return ale#Var(a:buffer, a:base_var_name . '_executable') +endfunction + +" Return 1 if a path is an absolute path. +function! ale#path#IsAbsolute(filename) abort + if has('win32') && a:filename[:0] is# '\' + return 1 + endif + + " Check for /foo and C:\foo, etc. + return a:filename[:0] is# '/' || a:filename[1:2] is# ':\' +endfunction + +let s:temp_dir = ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h:h')) +let s:resolved_temp_dir = resolve(s:temp_dir) + +" Given a filename, return 1 if the file represents some temporary file +" created by Vim. If the temporary location is symlinked (e.g. macOS), some +" linters may report the resolved version of the path, so both are checked. +function! ale#path#IsTempName(filename) abort + let l:filename = ale#path#Simplify(a:filename) + + return l:filename[:len(s:temp_dir) - 1] is# s:temp_dir + \|| l:filename[:len(s:resolved_temp_dir) - 1] is# s:resolved_temp_dir +endfunction + +" Given a base directory, which must not have a trailing slash, and a +" filename, which may have an absolute path a path relative to the base +" directory, return the absolute path to the file. +function! ale#path#GetAbsPath(base_directory, filename) abort + if ale#path#IsAbsolute(a:filename) + return ale#path#Simplify(a:filename) + endif + + let l:sep = has('win32') ? '\' : '/' + + return ale#path#Simplify(a:base_directory . l:sep . a:filename) +endfunction + +" Given a path, return the directory name for that path, with no trailing +" slashes. If the argument is empty(), return an empty string. +function! ale#path#Dirname(path) abort + if empty(a:path) + return '' + endif + + " For /foo/bar/ we need :h:h to get /foo + if a:path[-1:] is# '/' || (has('win32') && a:path[-1:] is# '\') + return fnamemodify(a:path, ':h:h') + endif + + return fnamemodify(a:path, ':h') +endfunction + +" Given a buffer number and a relative or absolute path, return 1 if the +" two paths represent the same file on disk. +function! ale#path#IsBufferPath(buffer, complex_filename) abort + " If the path is one of many different names for stdin, we have a match. + if a:complex_filename is# '-' + \|| a:complex_filename is# 'stdin' + \|| a:complex_filename[:0] is# '<' + return 1 + endif + + let l:test_filename = ale#path#Simplify(a:complex_filename) + + if l:test_filename[:1] is# './' + let l:test_filename = l:test_filename[2:] + endif + + if l:test_filename[:1] is# '..' + " Remove ../../ etc. from the front of the path. + let l:test_filename = substitute(l:test_filename, '\v^(\.\.[/\\])+', '/', '') + endif + + " Use the basename for temporary files, as they are likely our files. + if ale#path#IsTempName(l:test_filename) + let l:test_filename = fnamemodify(l:test_filename, ':t') + endif + + let l:buffer_filename = expand('#' . a:buffer . ':p') + + return l:buffer_filename is# l:test_filename + \ || l:buffer_filename[-len(l:test_filename):] is# l:test_filename +endfunction + +" Given a path, return every component of the path, moving upwards. +function! ale#path#Upwards(path) abort + let l:pattern = has('win32') ? '\v/+|\\+' : '\v/+' + let l:sep = has('win32') ? '\' : '/' + let l:parts = split(ale#path#Simplify(a:path), l:pattern) + let l:path_list = [] + + while !empty(l:parts) + call add(l:path_list, join(l:parts, l:sep)) + let l:parts = l:parts[:-2] + endwhile + + if has('win32') && a:path =~# '^[a-zA-z]:\' + " Add \ to C: for C:\, etc. + let l:path_list[-1] .= '\' + elseif a:path[0] is# '/' + " If the path starts with /, even on Windows, add / and / to all paths. + call map(l:path_list, '''/'' . v:val') + call add(l:path_list, '/') + endif + + return l:path_list +endfunction + +" Convert a filesystem path to a file:// URI +" relatives paths will not be prefixed with the protocol. +" For Windows paths, the `:` in C:\ etc. will not be percent-encoded. +function! ale#path#ToFileURI(path) abort + let l:has_drive_letter = a:path[1:2] is# ':\' + + return substitute( + \ ((l:has_drive_letter || a:path[:0] is# '/') ? 'file://' : '') + \ . (l:has_drive_letter ? '/' . a:path[:2] : '') + \ . ale#uri#Encode(l:has_drive_letter ? a:path[3:] : a:path), + \ '\\', + \ '/', + \ 'g', + \) +endfunction + +function! ale#path#FromFileURI(uri) abort + 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 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 +endfunction diff --git a/autoload/ale/pattern_options.vim b/autoload/ale/pattern_options.vim new file mode 100644 index 00000000..14e2142d --- /dev/null +++ b/autoload/ale/pattern_options.vim @@ -0,0 +1,47 @@ +" Author: w0rp +" Description: Set options in files based on regex patterns. + +" These variables are used to cache the sorting of patterns below. +let s:last_pattern_options = {} +let s:sorted_items = [] + +function! s:CmpPatterns(left_item, right_item) abort + if a:left_item[0] < a:right_item[0] + return -1 + endif + + if a:left_item[0] > a:right_item[0] + return 1 + endif + + return 0 +endfunction + +function! ale#pattern_options#SetOptions(buffer) abort + let l:pattern_options = get(g:, 'ale_pattern_options', {}) + + if empty(l:pattern_options) + " Stop if no options are set. + return + endif + + " The items will only be sorted whenever the patterns change. + if l:pattern_options != s:last_pattern_options + let s:last_pattern_options = deepcopy(l:pattern_options) + " The patterns are sorted, so they are applied consistently. + let s:sorted_items = sort( + \ items(l:pattern_options), + \ function('s:CmpPatterns') + \) + endif + + let l:filename = expand('#' . a:buffer . ':p') + + for [l:pattern, l:options] in s:sorted_items + if match(l:filename, l:pattern) >= 0 + for [l:key, l:value] in items(l:options) + call setbufvar(a:buffer, l:key, l:value) + endfor + endif + endfor +endfunction 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 +" 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 new file mode 100644 index 00000000..1aca03ea --- /dev/null +++ b/autoload/ale/preview.vim @@ -0,0 +1,137 @@ +" Author: w0rp +" Description: Preview windows for showing whatever information in. + +if !has_key(s:, 'last_list') + let s:last_list = [] +endif + +if !has_key(s:, 'last_options') + let s:last_options = {} +endif + +function! ale#preview#SetLastSelection(item_list, options) abort + let s:last_list = a:item_list + let s:last_options = { + \ 'open_in': get(a:options, 'open_in', 'current-buffer'), + \ 'use_relative_paths': get(a:options, 'use_relative_paths', 0), + \} +endfunction + +" Open a preview window and show some lines in it. +" A second argument can be passed as a Dictionary with options. They are... +" +" filetype - The filetype to use, defaulting to 'ale-preview' +" stay_here - If 1, stay in the window you came from. +function! ale#preview#Show(lines, ...) abort + let l:options = get(a:000, 0, {}) + + silent pedit ALEPreviewWindow + wincmd P + + setlocal modifiable + setlocal noreadonly + setlocal nobuflisted + setlocal buftype=nofile + setlocal bufhidden=wipe + :%d + call setline(1, a:lines) + setlocal nomodifiable + setlocal readonly + let &l:filetype = get(l:options, 'filetype', 'ale-preview') + + for l:command in get(l:options, 'commands', []) + call execute(l:command) + endfor + + if get(l:options, 'stay_here') + wincmd p + endif +endfunction + +" Close the preview window if the filetype matches the given one. +function! ale#preview#CloseIfTypeMatches(filetype) abort + for l:win in getwininfo() + let l:wintype = gettabwinvar(l:win.tabnr, l:win.winnr, '&filetype') + + if l:wintype is# a:filetype + silent! pclose! + endif + endfor +endfunction + +" Show a location selection preview window, given some items. +" Each item should have 'filename', 'line', and 'column' keys. +function! ale#preview#ShowSelection(item_list, ...) abort + let l: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:filename + \ . ':' . l:item.line + \ . ':' . l:item.column + \ . (!empty(l:match) ? ' ' . l:match : ''), + \) + endfor + + call ale#preview#Show(l:lines, {'filetype': 'ale-preview-selection'}) + let b:ale_preview_item_list = a:item_list + let b:ale_preview_item_open_in = get(l:options, 'open_in', 'current-buffer') + + " Jump to an index for a previous selection, if set. + if has_key(l:options, 'jump_to_index') + let l:pos = getpos('.') + let l:pos[1] = l:options.jump_to_index + 1 + call setpos('.', l:pos) + endif + + " Remember preview state, so we can repeat it later. + call ale#preview#SetLastSelection(a:item_list, l:options) +endfunction + +function! ale#preview#RepeatSelection() abort + if !empty(s:last_list) + call ale#preview#ShowSelection(s:last_list, s:last_options) + endif +endfunction + +function! s:Open(open_in) abort + let l:item_list = get(b:, 'ale_preview_item_list', []) + let l:index = getpos('.')[1] - 1 + let l:item = get(l:item_list, l:index, {}) + + if empty(l:item) + return + endif + + " Remember an index to jump to when repeating a selection. + let s:last_options.jump_to_index = l:index + + :q! + + call ale#util#Open( + \ l:item.filename, + \ l:item.line, + \ l:item.column, + \ {'open_in': a:open_in}, + \) +endfunction + +function! ale#preview#OpenSelection() abort + call s:Open(b:ale_preview_item_open_in) +endfunction + +function! ale#preview#OpenSelectionInTab() abort + call s:Open('tab') +endfunction diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim new file mode 100644 index 00000000..148683d6 --- /dev/null +++ b/autoload/ale/python.vim @@ -0,0 +1,206 @@ +" Author: w0rp +" Description: Functions for integrating with Python linters. + +call ale#Set('python_auto_pipenv', '0') +call ale#Set('python_auto_poetry', '0') +call ale#Set('python_auto_uv', '0') + +let s:sep = has('win32') ? '\' : '/' +" bin is used for Unix virtualenv directories, and Scripts is for Windows. +let s:bin_dir = has('unix') ? 'bin' : 'Scripts' +" The default virtualenv directory names are ordered from the likely most +" common names down to the least common. `.env` might be more common, but it's +" also likely to conflict with a `.env` file for environment variables, so we +" search for it last. (People really shouldn't use that name.) +let g:ale_virtualenv_dir_names = get(g:, 'ale_virtualenv_dir_names', [ +\ '.venv', +\ 'env', +\ 've', +\ 'venv', +\ 'virtualenv', +\ '.env', +\]) + +function! ale#python#FindProjectRootIni(buffer) abort + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + " If you change this, update ale-python-root documentation. + if filereadable(l:path . '/MANIFEST.in') + \|| filereadable(l:path . '/setup.cfg') + \|| filereadable(l:path . '/pytest.ini') + \|| filereadable(l:path . '/tox.ini') + \|| filereadable(l:path . '/.pyre_configuration.local') + \|| filereadable(l:path . '/mypy.ini') + \|| filereadable(l:path . '/.mypy.ini') + \|| filereadable(l:path . '/pycodestyle.cfg') + \|| filereadable(l:path . '/.flake8') + \|| filereadable(l:path . '/.flake8rc') + \|| filereadable(l:path . '/pylama.ini') + \|| filereadable(l:path . '/pylintrc') + \|| filereadable(l:path . '/.pylintrc') + \|| filereadable(l:path . '/pyrightconfig.json') + \|| filereadable(l:path . '/pyrightconfig.toml') + \|| filereadable(l:path . '/Pipfile') + \|| filereadable(l:path . '/Pipfile.lock') + \|| filereadable(l:path . '/poetry.lock') + \|| filereadable(l:path . '/pyproject.toml') + \|| filereadable(l:path . '/.tool-versions') + \|| filereadable(l:path . '/uv.lock') + return l:path + endif + endfor + + return '' +endfunction + +" Given a buffer number, find the project root directory for Python. +" The root directory is defined as the first directory found while searching +" upwards through paths, including the current directory, until a path +" containing an init file (one from MANIFEST.in, setup.cfg, pytest.ini, +" tox.ini) is found. If it is not possible to find the project root directory +" via init file, then it will be defined as the first directory found +" searching upwards through paths, including the current directory, until no +" __init__.py files is found. +function! ale#python#FindProjectRoot(buffer) abort + let l:ini_root = ale#python#FindProjectRootIni(a:buffer) + + if !empty(l:ini_root) + return l:ini_root + endif + + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + if !filereadable(l:path . '/__init__.py') + return l:path + endif + endfor + + return '' +endfunction + +" Given a buffer number, find a virtualenv path for Python. +function! ale#python#FindVirtualenv(buffer) abort + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + " Skip empty path components returned in MSYS. + if empty(l:path) + continue + endif + + for l:dirname in ale#Var(a:buffer, 'virtualenv_dir_names') + let l:venv_dir = ale#path#Simplify( + \ join([l:path, l:dirname], s:sep) + \) + let l:script_filename = ale#path#Simplify( + \ join([l:venv_dir, s:bin_dir, 'activate'], s:sep) + \) + + if filereadable(l:script_filename) + return l:venv_dir + endif + endfor + endfor + + return $VIRTUAL_ENV +endfunction + +" Automatically determine virtualenv environment variables and build +" a string of them to prefix linter commands with. +function! ale#python#AutoVirtualenvEnvString(buffer) abort + let l:venv_dir = ale#python#FindVirtualenv(a:buffer) + + if !empty(l:venv_dir) + let l:strs = [ ] + " venv/bin directory + let l:pathdir = join([l:venv_dir, s:bin_dir], s:sep) + + " expand PATH correctly inside of the appropriate shell. + " set VIRTUAL_ENV to point to venv + if has('win32') + call add(l:strs, 'set PATH=' . ale#Escape(l:pathdir) . ';%PATH% && ') + call add(l:strs, 'set VIRTUAL_ENV=' . ale#Escape(l:venv_dir) . ' && ') + else + call add(l:strs, 'PATH=' . ale#Escape(l:pathdir) . '":$PATH" ') + call add(l:strs, 'VIRTUAL_ENV=' . ale#Escape(l:venv_dir) . ' ') + endif + + return join(l:strs, '') + endif + + return '' +endfunction + +" Given a buffer number and a command name, find the path to the executable. +" First search on a virtualenv for Python, if nothing is found, try the global +" command. Returns an empty string if cannot find the executable +function! ale#python#FindExecutable(buffer, base_var_name, path_list) abort + if ale#Var(a:buffer, a:base_var_name . '_use_global') + return ale#Var(a:buffer, a:base_var_name . '_executable') + endif + + let l:virtualenv = ale#python#FindVirtualenv(a:buffer) + + if !empty(l:virtualenv) + for l:path in a:path_list + let l:ve_executable = ale#path#Simplify( + \ join([l:virtualenv, s:bin_dir, l:path], s:sep) + \) + + if executable(l:ve_executable) + return l:ve_executable + endif + endfor + endif + + 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# '' +endfunction + +" Detects whether a poetry environment is present. +function! ale#python#PoetryPresent(buffer) abort + return findfile('poetry.lock', expand('#' . a:buffer . ':p:h') . ';') isnot# '' +endfunction + +" Detects whether a poetry environment is present. +function! ale#python#UvPresent(buffer) abort + return findfile('uv.lock', expand('#' . a:buffer . ':p:h') . ';') isnot# '' +endfunction diff --git a/autoload/ale/racket.vim b/autoload/ale/racket.vim new file mode 100644 index 00000000..ff896108 --- /dev/null +++ b/autoload/ale/racket.vim @@ -0,0 +1,12 @@ +function! ale#racket#FindProjectRoot(buffer) abort + let l:cwd = expand('#' . a:buffer . ':p:h') + let l:highest_init = l:cwd + + for l:path in ale#path#Upwards(l:cwd) + if filereadable(l:path.'/init.rkt') + let l:highest_init = l:path + endif + endfor + + return l:highest_init +endfunction diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim new file mode 100644 index 00000000..e8cbda9e --- /dev/null +++ b/autoload/ale/references.vim @@ -0,0 +1,188 @@ +let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer') + +let s:references_map = {} + +" Used to get the references map in tests. +function! ale#references#GetMap() abort + return deepcopy(s:references_map) +endfunction + +" Used to set the references map in tests. +function! ale#references#SetMap(map) abort + let s:references_map = a:map +endfunction + +function! ale#references#ClearLSPData() abort + let s:references_map = {} +endfunction + +function! ale#references#FormatTSResponseItem(response_item, options) abort + let l:match = substitute(a:response_item.lineText, '^\s*\(.\{-}\)\s*$', '\1', '') + + if get(a:options, 'open_in') is# 'quickfix' + return { + \ 'filename': a:response_item.file, + \ 'lnum': a:response_item.start.line, + \ 'col': a:response_item.start.offset, + \ 'text': l:match, + \} + else + return { + \ 'filename': a:response_item.file, + \ 'line': a:response_item.start.line, + \ 'column': a:response_item.start.offset, + \ 'match': l:match, + \} + endif +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) + 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 = [] + + for l:response_item in a:response.body.refs + call add( + \ l:item_list, + \ ale#references#FormatTSResponseItem(l:response_item, l:options) + \) + endfor + + if empty(l:item_list) + call ale#util#Execute('echom ''No references found.''') + else + if get(l:options, 'open_in') is# 'quickfix' + call setqflist([], 'r') + call setqflist(l:item_list, 'a') + call ale#util#Execute('cc 1') + else + call ale#preview#ShowSelection(l:item_list, l:options) + endif + endif + endif + endif +endfunction + +function! ale#references#FormatLSPResponseItem(response_item, options) abort + if get(a:options, 'open_in') is# 'quickfix' + return { + \ 'filename': ale#util#ToResource(a:response_item.uri), + \ 'lnum': a:response_item.range.start.line + 1, + \ 'col': a:response_item.range.start.character + 1, + \} + else + return { + \ 'filename': ale#util#ToResource(a:response_item.uri), + \ 'line': a:response_item.range.start.line + 1, + \ 'column': a:response_item.range.start.character + 1, + \} + endif +endfunction + +function! ale#references#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(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 = [] + + if type(l:result) is v:t_list + for l:response_item in l:result + call add(l:item_list, + \ ale#references#FormatLSPResponseItem(l:response_item, l:options) + \) + endfor + endif + + if empty(l:item_list) + call ale#util#Execute('echom ''No references found.''') + else + if get(l:options, 'open_in') is# 'quickfix' + call setqflist([], 'r') + call setqflist(l:item_list, 'a') + call ale#util#Execute('cc 1') + else + call ale#preview#ShowSelection(l:item_list, l:options) + endif + endif + endif +endfunction + +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') + + call ale#lsp#RegisterCallback(l:id, l:Callback) + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#References( + \ l:buffer, + \ a:line, + \ a:column + \) + else + " Send a message saying the buffer has changed first, or the + " references position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:buffer) + + let l:message = ale#lsp#message#References(l:buffer, a:line, a:column) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message) + + let s:references_map[l:request_id] = { + \ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0, + \ 'open_in': get(a:options, 'open_in', 'current-buffer'), + \} +endfunction + +function! ale#references#Find(...) abort + let l:options = {} + + if len(a:000) > 0 + for l:option in a:000 + if l:option is? '-relative' + let l:options.use_relative_paths = 1 + elseif l:option is? '-tab' + let l:options.open_in = 'tab' + elseif l:option is? '-split' + let l:options.open_in = 'split' + elseif l:option is? '-vsplit' + let l:options.open_in = 'vsplit' + elseif l:option is? '-quickfix' + let l:options.open_in = 'quickfix' + endif + endfor + endif + + if !has_key(l:options, 'open_in') + let l:default_navigation = ale#Var(bufnr(''), 'default_navigation') + + if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0 + let l:options.open_in = l:default_navigation + endif + endif + + 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]) + + for l:linter in ale#lsp_linter#GetEnabled(l:buffer) + call ale#lsp_linter#StartLSP(l:buffer, l:linter, l:Callback) + endfor +endfunction diff --git a/autoload/ale/rename.vim b/autoload/ale/rename.vim new file mode 100644 index 00000000..90680e09 --- /dev/null +++ b/autoload/ale/rename.vim @@ -0,0 +1,204 @@ +" Author: Jerko Steiner +" Description: Rename symbol support for LSP / tsserver + +let s:rename_map = {} + +" Used to get the rename map in tests. +function! ale#rename#GetMap() abort + return deepcopy(s:rename_map) +endfunction + +" Used to set the rename map in tests. +function! ale#rename#SetMap(map) abort + let s:rename_map = a:map +endfunction + +function! ale#rename#ClearLSPData() abort + let s:rename_map = {} +endfunction + +let g:ale_rename_tsserver_find_in_comments = get(g:, 'ale_rename_tsserver_find_in_comments', v:false) +let g:ale_rename_tsserver_find_in_strings = get(g:, 'ale_rename_tsserver_find_in_strings', v:false) + +function! s:message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +function! ale#rename#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') isnot# 'rename' + return + endif + + if !has_key(s:rename_map, a:response.request_seq) + return + endif + + let l:options = remove(s:rename_map, a:response.request_seq) + + let l:old_name = l:options.old_name + let l:new_name = l:options.new_name + + if get(a:response, 'success', v:false) is v:false + let l:message = get(a:response, 'message', 'unknown') + call s:message('Error renaming "' . l:old_name . '" to: "' . l:new_name + \ . '". Reason: ' . l:message) + + return + endif + + let l:changes = [] + + for l:response_item in a:response.body.locs + let l:filename = l:response_item.file + let l:text_changes = [] + + for l:loc in l:response_item.locs + call add(l:text_changes, { + \ 'start': { + \ 'line': l:loc.start.line, + \ 'offset': l:loc.start.offset, + \ }, + \ 'end': { + \ 'line': l:loc.end.line, + \ 'offset': l:loc.end.offset, + \ }, + \ 'newText': l:new_name, + \}) + endfor + + call add(l:changes, { + \ 'fileName': l:filename, + \ 'textChanges': l:text_changes, + \}) + endfor + + if empty(l:changes) + call s:message('Error renaming "' . l:old_name . '" to: "' . l:new_name . '"') + + return + endif + + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'rename', + \ 'changes': l:changes, + \ }, + \ { + \ 'conn_id': a:conn_id, + \ 'should_save': g:ale_save_hidden || !&hidden, + \ }, + \) +endfunction + +function! ale#rename#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:rename_map, a:response.id) + let l:options = remove(s:rename_map, a:response.id) + + if !has_key(a:response, 'result') + call s:message('No rename result received from server') + + return + endif + + let l:changes_map = ale#code_action#GetChanges(a:response.result) + + if empty(l:changes_map) + call s:message('No changes received from server') + + return + endif + + let l:changes = ale#code_action#BuildChangesList(l:changes_map) + + call ale#code_action#HandleCodeAction( + \ { + \ 'description': 'rename', + \ 'changes': l:changes, + \ }, + \ { + \ 'conn_id': a:conn_id, + \ 'should_save': g:ale_save_hidden || !&hidden, + \ }, + \) + endif +endfunction + +function! s:OnReady(line, column, options, linter, lsp_details) abort + let l:id = a:lsp_details.connection_id + + if !ale#lsp#HasCapability(l:id, 'rename') + return + endif + + let l:buffer = a:lsp_details.buffer + + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#rename#HandleTSServerResponse') + \ : function('ale#rename#HandleLSPResponse') + + call ale#lsp#RegisterCallback(l:id, l:Callback) + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Rename( + \ l:buffer, + \ a:line, + \ a:column, + \ g:ale_rename_tsserver_find_in_comments, + \ g:ale_rename_tsserver_find_in_strings, + \) + else + " Send a message saying the buffer has changed first, or the + " rename position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:buffer) + + let l:message = ale#lsp#message#Rename( + \ l:buffer, + \ a:line, + \ a:column, + \ a:options.new_name + \) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message) + + let s:rename_map[l:request_id] = a:options +endfunction + +function! s:ExecuteRename(linter, options) abort + let l:buffer = bufnr('') + let [l:line, l:column] = getpos('.')[1:2] + + if a:linter.lsp isnot# 'tsserver' + let l:column = min([l:column, len(getline(l:line))]) + endif + + let l:Callback = function('s:OnReady', [l:line, l:column, a:options]) + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) +endfunction + +function! ale#rename#Execute() abort + let l:linters = ale#lsp_linter#GetEnabled(bufnr('')) + + if empty(l:linters) + call s:message('No active LSPs') + + return + endif + + let l:old_name = expand('') + let l:new_name = ale#util#Input('New name: ', l:old_name) + + if empty(l:new_name) + call s:message('New name cannot be empty!') + + return + endif + + for l:linter in l:linters + call s:ExecuteRename(l:linter, { + \ 'old_name': l:old_name, + \ 'new_name': l:new_name, + \}) + endfor +endfunction diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim new file mode 100644 index 00000000..d941bb2c --- /dev/null +++ b/autoload/ale/ruby.vim @@ -0,0 +1,83 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Functions for integrating with Ruby tools + +" 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'] + let l:dir = fnamemodify( + \ ale#path#FindNearestDirectory(a:buffer, l:name), + \ ':h:h' + \) + + if l:dir isnot# '.' + \&& isdirectory(l:dir . '/app') + \&& isdirectory(l:dir . '/config') + \&& isdirectory(l:dir . '/db') + return l:dir + endif + endfor + + return '' +endfunction + +" Find the nearest dir containing a potential ruby project. +function! ale#ruby#FindProjectRoot(buffer) abort + let l:dir = ale#ruby#FindRailsRoot(a:buffer) + + if isdirectory(l:dir) + return l:dir + endif + + for l:name in ['.solargraph.yml', 'Rakefile', 'Gemfile'] + let l:dir = fnamemodify( + \ ale#path#FindNearestFile(a:buffer, l:name), + \ ':h' + \) + + if l:dir isnot# '.' && isdirectory(l:dir) + return l:dir + endif + endfor + + 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 + +function! ale#ruby#EscapeExecutable(executable, bundle_exec) abort + let l:exec_args = a:executable =~? 'bundle' + \ ? ' exec ' . a:bundle_exec + \ : '' + + return ale#Escape(a:executable) . l:exec_args +endfunction diff --git a/autoload/ale/semver.vim b/autoload/ale/semver.vim new file mode 100644 index 00000000..e3eb49c0 --- /dev/null +++ b/autoload/ale/semver.vim @@ -0,0 +1,78 @@ +let s:version_cache = {} + +" Reset the version cache used for parsing the version. +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! 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) + + if !empty(l:parsed_version) + let l:version = l:parsed_version + let s:version_cache[a:executable] = l:version + endif + + return l:version +endfunction + +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 +" and return 1 if the LHS is greater than or equal to the RHS. +" +" Pairs of [major, minor] can also be used for either argument. +" +" 0 will be returned if the LHS is an empty List. +function! ale#semver#GTE(lhs, rhs) abort + if empty(a:lhs) + return 0 + endif + + if a:lhs[0] > a:rhs[0] + return 1 + elseif a:lhs[0] == a:rhs[0] + if a:lhs[1] > a:rhs[1] + return 1 + elseif a:lhs[1] == a:rhs[1] + return get(a:lhs, 2) >= get(a:rhs, 2) + endif + endif + + return 0 +endfunction diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim new file mode 100644 index 00000000..369896a1 --- /dev/null +++ b/autoload/ale/sign.vim @@ -0,0 +1,496 @@ +scriptencoding utf8 +" Author: w0rp +" Description: Draws error and warning signs into signcolumn + +" This flag can be set to some integer to control the maximum number of signs +" that ALE will set. +let g:ale_max_signs = get(g:, 'ale_max_signs', -1) +" This flag can be set to 1 to enable changing the sign column colors when +" there are errors. +let g:ale_change_sign_column_color = get(g:, 'ale_change_sign_column_color', v:false) +" These variables dictate what signs are used to indicate errors and warnings. +let g:ale_sign_error = get(g:, 'ale_sign_error', 'E') +let g:ale_sign_style_error = get(g:, 'ale_sign_style_error', g:ale_sign_error) +let g:ale_sign_warning = get(g:, 'ale_sign_warning', 'W') +let g:ale_sign_style_warning = get(g:, 'ale_sign_style_warning', g:ale_sign_warning) +let g:ale_sign_info = get(g:, 'ale_sign_info', 'I') +let g:ale_sign_priority = get(g:, 'ale_sign_priority', 30) +" This variable sets an offset which can be set for sign IDs. +" This ID can be changed depending on what IDs are set for other plugins. +" The dummy sign will use the ID exactly equal to the offset. +let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) +" This flag can be set to 1 to keep sign gutter always open +let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', v:false) +let g:ale_sign_highlight_linenrs = get(g:, 'ale_sign_highlight_linenrs', v:false) + +let s:supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614') + +if !hlexists('ALEErrorSign') + highlight link ALEErrorSign error +endif + +if !hlexists('ALEStyleErrorSign') + highlight link ALEStyleErrorSign ALEErrorSign +endif + +if !hlexists('ALEWarningSign') + highlight link ALEWarningSign todo +endif + +if !hlexists('ALEStyleWarningSign') + highlight link ALEStyleWarningSign ALEWarningSign +endif + +if !hlexists('ALEInfoSign') + highlight link ALEInfoSign ALEWarningSign +endif + +if !hlexists('ALESignColumnWithErrors') + highlight link ALESignColumnWithErrors error +endif + +function! ale#sign#SetUpDefaultColumnWithoutErrorsHighlight() abort + let l:verbose = &verbose + set verbose=0 + let l:output = execute('highlight SignColumn', 'silent') + let &verbose = l:verbose + + let l:highlight_syntax = join(split(l:output)[2:]) + let l:match = matchlist(l:highlight_syntax, '\vlinks to (.+)$') + + if !empty(l:match) + execute 'highlight link ALESignColumnWithoutErrors ' . l:match[1] + elseif l:highlight_syntax isnot# 'cleared' + execute 'highlight ALESignColumnWithoutErrors ' . l:highlight_syntax + endif +endfunction + +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=' . s:EscapeSignText(g:ale_sign_error) +\ . ' texthl=ALEErrorSign linehl=ALEErrorLine' +execute 'sign define ALEStyleErrorSign text=' . s:EscapeSignText(g:ale_sign_style_error) +\ . ' texthl=ALEStyleErrorSign linehl=ALEErrorLine' +execute 'sign define ALEWarningSign text=' . s:EscapeSignText(g:ale_sign_warning) +\ . ' texthl=ALEWarningSign linehl=ALEWarningLine' +execute 'sign define ALEStyleWarningSign text=' . s:EscapeSignText(g:ale_sign_style_warning) +\ . ' texthl=ALEStyleWarningSign linehl=ALEWarningLine' +execute 'sign define ALEInfoSign text=' . s:EscapeSignText(g:ale_sign_info) +\ . ' texthl=ALEInfoSign linehl=ALEInfoLine' +sign define ALEDummySign text=\ texthl=SignColumn + +if g:ale_sign_highlight_linenrs && (has('nvim-0.3.2') || has('patch-8.2.3874')) + if !hlexists('ALEErrorSignLineNr') + highlight link ALEErrorSignLineNr CursorLineNr + endif + + if !hlexists('ALEStyleErrorSignLineNr') + highlight link ALEStyleErrorSignLineNr CursorLineNr + endif + + if !hlexists('ALEWarningSignLineNr') + highlight link ALEWarningSignLineNr CursorLineNr + endif + + if !hlexists('ALEStyleWarningSignLineNr') + highlight link ALEStyleWarningSignLineNr CursorLineNr + endif + + if !hlexists('ALEInfoSignLineNr') + highlight link ALEInfoSignLineNr CursorLineNr + endif + + sign define ALEErrorSign numhl=ALEErrorSignLineNr + sign define ALEStyleErrorSign numhl=ALEStyleErrorSignLineNr + sign define ALEWarningSign numhl=ALEWarningSignLineNr + sign define ALEStyleWarningSign numhl=ALEStyleWarningSignLineNr + sign define ALEInfoSign numhl=ALEInfoSignLineNr +endif + +function! ale#sign#GetSignName(sublist) abort + let l:priority = g:ale#util#style_warning_priority + + " Determine the highest priority item for the line. + for l:item in a:sublist + let l:item_priority = ale#util#GetItemPriority(l:item) + + if l:item_priority > l:priority + let l:priority = l:item_priority + endif + endfor + + if l:priority is# g:ale#util#error_priority + return 'ALEErrorSign' + endif + + if l:priority is# g:ale#util#warning_priority + return 'ALEWarningSign' + endif + + if l:priority is# g:ale#util#style_error_priority + return 'ALEStyleErrorSign' + endif + + if l:priority is# g:ale#util#style_warning_priority + return 'ALEStyleWarningSign' + endif + + if l:priority is# g:ale#util#info_priority + return 'ALEInfoSign' + endif + + " Use the error sign for invalid severities. + return 'ALEErrorSign' +endfunction + +function! s:PriorityCmd() abort + if s:supports_sign_groups + return ' priority=' . g:ale_sign_priority . ' ' + else + return '' + endif +endfunction + +function! s:GroupCmd() abort + if s:supports_sign_groups + return ' group=ale_signs ' + else + return ' ' + endif +endfunction + +" Read sign data for a buffer to a list of lines. +function! ale#sign#ReadSigns(buffer) abort + let l:output = execute( + \ 'sign place ' . s:GroupCmd() . s:PriorityCmd() + \ . ' buffer=' . a:buffer + \ ) + + return split(l:output, "\n") +endfunction + +function! ale#sign#ParsePattern() abort + if s:supports_sign_groups + " Matches output like : + " line=4 id=1 group=ale_signs name=ALEErrorSign + " строка=1 id=1000001 группа=ale_signs имя=ALEErrorSign + " 行=1 識別子=1000001 グループ=ale_signs 名前=ALEWarningSign + " línea=12 id=1000001 grupo=ale_signs nombre=ALEWarningSign + " riga=1 id=1000001 gruppo=ale_signs nome=ALEWarningSign + " Zeile=235 id=1000001 Gruppe=ale_signs Name=ALEErrorSign + let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=ale_signs>.*\=(ALE[a-zA-Z]+Sign)' + else + " Matches output like : + " line=4 id=1 name=ALEErrorSign + " строка=1 id=1000001 имя=ALEErrorSign + " 行=1 識別子=1000001 名前=ALEWarningSign + " línea=12 id=1000001 nombre=ALEWarningSign + " riga=1 id=1000001 nome=ALEWarningSign + " Zeile=235 id=1000001 Name=ALEErrorSign + let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)' + endif + + return l:pattern +endfunction + +" Given a buffer number, return a List of placed signs [line, id, group] +function! ale#sign#ParseSignsWithGetPlaced(buffer) abort + let l:signs = sign_getplaced(a:buffer, { 'group': s:supports_sign_groups ? 'ale_signs' : '' })[0].signs + let l:result = [] + let l:is_dummy_sign_set = 0 + + for l:sign in l:signs + if l:sign['name'] is# 'ALEDummySign' + let l:is_dummy_sign_set = 1 + else + call add(l:result, [ + \ str2nr(l:sign['lnum']), + \ str2nr(l:sign['id']), + \ l:sign['name'], + \]) + endif + endfor + + return [l:is_dummy_sign_set, l:result] +endfunction + +" Given a list of lines for sign output, return a List of [line, id, group] +function! ale#sign#ParseSigns(line_list) abort + let l:pattern =ale#sign#ParsePattern() + let l:result = [] + let l:is_dummy_sign_set = 0 + + for l:line in a:line_list + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) > 0 + if l:match[3] is# 'ALEDummySign' + let l:is_dummy_sign_set = 1 + else + call add(l:result, [ + \ str2nr(l:match[1]), + \ str2nr(l:match[2]), + \ l:match[3], + \]) + endif + endif + endfor + + return [l:is_dummy_sign_set, l:result] +endfunction + +function! ale#sign#FindCurrentSigns(buffer) abort + if exists('*sign_getplaced') + return ale#sign#ParseSignsWithGetPlaced(a:buffer) + else + let l:line_list = ale#sign#ReadSigns(a:buffer) + + return ale#sign#ParseSigns(l:line_list) + endif +endfunction + +" Given a loclist, group the List into with one List per line. +function! s:GroupLoclistItems(buffer, loclist) abort + let l:grouped_items = [] + let l:last_lnum = -1 + + for l:obj in a:loclist + if l:obj.bufnr != a:buffer + continue + endif + + " Create a new sub-List when we hit a new line. + if l:obj.lnum != l:last_lnum + call add(l:grouped_items, []) + endif + + call add(l:grouped_items[-1], l:obj) + let l:last_lnum = l:obj.lnum + endfor + + return l:grouped_items +endfunction + +function! s:UpdateLineNumbers(buffer, current_sign_list, loclist) abort + let l:line_map = {} + let l:line_numbers_changed = 0 + + for [l:line, l:sign_id, l:name] in a:current_sign_list + let l:line_map[l:sign_id] = l:line + endfor + + for l:item in a:loclist + if l:item.bufnr == a:buffer + let l:lnum = get(l:line_map, get(l:item, 'sign_id', 0), 0) + + if l:lnum && l:item.lnum != l:lnum + let l:item.lnum = l:lnum + let l:line_numbers_changed = 1 + endif + endif + endfor + + " When the line numbers change, sort the list again + if l:line_numbers_changed + call sort(a:loclist, 'ale#util#LocItemCompare') + endif +endfunction + +function! s:BuildSignMap(buffer, current_sign_list, grouped_items) abort + let l:max_signs = ale#Var(a:buffer, 'max_signs') + + if l:max_signs is 0 + let l:selected_grouped_items = [] + elseif type(l:max_signs) is v:t_number && l:max_signs > 0 + let l:selected_grouped_items = a:grouped_items[:l:max_signs - 1] + else + let l:selected_grouped_items = a:grouped_items + endif + + let l:sign_map = {} + let l:sign_offset = g:ale_sign_offset + + for [l:line, l:sign_id, l:name] in a:current_sign_list + let l:sign_info = get(l:sign_map, l:line, { + \ 'current_id_list': [], + \ 'current_name_list': [], + \ 'new_id': 0, + \ 'new_name': '', + \ 'items': [], + \}) + + " Increment the sign offset for new signs, by the maximum sign ID. + if l:sign_id > l:sign_offset + let l:sign_offset = l:sign_id + endif + + " Remember the sign names and IDs in separate Lists, so they are easy + " to work with. + call add(l:sign_info.current_id_list, l:sign_id) + call add(l:sign_info.current_name_list, l:name) + + let l:sign_map[l:line] = l:sign_info + endfor + + for l:group in l:selected_grouped_items + let l:line = l:group[0].lnum + let l:sign_info = get(l:sign_map, l:line, { + \ 'current_id_list': [], + \ 'current_name_list': [], + \ 'new_id': 0, + \ 'new_name': '', + \ 'items': [], + \}) + + let l:sign_info.new_name = ale#sign#GetSignName(l:group) + let l:sign_info.items = l:group + + let l:index = index( + \ l:sign_info.current_name_list, + \ l:sign_info.new_name + \) + + if l:index >= 0 + " We have a sign with this name already, so use the same ID. + let l:sign_info.new_id = l:sign_info.current_id_list[l:index] + else + " This sign name replaces the previous name, so use a new ID. + let l:sign_info.new_id = l:sign_offset + 1 + let l:sign_offset += 1 + endif + + let l:sign_map[l:line] = l:sign_info + endfor + + return l:sign_map +endfunction + +function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort + let l:command_list = [] + let l:is_dummy_sign_set = a:was_sign_set + + " Set the dummy sign if we need to. + " The dummy sign is needed to keep the sign column open while we add + " and remove signs. + if !l:is_dummy_sign_set && (!empty(a:sign_map) || g:ale_sign_column_always) + call add(l:command_list, 'sign place ' + \ . g:ale_sign_offset + \ . s:GroupCmd() + \ . s:PriorityCmd() + \ . ' line=1 name=ALEDummySign ' + \ . ' buffer=' . a:buffer + \) + let l:is_dummy_sign_set = 1 + endif + + " Place new items first. + for [l:line_str, l:info] in items(a:sign_map) + if l:info.new_id + " Save the sign IDs we are setting back on our loclist objects. + " These IDs will be used to preserve items which are set many times. + for l:item in l:info.items + let l:item.sign_id = l:info.new_id + endfor + + if index(l:info.current_id_list, l:info.new_id) < 0 + call add(l:command_list, 'sign place ' + \ . (l:info.new_id) + \ . s:GroupCmd() + \ . s:PriorityCmd() + \ . ' line=' . l:line_str + \ . ' name=' . (l:info.new_name) + \ . ' buffer=' . a:buffer + \) + endif + endif + endfor + + " Remove signs without new IDs. + for l:info in values(a:sign_map) + for l:current_id in l:info.current_id_list + if l:current_id isnot l:info.new_id + call add(l:command_list, 'sign unplace ' + \ . l:current_id + \ . s:GroupCmd() + \ . ' buffer=' . a:buffer + \) + endif + endfor + endfor + + " Remove the dummy sign to close the sign column if we need to. + if l:is_dummy_sign_set && !g:ale_sign_column_always + call add(l:command_list, 'sign unplace ' + \ . g:ale_sign_offset + \ . s:GroupCmd() + \ . ' buffer=' . a:buffer + \) + endif + + return l:command_list +endfunction + +" This function will set the signs which show up on the left. +function! ale#sign#SetSigns(buffer, loclist) abort + if !bufexists(str2nr(a:buffer)) + " Stop immediately when attempting to set signs for a buffer which + " does not exist. + return + endif + + " Find the current markers + let [l:is_dummy_sign_set, l:current_sign_list] = + \ ale#sign#FindCurrentSigns(a:buffer) + + " Update the line numbers for items from before which may have moved. + call s:UpdateLineNumbers(a:buffer, l:current_sign_list, a:loclist) + + " Group items after updating the line numbers. + let l:grouped_items = s:GroupLoclistItems(a:buffer, a:loclist) + + " Build a map of current and new signs, with the lines as the keys. + let l:sign_map = s:BuildSignMap( + \ a:buffer, + \ l:current_sign_list, + \ l:grouped_items, + \) + + let l:command_list = ale#sign#GetSignCommands( + \ a:buffer, + \ l:is_dummy_sign_set, + \ l:sign_map, + \) + + " Change the sign column color if the option is on. + if g:ale_change_sign_column_color && !empty(a:loclist) + highlight clear SignColumn + highlight link SignColumn ALESignColumnWithErrors + endif + + for l:command in l:command_list + silent! execute l:command + endfor + + " Reset the sign column color when there are no more errors. + if g:ale_change_sign_column_color && empty(a:loclist) + highlight clear SignColumn + highlight link SignColumn ALESignColumnWithoutErrors + endif +endfunction + +" Remove all signs. +function! ale#sign#Clear() abort + if s:supports_sign_groups + sign unplace group=ale_signs * + else + sign unplace * + endif +endfunction diff --git a/autoload/ale/socket.vim b/autoload/ale/socket.vim new file mode 100644 index 00000000..61f11e70 --- /dev/null +++ b/autoload/ale/socket.vim @@ -0,0 +1,151 @@ +" Author: w0rp +" Description: APIs for working with asynchronous sockets, with an API +" normalised between Vim 8 and NeoVim. Socket connections only work in NeoVim +" 0.3+, and silently do nothing in earlier NeoVim versions. +" +" Important functions are described below. They are: +" +" ale#socket#Open(address, options) -> channel_id (>= 0 if successful) +" ale#socket#IsOpen(channel_id) -> 1 if open, 0 otherwise +" ale#socket#Close(channel_id) +" ale#socket#Send(channel_id, data) +" ale#socket#GetAddress(channel_id) -> Return the address for a job + +let s:channel_map = get(s:, 'channel_map', {}) + +function! s:VimOutputCallback(channel, data) abort + let l:channel_id = ch_info(a:channel).id + + " Only call the callbacks for jobs which are valid. + if l:channel_id >= 0 && has_key(s:channel_map, l:channel_id) + call ale#util#GetFunction(s:channel_map[l:channel_id].callback)(l:channel_id, a:data) + endif +endfunction + +function! s:NeoVimOutputCallback(channel_id, data, event) abort + let l:info = s:channel_map[a:channel_id] + + if a:event is# 'data' + let l:info.last_line = ale#util#JoinNeovimOutput( + \ a:channel_id, + \ l:info.last_line, + \ a:data, + \ l:info.mode, + \ ale#util#GetFunction(l:info.callback), + \) + endif +endfunction + +" Open a socket for a given address. The following options are accepted: +" +" callback - A callback for receiving input. (required) +" +" A non-negative number representing a channel ID will be returned is the +" connection was successful. 0 is a valid channel ID in Vim, so test if the +" connection ID is >= 0. +function! ale#socket#Open(address, options) abort + let l:mode = get(a:options, 'mode', 'raw') + let l:Callback = a:options.callback + + let l:channel_info = { + \ 'address': a:address, + \ 'mode': l:mode, + \ 'callback': a:options.callback, + \} + + if !has('nvim') + " Vim + let l:channel_options = { + \ 'mode': l:mode, + \ 'waittime': 0, + \ 'callback': function('s:VimOutputCallback'), + \} + + " Use non-blocking writes for Vim versions that support the option. + if has('patch-8.1.350') + let l:channel_options.noblock = 1 + endif + + let l:channel_info.channel = ch_open(a:address, l:channel_options) + let l:vim_info = ch_info(l:channel_info.channel) + let l:channel_id = !empty(l:vim_info) ? l:vim_info.id : -1 + elseif exists('*chansend') && exists('*sockconnect') + " NeoVim 0.3+ + try + let l:channel_id = sockconnect(stridx(a:address, ':') != -1 ? 'tcp' : 'pipe', + \ a:address, {'on_data': function('s:NeoVimOutputCallback')}) + let l:channel_info.last_line = '' + catch /connection failed/ + let l:channel_id = -1 + endtry + + " 0 means the connection failed some times in NeoVim, so make the ID + " invalid to match Vim. + if l:channel_id is 0 + let l:channel_id = -1 + endif + + let l:channel_info.channel = l:channel_id + else + " Other Vim versions. + let l:channel_id = -1 + endif + + if l:channel_id >= 0 + let s:channel_map[l:channel_id] = l:channel_info + endif + + return l:channel_id +endfunction + +" Return 1 is a channel is open, 0 otherwise. +function! ale#socket#IsOpen(channel_id) abort + if !has_key(s:channel_map, a:channel_id) + return 0 + endif + + if has('nvim') + " In NeoVim, we have to check if this channel is in the global list. + return index(map(nvim_list_chans(), 'v:val.id'), a:channel_id) >= 0 + endif + + let l:channel = s:channel_map[a:channel_id].channel + + return ch_status(l:channel) is# 'open' +endfunction + +" Close a socket, if it's still open. +function! ale#socket#Close(channel_id) abort + " IsRunning isn't called here, so we don't check nvim_list_chans() + if !has_key(s:channel_map, a:channel_id) + return 0 + endif + + let l:channel = remove(s:channel_map, a:channel_id).channel + + if has('nvim') + silent! call chanclose(l:channel) + elseif ch_status(l:channel) is# 'open' + call ch_close(l:channel) + endif +endfunction + +" Send some data to a socket. +function! ale#socket#Send(channel_id, data) abort + if !has_key(s:channel_map, a:channel_id) + return + endif + + let l:channel = s:channel_map[a:channel_id].channel + + if has('nvim') + call chansend(l:channel, a:data) + else + call ch_sendraw(l:channel, a:data) + endif +endfunction + +" Get an address for a channel, or an empty string. +function! ale#socket#GetAddress(channel_id) abort + return get(get(s:channel_map, a:channel_id, {}), 'address', '') +endfunction diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim new file mode 100644 index 00000000..6b93ba51 --- /dev/null +++ b/autoload/ale/statusline.vim @@ -0,0 +1,135 @@ +" Author: KabbAmine +" Additions by: petpetpetpet +" Description: Statusline related function(s) + +function! s:CreateCountDict() abort + " Keys 0 and 1 are for backwards compatibility. + " The count object used to be a List of [error_count, warning_count]. + return { + \ '0': 0, + \ '1': 0, + \ 'error': 0, + \ 'warning': 0, + \ 'info': 0, + \ 'style_error': 0, + \ 'style_warning': 0, + \ 'total': 0, + \} +endfunction + +" Update the buffer error/warning count with data from loclist. +function! ale#statusline#Update(buffer, loclist) abort + if !exists('g:ale_buffer_info') || !has_key(g:ale_buffer_info, a:buffer) + return + endif + + let l:loclist = filter(copy(a:loclist), 'v:val.bufnr == a:buffer') + 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 + + " Set keys for backwards compatibility. + let l:count[0] = l:count.error + l:count.style_error + 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: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 0 + endif + + 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..3232d42a --- /dev/null +++ b/autoload/ale/swift.vim @@ -0,0 +1,70 @@ +" Author: Dan Loman +" 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 + +" Support Apple Swift Format {{{1 + +call ale#Set('swift_appleswiftformat_executable', 'swift-format') +call ale#Set('swift_appleswiftformat_use_swiftpm', 0) + +" Return the executable depending on whether or not to use Swift Package Manager. +" +" If not asked to use Swift Package Manager (use_swiftpm = 0), the returned +" value is the global executable, else the returned value is 'swift' because +" the final command line will be `swift run swift-format ...`. +" +" Failure is expected if use_swiftpm is `1` but no Package.swift can be located. +function! ale#swift#GetAppleSwiftFormatExecutable(buffer) abort + if !ale#Var(a:buffer, 'swift_appleswiftformat_use_swiftpm') + return ale#Var(a:buffer, 'swift_appleswiftformat_executable') + endif + + if ale#path#FindNearestFile(a:buffer, 'Package.swift') is# '' + " If there is no Package.swift file, we don't use swift-format even if it exists, + " so we return '' to indicate failure. + return '' + endif + + return 'swift' +endfunction + +" Return the command depending on whether or not to use Swift Package Manager. +" +" If asked to use Swift Package Manager (use_swiftpm = 1), the command +" arguments are prefixed with 'swift run'. +" +" In either case, the configuration file is located and added to the command. +function! ale#swift#GetAppleSwiftFormatCommand(buffer) abort + let l:executable = ale#swift#GetAppleSwiftFormatExecutable(a:buffer) + let l:command_args = '' + + if ale#Var(a:buffer, 'swift_appleswiftformat_use_swiftpm') + let l:command_args = ' ' . 'run swift-format' + endif + + return ale#Escape(l:executable) . l:command_args +endfunction + +" Locate the nearest '.swift-format' configuration file, and return the +" arguments, else return an empty string. +function! ale#swift#GetAppleSwiftFormatConfigArgs(buffer) abort + let l:config_filepath = ale#path#FindNearestFile(a:buffer, '.swift-format') + + if l:config_filepath isnot# '' + return '--configuration' . ' ' . l:config_filepath + endif + + return '' +endfunction + +" }}} diff --git a/autoload/ale/symbol.vim b/autoload/ale/symbol.vim new file mode 100644 index 00000000..ba971e74 --- /dev/null +++ b/autoload/ale/symbol.vim @@ -0,0 +1,110 @@ +let s:symbol_map = {} + +" Used to get the symbol map in tests. +function! ale#symbol#GetMap() abort + return deepcopy(s:symbol_map) +endfunction + +" Used to set the symbol map in tests. +function! ale#symbol#SetMap(map) abort + let s:symbol_map = a:map +endfunction + +function! ale#symbol#ClearLSPData() abort + let s:symbol_map = {} +endfunction + +function! ale#symbol#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:symbol_map, a:response.id) + let l:options = remove(s:symbol_map, a:response.id) + + let l:result = get(a:response, 'result', v:null) + let l:item_list = [] + + if type(l:result) is v:t_list + " Each item looks like this: + " { + " 'name': 'foo', + " 'kind': 123, + " 'deprecated': v:false, + " 'location': { + " 'uri': 'file://...', + " 'range': { + " 'start': {'line': 0, 'character': 0}, + " 'end': {'line': 0, 'character': 0}, + " }, + " }, + " 'containerName': 'SomeContainer', + " } + for l:response_item in l:result + let l:location = l:response_item.location + + call add(l:item_list, { + \ 'filename': ale#util#ToResource(l:location.uri), + \ 'line': l:location.range.start.line + 1, + \ 'column': l:location.range.start.character + 1, + \ 'match': l:response_item.name, + \}) + endfor + endif + + if empty(l:item_list) + call ale#util#Execute('echom ''No symbols found.''') + else + call ale#preview#ShowSelection(l:item_list, l:options) + endif + endif +endfunction + +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. + if getbufvar(l:buffer, 'ale_symbol_request_made', 0) + return + endif + + let l:Callback = function('ale#symbol#HandleLSPResponse') + call ale#lsp#RegisterCallback(l:id, l:Callback) + + let l:message = ale#lsp#message#Symbol(a:query) + let l:request_id = ale#lsp#Send(l:id, l:message) + + 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! ale#symbol#Search(args) abort + let [l:opts, l:query] = ale#args#Parse(['relative'], a:args) + + 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#lsp_linter#GetEnabled(l:buffer) + if l:linter.lsp isnot# 'tsserver' + 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 new file mode 100644 index 00000000..e03ecb65 --- /dev/null +++ b/autoload/ale/test.vim @@ -0,0 +1,205 @@ +" Author: w0rp +" Description: Functions for making testing ALE easier. +" +" This file should not typically be loaded during the normal execution of ALE. + +" Change the directory for checking things in particular test directories +" +" This function will set the g:dir variable, which represents the working +" directory after changing the path. This variable allows a test to change +" directories, and then switch back to a directory at the start of the test +" run. +" +" This function should be run in a Vader Before: block. +function! ale#test#SetDirectory(docker_path) abort + if a:docker_path[:len('/testplugin/') - 1] isnot# '/testplugin/' + throw 'docker_path must start with /testplugin/!' + endif + + " Try to switch directory, which will fail when running tests directly, + " and not through the Docker image. + silent! execute 'cd ' . fnameescape(a:docker_path) + let g:dir = getcwd() " no-custom-checks +endfunction + +" When g:dir is defined, switch back to the directory we saved, and then +" delete that variable. +" +" The filename will be reset to dummy.txt +" +" This function should be run in a Vader After: block. +function! ale#test#RestoreDirectory() abort + call ale#test#SetFilename('dummy.txt') + silent execute 'cd ' . fnameescape(g:dir) + unlet! g:dir +endfunction + +" Get a filename for the current buffer using a relative path to the script. +" +" If a g:dir variable is set, it will be used as the path to the directory +" containing the test file. +function! ale#test#GetFilename(path) abort + let l:dir = get(g:, 'dir', '') + + if empty(l:dir) + let l:dir = getcwd() " no-custom-checks + endif + + let l:full_path = ale#path#IsAbsolute(a:path) + \ ? a:path + \ : l:dir . '/' . a:path + + return ale#path#Simplify(l:full_path) +endfunction + +" Change the filename for the current buffer using a relative path to +" the script without running autocmd commands. +" +" If a g:dir variable is set, it will be used as the path to the directory +" containing the test file. +function! ale#test#SetFilename(path) abort + let l:full_path = ale#test#GetFilename(a:path) + silent! noautocmd execute 'file ' . fnameescape(l:full_path) +endfunction + +function! RemoveNewerKeys(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, 'end_col') + call remove(l:item, 'end_col') + endif + + if has_key(l:item, 'end_lnum') + call remove(l:item, 'end_lnum') + endif + endfor +endfunction + +" Return loclist data with only the keys supported by the lowest Vim versions. +function! ale#test#GetLoclistWithoutNewerKeys() abort + let l:results = getloclist(0) + call RemoveNewerKeys(l:results) + + return l:results +endfunction + +" Return quickfix data with only the keys supported by the lowest Vim versions. +function! ale#test#GetQflistWithoutNewerKeys() abort + let l:results = getqflist() + call RemoveNewerKeys(l:results) + + 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 + " linters with chained commands. + 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 new file mode 100644 index 00000000..8b38e5ad --- /dev/null +++ b/autoload/ale/toggle.vim @@ -0,0 +1,101 @@ +function! s:EnablePreamble() abort + " Set pattern options again, if enabled. + if get(g:, 'ale_pattern_options_enabled', 0) + call ale#pattern_options#SetOptions(bufnr('')) + endif + + " Lint immediately, including running linters against the file. + call ale#Queue(0, 'lint_file') +endfunction + +function! s:DisablePostamble() abort + " Remove highlights for the current buffer now. + if g:ale_set_highlights + call ale#highlight#UpdateHighlights() + endif + + if g:ale_virtualtext_cursor isnot# 'disabled' && g:ale_virtualtext_cursor != 0 + call ale#virtualtext#Clear(bufnr('')) + endif +endfunction + +function! ale#toggle#Toggle() abort + let g:ale_enabled = !get(g:, 'ale_enabled') + + if g:ale_enabled + call s:EnablePreamble() + + if g:ale_set_balloons + call ale#balloon#Enable() + endif + else + call ale#engine#CleanupEveryBuffer() + call s:DisablePostamble() + + if exists('*ale#balloon#Disable') + call ale#balloon#Disable() + endif + endif + + call ale#events#Init() +endfunction + +function! ale#toggle#Enable() abort + if !g:ale_enabled + call ale#toggle#Toggle() + endif +endfunction + +function! ale#toggle#Disable() abort + if g:ale_enabled + call ale#toggle#Toggle() + endif +endfunction + +function! ale#toggle#Reset() abort + call ale#engine#CleanupEveryBuffer() + call ale#highlight#UpdateHighlights() +endfunction + +function! ale#toggle#ToggleBuffer(buffer) abort + " Get the new value for the toggle. + let l:enabled = !getbufvar(a:buffer, 'ale_enabled', 1) + + " Disabling ALE globally removes autocmd events, so we cannot enable + " linting locally when linting is disabled globally + if l:enabled && !g:ale_enabled + " no-custom-checks + echom 'ALE cannot be enabled locally when disabled globally' + + return + endif + + call setbufvar(a:buffer, 'ale_enabled', l:enabled) + + if l:enabled + call s:EnablePreamble() + else + " Stop all jobs and clear the results for everything, and delete + " all of the data we stored for the buffer. + call ale#engine#Cleanup(a:buffer) + call s:DisablePostamble() + endif +endfunction + +function! ale#toggle#EnableBuffer(buffer) abort + " ALE is enabled by default for all buffers. + if !getbufvar(a:buffer, 'ale_enabled', 1) + call ale#toggle#ToggleBuffer(a:buffer) + endif +endfunction + +function! ale#toggle#DisableBuffer(buffer) abort + if getbufvar(a:buffer, 'ale_enabled', 1) + call ale#toggle#ToggleBuffer(a:buffer) + endif +endfunction + +function! ale#toggle#ResetBuffer(buffer) abort + call ale#engine#Cleanup(a:buffer) + call ale#highlight#UpdateHighlights() +endfunction diff --git a/autoload/ale/uri.vim b/autoload/ale/uri.vim new file mode 100644 index 00000000..d696f03d --- /dev/null +++ b/autoload/ale/uri.vim @@ -0,0 +1,43 @@ +function! s:EncodeChar(char) abort + let l:result = '' + + for l:index in range(strlen(a:char)) + let l:result .= printf('%%%02x', char2nr(a:char[l:index])) + endfor + + return l:result +endfunction + +function! ale#uri#Encode(value) abort + return substitute( + \ a:value, + \ '\([^a-zA-Z0-9\\/$\-_.!*''(),]\)', + \ '\=s:EncodeChar(submatch(1))', + \ 'g' + \) +endfunction + +function! ale#uri#Decode(value) abort + return substitute( + \ a:value, + \ '%\(\x\x\)', + \ '\=printf("%c", str2nr(submatch(1), 16))', + \ 'g' + \) +endfunction + +let s:uri_handlers = { +\ 'jdt': { +\ 'OpenURILink': function('ale#uri#jdt#OpenJDTLink'), +\ } +\} + +function! ale#uri#GetURIHandler(uri) abort + for l:scheme in keys(s:uri_handlers) + if a:uri =~# '^'.l:scheme.'://' + return s:uri_handlers[scheme] + endif + endfor + + return v:null +endfunction diff --git a/autoload/ale/uri/jdt.vim b/autoload/ale/uri/jdt.vim new file mode 100644 index 00000000..7df10b4e --- /dev/null +++ b/autoload/ale/uri/jdt.vim @@ -0,0 +1,112 @@ +" Author: yoshi1123 +" Description: Functions for working with jdt:// URIs. + +function! s:OpenJDTLink(root, uri, line, column, options, result) abort + if has_key(a:result, 'error') + " no-custom-checks + echoerr a:result.error.message + + return + endif + + let l:contents = a:result['result'] + + if type(l:contents) is# type(v:null) + " no-custom-checks + echoerr 'File content not found' + endif + + " disable autocmd when opening buffer + autocmd! AleURISchemes + call ale#util#Open(a:uri, a:line, a:column, a:options) + autocmd AleURISchemes BufNewFile,BufReadPre jdt://** call ale#uri#jdt#ReadJDTLink(expand('')) + + if !empty(getbufvar(bufnr(''), 'ale_root', '')) + return + endif + + let b:ale_root = a:root + set filetype=java + + call setline(1, split(l:contents, '\n')) + call cursor(a:line, a:column) + normal! zz + + setlocal buftype=nofile nomodified nomodifiable readonly +endfunction + +" Load new buffer with jdt:// contents and jump to line and column. +function! ale#uri#jdt#OpenJDTLink(encoded_uri, line, column, options, conn_id) abort + let l:found_eclipselsp = v:false + + " We should only arrive here from a 'go to definition' request, so we'll + " assume the eclipselsp linter is enabled. + for l:linter in ale#linter#Get('java') + if l:linter.name is# 'eclipselsp' + let l:found_eclipselsp = v:true + endif + endfor + + if !l:found_eclipselsp + throw 'eclipselsp not running' + endif + + let l:root = a:conn_id[stridx(a:conn_id, ':')+1:] + let l:uri = a:encoded_uri + call ale#lsp_linter#SendRequest( + \ bufnr(''), + \ 'eclipselsp', + \ [0, 'java/classFileContents', {'uri': ale#util#ToURI(l:uri)}], + \ function('s:OpenJDTLink', [l:root, l:uri, a:line, a:column, a:options]) + \) +endfunction + +function! s:ReadClassFileContents(uri, result) abort + if has_key(a:result, 'error') + " no-custom-checks + echoerr a:result.error.message + + return + endif + + let l:contents = a:result['result'] + + if type(l:contents) is# type(v:null) + " no-custom-checks + echoerr 'File content not found' + endif + + call setline(1, split(l:contents, '\n')) + + setlocal buftype=nofile nomodified nomodifiable readonly +endfunction + +" Read jdt:// contents, as part of current project, into current buffer. +function! ale#uri#jdt#ReadJDTLink(encoded_uri) abort + if !empty(getbufvar(bufnr(''), 'ale_root', '')) + return + endif + + let l:linter_map = ale#lsp_linter#GetLSPLinterMap() + + for [l:conn_id, l:linter] in items(l:linter_map) + if l:linter.name is# 'eclipselsp' + let l:root = l:conn_id[stridx(l:conn_id, ':')+1:] + endif + endfor + + if l:root is# v:null + throw 'eclipselsp not running' + endif + + let l:uri = a:encoded_uri + let b:ale_root = l:root + set filetype=java + + call ale#lsp_linter#SendRequest( + \ bufnr(''), + \ 'eclipselsp', + \ [0, 'java/classFileContents', {'uri': ale#util#ToURI(l:uri)}], + \ function('s:ReadClassFileContents', [l:uri]) + \) +endfunction diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim new file mode 100644 index 00000000..98af1635 --- /dev/null +++ b/autoload/ale/util.vim @@ -0,0 +1,585 @@ +" Author: w0rp +" Description: Contains miscellaneous functions + +" A wrapper function for mode() so we can test calls for it. +function! ale#util#Mode(...) abort + return call('mode', a:000) +endfunction + +" A wrapper function for feedkeys so we can test calls for it. +function! ale#util#FeedKeys(...) abort + return call('feedkeys', a:000) +endfunction + +" Show a message in as small a window as possible. +" +" Vim 8 does not support echoing long messages from asynchronous callbacks, +" but NeoVim does. Small messages can be echoed in Vim 8, and larger messages +" have to be shown in preview windows. +function! ale#util#ShowMessage(string, ...) abort + let l:options = get(a:000, 0, {}) + + if !has('nvim') + call ale#preview#CloseIfTypeMatches('ale-preview.message') + endif + + " We have to assume the user is using a monospace font. + if has('nvim') || (a:string !~? "\n" && len(a:string) < &columns) + " no-custom-checks + echo a:string + else + call ale#preview#Show(split(a:string, "\n"), extend( + \ { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \ }, + \ l:options, + \)) + endif +endfunction + +" A wrapper function for execute, so we can test executing some commands. +function! ale#util#Execute(expr) abort + execute a:expr +endfunction + +if !exists('g:ale#util#nul_file') + " A null file for sending output to nothing. + let g:ale#util#nul_file = '/dev/null' + + if has('win32') + let g:ale#util#nul_file = 'nul' + endif +endif + +" Given a job, a buffered line of data, a list of parts of lines, a mode data +" is being read in, and a callback, join the lines of output for a NeoVim job +" or socket together, and call the callback with the joined output. +" +" Note that jobs and IDs are the same thing on NeoVim. +function! ale#util#JoinNeovimOutput(job, last_line, data, mode, callback) abort + if a:mode is# 'raw' + call a:callback(a:job, join(a:data, "\n")) + + return '' + endif + + let l:lines = a:data[:-2] + + if len(a:data) > 1 + let l:lines[0] = a:last_line . l:lines[0] + let l:new_last_line = a:data[-1] + else + let l:new_last_line = a:last_line . get(a:data, 0, '') + endif + + for l:line in l:lines + call a:callback(a:job, l:line) + endfor + + return l:new_last_line +endfunction + +" Return the number of lines for a given buffer. +function! ale#util#GetLineCount(buffer) abort + return len(getbufline(a:buffer, 1, '$')) +endfunction + +function! ale#util#GetFunction(string_or_ref) abort + if type(a:string_or_ref) is v:t_string + return function(a:string_or_ref) + endif + + return a:string_or_ref +endfunction + +" Open the file (at the given line). +" options['open_in'] can be: +" current-buffer (default) +" tab +" split +" vsplit +function! ale#util#Open(filename, line, column, options) abort + 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# 'split' + call ale#util#Execute('split ' . l:args_to_open) + elseif l:open_in is# 'vsplit' + 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 ' . l:args_to_open) + else + normal! m` + endif + + call cursor(a:line, a:column) + normal! zz +endfunction + +let g:ale#util#error_priority = 5 +let g:ale#util#warning_priority = 4 +let g:ale#util#info_priority = 3 +let g:ale#util#style_error_priority = 2 +let g:ale#util#style_warning_priority = 1 + +function! ale#util#GetItemPriority(item) abort + if a:item.type is# 'I' + return g:ale#util#info_priority + endif + + if a:item.type is# 'W' + if get(a:item, 'sub_type', '') is# 'style' + return g:ale#util#style_warning_priority + endif + + return g:ale#util#warning_priority + endif + + if get(a:item, 'sub_type', '') is# 'style' + return g:ale#util#style_error_priority + endif + + return g:ale#util#error_priority +endfunction + +" Compare two loclist items for ALE, sorted by their buffers, filenames, and +" line numbers and column numbers. +function! ale#util#LocItemCompare(left, right) abort + if a:left.bufnr < a:right.bufnr + return -1 + endif + + if a:left.bufnr > a:right.bufnr + return 1 + endif + + if a:left.bufnr == -1 + if a:left.filename < a:right.filename + return -1 + endif + + if a:left.filename > a:right.filename + return 1 + endif + endif + + if a:left.lnum < a:right.lnum + return -1 + endif + + if a:left.lnum > a:right.lnum + return 1 + endif + + if a:left.col < a:right.col + return -1 + endif + + if a:left.col > a:right.col + return 1 + endif + + " When either of the items lacks a problem type, then the two items should + " be considered equal. This is important for loclist jumping. + if !has_key(a:left, 'type') || !has_key(a:right, 'type') + return 0 + endif + + let l:left_priority = ale#util#GetItemPriority(a:left) + let l:right_priority = ale#util#GetItemPriority(a:right) + + if l:left_priority < l:right_priority + return -1 + endif + + if l:left_priority > l:right_priority + return 1 + endif + + return 0 +endfunction + +" Compare two loclist items, including the text for the items. +" +" This function can be used for de-duplicating lists. +function! ale#util#LocItemCompareWithText(left, right) abort + let l:cmp_value = ale#util#LocItemCompare(a:left, a:right) + + if l:cmp_value + return l:cmp_value + endif + + if a:left.text < a:right.text + return -1 + endif + + if a:left.text > a:right.text + return 1 + endif + + return 0 +endfunction + +" This function will perform a binary search and a small sequential search +" on the list to find the last problem in the buffer and line which is +" on or before the column. The index of the problem will be returned. +" +" -1 will be returned if nothing can be found. +function! ale#util#BinarySearch(loclist, buffer, line, column) abort + let l:min = 0 + let l:max = len(a:loclist) - 1 + + while 1 + if l:max < l:min + return -1 + endif + + let l:mid = (l:min + l:max) / 2 + let l:item = a:loclist[l:mid] + + " Binary search for equal buffers, equal lines, then near columns. + if l:item.bufnr < a:buffer + let l:min = l:mid + 1 + elseif l:item.bufnr > a:buffer + let l:max = l:mid - 1 + elseif l:item.lnum < a:line + let l:min = l:mid + 1 + elseif l:item.lnum > a:line + let l:max = l:mid - 1 + else + " This part is a small sequential search. + let l:index = l:mid + + " Search backwards to find the first problem on the line. + while l:index > 0 + \&& a:loclist[l:index - 1].bufnr == a:buffer + \&& a:loclist[l:index - 1].lnum == a:line + let l:index -= 1 + endwhile + + " Find the last problem on or before this column. + while l:index < l:max + \&& a:loclist[l:index + 1].bufnr == a:buffer + \&& a:loclist[l:index + 1].lnum == a:line + \&& a:loclist[l:index + 1].col <= a:column + let l:index += 1 + endwhile + + " Scan forwards to find the last item on the column for the item + " we found, which will have the most serious problem. + let l:item_column = a:loclist[l:index].col + + while l:index < l:max + \&& a:loclist[l:index + 1].bufnr == a:buffer + \&& a:loclist[l:index + 1].lnum == a:line + \&& a:loclist[l:index + 1].col == l:item_column + let l:index += 1 + endwhile + + return l:index + endif + endwhile +endfunction + +" A function for testing if a function is running inside a sandbox. +" See :help sandbox +function! ale#util#InSandbox() abort + try + let &l:equalprg=&l:equalprg + catch /E48/ + " E48 is the sandbox error. + return 1 + endtry + + return 0 +endfunction + +function! ale#util#Tempname() abort + let l:clear_tempdir = 0 + + if exists('$TMPDIR') && empty($TMPDIR) + let l:clear_tempdir = 1 + let $TMPDIR = '/tmp' + endif + + try + let l:name = tempname() " no-custom-checks + finally + if l:clear_tempdir + let $TMPDIR = '' + endif + endtry + + return l:name +endfunction + +" Given a single line, or a List of lines, and a single pattern, or a List +" of patterns, return all of the matches for the lines(s) from the given +" patterns, using matchlist(). +" +" Only the first pattern which matches a line will be returned. +function! ale#util#GetMatches(lines, patterns) abort + let l:matches = [] + let l:lines = type(a:lines) is v:t_list ? a:lines : [a:lines] + let l:patterns = type(a:patterns) is v:t_list ? a:patterns : [a:patterns] + + for l:line in l:lines + for l:pattern in l:patterns + let l:match = matchlist(l:line, l:pattern) + + if !empty(l:match) + call add(l:matches, l:match) + break + endif + endfor + endfor + + return l:matches +endfunction + +" Given a single line, or a List of lines, and a single pattern, or a List of +" patterns, and a callback function for mapping the items matches, return the +" result of mapping all of the matches for the lines from the given patterns, +" using matchlist() +" +" Only the first pattern which matches a line will be returned. +function! ale#util#MapMatches(lines, patterns, Callback) abort + return map(ale#util#GetMatches(a:lines, a:patterns), 'a:Callback(v:val)') +endfunction + +function! s:LoadArgCount(function) abort + try + let l:output = execute('function a:function') + catch /E123/ + return 0 + endtry + + let l:match = matchstr(split(l:output, "\n")[0], '\v\([^)]+\)')[1:-2] + let l:arg_list = filter(split(l:match, ', '), 'v:val isnot# ''...''') + + return len(l:arg_list) +endfunction + +" Given the name of a function, a Funcref, or a lambda, return the number +" of named arguments for a function. +function! ale#util#FunctionArgCount(function) abort + let l:Function = ale#util#GetFunction(a:function) + let l:count = s:LoadArgCount(l:Function) + + " If we failed to get the count, forcibly load the autoload file, if the + " function is an autoload function. autoload functions aren't normally + " defined until they are called. + if l:count == 0 + let l:function_name = matchlist(string(l:Function), 'function([''"]\(.\+\)[''"])')[1] + + if l:function_name =~# '#' + execute 'runtime autoload/' . join(split(l:function_name, '#')[:-2], '/') . '.vim' + let l:count = s:LoadArgCount(l:Function) + endif + endif + + return l:count +endfunction + +" Escape a string so the characters in it will be safe for use inside of PCRE +" or RE2 regular expressions without characters having special meanings. +function! ale#util#EscapePCRE(unsafe_string) abort + return substitute(a:unsafe_string, '\([\-\[\]{}()*+?.^$|]\)', '\\\1', 'g') +endfunction + +" Escape a string so that it can be used as a literal string inside an evaled +" vim command. +function! ale#util#EscapeVim(unsafe_string) abort + return "'" . substitute(a:unsafe_string, "'", "''", 'g') . "'" +endfunction + + +" Given a String or a List of String values, try and decode the string(s) +" as a JSON value which can be decoded with json_decode. If the JSON string +" is invalid, the default argument value will be returned instead. +" +" This function is useful in code where the data can't be trusted to be valid +" JSON, and where throwing exceptions is mostly just irritating. +function! ale#util#FuzzyJSONDecode(data, default) abort + if empty(a:data) + return a:default + endif + + let l:str = type(a:data) is v:t_string ? a:data : join(a:data, '') + + try + let l:result = json_decode(l:str) + + " Vim 8 only uses the value v:none for decoding blank strings. + if !has('nvim') && l:result is v:none + return a:default + endif + + return l:result + catch /E474\|E491/ + return a:default + endtry +endfunction + +" Write a file, including carriage return characters for DOS files. +" +" The buffer number is required for determining the fileformat setting for +" the buffer. +function! ale#util#Writefile(buffer, lines, filename) abort + let l:corrected_lines = getbufvar(a:buffer, '&fileformat') is# 'dos' + \ ? map(copy(a:lines), 'substitute(v:val, ''\r*$'', ''\r'', '''')') + \ : a:lines + + " Set binary flag if buffer doesn't have eol and nofixeol to avoid appending newline + let l:flags = !getbufvar(a:buffer, '&eol') && exists('+fixeol') && !&fixeol ? 'bS' : 'S' + + call writefile(l:corrected_lines, a:filename, l:flags) " no-custom-checks +endfunction + +if !exists('s:patial_timers') + let s:partial_timers = {} +endif + +function! s:ApplyPartialTimer(timer_id) abort + if has_key(s:partial_timers, a:timer_id) + let [l:Callback, l:args] = remove(s:partial_timers, a:timer_id) + call call(l:Callback, [a:timer_id] + l:args) + endif +endfunction + +" Given a delay, a callback, a List of arguments, start a timer with +" timer_start() and call the callback provided with [timer_id] + args. +" +" The timer must not be stopped with timer_stop(). +" Use ale#util#StopPartialTimer() instead, which can stop any timer, and will +" clear any arguments saved for executing callbacks later. +function! ale#util#StartPartialTimer(delay, callback, args) abort + let l:timer_id = timer_start(a:delay, function('s:ApplyPartialTimer')) + let s:partial_timers[l:timer_id] = [a:callback, a:args] + + return l:timer_id +endfunction + +function! ale#util#StopPartialTimer(timer_id) abort + call timer_stop(a:timer_id) + + if has_key(s:partial_timers, a:timer_id) + call remove(s:partial_timers, a:timer_id) + endif +endfunction + +" Given a possibly multi-byte string and a 1-based character position on a +" line, return the 1-based byte position on that line. +function! ale#util#Col(str, chr) abort + if a:chr < 2 + return a:chr + endif + + return strlen(join(split(a:str, '\zs')[0:a:chr - 2], '')) + 1 +endfunction + +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 = 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] : {} + + return [l:info, l:loc] +endfunction + +function! ale#util#Input(message, value, ...) abort + if a:0 > 0 + return input(a:message, a:value, a:1) + else + return input(a:message, a:value) + endif +endfunction + +function! ale#util#HasBuflineApi() abort + return exists('*deletebufline') && exists('*setbufline') +endfunction + +" Sets buffer contents to lines +function! ale#util#SetBufferContents(buffer, lines) abort + let l:has_bufline_api = ale#util#HasBuflineApi() + + if !l:has_bufline_api && a:buffer isnot bufnr('') + return + endif + + " If the file is in DOS mode, we have to remove carriage returns from + " the ends of lines before calling setline(), or we will see them + " twice. + let l:new_lines = getbufvar(a:buffer, '&fileformat') is# 'dos' + \ ? map(copy(a:lines), 'substitute(v:val, ''\r\+$'', '''', '''')') + \ : a:lines + let l:first_line_to_remove = len(l:new_lines) + 1 + + " Use a Vim API for setting lines in other buffers, if available. + if l:has_bufline_api + if has('nvim') + " save and restore signs to avoid flickering + let signs = sign_getplaced(a:buffer, {'group': 'ale'})[0].signs + + call nvim_buf_set_lines(a:buffer, 0, l:first_line_to_remove, 0, l:new_lines) + + " restore signs (invalid line numbers will be skipped) + call sign_placelist(map(signs, {_, v -> extend(v, {'buffer': a:buffer})})) + else + call setbufline(a:buffer, 1, l:new_lines) + endif + + call deletebufline(a:buffer, l:first_line_to_remove, '$') + " Fall back on setting lines the old way, for the current buffer. + else + let l:old_line_length = line('$') + + if l:old_line_length >= l:first_line_to_remove + let l:save = winsaveview() + silent execute + \ l:first_line_to_remove . ',' . l:old_line_length . 'd_' + call winrestview(l:save) + endif + + call setline(1, l:new_lines) + endif + + return l:new_lines +endfunction + +function! ale#util#GetBufferContents(buffer) abort + return join(getbufline(a:buffer, 1, '$'), "\n") . "\n" +endfunction + +function! ale#util#ToURI(resource) abort + let l:uri_handler = ale#uri#GetURIHandler(a:resource) + + if l:uri_handler is# v:null + " resource is a filesystem path + let l:uri = ale#path#ToFileURI(a:resource) + else + " resource is a URI + let l:uri = a:resource + endif + + return l:uri +endfunction + +function! ale#util#ToResource(uri) abort + let l:uri_handler = ale#uri#GetURIHandler(a:uri) + + if l:uri_handler is# v:null + " resource is a filesystem path + let l:resource = ale#path#FromFileURI(a:uri) + else + " resource is a URI + let l:resource = a:uri + endif + + return l:resource +endfunction diff --git a/autoload/ale/virtualtext.vim b/autoload/ale/virtualtext.vim new file mode 100644 index 00000000..c21762cb --- /dev/null +++ b/autoload/ale/virtualtext.vim @@ -0,0 +1,325 @@ +scriptencoding utf-8 +" Author: w0rp +" Author: Luan Santos +" Description: Shows lint message for the current line as virtualtext, if any + +if !hlexists('ALEVirtualTextError') + highlight link ALEVirtualTextError Comment +endif + +if !hlexists('ALEVirtualTextStyleError') + highlight link ALEVirtualTextStyleError ALEVirtualTextError +endif + +if !hlexists('ALEVirtualTextWarning') + highlight link ALEVirtualTextWarning Comment +endif + +if !hlexists('ALEVirtualTextStyleWarning') + highlight link ALEVirtualTextStyleWarning ALEVirtualTextWarning +endif + +if !hlexists('ALEVirtualTextInfo') + highlight link ALEVirtualTextInfo ALEVirtualTextWarning +endif + +let g:ale_virtualtext_prefix = +\ get(g:, 'ale_virtualtext_prefix', '%comment% %type%: ') +" Controls the milliseconds delay before showing a message. +let g:ale_virtualtext_delay = get(g:, 'ale_virtualtext_delay', 10) + +" Controls the positioning of virtualtext +let g:ale_virtualtext_column = get(g:, 'ale_virtualtext_column', 0) +let g:ale_virtualtext_maxcolumn = get(g:, 'ale_virtualtext_maxcolumn', 0) +" If 1, only show the first problem with virtualtext. +let g:ale_virtualtext_single = get(g:, 'ale_virtualtext_single', v:true) + +let s:cursor_timer = get(s:, 'cursor_timer', -1) +let s:last_pos = get(s:, 'last_pos', [0, 0, 0]) +let s:hl_list = get(s:, 'hl_list', []) +let s:last_message = '' + +if !has_key(s:, 'has_virt_text') + let s:has_virt_text = 0 + let s:emulate_virt = 0 + let s:last_virt = -1 + + if has('nvim-0.3.2') + let s:ns_id = nvim_create_namespace('ale') + let s:has_virt_text = 1 + elseif has('textprop') && has('popupwin') + let s:has_virt_text = 1 + let s:emulate_virt = !has('patch-9.0.0297') + + if s:emulate_virt + call prop_type_add('ale', {}) + endif + endif +endif + +function! s:StopCursorTimer() abort + if s:cursor_timer != -1 + call timer_stop(s:cursor_timer) + let s:cursor_timer = -1 + endif +endfunction + +function! ale#virtualtext#ResetDataForTests() abort + let s:last_pos = [0, 0, 0] + let s:last_message = '' +endfunction + +function! ale#virtualtext#GetLastMessageForTests() abort + return s:last_message +endfunction + +function! ale#virtualtext#GetComment(buffer) abort + let l:filetype = getbufvar(a:buffer, '&filetype') + let l:split = split(getbufvar(a:buffer, '&commentstring'), '%s') + + return !empty(l:split) ? trim(l:split[0]) : '#' +endfunction + +function! ale#virtualtext#Clear(buffer) abort + if !s:has_virt_text || !bufexists(str2nr(a:buffer)) + return + endif + + if has('nvim') + call nvim_buf_clear_namespace(a:buffer, s:ns_id, 0, -1) + else + if s:emulate_virt && s:last_virt != -1 + call prop_remove({'type': 'ale'}) + call popup_close(s:last_virt) + let s:last_virt = -1 + elseif !empty(s:hl_list) + call prop_remove({ + \ 'types': s:hl_list, + \ 'all': 1, + \ 'bufnr': a:buffer, + \}) + endif + endif +endfunction + +function! ale#virtualtext#GetGroup(item) abort + let l:type = get(a:item, 'type', 'E') + let l:sub_type = get(a:item, 'sub_type', '') + + if l:type is# 'E' + if l:sub_type is# 'style' + return 'ALEVirtualTextStyleError' + endif + + return 'ALEVirtualTextError' + endif + + if l:type is# 'W' + if l:sub_type is# 'style' + return 'ALEVirtualTextStyleWarning' + endif + + return 'ALEVirtualTextWarning' + endif + + return 'ALEVirtualTextInfo' +endfunction + +function! ale#virtualtext#GetColumnPadding(buffer, line) abort + let l:mincol = ale#Var(a:buffer, 'virtualtext_column') + let l:maxcol = ale#Var(a:buffer, 'virtualtext_maxcolumn') + let l:win = bufwinnr(a:buffer) + + if l:mincol[len(l:mincol)-1] is# '%' + let l:mincol = (winwidth(l:win) * l:mincol) / 100 + endif + + if l:maxcol[len(l:maxcol)-1] is# '%' + let l:maxcol = (winwidth(l:win) * l:maxcol) / 100 + endif + + " Calculate padding for virtualtext alignment + if l:mincol > 0 || l:maxcol > 0 + let l:line_width = strdisplaywidth(getline(a:line)) + + if l:line_width < l:mincol + return l:mincol - l:line_width + elseif l:maxcol > 0 && l:line_width >= l:maxcol + " Stop processing if virtualtext would start beyond maxcol + return -1 + endif + endif + + " no padding. + return 0 +endfunction + +function! ale#virtualtext#ShowMessage(buffer, item) abort + if !s:has_virt_text || !bufexists(str2nr(a:buffer)) + return + endif + + let l:line = max([1, a:item.lnum]) + let l:hl_group = ale#virtualtext#GetGroup(a:item) + + " Get a language-appropriate comment character, or default to '#'. + let l:comment = ale#virtualtext#GetComment(a:buffer) + let l:prefix = ale#Var(a:buffer, 'virtualtext_prefix') + let l:prefix = ale#GetLocItemMessage(a:item, l:prefix) + let l:prefix = substitute(l:prefix, '\V%comment%', '\=l:comment', 'g') + let l:msg = l:prefix . substitute(a:item.text, '\n', ' ', 'g') + let l:col_pad = ale#virtualtext#GetColumnPadding(a:buffer, l:line) + + " Store the last message we're going to set so we can read it in tests. + let s:last_message = l:msg + + " Discard virtualtext if padding is negative. + if l:col_pad < 0 + return + endif + + if has('nvim') + call nvim_buf_set_virtual_text( + \ a:buffer, + \ s:ns_id, l:line - 1, + \ [[l:msg, l:hl_group]], + \ {} + \) + elseif s:emulate_virt + let l:left_pad = col('$') + call prop_add(l:line, l:left_pad, {'type': 'ale'}) + let s:last_virt = popup_create(l:msg, { + \ 'line': -1, + \ 'padding': [0, 0, 0, 1], + \ 'mask': [[1, 1, 1, 1]], + \ 'textprop': 'ale', + \ 'highlight': l:hl_group, + \ 'fixed': 1, + \ 'wrap': 0, + \ 'zindex': 2 + \}) + else + let l:type = prop_type_get(l:hl_group) + + if l:type == {} + call prop_type_add(l:hl_group, {'highlight': l:hl_group}) + endif + + " Add highlight groups to the list so we can clear them later. + if index(s:hl_list, l:hl_group) == -1 + call add(s:hl_list, l:hl_group) + endif + + " We ignore all errors from prop_add. + silent! call prop_add(l:line, 0, { + \ 'type': l:hl_group, + \ 'text': ' ' . l:msg, + \ 'bufnr': a:buffer, + \ 'text_padding_left': l:col_pad, + \}) + endif +endfunction + +function! ale#virtualtext#ShowCursorWarning(...) abort + if g:ale_virtualtext_cursor isnot# 'current' + \&& g:ale_virtualtext_cursor != 1 + return + endif + + let l:buffer = bufnr('') + + if mode(1) isnot# 'n' + \|| g:ale_use_neovim_diagnostics_api + \|| ale#ShouldDoNothing(l:buffer) + return + endif + + let [l:info, l:item] = ale#util#FindItemAtCursor(l:buffer) + call ale#virtualtext#Clear(l:buffer) + + if !empty(l:item) + call ale#virtualtext#ShowMessage(l:buffer, l:item) + endif +endfunction + +function! ale#virtualtext#ShowCursorWarningWithDelay() abort + let l:buffer = bufnr('') + + if g:ale_virtualtext_cursor isnot# 'current' + \&& g:ale_virtualtext_cursor != 1 + return + endif + + call s:StopCursorTimer() + + if mode(1) isnot# 'n' + \|| g:ale_use_neovim_diagnostics_api + return + endif + + 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* + " we should show something. Otherwise we can end up doing processing + " the show message far too frequently. + if l:pos != s:last_pos + let l:delay = ale#Var(l:buffer, 'virtualtext_delay') + + let s:last_pos = l:pos + let s:cursor_timer = timer_start( + \ l:delay, + \ function('ale#virtualtext#ShowCursorWarning') + \) + endif +endfunction + +function! ale#virtualtext#CompareSeverityPerLine(left, right) abort + " Compare lines + if a:left.lnum < a:right.lnum + return -1 + endif + + if a:left.lnum > a:right.lnum + return 1 + endif + + let l:left_priority = ale#util#GetItemPriority(a:left) + let l:right_priority = ale#util#GetItemPriority(a:right) + + " Put highest priority items first. + if l:left_priority > l:right_priority + return -1 + endif + + if l:left_priority < l:right_priority + return 1 + endif + + " Put the first seen problem first. + return a:left.col - a:right.col +endfunction + +function! ale#virtualtext#SetTexts(buffer, loclist) abort + if !has('nvim') && s:emulate_virt + return + endif + + call ale#virtualtext#Clear(a:buffer) + + let l:buffer_list = filter(copy(a:loclist), 'v:val.bufnr == a:buffer') + + if ale#Var(a:buffer,'virtualtext_single') + " If we want a single problem per line, sort items on each line by + " highest severity and then lowest column position, then de-duplicate + " the items by line. + call uniq( + \ sort(l:buffer_list, function('ale#virtualtext#CompareSeverityPerLine')), + \ {a, b -> a.lnum - b.lnum} + \) + endif + + for l:item in l:buffer_list + call ale#virtualtext#ShowMessage(a:buffer, l:item) + endfor +endfunction diff --git a/autoload/asyncomplete/sources/ale.vim b/autoload/asyncomplete/sources/ale.vim new file mode 100644 index 00000000..ce793773 --- /dev/null +++ b/autoload/asyncomplete/sources/ale.vim @@ -0,0 +1,26 @@ +function! asyncomplete#sources#ale#get_source_options(...) abort + let l:default = extend({ + \ 'name': 'ale', + \ 'completor': function('asyncomplete#sources#ale#completor'), + \ 'whitelist': ['*'], + \ 'triggers': asyncomplete#sources#ale#get_triggers(), + \ }, a:0 >= 1 ? a:1 : {}) + + return extend(l:default, {'refresh_pattern': '\k\+$'}) +endfunction + +function! asyncomplete#sources#ale#get_triggers() abort + let l:triggers = ale#completion#GetAllTriggers() + let l:triggers['*'] = l:triggers[''] + + return l:triggers +endfunction + +function! asyncomplete#sources#ale#completor(options, context) abort + let l:keyword = matchstr(a:context.typed, '\w\+$') + let l:startcol = a:context.col - len(l:keyword) + + call ale#completion#GetCompletions('ale-callback', { 'callback': {completions -> + \ asyncomplete#complete(a:options.name, a:context, l:startcol, completions) + \ }}) +endfunction diff --git a/doc/ale-ada.txt b/doc/ale-ada.txt new file mode 100644 index 00000000..8b05df52 --- /dev/null +++ b/doc/ale-ada.txt @@ -0,0 +1,87 @@ +=============================================================================== +ALE Ada Integration *ale-ada-options* + + +=============================================================================== +cspell *ale-ada-cspell* + +See |ale-cspell-options| + + +=============================================================================== +gcc *ale-ada-gcc* + + *ale-options.ada_gcc_executable* + *g:ale_ada_gcc_executable* + *b:ale_ada_gcc_executable* +ada_gcc_executable +g:ale_ada_gcc_executable + Type: |String| + Default: `'gcc'` + + This variable can be changed to use a different executable for gcc. + + *ale-options.ada_gcc_options* + *g:ale_ada_gcc_options* + *b:ale_ada_gcc_options* +ada_gcc_options +g:ale_ada_gcc_options + Type: |String| + Default: `'-gnatwa -gnatq'` + + This variable can be set to pass additional options to gcc. + + +=============================================================================== +gnatpp *ale-ada-gnatpp* + + *ale-options.ada_gnatpp_options* + *g:ale_ada_gnatpp_options* + *b:ale_ada_gnatpp_options* +ada_gnatpp_options +g:ale_ada_gnatpp_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to the gnatpp fixer. + + +=============================================================================== +ada-language-server *ale-ada-language-server* + + *ale-options.ada_adals_executable* + *g:ale_ada_adals_executable* + *b:ale_ada_adals_executable* +ada_adals_executable +g:ale_ada_adals_executable + Type: |String| + Default: `'ada_language_server'` + + This variable can be changed to use a different executable for Ada Language + Server. + + *ale-options.ada_adals_project* + *g:ale_ada_adals_project* + *b:ale_ada_adals_project* +ada_adals_project +g:ale_ada_adals_project + Type: |String| + Default: `'default.gpr'` + + This variable can be changed to use a different GPR file for Ada Language + Server. + + *ale-options.ada_adals_encoding* + *g:ale_ada_adals_encoding* + *b:ale_ada_adals_encoding* +ada_adals_encoding +g:ale_ada_adals_encoding + Type: |String| + Default: `'utf-8'` + + This variable can be changed to use a different file encoding for Ada + Language Server. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-ansible.txt b/doc/ale-ansible.txt new file mode 100644 index 00000000..01554bcc --- /dev/null +++ b/doc/ale-ansible.txt @@ -0,0 +1,48 @@ +=============================================================================== +ALE Ansible Integration *ale-ansible-options* + + +=============================================================================== +ansible-language-server *ale-ansible-language-server* + + + *ale-options.ansible_language_server_executable* + *g:ale_ansible_language_server_executable* + *b:ale_ansible_language_server_executable* +ansible_language_server_executable +g:ale_ansible_language_server_executable + Type: |String| + Default: `'ansible-language-server'` + + Variable can be used to modify the executable used for Ansible language + server. + + *ale-options.ansible_language_server_config* + *g:ale_ansible_language_server_config* + *b:ale_ansible_language_server_config* +ansible_language_server_config +g:ale_ansible_language_server_config + Type: |Dictionary| + Default: `'{}'` + + Configuration parameters sent to the language server on start. Refer to the + ansible language server configuration documentation for list of available + options: https://als.readthedocs.io/en/latest/settings/ + + +=============================================================================== +ansible-lint *ale-ansible-ansible-lint* + + *ale-options.ansible_ansible_lint_executable* + *g:ale_ansible_ansible_lint_executable* + *b:ale_ansible_ansible_lint_executable* +ansible_ansible_lint_executable +g:ale_ansible_ansible_lint_executable + Type: |String| + Default: `'ansible-lint'` + + This variable can be changed to modify the executable used for ansible-lint. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-apkbuild.txt b/doc/ale-apkbuild.txt new file mode 100644 index 00000000..95d80db9 --- /dev/null +++ b/doc/ale-apkbuild.txt @@ -0,0 +1,71 @@ +=============================================================================== +ALE APKBUILD Integration *ale-apkbuild-options* + + +=============================================================================== +apkbuild-fixer *ale-apkbuild-apkbuild-fixer* + + *ale-options.apkbuild_apkbuild_fixer_options* + *g:ale_apkbuild_apkbuild_fixer_options* + *b:ale_apkbuild_apkbuild_fixer_options* +apkbuild_apkbuild_fixer_options +g:ale_apkbuild_apkbuild_fixer_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the apkbuild_fixer + fixer. + + *ale-options.apkbuild_apkbuild_fixer_executable* + *g:ale_apkbuild_apkbuild_fixer_executable* + *b:ale_apkbuild_apkbuild_fixer_executable* +apkbuild_apkbuild_fixer_executable +g:ale_apkbuild_apkbuild_fixer_executable + Type: |String| + Default: `'apkbuild-fixer'` + + This variable can be modified to change the executable path for + `apkbuild-fixer`. + + *ale-options.apkbuild_apkbuild_fixer_lint_executable* + *g:ale_apkbuild_apkbuild_fixer_lint_executable* + *b:ale_apkbuild_apkbuild_fixer_lint_executable* +apkbuild_apkbuild_fixer_lint_executable +g:ale_apkbuild_apkbuild_fixer_lint_executable + Type: |String| + Default: `'apkbuild-fixer'` + + This variable can be modified to change the executable path for + `apkbuild-lint`, the binary used to find violations. + + +=============================================================================== +apkbuild-lint *ale-apkbuild-apkbuild-lint* + + *ale-options.apkbuild_apkbuild_lint_executable* + *g:ale_apkbuild_apkbuild_lint_executable* + *b:ale_apkbuild_apkbuild_lint_executable* +apkbuild_apkbuild_lint_executable +g:ale_apkbuild_apkbuild_lint_executable + Type: |String| + Default: `'apkbuild-lint'` + + This variable can be set to change the path to apkbuild-lint + + +=============================================================================== +secfixes-check *ale-apkbuild-secfixes-check* + + *ale-options.apkbuild_secfixes_check_executable* + *g:ale_apkbuild_secfixes_check_executable* + *b:ale_apkbuild_secfixes_check_executable* +apkbuild_secfixes_check_executable +g:ale_apkbuild_secfixes_check_executable + Type: |String| + Default: `'secfixes-check'` + + This variable can be set to change the path to secfixes-check + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-asciidoc.txt b/doc/ale-asciidoc.txt new file mode 100644 index 00000000..dd8b12ff --- /dev/null +++ b/doc/ale-asciidoc.txt @@ -0,0 +1,24 @@ +=============================================================================== +ALE AsciiDoc Integration *ale-asciidoc-options* + + +=============================================================================== +cspell *ale-asciidoc-cspell* + +See |ale-cspell-options| + + +=============================================================================== +write-good *ale-asciidoc-write-good* + +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-asm.txt b/doc/ale-asm.txt new file mode 100644 index 00000000..462a3494 --- /dev/null +++ b/doc/ale-asm.txt @@ -0,0 +1,54 @@ +=============================================================================== +ALE ASM Integration *ale-asm-options* + + +=============================================================================== +gcc *ale-asm-gcc* + + *ale-options.asm_gcc_executable* + *g:ale_asm_gcc_executable* + *b:ale_asm_gcc_executable* +asm_gcc_executable +g:ale_asm_gcc_executable + Type: |String| + Default: `'gcc'` + + This variable can be changed to use a different executable for gcc. + + *ale-options.asm_gcc_options* + *g:ale_asm_gcc_options* + *b:ale_asm_gcc_options* +asm_gcc_options +g:ale_asm_gcc_options + Type: |String| + Default: `'-Wall'` + + This variable can be set to pass additional options to gcc. + + +=============================================================================== +llvm_mc *ale-asm-llvm_mc* + + *ale-options.asm_llvm_mc_executable* + *g:ale_asm_llvm_mc_executable* + *b:ale_asm_llvm_mc_executable* +asm_llvm_mc_executable +g:ale_asm_llvm_mc_executable + Type: |String| + Default: `'llvm-mc'` + +This variable can be changed to use a different executable for llvm-mc. + + *ale-options.asm_llvm_mc_options* + *g:ale_asm_llvm_mc_options* + *b:ale_asm_llvm_mc_options* +asm_llvm_mc_options +g:ale_asm_llvm_mc_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to llvm-mc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-astro.txt b/doc/ale-astro.txt new file mode 100644 index 00000000..0132aa80 --- /dev/null +++ b/doc/ale-astro.txt @@ -0,0 +1,16 @@ +=============================================================================== +ALE Astro Integration *ale-astro-options* + + +=============================================================================== +eslint *ale-astro-eslint* + +See |ale-javascript-eslint| for information about the available options. + +=============================================================================== +prettier *ale-astro-prettier* + +See |ale-javascript-prettier| for information about the available options. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-avra.txt b/doc/ale-avra.txt new file mode 100644 index 00000000..4fde2a99 --- /dev/null +++ b/doc/ale-avra.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE AVRA Integration *ale-avra-options* + + +=============================================================================== +avra *ale-avra-avra* + + *ale-options.avra_avra_executable* + *g:ale_avra_avra_executable* + *b:ale_avra_avra_executable* +avra_avra_executable +g:ale_avra_avra_executable + Type: |String| + Default `'avra'` + + This variable can be changed to use different executable for AVRA. + + *ale-options.avra_avra_options* + *g:ale_avra_avra_options* + *b:ale_avra_avra_options* +avra_avra_options +g:ale_avra_avra_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to AVRA. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-awk.txt b/doc/ale-awk.txt new file mode 100644 index 00000000..b3d1a95f --- /dev/null +++ b/doc/ale-awk.txt @@ -0,0 +1,31 @@ +=============================================================================== +ALE Awk Integration *ale-awk-options* + + +=============================================================================== +gawk *ale-awk-gawk* + + *ale-options.awk_gawk_executable* + *g:ale_awk_gawk_executable* + *b:ale_awk_gawk_executable* +awk_gawk_executable +g:ale_awk_gawk_executable + Type: |String| + Default: `'gawk'` + + This variable sets executable used for gawk. + + *ale-options.awk_gawk_options* + *g:ale_awk_gawk_options* + *b:ale_awk_gawk_options* +awk_gawk_options +g:ale_awk_gawk_options + Type: |String| + Default: `''` + + With this variable we are able to pass extra arguments for gawk for + invocation. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-bats.txt b/doc/ale-bats.txt new file mode 100644 index 00000000..cf2199ec --- /dev/null +++ b/doc/ale-bats.txt @@ -0,0 +1,13 @@ +=============================================================================== +ALE Bats Integration *ale-bats-options* + + +=============================================================================== +shellcheck *ale-bats-shellcheck* + +The `shellcheck` linter for Bats uses the sh options for `shellcheck`; see: +|ale-sh-shellcheck|. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-bazel.txt b/doc/ale-bazel.txt new file mode 100644 index 00000000..d810ea49 --- /dev/null +++ b/doc/ale-bazel.txt @@ -0,0 +1,39 @@ +=============================================================================== +ALE Bazel Integration *ale-bazel-options* + +=============================================================================== +buildifier *ale-bazel-buildifier* + + *ale-options.bazel_buildifier_executable* + *g:ale_bazel_buildifier_executable* + *b:ale_bazel_buildifier_executable* +bazel_buildifier_executable +g:ale_bazel_buildifier_executable + Type: |String| + Default: `'buildifier'` + + See |ale-integrations-local-executables| + + *ale-options.bazel_buildifier_options* + *g:ale_bazel_buildifier_options* + *b:ale_bazel_buildifier_options* +bazel_buildifier_options +g:ale_bazel_buildifier_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to buildifier. + + *ale-options.bazel_buildifier_use_global* + *g:ale_bazel_buildifier_use_global* + *b:ale_bazel_buildifier_use_global* +bazel_buildifier_use_global +g:ale_bazel_buildifier_use_global + Type: |Number| + 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-bib.txt b/doc/ale-bib.txt new file mode 100644 index 00000000..6453c23e --- /dev/null +++ b/doc/ale-bib.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE BibTeX Integration *ale-bib-options* + + +=============================================================================== +bibclean *ale-bib-bibclean* + + *ale-options.bib_bibclean_executable* + *g:ale_bib_bibclean_executable* + *b:ale_bib_bibclean_executable* +bib_bibclean_executable +g:ale_bib_bibclean_executable + Type: |String| + Default: `'bibclean'` + + *ale-options.bib_bibclean_options* + *g:ale_bib_bibclean_options* + *b:ale_bib_bibclean_options* +bib_bibclean_options +g:ale_bib_bibclean_options + Type: |String| + Default: `'-align-equals'` + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-bicep.txt b/doc/ale-bicep.txt new file mode 100644 index 00000000..888e451d --- /dev/null +++ b/doc/ale-bicep.txt @@ -0,0 +1,54 @@ +=============================================================================== +ALE Bicep Integration *ale-bicep-options* + + +=============================================================================== +bicep *ale-bicep-bicep* + + *ale-options.bicep_bicep_executable* + *g:ale_bicep_bicep_executable* + *b:ale_bicep_bicep_executable* +bicep_bicep_executable +g:ale_bicep_bicep_executable + Type: |String| + Default: `'bicep'` + + This variable can be set to change the path to bicep. + + *ale-options.bicep_bicep_options* + *g:ale_bicep_bicep_options* + *b:ale_bicep_bicep_options* +bicep_bicep_options +g:ale_bicep_bicep_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to bicep. + + +=============================================================================== +az_bicep *ale-bicep-az_bicep* + + *ale-options.bicep_az_bicep_executable* + *g:ale_bicep_az_bicep_executable* + *b:ale_bicep_az_bicep_executable* +bicep_az_bicep_executable +g:ale_bicep_az_bicep_executable + Type: |String| + Default: `'az'` + + This variable can be set to change the path to az_bicep. + + *ale-options.bicep_az_bicep_options* + *g:ale_bicep_az_bicep_options* + *b:ale_bicep_az_bicep_options* +bicep_az_bicep_options +g:ale_bicep_az_bicep_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to az_bicep. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-bitbake.txt b/doc/ale-bitbake.txt new file mode 100644 index 00000000..a656f569 --- /dev/null +++ b/doc/ale-bitbake.txt @@ -0,0 +1,40 @@ +=============================================================================== +ALE BitBake Integration *ale-bitbake-options* + + +=============================================================================== +oelint-adv *ale-bitbake-oelint_adv* + + *ale-options.bitbake_oelint_adv_executable* + *g:ale_bitbake_oelint_adv_executable* + *b:ale_bitbake_oelint_adv_executable* +bitbake_oelint_adv_executable +g:ale_bitbake_oelint_adv_executable + + Type: |String| + Default: `'oelint-adv'` + + This variable can be changed to use a different executable for oelint-adv. + + *ale-options.bitbake_oelint_adv_options* + *g:ale_bitbake_oelint_adv_options* + *b:ale_bitbake_oelint_adv_options* +bitbake_oelint_adv_options +g:ale_bitbake_oelint_adv_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to oelint-adv. + + *ale-options.bitbake_oelint_adv_config* + *g:ale_bitbake_oelint_adv_config* + *b:ale_bitbake_oelint_adv_config* +g:ale_bitbake_oelint_adv_config + Type: |String| + Default: `'.oelint.cfg'` + + This variable can be set to use a different config file. + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-c.txt b/doc/ale-c.txt new file mode 100644 index 00000000..96017cf4 --- /dev/null +++ b/doc/ale-c.txt @@ -0,0 +1,569 @@ +=============================================================================== +ALE C Integration *ale-c-options* + +For basic checking of problems with C files, ALE offers the `cc` linter, which +runs either `clang`, or `gcc`. See |ale-c-cc|. + + +=============================================================================== +Global Options + *ale-options.c_always_make* + *g:ale_c_always_make* + *b:ale_c_always_make* +c_always_make +g:ale_c_always_make + Type: |Number| + Default: `has('unix') && !has('macunix')` + + If set to `1`, use `--always-make` for `make`, which means that output will + always be parsed from `make` dry runs with GNU make. BSD `make` does not + support this option, so you probably want to turn this option off when using + a BSD variant. + + *ale-options.c_build_dir_names* + *g:ale_c_build_dir_names* + *b:ale_c_build_dir_names* +c_build_dir_names +g:ale_c_build_dir_names + Type: |List| + Default: `['build', 'bin']` + + A list of directory names to be used when searching upwards from C files + to discover compilation databases with. For directory named `'foo'`, ALE + will search for `'foo/compile_commands.json'` in all directories on and + above the directory containing the C file to find path to compilation + database. This feature is useful for the clang tools wrapped around + LibTooling (namely here, clang-tidy) + + *ale-options.c_build_dir* + *g:ale_c_build_dir* + *b:ale_c_build_dir* +c_build_dir +g:ale_c_build_dir + Type: |String| + Default: `''` + + For programs that can read `compile_commands.json` files, this option can be + set to the directory containing the file for the project. ALE will try to + determine the location of `compile_commands.json` automatically, but if your + file exists in some other directory, you can set this option so ALE will + know where it is. + + This directory will be searched instead of |g:ale_c_build_dir_names|. + + *ale-options.c_parse_compile_commands* + *g:ale_c_parse_compile_commands* + *b:ale_c_parse_compile_commands* +c_parse_compile_commands +g:ale_c_parse_compile_commands + Type: |Number| + Default: `1` + + If set to `1`, ALE will parse `compile_commands.json` files to automatically + determine flags for C or C++ compilers. ALE will first search for the + nearest `compile_commands.json` file, and then look for + `compile_commands.json` files in the directories for + |g:ale_c_build_dir_names|. + + *ale-options.c_parse_makefile* + *g:ale_c_parse_makefile* + *b:ale_c_parse_makefile* +c_parse_makefile +g:ale_c_parse_makefile + Type: |Number| + Default: `0` + + If set to `1`, ALE will run `make -n` to automatically determine flags to + set for C or C++ compilers. This can make it easier to determine the correct + build flags to use for different files. + + NOTE: When using this option on BSD, you may need to set + |g:ale_c_always_make| to `0`, and `make -n` will not provide consistent + results if binaries have already been built, so use `make clean` when + editing your files. + + WARNING: Running `make -n` automatically can execute arbitrary code, even + though it's supposed to be a dry run, so enable this option with care. You + might prefer to use the buffer-local version of the option instead with + |g:ale_pattern_options|, or you own code for checking which project you're + in. + + You might want to disable this option if `make -n` takes too long to run for + projects you work on. + + If |g:ale_c_parse_compile_commands| or |b:ale_c_parse_compile_commands| is + set to `1`, flags taken from `compile_commands.json` will be preferred over + `make -n` output. + + +=============================================================================== +astyle *ale-c-astyle* + + *ale-options.c_astyle_executable* + *g:ale_c_astyle_executable* + *b:ale_c_astyle_executable* +c_astyle_executable +g:ale_c_astyle_executable + Type: |String| + Default: `'astyle'` + + This variable can be changed to use a different executable for astyle. + + *ale-options.c_astyle_project_options* + *g:ale_c_astyle_project_options* + *b:ale_c_astyle_project_options* +c_astyle_project_options +g:ale_c_astyle_project_options + Type: |String| + Default: `''` + + This variable can be changed to use an option file for project level + configurations. Provide only the filename of the option file that should be + present at the project's root directory. + + For example, if .astylrc is specified, the file is searched in the parent + directories of the source file's directory. + + +=============================================================================== +cc *ale-c-cc* + *ale-c-gcc* + *ale-c-clang* + + *ale-options.c_cc_executable* + *g:ale_c_cc_executable* + *b:ale_c_cc_executable* +c_cc_executable +g:ale_c_cc_executable + Type: |String| + Default: `''` + + This variable can be changed to use a different executable for a C compiler. + + ALE will try to use `clang` if Clang is available, otherwise ALE will + default to checking C code with `gcc`. + + *ale-options.c_cc_options* + *g:ale_c_cc_options* + *b:ale_c_cc_options* +c_cc_options +g:ale_c_cc_options + Type: |String| + Default: `'-std=c11 -Wall'` + + This variable can be changed to modify flags given to the C compiler. + + *ale-options.c_cc_use_header_lang_flag* + *g:ale_c_cc_use_header_lang_flag* + *b:ale_c_cc_use_header_lang_flag* +c_cc_use_header_lang_flag +g:ale_c_cc_use_header_lang_flag + Type: |Number| + Default: `-1` + + By default, ALE will use `'-x c-header'` instead of `'-x c'` for header files + when using Clang. + + This variable can be changed to manually activate or deactivate this flag + for header files. + + - When set to `-1`, the default beviour is used, `'-x c-header'` is used with + Clang and `'-x c'` is used with other compilers. + - When set to `0`, the flag is deactivated, `'-x c'` is always used + independently of the compiler. + - When set to `1`, the flag is activated, `'-x c-header'` is always used + independently of the compiler. + + Gcc does not support `'-x c-header'` when using `'-'` as input filename, + which is what ALE does. This why, by default, ALE only uses `'-x c-header'` + with Clang. + + *ale-options.c_cc_header_exts* + *g:ale_c_cc_header_exts* + *b:ale_c_cc_header_exts* +c_cc_header_exts +g:ale_c_cc_header_exts + Type: |List| + Default: `['h']` + + This variable can be changed to modify the list of extensions of the files + considered as header files. + + This variable is only used when `'-x c-header'` is used instead of `'-x c'`, + see |g:ale_c_cc_use_header_lang_flag|. + + +=============================================================================== +ccls *ale-c-ccls* + + *ale-options.c_ccls_executable* + *g:ale_c_ccls_executable* + *b:ale_c_ccls_executable* +c_ccls_executable +g:ale_c_ccls_executable + Type: |String| + Default: `'ccls'` + + This variable can be changed to use a different executable for ccls. + + *ale-options.c_ccls_init_options* + *g:ale_c_ccls_init_options* + *b:ale_c_ccls_init_options* +c_ccls_init_options +g:ale_c_ccls_init_options + Type: |Dictionary| + Default: `{}` + + This variable can be changed to customize ccls initialization options. + For example: > + + let g:ale_c_ccls_init_options = { + \ 'cacheDirectory': '/tmp/ccls', + \ 'cacheFormat': 'binary', + \ 'diagnostics': { + \ 'onOpen': 0, + \ 'opChange': 1000, + \ }, + \} +< + For all available options and explanations, visit + https://github.com/MaskRay/ccls/wiki/Customization#initialization-options. + + +=============================================================================== +clangcheck *ale-c-clangcheck* + +`clang-check` will be run only when files are saved to disk, so that +`compile_commands.json` files can be used. It is recommended to use this +linter in combination with `compile_commands.json` files. +Therefore, `clang-check` linter reads the options |g:ale_c_build_dir| and +|g:ale_c_build_dir_names|. Also, setting |g:ale_c_build_dir| actually +overrides |g:ale_c_build_dir_names|. + + +------------------------------------------------------------------------------- +Options + *ale-options.c_clangcheck_executable* + *g:ale_c_clangcheck_executable* + *b:ale_c_clangcheck_executable* +c_clangcheck_executable +g:ale_c_clangcheck_executable + Type: |String| + Default: `'clang-check'` + + This variable can be changed to use a different executable for clangcheck. + + *ale-options.c_clangcheck_options* + *g:ale_c_clangcheck_options* + *b:ale_c_clangcheck_options* +c_clangcheck_options +g:ale_c_clangcheck_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clang-check. + + This variable should not be set to point to build subdirectory with + `-p path/to/build` option, as it is handled by the |g:ale_c_build_dir| + option. + + +=============================================================================== +clangd *ale-c-clangd* + + *ale-options.c_clangd_executable* + *g:ale_c_clangd_executable* + *b:ale_c_clangd_executable* +c_clangd_executable +g:ale_c_clangd_executable + Type: |String| + Default: `'clangd'` + + This variable can be changed to use a different executable for clangd. + + *ale-options.c_clangd_options* + *g:ale_c_clangd_options* + *b:ale_c_clangd_options* +c_clangd_options +g:ale_c_clangd_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clangd. + + +=============================================================================== +clang-format *ale-c-clangformat* + + *ale-options.c_clangformat_executable* + *g:ale_c_clangformat_executable* + *b:ale_c_clangformat_executable* +c_clangformat_executable +g:ale_c_clangformat_executable + Type: |String| + Default: `'clang-format'` + + This variable can be changed to use a different executable for clang-format. + + *ale-options.c_clangformat_options* + *g:ale_c_clangformat_options* + *b:ale_c_clangformat_options* +c_clangformat_options +g:ale_c_clangformat_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clang-format. + + *ale-options.c_clangformat_style_option* + *g:ale_c_clangformat_style_option* + *b:ale_c_clangformat_style_option* +c_clangformat_style_option +g:ale_c_clangformat_style_option + Type: |String| + Default: `''` + + This variable can be changed to modify only the style flag given to + clang-format. The contents of the variable are passed directly to the -style + flag of clang-format. + + Example: > + let g:ale_c_clangformat_style_option = { + \ 'BasedOnStyle': 'Microsoft', + \ 'ColumnLimit': 80, + \ 'AllowShortBlocksOnASingleLine': 'Always', + \ 'AllowShortFunctionsOnASingleLine': 'Inline', + \} +< + If you set this variable, ensure you don't modify -style in + |g:ale_c_clangformat_options|, as this will cause clang-format to error. + + *ale-options.c_clangformat_use_local_file* + *g:ale_c_clangformat_use_local_file* + *b:ale_c_clangformat_use_local_file* +c_clangformat_use_local_file +g:ale_c_clangformat_use_local_file + Type: |Number| + Default: `0` + + This variable can be changed to modify whether to use a local .clang-format + file. If the file is found, the flag '-style=file' is passed to clang-format + and any options configured via |g:ale_c_clangformat_style_option| are not + passed. + + If this option is enabled but no .clang-format file is found, default back to + |g:ale_c_clangformat_style_option|, if it set. + + If you set this variable, ensure you don't modify -style in + |g:ale_c_clangformat_options|, as this will cause clang-format to error. + + +=============================================================================== +clangtidy *ale-c-clangtidy* + +`clang-tidy` will be run only when files are saved to disk, so that +`compile_commands.json` files can be used. It is recommended to use this +linter in combination with `compile_commands.json` files. +Therefore, `clang-tidy` linter reads the options |g:ale_c_build_dir| and +|g:ale_c_build_dir_names|. Also, setting |g:ale_c_build_dir| actually +overrides |g:ale_c_build_dir_names|. + + +------------------------------------------------------------------------------- +Options + *ale-options.c_clangtidy_checks* + *g:ale_c_clangtidy_checks* + *b:ale_c_clangtidy_checks* +c_clangtidy_checks +g:ale_c_clangtidy_checks + Type: |List| + Default: `[]` + + The checks to enable for clang-tidy with the `-checks` argument. + + All options will be joined with commas, and escaped appropriately for + the shell. The `-checks` flag can be removed entirely by setting this + option to an empty List. + + Not all of clangtidy checks are applicable for C. You should consult the + clang documentation for an up-to-date list of compatible checks: + http://clang.llvm.org/extra/clang-tidy/checks/list.html + + *ale-options.c_clangtidy_executable* + *g:ale_c_clangtidy_executable* + *b:ale_c_clangtidy_executable* +c_clangtidy_executable +g:ale_c_clangtidy_executable + Type: |String| + Default: `'clang-tidy'` + + This variable can be changed to use a different executable for clangtidy. + + *ale-options.c_clangtidy_options* + *g:ale_c_clangtidy_options* + *b:ale_c_clangtidy_options* +c_clangtidy_options +g:ale_c_clangtidy_options + Type: |String| + Default: `''` + + This variable can be changed to modify compiler flags given to clang-tidy. + + - Setting this variable to a non-empty string, + - and working in a buffer where no compilation database is found using + |g:ale_c_build_dir_names| or |g:ale_c_build_dir|, + will cause the `--` argument to be passed to `clang-tidy`, which will mean + that detection of `compile_commands.json` files for compile command + databases will be disabled. + Only set this option if you want to control compiler flags + entirely manually, and no `compile_commands.json` file is in one + of the |g:ale_c_build_dir_names| directories of the project tree. + + *ale-options.c_clangtidy_extra_options* + *g:ale_c_clangtidy_extra_options* + *b:ale_c_clangtidy_extra_options* +c_clangtidy_extra_options +g:ale_c_clangtidy_extra_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clang-tidy. + + *ale-options.c_clangtidy_fix_errors* + *g:ale_c_clangtidy_fix_errors* + *b:ale_c_clangtidy_fix_errors* +c_clangtidy_fix_errors +g:ale_c_clangtidy_fix_errors + Type: |Number| + Default: `1` + + This variable can be changed to disable the `-fix-errors` option for the + |clangtidy| fixer. + + +=============================================================================== +cppcheck *ale-c-cppcheck* + + *ale-options.c_cppcheck_executable* + *g:ale_c_cppcheck_executable* + *b:ale_c_cppcheck_executable* +c_cppcheck_executable +g:ale_c_cppcheck_executable + Type: |String| + Default: `'cppcheck'` + + This variable can be changed to use a different executable for cppcheck. + + *ale-options.c_cppcheck_options* + *g:ale_c_cppcheck_options* + *b:ale_c_cppcheck_options* +c_cppcheck_options +g:ale_c_cppcheck_options + Type: |String| + Default: `'--enable=style'` + + This variable can be changed to modify flags given to cppcheck. + + +=============================================================================== +cquery *ale-c-cquery* + + *ale-options.c_cquery_executable* + *g:ale_c_cquery_executable* + *b:ale_c_cquery_executable* +c_cquery_executable +g:ale_c_cquery_executable + Type: |String| + Default: `'cquery'` + + This variable can be changed to use a different executable for cquery. + + *ale-options.c_cquery_cache_directory* + *g:ale_c_cquery_cache_directory* + *b:ale_c_cquery_cache_directory* +c_cquery_cache_directory +g:ale_c_cquery_cache_directory + Type: |String| + Default: `'~/.cache/cquery'` + + This variable can be changed to decide which directory cquery uses for its + cache. + + +=============================================================================== +cspell *ale-c-cspell* + +See |ale-cspell-options| + + +=============================================================================== +flawfinder *ale-c-flawfinder* + + *ale-options.c_flawfinder_executable* + *g:ale_c_flawfinder_executable* + *b:ale_c_flawfinder_executable* +c_flawfinder_executable +g:ale_c_flawfinder_executable + Type: |String| + Default: `'flawfinder'` + + This variable can be changed to use a different executable for flawfinder. + + *ale-options.c_flawfinder_minlevel* + *g:ale_c_flawfinder_minlevel* + *b:ale_c_flawfinder_minlevel* +c_flawfinder_minlevel +g:ale_c_flawfinder_minlevel + Type: |Number| + Default: `1` + + This variable can be changed to ignore risks under the given risk threshold. + + *ale-options.c_flawfinder_options* + *g:ale_c_flawfinder_options* + *b:ale-c-flawfinder* +c_flawfinder_options +g:ale_c_flawfinder_options + Type: |String| + Default: `''` + + This variable can be used to pass extra options into the flawfinder command. + + *ale-options.c_flawfinder_error_severity* + *g:ale_c_flawfinder_error_severity* + *b:ale_c_flawfinder_error_severity* +c_flawfinder_error_severity +g:ale_c_flawfinder_error_severity + Type: |Number| + Default: `6` + + This variable can be changed to set the minimum severity to be treated as an + error. This setting also applies to flawfinder for c++. + + +=============================================================================== +uncrustify *ale-c-uncrustify* + + *ale-options.c_uncrustify_executable* + *g:ale_c_uncrustify_executable* + *b:ale_c_uncrustify_executable* +c_uncrustify_executable +g:ale_c_uncrustify_executable + Type: |String| + Default: `'uncrustify'` + + This variable can be changed to use a different executable for uncrustify. + + *ale-options.c_uncrustify_options* + *g:ale_c_uncrustify_options* + *b:ale_c_uncrustify_options* +c_uncrustify_options +g:ale_c_uncrustify_options + Type: |String| + Default: `''` + + This variable can be change to modify flags given to uncrustify. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-c3.txt b/doc/ale-c3.txt new file mode 100644 index 00000000..18f3ce66 --- /dev/null +++ b/doc/ale-c3.txt @@ -0,0 +1,41 @@ +=============================================================================== +ALE C3 Integration *ale-c3-options* + +=============================================================================== +c3lsp *ale-c3-c3lsp* + + *ale-options.c3_c3lsp_executable* + *g:ale_c3_c3lsp_executable* + *b:ale_c3_c3lsp_executable* +c3_c3lsp_executable +g:ale_c3_c3lsp_executable + Type: |String| + Default: `c3lsp` + + This variable can be changed to set the path to c3lsp executable. + + *ale-options.c3_c3lsp_options* + *g:ale_c3_c3lsp_options* + *b:ale_c3_c3lsp_options* +c3_c3lsp_options +g:ale_c3_c3lsp_options + Type: |String| + Default: `''` + + Add command line options to the c3lsp executable. This is useful to specify + the path to the C3 standard library with '-stdlib-path='. + + *ale-options.c3_c3lsp_init_options* + *g:ale_c3_c3lsp_init_options* + *b:ale_c3_c3lsp_init_options* +c3_c3lsp_init_options +g:ale_c3_c3lsp_init_options + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cairo.txt b/doc/ale-cairo.txt new file mode 100644 index 00000000..c84535be --- /dev/null +++ b/doc/ale-cairo.txt @@ -0,0 +1,38 @@ +=============================================================================== +ALE Cairo Integration *ale-cairo-options* + + +=============================================================================== +scarb *ale-cairo-scarb* + + *ale-options.cairo_scarb_executable* + *g:ale_cairo_scarb_executable* + *b:ale_cairo_scarb_executable* +cairo_scarb_executable +g:ale_cairo_scarb_executable + Type: |String| + Default: `'scarb build'` + + For Cairo1 projects using Scarb + + For more information read 'https://docs.swmansion.com/scarb/' + + +=============================================================================== +starknet *ale-cairo-starknet* + + *ale-options.cairo_starknet_executable* + *g:ale_cairo_starknet_executable* + *b:ale_cairo_starknet_executable* +cairo_starknet_executable +g:ale_cairo_starknet_executable + Type: |String| + Default: `'starknet-compile'` + + Overrides the starknet-compile binary after installing the cairo-language. + + For more information read 'https://starknet.io/docs/quickstart.html' + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-chef.txt b/doc/ale-chef.txt new file mode 100644 index 00000000..d4f931f4 --- /dev/null +++ b/doc/ale-chef.txt @@ -0,0 +1,56 @@ +=============================================================================== +ALE Chef Integration *ale-chef-options* + + +=============================================================================== +cookstyle *ale-chef-cookstyle* + + *ale-options.chef_cookstyle_options* + *g:ale_chef_cookstyle_options* + *b:ale_chef_cookstyle_options* +chef_cookstyle_options +g:ale_chef_cookstyle_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to cookstyle. + + *ale-options.chef_cookstyle_executable* + *g:ale_chef_cookstyle_executable* + *b:ale_chef_cookstyle_executable* +chef_cookstyle_executable +g: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* + + *ale-options.chef_foodcritic_options* + *g:ale_chef_foodcritic_options* + *b:ale_chef_foodcritic_options* +chef_foodcritic_options +g:ale_chef_foodcritic_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to foodcritic. + + *ale-options.chef_foodcritic_executable* + *g:ale_chef_foodcritic_executable* + *b:ale_chef_foodcritic_executable* +chef_foodcritic_executable +g:ale_chef_foodcritic_executable + Type: |String| + Default: `'foodcritic'` + + This variable can be changed to point to the foodcritic binary in case it's + not on the $PATH or a specific version/path must be used. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-clojure.txt b/doc/ale-clojure.txt new file mode 100644 index 00000000..a562d832 --- /dev/null +++ b/doc/ale-clojure.txt @@ -0,0 +1,55 @@ +=============================================================================== +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 + + +------------------------------------------------------------------------------- +Options + *ale-options.clojure_clj_kondo_options* + *g:ale_clojure_clj_kondo_options* + *b:ale_clojure_clj_kondo_options* +clojure_clj_kondo_options +g:ale_clojure_clj_kondo_options + Type: |String| + Default: `'--cache'` + + This variable can be changed to modify options passed to clj-kondo. + + +=============================================================================== +cljfmt *ale-clojure-cljfmt* + +cljfmt is a linter and fixer for Clojure code, with defaults adhering to the +Clojure Style Guide (see https://guide.clojure.style/ ) + +https://github.com/weavejester/cljfmt + +Linting options are not configurable by ale, but instead are controlled by +Leiningen, or a cljfmt file in the current or parent directories. + +see https://github.com/weavejester/cljfmt#Configuration for more information. + +=============================================================================== +joker *ale-clojure-joker* + +Joker is a small Clojure interpreter and linter written in Go. + +https://github.com/candid82/joker + +Linting options are not configurable by ale, but instead are controlled by a +`.joker` file in same directory as the file (or current working directory if +linting stdin), a parent directory relative to the file, or the users home +directory. + +see https://github.com/candid82/joker#linter-mode for more information. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cloudformation.txt b/doc/ale-cloudformation.txt new file mode 100644 index 00000000..56390149 --- /dev/null +++ b/doc/ale-cloudformation.txt @@ -0,0 +1,45 @@ +=============================================================================== +ALE CloudFormation Integration *ale-cloudformation-options* + + +=============================================================================== +cfn-python-lint *ale-cloudformation-cfn-python-lint* + +cfn-python-lint is a linter for AWS CloudFormation template file. + +Website: https://github.com/awslabs/cfn-python-lint + + +------------------------------------------------------------------------------- +Installation + +Install cfn-python-lint using either pip or brew: > + + pip install cfn-lint +< +If pip is not available use setuptools. > + + python setup.py clean --all + python setup.py install +< +You can install the linter via brew on macOS. > + + brew install cfn-lint +< + +------------------------------------------------------------------------------- +Configuration + +To get cloudformation linter to work on only CloudFormation files we must set +the buffer |filetype| to `yaml.cloudformation`. This causes ALE to lint the +file with linters configured for cloudformation and YAML files. + +Just put the following in `ftdetect/cloudformation.vim`: > + + au BufRead,BufNewFile *.template.yaml set filetype=yaml.cloudformation + +This will get both cloudformation and yaml linters to work on any file with +`.template.yaml` extension. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cmake.txt b/doc/ale-cmake.txt new file mode 100644 index 00000000..9ac1b993 --- /dev/null +++ b/doc/ale-cmake.txt @@ -0,0 +1,78 @@ +=============================================================================== +ALE CMake Integration *ale-cmake-options* + + +=============================================================================== +cmakelint *ale-cmake-cmakelint* + + *ale-options.cmake_cmakelint_executable* + *g:ale_cmake_cmakelint_executable* + *b:ale_cmake_cmakelint_executable* +cmake_cmakelint_executable +g:ale_cmake_cmakelint_executable + Type: |String| + Default: `'cmakelint'` + + This variable can be set to change the path the cmakelint. + + *ale-options.cmake_cmakelint_options* + *g:ale_cmake_cmakelint_options* + *b:ale_cmake_cmakelint_options* +cmake_cmakelint_options +g:ale_cmake_cmakelint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to cmakelint. + + +=============================================================================== +cmake-lint *ale-cmake-cmake-lint* + + *ale-options.cmake_cmake_lint_executable* + *g:ale_cmake_cmake_lint_executable* + *b:ale_cmake_cmake_lint_executable* +cmake_cmake_lint_executable +g:ale_cmake_cmake_lint_executable + Type: |String| + Default: `'cmake-lint'` + + This variable can be set to change the path the cmake-lint. + + *ale-options.cmake_cmake_lint_options* + *g:ale_cmake_cmake_lint_options* + *b:ale_cmake_cmake_lint_options* +cmake_cmake_lint_options +g:ale_cmake_cmake_lint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to cmake-lint. + + +=============================================================================== +cmake-format *ale-cmake-cmakeformat* + + *ale-options.cmake_cmakeformat_executable* + *g:ale_cmake_cmakeformat_executable* + *b:ale_cmake_cmakeformat_executable* +cmake_cmakeformat_executable +g:ale_cmake_cmakeformat_executable + Type: |String| + Default: `'cmakeformat'` + + This variable can be set to change the path the cmake-format. + + *ale-options.cmake_cmakeformat_options* + *g:ale_cmake_cmakeformat_options* + *b:ale_cmake_cmakeformat_options* +cmake_cmakeformat_options +g: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-cpp.txt b/doc/ale-cpp.txt new file mode 100644 index 00000000..fdd1a92f --- /dev/null +++ b/doc/ale-cpp.txt @@ -0,0 +1,480 @@ +=============================================================================== +ALE C++ Integration *ale-cpp-options* + +For basic checking of problems with C++ files, ALE offers the `cc` linter, +which runs either `clang++`, or `gcc`. See |ale-cpp-cc|. + + +=============================================================================== +Global Options + +The following C options also apply to some C++ linters too. + +* |g:ale_c_always_make| +* |g:ale_c_build_dir_names| +* |g:ale_c_build_dir| +* |g:ale_c_parse_makefile| +* |g:ale_c_parse_compile_commands| + + +=============================================================================== +astyle *ale-cpp-astyle* + + *ale-options.cpp_astyle_executable* + *g:ale_cpp_astyle_executable* + *b:ale_cpp_astyle_executable* +cpp_astyle_executable +g:ale_cpp_astyle_executable + Type: |String| + Default: `'astyle'` + + This variable can be changed to use a different executable for astyle. + + *ale-options.cpp_astyle_project_options* + *g:ale_cpp_astyle_project_options* + *b:ale_cpp_astyle_project_options* +cpp_astyle_project_options +g:ale_cpp_astyle_project_options + Type: |String| + Default: `''` + + This variable can be changed to use an option file for project level + configurations. Provide only the filename of the option file that should be + present at the project's root directory. + + For example, if .astylrc is specified, the file is searched in the parent + directories of the source file's directory. + + +=============================================================================== +cc *ale-cpp-cc* + *ale-cpp-gcc* + *ale-cpp-clang* + + *ale-options.cpp_cc_executable* + *g:ale_cpp_cc_executable* + *b:ale_cpp_cc_executable* +cpp_cc_executable +g:ale_cpp_cc_executable + Type: |String| + Default: `''` + + This variable can be changed to use a different executable for a C++ compiler. + + ALE will try to use `clang++` if Clang is available, otherwise ALE will + default to checking C++ code with `gcc`. + + *ale-options.cpp_cc_options* + *g:ale_cpp_cc_options* + *b:ale_cpp_cc_options* +cpp_cc_options +g:ale_cpp_cc_options + Type: |String| + Default: `'-std=c++14 -Wall'` + + This variable can be changed to modify flags given to the C++ compiler. + + *ale-options.cpp_cc_use_header_lang_flag* + *g:ale_cpp_cc_use_header_lang_flag* + *b:ale_cpp_cc_use_header_lang_flag* +cpp_cc_use_header_lang_flag +g:ale_cpp_cc_use_header_lang_flag + Type: |Number| + Default: `-1` + + By default, ALE will use `'-x c++-header'` instead of `'-x c++'` for header + files when using Clang. + + This variable can be changed to manually activate or deactivate this flag + for header files. + + - When set to `-1`, the default behavior is used, `'-x c++-header'` is used + with Clang and `'-x c++'` is used with other compilers. + - When set to `0`, the flag is deactivated, `'-x c++'` is always used + independently of the compiler. + - When set to `1`, the flag is activated, `'-x c++-header'` is always used + independently of the compiler. + + GCC does not support `'-x c++-header'` when using `'-'` as input filename, + which is what ALE does. This why, by default, ALE only uses `'-x c++-header'` + with Clang. + + *ale-options.cpp_cc_header_exts* + *g:ale_cpp_cc_header_exts* + *b:ale_cpp_cc_header_exts* +cpp_cc_header_exts +g:ale_cpp_cc_header_exts + Type: |List| + Default: `['h', 'hpp']` + + This variable can be changed to modify the list of extensions of the files + considered as header files. + + This variable is only used when `'-x c++-header'` is used instead of `'-x c++'`, + see |g:ale_cpp_cc_use_header_lang_flag|. + + +=============================================================================== +ccls *ale-cpp-ccls* + + *ale-options.cpp_ccls_executable* + *g:ale_cpp_ccls_executable* + *b:ale_cpp_ccls_executable* +cpp_ccls_executable +g:ale_cpp_ccls_executable + Type: |String| + Default: `'ccls'` + + This variable can be changed to use a different executable for ccls. + + *ale-options.cpp_ccls_init_options* + *g:ale_cpp_ccls_init_options* + *b:ale_cpp_ccls_init_options* +cpp_ccls_init_options +g:ale_cpp_ccls_init_options + Type: |Dictionary| + Default: `{}` + + This variable can be changed to customize ccls initialization options. + Example: > + + let g:ale_cpp_ccls_init_options = { + \ 'cacheDirectory': '/tmp/ccls', + \ 'cacheFormat': 'binary', + \ 'diagnostics': { + \ 'onOpen': 0, + \ 'opChange': 1000, + \ }, + \} +< + Visit https://github.com/MaskRay/ccls/wiki/Initialization-options for all + available options and explanations. + + +=============================================================================== +clangcheck *ale-cpp-clangcheck* + +`clang-check` will be run only when files are saved to disk, so that +`compile_commands.json` files can be used. It is recommended to use this +linter in combination with `compile_commands.json` files. Therefore, +`clang-check` linter reads the options |g:ale_c_build_dir| and +|g:ale_c_build_dir_names|. Also, setting |g:ale_c_build_dir| actually +overrides |g:ale_c_build_dir_names|. + + +------------------------------------------------------------------------------- +Options + *ale-options.cpp_clangcheck_executable* + *g:ale_cpp_clangcheck_executable* + *b:ale_cpp_clangcheck_executable* +cpp_clangcheck_executable +g:ale_cpp_clangcheck_executable + Type: |String| + Default: `'clang-check'` + + This variable can be changed to use a different executable for clangcheck. + + *ale-options.cpp_clangcheck_options* + *g:ale_cpp_clangcheck_options* + *b:ale_cpp_clangcheck_options* +cpp_clangcheck_options +g:ale_cpp_clangcheck_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clang-check. + + This variable should not be set to point to build subdirectory with + `-p path/to/build` option, as it is handled by the |g:ale_c_build_dir| + option. + + +=============================================================================== +clangd *ale-cpp-clangd* + + *ale-options.cpp_clangd_executable* + *g:ale_cpp_clangd_executable* + *b:ale_cpp_clangd_executable* +cpp_clangd_executable +g:ale_cpp_clangd_executable + Type: |String| + Default: `'clangd'` + + This variable can be changed to use a different executable for clangd. + + *ale-options.cpp_clangd_options* + *g:ale_cpp_clangd_options* + *b:ale_cpp_clangd_options* +cpp_clangd_options +g:ale_cpp_clangd_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clangd. + + +=============================================================================== +clang-format *ale-cpp-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for C++. + + +=============================================================================== +clangtidy *ale-cpp-clangtidy* + +`clang-tidy` will be run only when files are saved to disk, so that +`compile_commands.json` files can be used. It is recommended to use this +linter in combination with `compile_commands.json` files. +Therefore, `clang-tidy` linter reads the options |g:ale_c_build_dir| and +|g:ale_c_build_dir_names|. Also, setting |g:ale_c_build_dir| actually +overrides |g:ale_c_build_dir_names|. + + +------------------------------------------------------------------------------- +Options + *ale-options.cpp_clangtidy_checks* + *g:ale_cpp_clangtidy_checks* + *b:ale_cpp_clangtidy_checks* +cpp_clangtidy_checks +g:ale_cpp_clangtidy_checks + Type: |List| + Default: `[]` + + The checks to enable for clang-tidy with the `-checks` argument. + + All options will be joined with commas, and escaped appropriately for + the shell. The `-checks` flag can be removed entirely by setting this + option to an empty List. + + *ale-options.cpp_clangtidy_executable* + *g:ale_cpp_clangtidy_executable* + *b:ale_cpp_clangtidy_executable* +cpp_clangtidy_executable +g:ale_cpp_clangtidy_executable + Type: |String| + Default: `'clang-tidy'` + + This variable can be changed to use a different executable for clangtidy. + + *ale-options.cpp_clangtidy_options* + *g:ale_cpp_clangtidy_options* + *b:ale_cpp_clangtidy_options* +cpp_clangtidy_options +g:ale_cpp_clangtidy_options + Type: |String| + Default: `''` + + This variable can be changed to modify compiler flags given to clang-tidy. + + - Setting this variable to a non-empty string, + - and working in a buffer where no compilation database is found using + |g:ale_c_build_dir_names| or |g:ale_c_build_dir|, + will cause the `--` argument to be passed to `clang-tidy`, which will mean + that detection of `compile_commands.json` files for compile command + databases will be disabled. + Only set this option if you want to control compiler flags + entirely manually, and no `compile_commands.json` file is in one + of the |g:ale_c_build_dir_names| directories of the project tree. + + *ale-options.cpp_clangtidy_extra_options* + *g:ale_cpp_clangtidy_extra_options* + *b:ale_cpp_clangtidy_extra_options* +cpp_clangtidy_extra_options +g:ale_cpp_clangtidy_extra_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clang-tidy. + + *ale-options.cpp_clangtidy_fix_errors* + *g:ale_cpp_clangtidy_fix_errors* + *b:ale_cpp_clangtidy_fix_errors* +cpp_clangtidy_fix_errors +g:ale_cpp_clangtidy_fix_errors + Type: |Number| + Default: `1` + + This variable can be changed to disable the `-fix-errors` option for the + |clangtidy| fixer. + + +=============================================================================== +clazy *ale-cpp-clazy* + + *ale-options.cpp_clazy_executable* + *g:ale_cpp_clazy_executable* + *b:ale_cpp_clazy_executable* +cpp_clazy_executable +g:ale_cpp_clazy_executable + Type: |String| + Default: `'clazy-standalone'` + + This variable can be changed to use a different executable for clazy. + + *ale-options.cpp_clazy_checks* + *g:ale_cpp_clazy_checks* + *b:ale_cpp_clazy_checks* +cpp_clazy_checks +g:ale_cpp_clazy_checks + Type: |List| + Default: `['level1']` + + The checks to enable for clazy with the `-checks` argument. + + All options will be joined with commas, and escaped appropriately for + the shell. The `-checks` flag can be removed entirely by setting this + option to an empty List. + + *ale-options.cpp_clazy_options* + *g:ale_cpp_clazy_options* + *b:ale_cpp_clazy_options* +cpp_clazy_options +g:ale_cpp_clazy_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clazy. + + +=============================================================================== +cppcheck *ale-cpp-cppcheck* + + *ale-options.cpp_cppcheck_executable* + *g:ale_cpp_cppcheck_executable* + *b:ale_cpp_cppcheck_executable* +cpp_cppcheck_executable +g:ale_cpp_cppcheck_executable + Type: |String| + Default: `'cppcheck'` + + This variable can be changed to use a different executable for cppcheck. + + *ale-options.cpp_cppcheck_options* + *g:ale_cpp_cppcheck_options* + *b:ale_cpp_cppcheck_options* +cpp_cppcheck_options +g:ale_cpp_cppcheck_options + Type: |String| + Default: `'--enable=style'` + + This variable can be changed to modify flags given to cppcheck. + + +=============================================================================== +cpplint *ale-cpp-cpplint* + + *ale-options.cpp_cpplint_executable* + *g:ale_cpp_cpplint_executable* + *b:ale_cpp_cpplint_executable* +cpp_cpplint_executable +g:ale_cpp_cpplint_executable + Type: |String| + Default: `'cpplint'` + + This variable can be changed to use a different executable for cpplint. + + *ale-options.cpp_cpplint_options* + *g:ale_cpp_cpplint_options* + *b:ale_cpp_cpplint_options* +cpp_cpplint_options +g:ale_cpp_cpplint_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to cpplint. + + *ale-options.c_cpplint_executable* + *g:ale_c_cpplint_executable* + *b:ale_c_cpplint_executable* +c_cpplint_executable +g:ale_c_cpplint_executable + Type: |String| + Default: `'cpplint'` + + This variable can be changed to use a different executable for cpplint. + + *ale-options.c_cpplint_options* + *g:ale_c_cpplint_options* + *b:ale_c_cpplint_options* +c_cpplint_options +g:ale_c_cpplint_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to cpplint. + + +=============================================================================== +cquery *ale-cpp-cquery* + + *ale-options.cpp_cquery_executable* + *g:ale_cpp_cquery_executable* + *b:ale_cpp_cquery_executable* +cpp_cquery_executable +g:ale_cpp_cquery_executable + Type: |String| + Default: `'cquery'` + + This variable can be changed to use a different executable for cquery. + + *ale-options.cpp_cquery_cache_directory* + *g:ale_cpp_cquery_cache_directory* + *b:ale_cpp_cquery_cache_directory* +cpp_cquery_cache_directory +g:ale_cpp_cquery_cache_directory + Type: |String| + Default: `'~/.cache/cquery'` + + This variable can be changed to decide which directory cquery uses for its + cache. + + +=============================================================================== +cspell *ale-cpp-cspell* + +See |ale-cspell-options| + + +=============================================================================== +flawfinder *ale-cpp-flawfinder* + + *ale-options.cpp_flawfinder_executable* + *g:ale_cpp_flawfinder_executable* + *b:ale_cpp_flawfinder_executable* +cpp_flawfinder_executable +g:ale_cpp_flawfinder_executable + Type: |String| + Default: `'flawfinder'` + + This variable can be changed to use a different executable for flawfinder. + + *ale-options.cpp_flawfinder_minlevel* + *g:ale_cpp_flawfinder_minlevel* + *b:ale_cpp_flawfinder_minlevel* +cpp_flawfinder_minlevel +g:ale_cpp_flawfinder_minlevel + Type: |Number| + Default: `1` + + This variable can be changed to ignore risks under the given risk threshold. + + *ale-options.cpp_flawfinder_options* + *g:ale_cpp_flawfinder_options* + *b:ale-cpp-flawfinder* +cpp_flawfinder_options +g:ale_cpp_flawfinder_options + Type: |String| + Default: `''` + + This variable can be used to pass extra options into the flawfinder command. + + +=============================================================================== +uncrustify *ale-cpp-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cs.txt b/doc/ale-cs.txt new file mode 100644 index 00000000..56e071c3 --- /dev/null +++ b/doc/ale-cs.txt @@ -0,0 +1,273 @@ +=============================================================================== +ALE C# Integration *ale-cs-options* + +In addition to the linters that are provided with ALE, C# code can be checked +with the OmniSharp plugin. See here: https://github.com/OmniSharp/omnisharp-vim + + +=============================================================================== +clang-format *ale-cs-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for C#. + + +=============================================================================== +csc *ale-cs-csc* + +The |ale-cs-csc| linter checks for semantic errors when files are opened or +saved. + +See |ale-lint-file-linters| for more information on linters which do not check +for problems while you type. + +The csc linter uses the mono csc compiler, providing full C# 7 and newer +support, to generate a temporary module target file (/t:module). The module +includes all '*.cs' files contained in the directory tree rooted at the path +defined by the |g:ale_cs_csc_source| or |b:ale_cs_csc_source| variable and all +sub directories. + +It will in future replace the |ale-cs-mcs| and |ale-cs-mcsc| linters as both +utilize the mcsc compiler which, according to the mono project, is no longer +actively developed, and only receives maintenance updates. However, because +the csc compiler does not support the -syntax option, this linter does not +offer any as-you-type syntax checking, similar to the |ale-cs-mcsc| linter. + +The paths to search for additional assembly files can be specified using the +|g:ale_cs_csc_assembly_path| or |b:ale_cs_csc_assembly_path| variables. + +NOTE: ALE will not find any errors in files apart from syntax errors if any +one of the source files contains a syntax error. Syntax errors must be fixed +first before other errors will be shown. + + +------------------------------------------------------------------------------- +Options + *ale-options.cs_csc_options* + *g:ale_cs_csc_options* + *b:ale_cs_csc_options* +cs_csc_options +g:ale_cs_csc_options + Type: |String| + Default: `''` + + This option can be set to pass additional arguments to the `csc` compiler. + + For example, to add the dotnet package which is not added per default: > + + let g:ale_cs_mcs_options = ' /warn:4 /langversion:7.2' +< + NOTE: the `/unsafe` option is always passed to `csc`. + + *ale-options.cs_csc_source* + *g:ale_cs_csc_source* + *b:ale_cs_csc_source* +cs_csc_source +g:ale_cs_csc_source + Type: |String| + Default: `''` + + This variable defines the root path of the directory tree searched for the + '*.cs' files to be linted. If this option is empty, the source file's + directory will be used. + + NOTE: Currently it is not possible to specify sub directories and + directory sub trees which shall not be searched for *.cs files. + + *ale-options.cs_csc_assembly_path* + *g:ale_cs_csc_assembly_path* + *b:ale_cs_csc_assembly_path* +cs_csc_assembly_path +g:ale_cs_csc_assembly_path + Type: |List| + Default: `[]` + + This variable defines a list of path strings to be searched for external + assembly files. The list is passed to the csc compiler using the `/lib:` + flag. + + *ale-options.cs_csc_assemblies* + *g:ale_cs_csc_assemblies* + *b:ale_cs_csc_assemblies* +cs_csc_assemblies +g:ale_cs_csc_assemblies + Type: |List| + Default: `[]` + + This variable defines a list of external assembly (*.dll) files required + by the mono mcs compiler to generate a valid module target. The list is + passed the csc compiler using the `/r:` flag. + + For example: > + + " Compile C# programs with the Unity engine DLL file on Mac. + let g:ale_cs_mcsc_assemblies = [ + \ '/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll', + \ 'path-to-unityproject/obj/Debug', + \] +< + +=============================================================================== +cspell *ale-cs-cspell* + +See |ale-cspell-options| + + +=============================================================================== +dotnet-format *ale-cs-dotnet-format* + + +------------------------------------------------------------------------------- +Installation + +Installing .NET SDK should probably ensure that `dotnet` is in your `$PATH`. +For .NET 6 the `dotnet format` tool is already included in the .NET SDK. For +.NET 5 or below you will have to manually install it using the instructions +from listed in this repository: https://github.com/dotnet/format + + +------------------------------------------------------------------------------- +Options + *ale-options.cs_dotnet_format_executable* + *g:ale_cs_dotnet_format_executable* + *b:ale_cs_dotnet_format_executable* +cs_dotnet_format_executable +g:ale_cs_dotnet_format_executable + Type: |String| + Default: `'dotnet'` + + This variable can be set to specify an absolute path to the + `dotnet` executable (or to specify an alternate executable). + + *ale-options.cs_dotnet_format_options* + *g:ale_cs_dotnet_format_options* + *b:ale_cs_dotnet_format_options* +cs_dotnet_format_options +g:ale_cs_dotnet_format_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the `dotnet format` + fixer. + + +=============================================================================== +mcs *ale-cs-mcs* + +The `mcs` linter looks only for syntax errors while you type. See +|ale-cs-mcsc| for the separately configured linter for checking for semantic +errors. + + +------------------------------------------------------------------------------- +Options + *ale-options.cs_mcs_options* + *g:ale_cs_mcs_options* + *b:ale_cs_mcs_options* +cs_mcs_options +g:ale_cs_mcs_options + Type: |String| + Default: `''` + + This variable can be changed to pass additional flags given to mcs. + + NOTE: The -unsafe flag is selected implicitly and thus does not need to be + explicitly included in the |g:ale_cs_mcs_options| or |b:ale_cs_mcs_options| + parameter. + + +=============================================================================== +mcsc *ale-cs-mcsc* + +The mcsc linter checks for semantic errors when files are opened or saved See +|ale-lint-file-linters| for more information on linters which do not check for +problems while you type. + +The mcsc linter uses the mono mcs compiler to generate a temporary module +target file (-t:module). The module includes including all '*.cs' files +contained in the directory tree rooted at the path defined by the +|g:ale_cs_mcsc_source| or |b:ale_cs_mcsc_source| variable. variable and all +sub directories. + +The paths to search for additional assembly files can be specified using the +|g:ale_cs_mcsc_assembly_path| or |b:ale_cs_mcsc_assembly_path| variables. + +NOTE: ALE will not find any errors in files apart from syntax errors if any +one of the source files contains a syntax error. Syntax errors must be fixed +first before other errors will be shown. + + +------------------------------------------------------------------------------- +Options + *ale-options.cs_mcsc_options* + *g:ale_cs_mcsc_options* + *b:ale_cs_mcsc_options* +cs_mcsc_options +g:ale_cs_mcsc_options + Type: |String| + Default: `''` + + This option can be set to pass additional arguments to the `mcs` compiler. + + For example, to add the dotnet package which is not added per default: > + + let g:ale_cs_mcs_options = '-pkg:dotnet' +< + NOTE: the `-unsafe` option is always passed to `mcs`. + + *ale-options.cs_mcsc_source* + *g:ale_cs_mcsc_source* + *b:ale_cs_mcsc_source* +cs_mcsc_source +g:ale_cs_mcsc_source + Type: |String| + Default: `''` + + This variable defines the root path of the directory tree searched for the + '*.cs' files to be linted. If this option is empty, the source file's + directory will be used. + + NOTE: Currently it is not possible to specify sub directories and + directory sub trees which shall not be searched for *.cs files. + + *ale-options.cs_mcsc_assembly_path* + *g:ale_cs_mcsc_assembly_path* + *b:ale_cs_mcsc_assembly_path* +cs_mcsc_assembly_path +g:ale_cs_mcsc_assembly_path + Type: |List| + Default: `[]` + + This variable defines a list of path strings to be searched for external + assembly files. The list is passed to the mcs compiler using the `-lib:` + flag. + + *ale-options.cs_mcsc_assemblies* + *g:ale_cs_mcsc_assemblies* + *b:ale_cs_mcsc_assemblies* +cs_mcsc_assemblies +g:ale_cs_mcsc_assemblies + Type: |List| + Default: `[]` + + This variable defines a list of external assembly (*.dll) files required + by the mono mcs compiler to generate a valid module target. The list is + passed the mcs compiler using the `-r:` flag. + + For example: > + + " Compile C# programs with the Unity engine DLL file on Mac. + let g:ale_cs_mcsc_assemblies = [ + \ '/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll', + \ 'path-to-unityproject/obj/Debug', + \] +< + +=============================================================================== +uncrustify *ale-cs-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-css.txt b/doc/ale-css.txt new file mode 100644 index 00000000..727f0c3e --- /dev/null +++ b/doc/ale-css.txt @@ -0,0 +1,108 @@ +=============================================================================== +ALE CSS Integration *ale-css-options* + + +=============================================================================== +cspell *ale-css-cspell* + +See |ale-cspell-options| + + +=============================================================================== +css-beautify *ale-css-css-beautify* + + *ale-options.css_css_beautify_executable* + *g:ale_css_css_beautify_executable* + *b:ale_css_css_beautify_executable* +css_css_beautify_executable +g:ale_css_css_beautify_executable + Type: |String| + Default: `'css-beautify'` + + See |ale-integrations-local-executables| + + *ale-options.css_css_beautify_options* + *g:ale_css_css_beautify_options* + *b:ale_css_css_beautify_options* +css_css_beautify_options +g:ale_css_css_beautify_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to css-beautify. + + *ale-options.css_css_beautify_use_global* + *g:ale_css_css_beautify_use_global* + *b:ale_css_css_beautify_use_global* +css_css_beautify_use_global +g:ale_css_css_beautify_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +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. + + +=============================================================================== +stylelint *ale-css-stylelint* + + *ale-options.css_stylelint_executable* + *g:ale_css_stylelint_executable* + *b:ale_css_stylelint_executable* +css_stylelint_executable +g:ale_css_stylelint_executable + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + *ale-options.css_stylelint_options* + *g:ale_css_stylelint_options* + *b:ale_css_stylelint_options* +css_stylelint_options +g:ale_css_stylelint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + *ale-options.css_stylelint_use_global* + *g:ale_css_stylelint_use_global* + *b:ale_css_stylelint_use_global* +css_stylelint_use_global +g:ale_css_stylelint_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +vscodecss *ale-css-vscode* + +Website: https://github.com/hrsh7th/vscode-langservers-extracted + + +------------------------------------------------------------------------------- +Installation + +Install VSCode css language server either globally or locally: > + + npm install -g vscode-langservers-extracted +< + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cuda.txt b/doc/ale-cuda.txt new file mode 100644 index 00000000..1ffe22fd --- /dev/null +++ b/doc/ale-cuda.txt @@ -0,0 +1,62 @@ +=============================================================================== +ALE CUDA Integration *ale-cuda-options* + + +=============================================================================== +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. + + +=============================================================================== +clangd *ale-cuda-clangd* + + *ale-options.cuda_clangd_executable* + *g:ale_cuda_clangd_executable* + *b:ale_cuda_clangd_executable* +cuda_clangd_executable +g:ale_cuda_clangd_executable + Type: |String| + Default: `'clangd'` + + This variable can be changed to use a different executable for clangd. + + *ale-options.cuda_clangd_options* + *g:ale_cuda_clangd_options* + *b:ale_cuda_clangd_options* +cuda_clangd_options +g:ale_cuda_clangd_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clangd. + + +=============================================================================== +nvcc *ale-cuda-nvcc* + + *ale-options.cuda_nvcc_executable* + *g:ale_cuda_nvcc_executable* + *b:ale_cuda_nvcc_executable* +cuda_nvcc_executable +g:ale_cuda_nvcc_executable + Type: |String| + Default: `'nvcc'` + + This variable can be changed to use a different executable for nvcc. + Currently only nvcc 8.0 is supported. + + *ale-options.cuda_nvcc_options* + *g:ale_cuda_nvcc_options* + *b:ale_cuda_nvcc_options* +cuda_nvcc_options +g:ale_cuda_nvcc_options + Type: |String| + Default: `'-std=c++11'` + + This variable can be changed to modify flags given to nvcc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-d.txt b/doc/ale-d.txt new file mode 100644 index 00000000..51446dba --- /dev/null +++ b/doc/ale-d.txt @@ -0,0 +1,39 @@ +=============================================================================== +ALE D Integration *ale-d-options* + + +=============================================================================== +dfmt *ale-d-dfmt* + + *ale-options.d_dfmt_options* + *g:ale_d_dfmt_options* + *b:ale_d_dfmt_options* +d_dfmt_options +g:ale_d_dfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the dfmt fixer. + +=============================================================================== +dls *ale-d-dls* + + *ale-options.d_dls_executable* + *g:ale_d_dls_executable* + *b:ale_d_dls_executable* +d_dls_executable +g:ale_d_dls_executable + Type: |String| + Default: `dls` + + See |ale-integrations-local-executables| + + +=============================================================================== +uncrustify *ale-d-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-dafny.txt b/doc/ale-dafny.txt new file mode 100644 index 00000000..4ac1ad77 --- /dev/null +++ b/doc/ale-dafny.txt @@ -0,0 +1,20 @@ +=============================================================================== +ALE Dafny Integration *ale-dafny-options* + + +=============================================================================== +dafny *ale-dafny-dafny* + + *ale-options.dafny_dafny_timelimit* + *g:ale_dafny_dafny_timelimit* + *b:ale_dafny_dafny_timelimit* +dafny_dafny_timelimit +g:ale_dafny_dafny_timelimit + Type: |Number| + Default: `10` + + This variable sets the `/timeLimit` used for dafny. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-dart.txt b/doc/ale-dart.txt new file mode 100644 index 00000000..d4969ab7 --- /dev/null +++ b/doc/ale-dart.txt @@ -0,0 +1,157 @@ +=============================================================================== +ALE Dart Integration *ale-dart-options* + + +=============================================================================== +analysis_server *ale-dart-analysis_server* + + +------------------------------------------------------------------------------- +Installation + +Install Dart via whatever means. `analysis_server` will be included in the SDK. + +In case that `dart` is not in your path, try to set the executable option to +its absolute path. : > + " Set the executable path for dart to the absolute path to it. + let g:ale_dart_analysis_server_executable = '/usr/local/bin/dart' +< + +------------------------------------------------------------------------------- +Options + *ale-options.dart_analysis_server_executable* + *g:ale_dart_analysis_server_executable* + *b:ale_dart_analysis_server_executable* +dart_analysis_server_executable +g:ale_dart_analysis_server_executable + Type: |String| + Default: `'dart'` + + This variable can be set to change the path of dart. + + *ale-options.dart_analysis_server_enable_language_server* + *g:ale_dart_analysis_server_enable_language_server* + *b:ale_dart_analysis_server_enable_language_server* +dart_analysis_server_enable_language_server +g:ale_dart_analysis_server_enable_language_server + Type: |Number| + Default: `1` + + When set to `1`, ALE will use the new `dart language-server` command, + available from Dart version 2.16.0, to launch the language server. When set + to `0`, ALE will instead use the deprecated + `./snapshots/analysis_server.dart.snapshot --lsp` command used by older + versions of Dart. + + +=============================================================================== +dart-analyze *ale-dart-analyze* + + +------------------------------------------------------------------------------- +Installation + +Installing Dart should probably ensure that `dart` is in your `$PATH`. + +In case it is not, try to set the executable option to its absolute path. : > + " Set the executable path for dart to the absolute path to it. + let g:ale_dart_format_executable = '/usr/lib/dart/bin/dart' + > + +Install Dart via whatever means. `dart analyze` will be included in the SDK. + + +------------------------------------------------------------------------------- +Options + *ale-options.dart_analyze_executable* + *g:ale_dart_analyze_executable* + *b:ale_dart_analyze_executable* +dart_analyze_executable +g:ale_dart_analyze_executable + Type: |String| + Default: `'dart'` + + This variable can be set to specify an absolute path to the + format executable (or to specify an alternate executable). + + +=============================================================================== +dart-format *ale-dart-format* + + +------------------------------------------------------------------------------- +Installation + +Installing Dart should probably ensure that `dart` is in your `$PATH`. + +In case it is not, try to set the executable option to its absolute path: > + + " Set the executable path for dart to the absolute path to it. + let g:ale_dart_format_executable = '/usr/lib/dart/bin/dart' +< + +------------------------------------------------------------------------------- +Options + *ale-options.dart_format_executable* + *g:ale_dart_format_executable* + *b:ale_dart_format_executable* +dart_format_executable +g:ale_dart_format_executable + Type: |String| + Default: `'dart'` + + This variable can be set to specify an absolute path to the + format executable (or to specify an alternate executable). + + *ale-options.dart_format_options* + *g:ale_dart_format_options* + *b:ale_dart_format_options* +dart_format_options +g:ale_dart_format_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the dart format fixer. + + +=============================================================================== +dartfmt *ale-dart-dartfmt* + + +------------------------------------------------------------------------------- +Installation + +Installing Dart should probably ensure that `dartfmt` is in your `$PATH`. + +In case it is not, try to set the executable option to its absolute path: > + + " Set the executable path for dartfmt to the absolute path to it. + let g:ale_dart_dartfmt_executable = '/usr/lib/dart/bin/dartfmt' +< + +------------------------------------------------------------------------------- +Options + *ale-options.dart_dartfmt_executable* + *g:ale_dart_dartfmt_executable* + *b:ale_dart_dartfmt_executable* +dart_dartfmt_executable +g:ale_dart_dartfmt_executable + Type: |String| + Default: `''` + + This variable can be set to specify an absolute path to the + dartfmt executable (or to specify an alternate executable). + + *ale-options.dart_dartfmt_options* + *g:ale_dart_dartfmt_options* + *b:ale_dart_dartfmt_options* +dart_dartfmt_options +g:ale_dart_dartfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the dartfmt fixer. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-desktop.txt b/doc/ale-desktop.txt new file mode 100644 index 00000000..9c16de2f --- /dev/null +++ b/doc/ale-desktop.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE desktop Integration *ale-desktop-options* + + +=============================================================================== +desktop-file-validate *ale-desktop-desktop-file-validate* + +ALE supports checking .desktop files with `desktop-file-validate.` + + +------------------------------------------------------------------------------- +Options + *ale-options.desktop_desktop_file_validate_options* + *g:ale_desktop_desktop_file_validate_options* + *b:ale_desktop_desktop_file_validate_options* +desktop_desktop_file_validate_options +g:ale_desktop_desktop_file_validate_options + Type: |String| + Default: `''` + + This variable can be changed to set options for `desktop-file-validate`, + such as `'--warn-kde'`. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-development.txt b/doc/ale-development.txt new file mode 100644 index 00000000..ea36a453 --- /dev/null +++ b/doc/ale-development.txt @@ -0,0 +1,546 @@ +*ale-development.txt* For Vim version 8.0. +*ale-dev* +*ale-development* + +ALE Development Documentation + +=============================================================================== +CONTENTS *ale-development-contents* + + 1. Introduction.........................|ale-development-introduction| + 2. Design Goals.........................|ale-design-goals| + 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| + 4.3. Running Tests in a Windows VM....|ale-development-windows-tests| + 5. Contributing.........................|ale-development-contributing| + 5.1. Preparing a Release..............|ale-development-release| + + +=============================================================================== +1. Introduction *ale-development-introduction* + +This document contains helpful information for ALE developers, including +design goals, information on how to run the tests, coding standards, and so +on. You should read this document if you want to get involved with ALE +development. + + +=============================================================================== +2. Design Goals *ale-design-goals* + +This section lists design goals for ALE, in no particular order. They are as +follows. + +ALE code should be almost 100% VimL. This makes the plugin as portable as +possible. + +ALE should run without needing any other plugins to be installed, to make +installation simple. ALE can integrate with other plugins for more advanced +functionality, non-essential functionality, or improving on basic first party +functionality. + +ALE should check files with as many tools as possible by default, except where +they cause security issues or make excessive use of resources on modern +machines. + +ALE should be free of breaking changes to the public API, which is comprised of +documented functions and options, until a major version is planned. Breaking +changes should be preceded by a deprecation phase complete with warnings. +Changes required for security may be an exception. + +ALE supports Vim 8 and above, and Neovim 0.7.0 or newer. These are the +earliest versions of Vim and Neovim which support |+job|, |+timer|, +|+closure|, and |+lambda| features. All ALE code should be written so it is +compatible with these versions of Vim, or with version checks so particular +features can degrade or fail gracefully. + +Just about everything should be documented and covered with tests. + +By and large, people shouldn't pay for the functionality they don't use. Care +should be taken when adding new features, so supporting new features doesn't +degrade the general performance of anything ALE does. + +LSP support will become more important as time goes on. ALE should provide +better support for LSP features as time goes on. + +When merging pull requests, you should respond with `Cheers! :beers:`, purely +for comedy value. + + +=============================================================================== +3. Coding Standards *ale-coding-standards* + +The following general coding standards should be adhered to for Vim code. + +* Check your Vim code with `Vint` and do everything it says. ALE will check + your Vim code with Vint automatically. See: https://github.com/Kuniwak/vint + Read ALE's `Dockerfile` to see which version of `Vint` it uses. +* Try to write descriptive and concise names for variables and functions. + Names shouldn't be too short or too long. Think about others reading your + code later on. +* Use `snake_case` names for variables and arguments, and `PascalCase` names + for functions. Prefix every variable name with its scope. (`l:`, `g:`, etc.) +* Try to keep lines no longer than 80 characters, but this isn't an absolute + requirement. +* Use 4 spaces for every level of indentation in Vim code. +* Add a blank line before every `function`, `if`, `for`, `while`, or `return`, + which doesn't start a new level of indentation. This makes the logic in + your code easier to follow. +* End every file with a trailing newline character, but not with extra blank + lines. Remove trailing whitespace from the ends of lines. +* Write the full names of commands instead of abbreviations. For example, write + `function` instead of `func`, and `endif` instead of `end`. +* Write functions with `!`, so files can be reloaded. Use the |abort| keyword + for all functions, so functions exit on the first error. +* Make sure to credit yourself in files you have authored with `Author:` + and `Description:` comments. + +In addition to the above general guidelines for the style of your code, you +should also follow some additional rules designed to prevent mistakes. Some of +these are reported with ALE's `custom-linting-rules` script. See +|ale-development-tests|. + +* Don't leave stray `:echo` lines in code. Write `" no-custom-checks` above + the line if you must echo something. +* For strings use |is#| instead of |==#|, `is?` instead of `==?`, `isnot#` + instead of `!=#`, and `isnot?` instead of `!=?`. This is because `'x' ==# 0` + returns 1, while `'x' is# 0` returns 0, so you will experience fewer issues + when numbers are compared with strings. `is` and `isnot` also do not throw + errors when other objects like List or Dictionaries are compared with + strings. +* Don't use the `getcwd()` function in the ALE codebase. Most of ALE's code + runs from asynchronous callback functions, and these functions can execute + from essentially random buffers. Therefore, the `getcwd()` output is + useless. Use `expand('#' . a:buffer . ':p:h')` instead. Don't use + `expand('%...')` for the same reason. +* Don't use the `simplify()` function. It doesn't simplify paths enough. Use + `ale#path#Simplify()` instead. +* Don't use the `shellescape()` function. It doesn't escape arguments properly + on Windows. Use `ale#Escape()` instead, which will avoid escaping where it + isn't needed, and generally escape arguments better on Windows. +* Don't use the `tempname()` function. It doesn't work when `$TMPDIR` isn't + set. Use `ale#util#Tempname()` instead, which temporarily sets `$TMPDIR` + appropriately where needed. +* Use `snake_case` names for linter names, so they can be used as part of + variable names. You can define `aliases` for linters, for other names people + might try to configure linters with. +* Use |v:t_TYPE| variables instead of `type()`, which are more readable. + +Apply the following guidelines when writing Vader test files. + +* Use 2 spaces for Vader test files, instead of the 4 spaces for Vim files. +* If you write `Before` and `After` blocks, you should typically write them at + the top of the file, so they run for all tests. There may be some tests + where it make sense to modify the `Before` and `After` code part of the way + through the file. +* If you modify any settings or global variables, reset them in `After` + blocks. The Vader `Save` and `Restore` commands can be useful for this + purpose. +* If you load or define linters in tests, write `call ale#linter#Reset()` in + an `After` block. +* Just write `Execute` blocks for Vader tests, and don't bother writing `Then` + blocks. `Then` blocks execute after `After` blocks in older versions, and + that can be confusing. + +Apply the following rules when writing Bash scripts. + +* Run `shellcheck`, and do everything it says. + See: https://github.com/koalaman/shellcheck +* Try to write scripts so they will run on Linux, BSD, or Mac OSX. + + +=============================================================================== +4. Testing ALE *ale-development-tests* *ale-dev-tests* *ale-tests* + +ALE is tested with a suite of tests executed via GitHub Actions and AppVeyor. +ALE runs tests with the following versions of Vim in the following +environments. + +1. Vim 8.0.0027 on Linux via GitHub Actions. +2. Vim 9.0.0297 on Linux via GitHub Actions. +3. Neovim 0.7.0 on Linux via GitHub Actions. +4. Neovim 0.8.0 on Linux via GitHub Actions. +6. Vim 8 (stable builds) on Windows via AppVeyor. + +If you are developing ALE code on Linux, Mac OSX, or BSD, you can run ALEs +tests by installing Docker and running the `run-tests` script. Follow the +instructions on the Docker site for installing Docker. +See: https://docs.docker.com/install/ + +NOTE: Don't forget to add your user to the `docker` group on Linux, or Docker +just won't work. See: https://docs.docker.com/install/linux/linux-postinstall/ + +If you run simply `./run-tests` from the ALE repository root directory, the +latest Docker image for tests will be downloaded if needed, and the script +will run all of the tests in Vader, Vint checks, and several Bash scripts for +finding extra issues. Run `./run-tests --help` to see all of the options the +script supports. Note that the script supports selecting particular test files. + +Once you get used to dealing with Vim and NeoVim compatibility issues, you +probably want to use `./run-tests --fast -q` for running tests with only the +fastest available Vim version, and with success messages from tests +suppressed. + +Generally write tests for any changes you make. The following types of tests +are recommended for the following types of code. + +* New/edited error handler callbacks -> Write tests in `test/handler` +* New/edited linter definition -> Write tests in `test/linter` +* New/edited fixer functions -> Write tests in `test/fixers` + +Look at existing tests in the codebase for examples of how to write tests. +Refer to the Vader documentation for general information on how to write Vader +tests: https://github.com/junegunn/vader.vim + +If you need to add any supporting files for tests, such as empty files present +to test searching upwards through paths for configuration files, they can be +added to the `test/test-files` directory. + +See |ale-development-linter-tests| for more information on how to write linter +tests. + +When you add new linters or fixers, make sure to add them into the tables in +supported-tools.md and |ale-supported-languages-and-tools.txt|. If you forget to +keep them both in sync, you should see an error like the following in the +builds run for GitHub Actions. +> + ======================================== + diff supported-tools.md and doc/ale-supported-languages-and-tools.txt tables + ======================================== + Differences follow: + + --- /tmp/readme.qLjNhJdB 2018-07-01 16:29:55.590331972 +0100 + +++ /tmp/doc.dAi8zfVE 2018-07-01 16:29:55.582331877 +0100 + @@ -1 +1 @@ + - ASM: gcc, foobar + + ASM: gcc +< +Make sure to list documentation entries for linters and fixers in individual +help files in the table of contents, and to align help tags to the right +margin. For example, if you add a heading for an `aardvark` tool to +`ale-python.txt` with a badly aligned doc tag, you will see errors like so. > + + ======================================== + Look for badly aligned doc tags + ======================================== + Badly aligned tags follow: + + doc/ale-python.txt:aardvark ... + ======================================== + Look for table of contents issues + ======================================== + + Check for bad ToC sorting: + + Check for mismatched ToC and headings: + + --- /tmp/table-of-contents.mwCFOgSI 2018-07-01 16:33:25.068811878 +0100 + +++ /tmp/headings.L4WU0hsO 2018-07-01 16:33:25.076811973 +0100 + @@ -168,6 +168,7 @@ + pyrex (cython), ale-pyrex-options + cython, ale-pyrex-cython + python, ale-python-options + + aardvark, ale-python-aardvark + autopep8, ale-python-autopep8 + black, ale-python-black + flake8, ale-python-flake8 +< +Make sure to make the table of contents match the headings, and to keep the +doc tags on the right margin. + + +=============================================================================== +4.1 Writing Linter Tests *ale-development-linter-tests* + +Tests for ALE linters take two forms. + +1. Tests for handling the output of commands. +2. Tests for checking which commands are run, or connections are made. + +Tests of the first form should go in the `test/handler` directory, and should +be written like so. > + + Before: + " Load the file which defines the linter. + runtime ale_linters/filetype/linter_name_here.vim + + After: + " Unload all linters again. + call ale#linter#Reset() + + Execute(The output should be correct): + + " Test that the right loclist items are parsed from the handler. + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': 'Something went wrong', + \ }, + \ ], + \ ale_linters#filetype#linter_name#Handle(bufnr(''), [ + \ '1:Something went wrong', + \ ] +< +Tests for what ALE runs should go in the `test/linter` directory, and should +be written like so. > + + Before: + " Load the linter and set up a series of commands, reset linter variables, + " clear caches, etc. + " + " Vader's 'Save' command will be called here for linter variables. + call ale#assert#SetUpLinterTest('filetype', 'linter_name') + + After: + " Reset linters, variables, etc. + " + " Vader's 'Restore' command will be called here. + call ale#assert#TearDownLinterTest() + + Execute(The default command should be correct): + " AssertLinter checks the executable and command. + " Pass expected_executable, expected_command + AssertLinter 'some-command', ale#Escape('some-command') . ' --foo' + + Execute(Check chained commands): + " 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. + 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', [ + \ ale#Escape('some-command') . ' --version', + \ ale#Escape('some-command') . ' --foo', + \] +< +The full list of commands that will be temporarily defined for linter tests +given the above setup are as follows. + +`GivenCommandOutput [...]` - Define output for ale#command#Run. +`AssertLinterCwd cwd` - Check the `cwd` for the linter. +`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. +`AssertLSPOptions options_dict` - Check the options given to an LSP server. +`AssertLSPConfig config_dict` - Check the config given to an LSP server. +`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. +`AssertFixerCwd cwd` - Check the `cwd` for the fixer. +`AssertFixer results` - Check the fixer results +`AssertFixerNotExecuted` - Check that fixers will not be executed. + + +=============================================================================== +4.3 Running Tests in a Windows VM *ale-development-windows-tests* + +Tests are run for ALE in a build of Vim 8 for Windows via AppVeyor. These +tests can frequently break due to minor differences in paths and how escaping +is done for commands on Windows. If you are a Linux or Mac user, running these +tests locally can be difficult. Here is a process that will make that easier. + +First, you want to install a Windows image with VirtualBox. Install VirtualBox +and grab a VirtualBox image for Windows such as from here: +https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/ + +NOTE: If you need to enter a password for the virtual machine at any point, +the password is "Passw0rd!" without the double quotes. + +NOTE: If your trial period for Windows runs out, run the commands like the +wallpaper tells you to. + +Your virtual machine will need to have PowerShell installed. Before you go any +further, confirm that PowerShell is installed in your Windows virtual machine. + +Consult the VirtualBox documentation on how to install "Guest Additions." +You probably want to install "Guest Additions" for most things to work +properly. + +After you've loaded your virtual machine image, go into "Settings" for your +virtual machine, and "Shared Folders." Add a shared folder with the name +"ale", and set the "Folder Path" to the path to your ALE repository, for +example: "/home/w0rp/ale" + +Find out which drive letter "ale" has been mounted as in Windows. We'll use +"E:" as the drive letter, for example. Open the command prompt as an +administrator by typing in `cmd` in the start menu, right clicking on the +command prompt application, and clicking "Run as administrator." Click "Yes" +when prompted to ask if you're sure you want to run the command prompt. You +should type in the following command to mount the "ale" directory for testing, +where "E:" is replaced with your drive letter. > + + mklink /D C:\testplugin E: +< +Close the administrator Command Prompt, and try running the command +`type C:\testplugin\LICENSE` in a new Command Prompt which you are NOT running +as administrator. You should see the license for ALE in your terminal. After +you have confirmed that you have mounted ALE on your machine, search in the +Start Menu for "power shell," run PowerShell as an administrator, and issue +the following commands to install the correct Vim and Vader versions for +running tests. > + + Add-Type -A System.IO.Compression.FileSystem + + Invoke-WebRequest ftp://ftp.vim.org/pub/vim/pc/vim80-586w32.zip -OutFile C:\vim.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\vim.zip', 'C:\vim') + rm C:\vim.zip + + Invoke-WebRequest ftp://ftp.vim.org/pub/vim/pc/vim80-586rt.zip -OutFile C:\rt.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\rt.zip', 'C:\vim') + rm C:\rt.zip + + Invoke-WebRequest https://github.com/junegunn/vader.vim/archive/c6243dd81c98350df4dec608fa972df98fa2a3af.zip -OutFile C:\vader.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\vader.zip', 'C:\') + mv C:\vader.vim-c6243dd81c98350df4dec608fa972df98fa2a3af C:\vader + rm C:\vader.zip +< +After you have finished installing everything, you can run all of the tests +in Windows by opening a Command Prompt NOT as an administrator by navigating +to the directory where you've mounted the ALE code, which must be named +`C:\testplugin`, and by running the `run-tests.bat` batch file. > + + cd C:\testplugin + run-tests +< +It will probably take several minutes for all of the tests to run. Be patient. +You can run a specific test by passing the filename as an argument to the +batch file, for example: `run-tests test/test_c_flag_parsing.vader` . This will +give you results much more quickly. + + +=============================================================================== +5. Contributing *ale-development-contributing* + +All integration of new code into ALE is done through GitHub pull requests. +Using that tool streamlines the process and minimizes the time and effort +required to e.g. ensure test suites are run for every change. + +As for any project hosted by GitHub, the choice of platform demands every +contributor to take care to setup an account and configure it accordingly. + +Due to details of our process, a difference to many other GitHub hosted +projects is that contributors who wish to keep the author fields for their +commits unaltered need to configure a public email address in their account +and profile settings. See: https://docs.github.com/en/account-and-profile/ + +Unless configuring GitHub to expose contact details, commits will be rewritten +to appear by `USERNAME ` . + + +=============================================================================== +5.1 Preparing a Release *ale-development-release* + +ALE offers release packages through GitHub, for two reasons: + +1. Some users like to target specific release versions rather than simply + installing the plugin from `master`. This includes users who create Linux + distribution specific packages from GitHub releases. +2. The releases provide a nice way to get an overview of what has changed in + ALE over time. + +ALE has no fixed release schedule. Release versions are created whenever the +ALE developers feel the need to create one. ALE release versions follow the +typical Semantic Versioning scheme. See: https://semver.org/ + +Minor version releases for ALE should be the most common, followed by patch +releases. Every minor version release should be followed by a `vA.B.x` branch +such as `v2.0.x` for version `2.0.0` and every following patch version before +`2.1.0`. The `git` branch strategy for patches is to first merge a bug fix to +`master`, and then `git cherry-pick` a patch to a branch for a specific +version. ALE developers do not generally support anything but `master` or the +last minor version. + +Generally ALE releases hit a major version only when there are breaking +changes to a public ALE setting or function. A "public" setting or function is +defined as any setting or function documented in the `:help` |ale.txt| file. +Major ALE versions ought to be so rare that they only come once a year at +most. ALE should not typically introduce any breaking changes. + +If there are ever to be any breaking changes made for ALE, there should first +come a minor version release for ALE documenting all of the coming breaking +changes to ALE. It should be described how users can prepare for a breaking +change that is coming before it is done. + +To create a release for ALE, you will need sufficient permissions in GitHub. +Once you do, follow these steps. + +1. Create a new release draft, or edit an existing one. It helps to craft + drafts ahead of time and write the last commit ID checked for release notes + on the last update to a draft. + See the releases page: https://github.com/dense-analysis/ale/releases +2. Examine `git log` and read changes made between the last ID checked, or the + git tag of the previous release, and the current commit in `master`. +3. Write updates in separate sections (except where empty) for: + 3.a. Breaking Changes + 3.b. Deprecated Features + 3.c. New Features + 3.d. New Linters + 3.e. New Fixers + 3.f. Linter Enhancements + 3.g. Fixer Enhancements + 3.h. Bugs Fixed +4. Once you've finished writing the draft for the release, bump + `s:current_ale_version` in `autoload/ale.vim` to the current version, and + add a line to `test/test_ale_has.vader` to test for the version. See + |ale#Has()| documentation for more information. +5. Commit the changes after `./run-tests --fast -q` passes. +6. Tag the release with `git tag vA.B.C`, replacing `A`, `B`, and `C` with the + version numbers. See `git tag --list` for examples. +7. Run `git push` and `git push --tags` to push the commit and the tag. +8. Edit the release draft in GitHub, select the tag you just pushed, and + publish the draft. +9. If you're creating a new major or minor version: `git checkout -b vA.B.x`, + replacing `A` and `B` with the major and minor versions. `git push` the new + branch, and the GitHub branch protection settings should automatically + apply to the new release branch. +10. You have already completed the last step. + +Have fun creating ALE releases. Drink responsibly, or not at all, which is the +preference of w0rp. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-dhall.txt b/doc/ale-dhall.txt new file mode 100644 index 00000000..9e6c85fb --- /dev/null +++ b/doc/ale-dhall.txt @@ -0,0 +1,57 @@ +=============================================================================== +ALE Dhall Integration *ale-dhall-options* + +Dhall - https://dhall-lang.org/ + + *ale-options.dhall_executable* + *g:ale_dhall_executable* + *b:ale_dhall_executable* +dhall_executable +g:ale_dhall_executable + Type: |String| + Default: `'dhall'` + + *ale-options.dhall_options* + *g:ale_dhall_options* + *b:ale_dhall_options* +dhall_options +g:ale_dhall_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the 'dhall` executable. + This is shared with `dhall-freeze` and `dhall-lint`. +> + let g:ale_dhall_options = '--ascii' +< + +=============================================================================== +dhall-format *ale-dhall-format* + + +=============================================================================== +dhall-freeze *ale-dhall-freeze* + + +------------------------------------------------------------------------------- +Options + *ale-options.dhall_freeze_options* + *g:ale_dhall_freeze_options* + *b:ale_dhall_freeze_options* +dhall_freeze_options +g:ale_dhall_freeze_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the 'dhall freeze` + executable. +> + let g:ale_dhall_freeze_options = '--all' +< + +=============================================================================== +dhall-lint *ale-dhall-lint* + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-dockerfile.txt b/doc/ale-dockerfile.txt new file mode 100644 index 00000000..253462dc --- /dev/null +++ b/doc/ale-dockerfile.txt @@ -0,0 +1,115 @@ +=============================================================================== +ALE Dockerfile Integration *ale-dockerfile-options* + + +=============================================================================== +dockerfile_lint *ale-dockerfile-dockerfile_lint* + + *ale-options.dockerfile_dockerfile_lint_executable* + *g:ale_dockerfile_dockerfile_lint_executable* + *b:ale_dockerfile_dockerfile_lint_executable* +dockerfile_dockerfile_lint_executable +g:ale_dockerfile_dockerfile_lint_executable + Type: |String| + Default: `'dockerfile_lint'` + + This variable can be changed to specify the executable used to run + dockerfile_lint. + + *ale-options.dockerfile_dockerfile_lint_options* + *g:ale_dockerfile_dockerfile_lint_options* + *b:ale_dockerfile_dockerfile_lint_options* +dockerfile_dockerfile_lint_options +g:ale_dockerfile_dockerfile_lint_options + Type: |String| + Default: `''` + + This variable can be changed to add additional command-line arguments to + the dockerfile lint invocation - like custom rule file definitions. + + +=============================================================================== +dockerlinter *ale-dockerfile-dockerlinter* + + *ale-options.dockerfile_dockerlinter_executable* + *g:ale_dockerfile_dockerlinter_executable* + *b:ale_dockerfile_dockerlinter_executable* +dockerfile_dockerlinter_executable +g:ale_dockerfile_dockerlinter_executable + Type: |String| + Default: `'dockerlinter'` + + This variable can be changed to specify the executable used to run + dockerlinter. + + + *ale-options.dockerfile_dockerlinter_options* + *g:ale_dockerfile_dockerlinter_options* + *b:ale_dockerfile_dockerlinter_options* +dockerfile_dockerlinter_options +g:ale_dockerfile_dockerlinter_options + Type: |String| + Default: `''` + + This variable can be changed to add additional command-line arguments to + the dockerfile lint invocation - like custom rule file definitions. + + +=============================================================================== +dprint *ale-dockerfile-dprint* + +See |ale-dprint-options| and https://dprint.dev/plugins/dockerfile + + +=============================================================================== +hadolint *ale-dockerfile-hadolint* + +hadolint can be found at: https://github.com/hadolint/hadolint + + +------------------------------------------------------------------------------- +Options + *ale-options.dockerfile_hadolint_options* + *g:ale_dockerfile_hadolint_options* + *b:ale_dockerfile_hadolint_options* +dockerfile_hadolint_options +g:ale_dockerfile_hadolint_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the hadolint + invocation. These arguments will be used whether docker is being used or not + (see below). + + *ale-options.dockerfile_hadolint_use_docker* + *g:ale_dockerfile_hadolint_use_docker* + *b:ale_dockerfile_hadolint_use_docker* +dockerfile_hadolint_use_docker +g:ale_dockerfile_hadolint_use_docker + Type: |String| + Default: `'never'` + + This variable controls if docker and the hadolint image are used to run this + linter: if 'never', docker will never be used; 'always' means docker will + always be used; 'yes' and docker will be used if the hadolint executable + cannot be found. + + For now, the default is 'never'. This may change as ale's support for using + docker to lint evolves. + + *ale-options.dockerfile_hadolint_image* + *g:ale_dockerfile_hadolint_image* + *b:ale_dockerfile_hadolint_image* +dockerfile_hadolint_image +g:ale_dockerfile_hadolint_image + Type: |String| + Default: `'hadolint/hadolint'` + + This variable controls the docker image used to run hadolint. The default + is hadolint's author's build, and can be found at: + + https://hub.docker.com/r/hadolint/hadolint/ + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-elixir.txt b/doc/ale-elixir.txt new file mode 100644 index 00000000..a4cbf309 --- /dev/null +++ b/doc/ale-elixir.txt @@ -0,0 +1,146 @@ +=============================================================================== +ALE Elixir Integration *ale-elixir-options* + + +=============================================================================== +mix *ale-elixir-mix* + +The `mix` linter is disabled by default, as it can be too expensive to run. +See `:help g:ale_linters` + + *ale-options.elixir_mix_options* + *g:ale_elixir_mix_options* + *b:ale_elixir_mix_options* +elixir_mix_options +g:ale_elixir_mix_options + Type: |String| + Default: `'mix'` + + + This variable can be changed to specify the mix executable. + + +=============================================================================== +mix_format *ale-elixir-mix-format* + + *ale-options.elixir_mix_format_options* + *g:ale_elixir_mix_format_options* + *b:ale_elixir_mix_format_options* +elixir_mix_format_options +g:ale_elixir_mix_format_options + Type: |String| + Default: `''` + + + This variable can be changed to specify the mix options passed to the + mix_format fixer + + +=============================================================================== +dialyxir *ale-elixir-dialyxir* + +Dialyzer, a DIscrepancy AnaLYZer for ERlang programs. +http://erlang.org/doc/man/dialyzer.html + +It can be used with elixir through dialyxir +https://github.com/jeremyjh/dialyxir + +Options for dialyzer are not configurable by ale, but they are instead +configured on your project's `mix.exs`. + +See https://github.com/jeremyjh/dialyxir#with-explaining-stuff for more +information. + + +=============================================================================== +elixir-ls *ale-elixir-elixir-ls* + +Elixir Language Server (https://github.com/JakeBecker/elixir-ls) + + *ale-options.elixir_elixir_ls_release* + *g:ale_elixir_elixir_ls_release* + *b:ale_elixir_elixir_ls_release* +elixir_elixir_ls_release +g:ale_elixir_elixir_ls_release + Type: |String| + Default: `'elixir-ls'` + + Location of the elixir-ls release directory. This directory must contain + the language server scripts (language_server.sh and language_server.bat). + + *ale-options.elixir_elixir_ls_config* + *g:ale_elixir_elixir_ls_config* + *b:ale_elixir_elixir_ls_config* +elixir_elixir_ls_config +g:ale_elixir_elixir_ls_config + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server. For example, to disable Dialyzer: > + + let g:ale_elixir_elixir_ls_config = { + \ 'elixirLS': { + \ 'dialyzerEnabled': v:false, + \ }, + \} +< + Consult the ElixirLS documentation for more information about settings. + + +=============================================================================== +credo *ale-elixir-credo* + +Credo (https://github.com/rrrene/credo) + + *ale-options.elixir_credo_strict* + *g:ale_elixir_credo_strict* + *b:ale_elixir_credo_strict* +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. + + *ale-options.elixir_credo_config_file* + *g:ale_elixir_credo_config_file* + *b:ale_elixir_credo_config_file* +elixir_credo_config_file +g:ale_elixir_credo_config_file + Type: |String| + Default: `''` + + Tells credo to use a custom configuration file. + + +=============================================================================== +cspell *ale-elixir-cspell* + +See |ale-cspell-options| + + +=============================================================================== +lexical *ale-elixir-lexical* + +Lexical (https://github.com/lexical-lsp/lexical) + + *ale-options.elixir_lexical_release* + *g:ale_elixir_lexical_release* + *b:ale_elixir_lexical_release* +elixir_lexical_release +g:ale_elixir_lexical_release + Type: |String| + Default: `'lexical'` + + Location of the lexical release directory. This directory must contain + the language server scripts (start_lexical.sh and start_lexical.bat). + + For example, set release to: `/home/projects/lexical/_build/dev/rel/lexical` + + There are currnetly no configuration options for lexical. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-elm.txt b/doc/ale-elm.txt new file mode 100644 index 00000000..59be8952 --- /dev/null +++ b/doc/ale-elm.txt @@ -0,0 +1,128 @@ +=============================================================================== +ALE Elm Integration *ale-elm-options* + + +=============================================================================== +elm-format *ale-elm-elm-format* + + *ale-options.elm_format_executable* + *g:ale_elm_format_executable* + *b:ale_elm_format_executable* +elm_format_executable +g:ale_elm_format_executable + Type: |String| + Default: `'elm-format'` + + See |ale-integrations-local-executables| + + *ale-options.elm_format_use_global* + *g:ale_elm_format_use_global* + *b:ale_elm_format_use_global* +elm_format_use_global +g:ale_elm_format_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.elm_format_options* + *g:ale_elm_format_options* + *b:ale_elm_format_options* +elm_format_options +g:ale_elm_format_options + Type: |String| + Default: `'--yes'` + + This variable can be set to pass additional options to elm-format. + + +=============================================================================== +elm-ls *ale-elm-elm-ls* + + *ale-options.elm_ls_executable* + *g:ale_elm_ls_executable* + *b:ale_elm_ls_executable* +elm_ls_executable +g:ale_elm_ls_executable + Type: |String| + Default: `'elm-language-server'` + + See |ale-integrations-local-executables| + + *ale-options.elm_ls_use_global* + *g:ale_elm_ls_use_global* + *b:ale_elm_ls_use_global* +elm_ls_use_global +g:ale_elm_ls_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 1)` + + See |ale-integrations-local-executables| + + *ale-options.elm_ls_elm_path* + *g:ale_elm_ls_elm_path* + *b:ale_elm_ls_elm_path* +elm_ls_elm_path +g:ale_elm_ls_elm_path + Type: |String| + Default: `''` + + See |ale-integrations-local-executables| + + *ale-options.elm_ls_elm_format_path* + *g:ale_elm_ls_elm_format_path* + *b:ale_elm_ls_elm_format_path* +elm_ls_elm_format_path +g:ale_elm_ls_elm_format_path + Type: |String| + Default: `''` + + See |ale-integrations-local-executables| + + *ale-options.elm_ls_elm_test_path* + *g:ale_elm_ls_elm_test_path* + *b:ale_elm_ls_elm_test_path* +elm_ls_elm_test_path +g:ale_elm_ls_elm_test_path + Type: |String| + Default: `''` + + See |ale-integrations-local-executables| + + *ale-options.elm_ls_elm_analyse_trigger* + *g:ale_elm_ls_elm_analyse_trigger* + *b:ale_elm_ls_elm_analyse_trigger* +elm_ls_elm_analyse_trigger +g:ale_elm_ls_elm_analyse_trigger + Type: |String| + Default: `'change'` + + One of 'change', 'save' or 'never' + + +=============================================================================== +elm-make *ale-elm-elm-make* + + *ale-options.elm_make_executable* + *g:ale_elm_make_executable* + *b:ale_elm_make_executable* +elm_make_executable +g:ale_elm_make_executable + Type: |String| + Default: `'elm'` + + See |ale-integrations-local-executables| + + *ale-options.elm_make_use_global* + *g:ale_elm_make_use_global* + *b:ale_elm_make_use_global* +elm_make_use_global +g:ale_elm_make_use_global + Type: |Number| + 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-erlang.txt b/doc/ale-erlang.txt new file mode 100644 index 00000000..388f80ad --- /dev/null +++ b/doc/ale-erlang.txt @@ -0,0 +1,232 @@ +=============================================================================== +ALE Erlang Integration *ale-erlang-options* + + +=============================================================================== +dialyzer *ale-erlang-dialyzer* + + *ale-options.erlang_dialyzer_executable* + *g:ale_erlang_dialyzer_executable* + *b:ale_erlang_dialyzer_executable* +erlang_dialyzer_executable +g:ale_erlang_dialyzer_executable + Type: |String| + Default: `'dialyzer'` + + This variable can be changed to specify the dialyzer executable. + + *ale-options.erlang_dialyzer_options* + *g:ale_erlang_dialyzer_options* + *b:ale_erlang_dialyzer_options* +erlang_dialyzer_options +g:ale_erlang_dialyzer_options + Type: |String| + Default: `'-Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspec'` + + This variable can be changed to specify the options to pass to the dialyzer + executable. + *ale-options.erlang_dialyzer_plt_file* + *g:ale_erlang_dialyzer_plt_file* + *b:ale_erlang_dialyzer_plt_file* +erlang_dialyzer_plt_file +g:ale_erlang_dialyzer_plt_file + Type: |String| + Default: `''` + + This variable can be changed to specify the path to the PLT file. By + default, it will search for the PLT file inside the `_build` directory. If + there isn't one, it will fallback to the path `$REBAR_PLT_DIR/dialyzer/plt`. + Otherwise, it will default to `$HOME/.dialyzer_plt`. + + *ale-options.erlang_dialyzer_rebar3_profile* + *g:ale_erlang_dialyzer_rebar3_profile* + *b:ale_erlang_dialyzer_rebar3_profile* +erlang_dialyzer_rebar3_profile +g:ale_erlang_dialyzer_rebar3_profile + Type: |String| + Default: `'default'` + + This variable can be changed to specify the profile that is used to + run dialyzer with rebar3. + + +=============================================================================== +elvis *ale-erlang-elvis* + + *ale-options.erlang_elvis_executable* + *g:ale_erlang_elvis_executable* + *b:ale_erlang_elvis_executable* +erlang_elvis_executable +g:ale_erlang_elvis_executable + Type: |String| + Default: `'elvis'` + + This variable can be changed to specify the elvis executable. + + +=============================================================================== +erlang-mode *ale-erlang-erlang-mode* + + *ale-options.erlang_erlang_mode_emacs_executable* + *g:ale_erlang_erlang_mode_emacs_executable* + *b:ale_erlang_erlang_mode_emacs_executable* +erlang_erlang_mode_emacs_executable +g:ale_erlang_erlang_mode_emacs_executable + Type: |String| + Default: `'emacs'` + + This variable can be changed to specify the Emacs executable. + + *ale-options.erlang_erlang_mode_indent_level* + *g:ale_erlang_erlang_mode_indent_level* + *b:ale_erlang_erlang_mode_indent_level* +erlang_erlang_mode_indent_level +g:ale_erlang_erlang_mode_indent_level + Type: |Number| + Default: `4` + + Indentation of Erlang calls/clauses within blocks. + + *ale-options.erlang_erlang_mode_icr_indent* + *g:ale_erlang_erlang_mode_icr_indent* + *b:ale_erlang_erlang_mode_icr_indent* +erlang_erlang_mode_icr_indent +g:ale_erlang_erlang_mode_icr_indent + Type: `'nil'` or |Number| + Default: `'nil'` + + Indentation of Erlang if/case/receive patterns. `'nil'` means keeping default + behavior. When non-`'nil'`, indent to the column of if/case/receive. + + *ale-options.erlang_erlang_mode_indent_guard* + *g:ale_erlang_erlang_mode_indent_guard* + *b:ale_erlang_erlang_mode_indent_guard* +erlang_erlang_mode_indent_guard +g:ale_erlang_erlang_mode_indent_guard + Type: |Number| + Default: `2` + + Indentation of Erlang guards. + + *ale-options.erlang_erlang_mode_argument_indent* + *g:ale_erlang_erlang_mode_argument_indent* + *b:ale_erlang_erlang_mode_argument_indent* +erlang_erlang_mode_argument_indent +g:ale_erlang_erlang_mode_argument_indent + Type: `'nil'` or |Number| + Default: `2` + + Indentation of the first argument in a function call. When `'nil'`, indent + to the column after the `'('` of the function. + + *ale-options.erlang_erlang_mode_indent_tabs_mode* + *g:ale_erlang_erlang_mode_indent_tabs_mode* + *b:ale_erlang_erlang_mode_indent_tabs_mode* +erlang_erlang_mode_indent_tabs_mode +g:ale_erlang_erlang_mode_indent_tabs_mode + Type: `'nil'` or `'t'` + Default: `'nil'` + + Indentation can insert tabs if this is non-`'nil'`. + + +=============================================================================== +erlang_ls *ale-erlang-erlang_ls* + + *ale-options.erlang_erlang_ls_executable* + *g:ale_erlang_erlang_ls_executable* + *b:ale_erlang_erlang_ls_executable* +erlang_erlang_ls_executable +g:ale_erlang_erlang_ls_executable + Type: |String| + Default: `'erlang_ls'` + + This variable can be changed to specify the erlang_ls executable. + + *ale-options.erlang_erlang_ls_log_dir* + *g:ale_erlang_erlang_ls_log_dir* + *b:ale_erlang_erlang_ls_log_dir* +erlang_erlang_ls_log_dir +g:ale_erlang_erlang_ls_log_dir + Type: |String| + Default: `''` + + If set this variable overrides default directory where logs will be written. + + *ale-options.erlang_erlang_ls_log_level* + *g:ale_erlang_erlang_ls_log_level* + *b:ale_erlang_erlang_ls_log_level* +erlang_erlang_ls_log_level +g:ale_erlang_erlang_ls_log_level + Type: |String| + Default: `'info'` + + This variable can be changed to specify log level. + + +=============================================================================== +erlc *ale-erlang-erlc* + + *ale-options.erlang_erlc_executable* + *g:ale_erlang_erlc_executable* + *b:ale_erlang_erlc_executable* +erlang_erlc_executable +g:ale_erlang_erlc_executable + Type: |String| + Default: `'erlc'` + + This variable can be changed to specify the erlc executable. + + *ale-options.erlang_erlc_options* + *g:ale_erlang_erlc_options* + *b:ale_erlang_erlc_options* +erlang_erlc_options +g:ale_erlang_erlc_options + Type: |String| + Default: `''` + + This variable controls additional parameters passed to `erlc`, such as `-I` + or `-pa`. + + +=============================================================================== +erlfmt *ale-erlang-erlfmt* + + *ale-options.erlang_erlfmt_executable* + *g:ale_erlang_erlfmt_executable* + *b:ale_erlang_erlfmt_executable* +erlang_erlfmt_executable +g:ale_erlang_erlfmt_executable + Type: |String| + Default: `'erlfmt'` + + This variable can be changed to specify the erlfmt executable. + + *ale-options.erlang_erlfmt_options* + *g:ale_erlang_erlfmt_options* + *b:ale_erlang_erlfmt_options* +erlang_erlfmt_options +g:ale_erlang_erlfmt_options + Type: |String| + Default: `''` + + This variable controls additional parameters passed to `erlfmt`, such as + `--insert-pragma` or `--print-width`. + + +=============================================================================== +syntaxerl *ale-erlang-syntaxerl* + + *ale-options.erlang_syntaxerl_executable* + *g:ale_erlang_syntaxerl_executable* + *b:ale_erlang_syntaxerl_executable* +erlang_syntaxerl_executable +g:ale_erlang_syntaxerl_executable + Type: |String| + Default: `'syntaxerl'` + + This variable can be changed to specify the syntaxerl executable. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-eruby.txt b/doc/ale-eruby.txt new file mode 100644 index 00000000..07a62cdb --- /dev/null +++ b/doc/ale-eruby.txt @@ -0,0 +1,100 @@ +=============================================================================== +ALE Eruby Integration *ale-eruby-options* + +There are four linters for `eruby` files: + +- `erb` +- `erblint` +- `erubis` +- `erubi` +- `htmlbeautifier` +- `ruumba` + +`erb` is in the Ruby standard library and is mostly universal. `erubis` is the +default parser in Rails between 3.0 and 5.1. `erubi` is the default in Rails +5.1 and later. `ruumba` can extract Ruby from eruby files and run rubocop on +the result. To selectively enable a subset, see |g:ale_linters|. + + +=============================================================================== +erb-formatter *ale-eruby-erbformatter* + + *ale-options.eruby_erbformatter_executable* + *g:ale_eruby_erbformatter_executable* + *b:ale_eruby_erbformatter_executable* +eruby_erbformatter_executable +g:ale_eruby_erbformatter_executable + Type: |String| + Default: `'erb-formatter'` + + Override the invoked erb-formatter binary. This is useful for running + erb-formatter from binstubs or a bundle. + + +=============================================================================== +erblint *ale-eruby-erblint* + + *ale-options.eruby_erblint_executable* + *g:ale_eruby_erblint_executable* + *b:ale_eruby_erblint_executable* +eruby_erblint_executable +g:ale_eruby_erblint_executable + Type: |String| + Default: `'erblint'` + + Override the invoked erblint binary. This is useful for running erblint + from binstubs or a bundle. + + *ale-options.eruby_erblint_options* + *g:ale_eruby_erblint_options* + *b:ale_ruby_erblint_options* +eruby_erblint_options +g:ale_eruby_erblint_options + Type: |String| + Default: `''` + + This variable can be change to modify flags given to erblint. + + +=============================================================================== +htmlbeautifier *ale-eruby-htmlbeautifier* + + *ale-options.eruby_htmlbeautifier_executable* + *g:ale_eruby_htmlbeautifier_executable* + *b:ale_eruby_htmlbeautifier_executable* +eruby_htmlbeautifier_executable +g:ale_eruby_htmlbeautifier_executable + Type: |String| + Default: `'htmlbeautifier'` + + Override the invoked htmlbeautifier binary. This is useful for running + htmlbeautifier from binstubs or a bundle. + + +=============================================================================== +ruumba *ale-eruby-ruumba* + + *ale-options.eruby_ruumba_executable* + *g:ale_eruby_ruumba_executable* + *b:ale_eruby_ruumba_executable* +eruby_ruumba_executable +g:ale_eruby_ruumba_executable + Type: |String| + Default: `'ruumba'` + + Override the invoked ruumba binary. This is useful for running ruumba + from binstubs or a bundle. + + *ale-options.eruby_ruumba_options* + *g:ale_eruby_ruumba_options* + *b:ale_ruby_ruumba_options* +eruby_ruumba_options +g:ale_eruby_ruumba_options + Type: |String| + Default: `''` + + This variable can be change to modify flags given to ruumba. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-fish.txt b/doc/ale-fish.txt new file mode 100644 index 00000000..fe93f023 --- /dev/null +++ b/doc/ale-fish.txt @@ -0,0 +1,39 @@ +=============================================================================== +ALE Fish Integration *ale-fish-options* + +Lints fish files using `fish -n`. + +Note that `fish -n` is not foolproof: it sometimes gives false positives or +errors that are difficult to parse without more context. This integration skips +displaying errors if an error message is not found. + +If ALE is not showing any errors but your file does not run as expected, run +`fish -n ` from the command line. + + +=============================================================================== +fish_indent *ale-fish-fish_indent* + + *ale-options.fish_fish_indent_executable* + *g:ale_fish_fish_indent_executable* + *b:ale_fish_fish_indent_executable* +fish_fish_indent_executable +g:ale_fish_fish_indent_executable + Type: |String| + Default: `'fish_indent'` + + This variable can be changed to use a different executable for fish_indent. + + *ale-options.fish_fish_indent_options* + *g:ale_fish_fish_indent_options* + *b:ale_fish_fish_indent_options* +fish_fish_indent_options +g:ale_fish_fish_indent_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to fish_indent. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-fortran.txt b/doc/ale-fortran.txt new file mode 100644 index 00000000..9ef1f387 --- /dev/null +++ b/doc/ale-fortran.txt @@ -0,0 +1,68 @@ +=============================================================================== +ALE Fortran Integration *ale-fortran-options* + + +=============================================================================== +gcc *ale-fortran-gcc* + + *ale-options.fortran_gcc_executable* + *g:ale_fortran_gcc_executable* + *b:ale_fortran_gcc_executable* +fortran_gcc_executable +g:ale_fortran_gcc_executable + Type: |String| + Default: `'gcc'` + + This variable can be changed to modify the executable used for checking + Fortran code with GCC. + + *ale-options.fortran_gcc_options* + *g:ale_fortran_gcc_options* + *b:ale_fortran_gcc_options* +fortran_gcc_options +g:ale_fortran_gcc_options + Type: |String| + Default: `'-Wall'` + + This variable can be changed to modify flags given to gcc. + + *ale-options.fortran_gcc_use_free_form* + *g:ale_fortran_gcc_use_free_form* + *b:ale_fortran_gcc_use_free_form* +fortran_gcc_use_free_form +g:ale_fortran_gcc_use_free_form + Type: |Number| + Default: `1` + + When set to `1`, the `-ffree-form` flag will be used for GCC, to check files + with the free form layout. When set to `0`, `-ffixed-form` will be used + instead, for checking files with fixed form layouts. + + +=============================================================================== +language_server *ale-fortran-language-server* + + *ale-options.fortran_language_server_executable* + *g:ale_fortran_language_server_executable* + *b:ale_fortran_language_server_executable* +fortran_language_server_executable +g:ale_fortran_language_server_executable + Type: |String| + Default: `'fortls'` + + This variable can be changed to modify the executable used for the Fortran + Language Server. + + *ale-options.fortran_language_server_use_global* + *g:ale_fortran_language_server_use_global* + *b:ale_fortran_language_server_use_global* +fortran_language_server_use_global +g:ale_fortran_language_server_use_global + Type: |Number| + 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-fountain.txt b/doc/ale-fountain.txt new file mode 100644 index 00000000..ac0870cf --- /dev/null +++ b/doc/ale-fountain.txt @@ -0,0 +1,6 @@ +=============================================================================== +ALE Fountain Integration *ale-fountain-options* + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-fuse.txt b/doc/ale-fuse.txt new file mode 100644 index 00000000..7f63b1d0 --- /dev/null +++ b/doc/ale-fuse.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE FusionScript Integration *ale-fuse-options* + + +=============================================================================== +fusion-lint *ale-fuse-fusionlint* + + *ale-options.fusion_fusionlint_executable* + *g:ale_fusion_fusionlint_executable* + *b:ale_fuse_fusionlint_executable* +fusion_fusionlint_executable +g:ale_fusion_fusionlint_executable + Type: |String| + Default: `'fusion-lint'` + + This variable can be changed to change the path to fusion-lint. + + *ale-options.fuse_fusionlint_options* + *g:ale_fuse_fusionlint_options* + *b:ale_fuse_fusionlint_options* +fuse_fusionlint_options +g:ale_fuse_fusionlint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to fusion-lint. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-gitcommit.txt b/doc/ale-gitcommit.txt new file mode 100644 index 00000000..f8092c2b --- /dev/null +++ b/doc/ale-gitcommit.txt @@ -0,0 +1,52 @@ +=============================================================================== +ALE Git Commit Integration *ale-gitcommit-options* + + +=============================================================================== +gitlint *ale-gitcommit-gitlint* + + *ale-options.gitcommit_gitlint_executable* + *g:ale_gitcommit_gitlint_executable* + *b:ale_gitcommit_gitlint_executable* +gitcommit_gitlint_executable +g:ale_gitcommit_gitlint_executable + Type: |String| + Default: `'gitlint'` + + This variable can be changed to modify the executable used for gitlint. + + *ale-options.gitcommit_gitlint_options* + *g:ale_gitcommit_gitlint_options* + *b:ale_gitcommit_gitlint_options* +gitcommit_gitlint_options +g:ale_gitcommit_gitlint_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the gitlint + invocation. For example, you can specify the path to a configuration file. > + + let g:ale_gitcommit_gitlint_options = '-C /home/user/.config/gitlint.ini' +< + You can also disable particular error codes using this option. For example, + you can ignore errors for git commits with a missing body. > + + let g:ale_gitcommit_gitlint_options = '--ignore B6' +< + *ale-options.gitcommit_gitlint_use_global* + *g:ale_gitcommit_gitlint_use_global* + *b:ale_gitcommit_gitlint_use_global* +gitcommit_gitlint_use_global +g:ale_gitcommit_gitlint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable controls whether or not ALE will search for gitlint in a + virtualenv directory first. If this variable is set to `1`, then ALE will + always use |g:ale_gitcommit_gitlint_executable| for the executable path. + + Both variables can be set with `b:` buffer variables instead. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-gleam.txt b/doc/ale-gleam.txt new file mode 100644 index 00000000..dbf7ad9a --- /dev/null +++ b/doc/ale-gleam.txt @@ -0,0 +1,36 @@ +=============================================================================== +ALE Gleam Integration *ale-gleam-options* + *ale-integration-gleam* + + +=============================================================================== +gleam_format *ale-gleam-gleam_format* + + *ale-options.gleam_gleam_format_executable* + *g:ale_gleam_gleam_format_executable* + *b:ale_gleam_gleam_format_executable* +gleam_gleam_format_executable +g:ale_gleam_gleam_format_executable + Type: |String| + Default: `'gleam'` + + This variable can be modified to change the executable path for + `gleam format`. + + +=============================================================================== +gleamlsp *ale-gleam-gleamlsp* + + *ale-options.gleam_gleamlsp_executable* + *g:ale_gleam_gleamlsp_executable* + *b:ale_gleam_gleamlsp_executable* +gleam_gleamlsp_executable +g:ale_gleam_gleamlsp_executable + Type: |String| + Default: `'gleam'` + + This variable can be modified to change the executable path for `gleamlsp`. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-glsl.txt b/doc/ale-glsl.txt new file mode 100644 index 00000000..986be502 --- /dev/null +++ b/doc/ale-glsl.txt @@ -0,0 +1,67 @@ +=============================================================================== +ALE GLSL Integration *ale-glsl-options* + *ale-integration-glsl* + +=============================================================================== +Integration Information + + Since Vim does not detect the glsl file types out-of-the-box, you need the + runtime files for glsl from here: https://github.com/tikhomirov/vim-glsl + + Note that the current glslang-based linter expects glslangValidator in + standard paths. If it's not installed system-wide you can set + |g:ale_glsl_glslang_executable| to a specific path. + + +=============================================================================== +glslang *ale-glsl-glslang* + + *ale-options.glsl_glslang_executable* + *g:ale_glsl_glslang_executable* + *b:ale_glsl_glslang_executable* +glsl_glslang_executable +g:ale_glsl_glslang_executable + Type: |String| + Default: `'glslangValidator'` + + This variable can be changed to change the path to glslangValidator. + + *ale-options.glsl_glslang_options* + *g:ale_glsl_glslang_options* + *b:ale_glsl_glslang_options* +glsl_glslang_options +g:ale_glsl_glslang_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to glslangValidator. + + +=============================================================================== +glslls *ale-glsl-glslls* + + *ale-options.glsl_glslls_executable* + *g:ale_glsl_glslls_executable* + *b:ale_glsl_glslls_executable* +glsl_glslls_executable +g:ale_glsl_glslls_executable + Type: |String| + Default: `'glslls'` + + This variable can be changed to change the path to glslls. + See |ale-integrations-local-executables| + + *ale-options.glsl_glslls_logfile* + *g:ale_glsl_glslls_logfile* + *b:ale_glsl_glslls_logfile* +glsl_glslls_logfile +g:ale_glsl_glslls_logfile + Type: |String| + Default: `''` + + Setting this variable to a writeable file path will enable logging to that + file. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-go.txt b/doc/ale-go.txt new file mode 100644 index 00000000..c548a310 --- /dev/null +++ b/doc/ale-go.txt @@ -0,0 +1,398 @@ +=============================================================================== +ALE Go Integration *ale-go-options* + + +=============================================================================== +Integration Information + +ALE enables `gofmt`, `gopls` and `go vet` by default. It also supports `staticcheck`, +`go build, ``gosimple`, `golangserver`, and `golangci-lint. + +To enable `golangci-lint`, update |g:ale_linters| as appropriate. +A possible configuration is to enable golangci-lint and `gofmt: +> + " Enable all of the linters you want for Go. + let g:ale_linters = {'go': ['golangci-lint', 'gofmt']} +< + *ale-options.go_go_executable* + *g:ale_go_go_executable* + *b:ale_go_go_executable* +go_go_executable +g:ale_go_go_executable + Type: |String| + Default: `'go'` + + The executable that will be run for the `gobuild` and `govet` linters, and + the `gomod` fixer. + + *ale-options.go_go111module* + *g:ale_go_go111module* + *b:ale_go_go111module* +go_go111module +g:ale_go_go111module + Type: |String| + Default: `''` + + Override the value of the `$GO111MODULE` environment variable for + golang tools. + + +=============================================================================== +bingo *ale-go-bingo* + + *ale-options.go_bingo_executable* + *g:ale_go_bingo_executable* + *b:ale_go_bingo_executable* +go_bingo_executable +g:ale_go_bingo_executable + Type: |String| + Default: `'bingo'` + + Location of the bingo binary file. + + *ale-options.go_bingo_options* + *g:ale_go_bingo_options* + *b:ale_go_bingo_options* +go_bingo_options +g:ale_go_bingo_options + Type: |String| + Default: `''` + + +=============================================================================== +cspell *ale-go-cspell* + +See |ale-cspell-options| + + +=============================================================================== +gobuild *ale-go-gobuild* + + *ale-options.go_gobuild_options* + *g:ale_go_gobuild_options* + *b:ale_go_gobuild_options* +go_gobuild_options +g:ale_go_gobuild_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the gobuild linter. + They are injected directly after "go test". + + +=============================================================================== +gofmt *ale-go-gofmt* + + *ale-options.go_gofmt_options* + *g:ale_go_gofmt_options* + *b:ale_go_gofmt_options* +go_gofmt_options +g:ale_go_gofmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the gofmt fixer. + + +=============================================================================== +gofumpt *ale-go-gofumpt* + + *ale-options.go_gofumpt_executable* + *g:ale_go_gofumpt_executable* + *b:ale_go_gofumpt_executable* +go_gofumpt_executable +g:ale_go_gofumpt_executable + Type: |String| + Default: `'gofumpt'` + + Executable to run to use as the gofumpt fixer. + + *ale-options.go_gofumpt_options* + *g:ale_go_gofumpt_options* + *b:ale_go_gofumpt_options* +go_gofumpt_options +g:ale_go_gofumpt_options + Type: |String| + Default: `''` + + Options to pass to the gofumpt fixer. + + +=============================================================================== +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| + + *ale-options.go_golangci_lint_executable* + *g:ale_go_golangci_lint_executable* + *b:ale_go_golangci_lint_executable* +go_golangci_lint_executable +g:ale_go_golangci_lint_executable + Type: |String| + Default: `'golangci-lint'` + + The executable that will be run for golangci-lint. + + *ale-options.go_golangci_lint_options* + *g:ale_go_golangci_lint_options* + *b:ale_go_golangci_lint_options* +go_golangci_lint_options +g:ale_go_golangci_lint_options + Type: |String| + Default: `''` + + This variable can be changed to alter the command-line arguments to the + golangci-lint invocation. + + *ale-options.go_golangci_lint_package* + *g:ale_go_golangci_lint_package* + *b:ale_go_golangci_lint_package* +go_golangci_lint_package +g: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* + + *ale-options.go_langserver_executable* + *g:ale_go_langserver_executable* + *b:ale_go_langserver_executable* +go_langserver_executable +g:ale_go_langserver_executable + Type: |String| + Default: `'go-langserver'` + + Location of the go-langserver binary file. + + *ale-options.go_langserver_options* + *g:ale_go_langserver_options* + *b:ale_go_langserver_options* +go_langserver_options +g: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. + + +=============================================================================== +golines *ale-go-golines* + + *ale-options.go_golines_executable* + *g:ale_go_golines_executable* + *b:ale_go_lines_executable* +go_golines_executable +g:ale_go_golines_executable + Type: |String| + Default: `'golines'` + + Location of the golines binary file + + *ale-options.go_golines_options* + *g:ale_go_golines_options* + *b:ale_go_golines_options* +go_golines_options +g:ale_go_golines_options + Type: |String| + Default: `''` + + Additional options passed to the golines command. By default golines has + --max-length=100 (lines above 100 characters will be wrapped) + + +=============================================================================== +gopls *ale-go-gopls* + +gopls is the official Go language server, and is enabled for use with ALE by +default. + +To install the latest stable version of `gopls` to your `$GOPATH`, try the +following command: > + + GO111MODULE=on go get golang.org/x/tools/gopls@latest +< +If `$GOPATH` is readable by ALE, it should probably work without you having to +do anything else. See the `gopls` README file for more information: + +https://github.com/golang/tools/blob/master/gopls/README.md + + +------------------------------------------------------------------------------- +Options + *ale-options.go_gopls_executable* + *g:ale_go_gopls_executable* + *b:ale_go_gopls_executable* +go_gopls_executable +g:ale_go_gopls_executable + Type: |String| + Default: `'gopls'` + + See |ale-integrations-local-executables| + + ALE will search for `gopls` in locally installed directories first by + default, and fall back on a globally installed `gopls` if it can't be found + otherwise. + + *ale-options.go_gopls_options* + *g:ale_go_gopls_options* + *b:ale_go_gopls_options* +go_gopls_options +g:ale_go_gopls_options + Type: |String| + Default: `''` + + Command-line options passed to the gopls executable. See `gopls -h`. + + *ale-options.go_gopls_fix_executable* + *g:ale_go_gopls_fix_executable* + *b:ale_go_gopls_fix_executable* +go_gopls_fix_executable +g:ale_go_gopls_fix_executable + Type: |String| + Default: `'gopls'` + + Executable to run to use as the gopls fixer. + + *ale-options.go_gopls_fix_options* + *g:ale_go_gopls_fix_options* + *b:ale_go_gopls_fix_options* +go_gopls_fix_options +g:ale_go_gopls_fix_options + Type: |String| + Default: `''` + + Options to pass to the gopls fixer. + + *ale-options.go_gopls_init_options* + *g:ale_go_gopls_init_options* + *b:ale_go_gopls_init_options* +go_gopls_init_options +g:ale_go_gopls_init_options + Type: |Dictionary| + Default: `{}` + + LSP initialization options passed to gopls. This can be used to configure + the behaviour of gopls. + + For example: > + let g:ale_go_gopls_init_options = { + \ 'ui.diagnostic.analyses': { + \ 'composites': v:false, + \ 'unusedparams': v:true, + \ 'unusedresult': v:true, + \ }, + \} +< + For a full list of supported analyzers, see: + https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md + + *ale-options.go_gopls_use_global* + *g:ale_go_gopls_use_global* + *b:ale_go_gopls_use_global* +go_gopls_use_global +g:ale_go_gopls_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +govet *ale-go-govet* + + *ale-options.go_govet_options* + *g:ale_go_govet_options* + *b:ale_go_govet_options* +go_govet_options +g:ale_go_govet_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the go vet linter. + + +=============================================================================== +revive *ale-go-revive* + + *ale-options.go_revive_executable* + *g:ale_go_revive_executable* + *b:ale_go_revive_executable* +go_revive_executable +g:ale_go_revive_executable + Type: |String| + Default: `'revive'` + + This variable can be set to change the revive executable path. + + *ale-options.go_revive_options* + *g:ale_go_revive_options* + *b:ale_go_revive_options* +go_revive_options +g:ale_go_revive_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the revive + + +=============================================================================== +staticcheck *ale-go-staticcheck* + + *ale-options.go_staticcheck_executable* + *g:ale_go_staticcheck_executable* + *b:ale_go_staticcheck_executable* +go_staticcheck_executable +g:ale_go_staticcheck_executable + Type: |String| + Default: `'staticcheck'` + + See |ale-integrations-local-executables| + + ALE will search for `staticcheck` in locally installed directories first by + default, and fall back on a globally installed `staticcheck` if it can't be + found otherwise. + + *ale-options.go_staticcheck_options* + *g:ale_go_staticcheck_options* + *b:ale_go_staticcheck_options* +go_staticcheck_options +g:ale_go_staticcheck_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the staticcheck + linter. + + *ale-options.go_staticcheck_lint_package* + *g:ale_go_staticcheck_lint_package* + *b:ale_go_staticcheck_lint_package* +go_staticcheck_lint_package +g:ale_go_staticcheck_lint_package + Type: |Number| + Default: `1` + + When set to `1`, the whole Go package will be checked instead of only the + current file. + + *ale-options.go_staticcheck_use_global* + *g:ale_go_staticcheck_use_global* + *b:ale_go_staticcheck_use_global* +go_staticcheck_use_global +g:ale_go_staticcheck_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-gohtmltmpl.txt b/doc/ale-gohtmltmpl.txt new file mode 100644 index 00000000..8672f5d4 --- /dev/null +++ b/doc/ale-gohtmltmpl.txt @@ -0,0 +1,11 @@ +=============================================================================== +ALE Go HTML Template Integration *ale-gohtmltmpl-options* + + +=============================================================================== +djlint *ale-gohtmltmpl-djlint* + +See |ale-html-djlint| + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-graphql.txt b/doc/ale-graphql.txt new file mode 100644 index 00000000..603694bb --- /dev/null +++ b/doc/ale-graphql.txt @@ -0,0 +1,22 @@ +=============================================================================== +ALE GraphQL Integration *ale-graphql-options* + + +=============================================================================== +eslint *ale-graphql-eslint* + +The `eslint` linter for GraphQL uses the JavaScript options for `eslint`; see: +|ale-javascript-eslint|. + +You will need the GraphQL ESLint plugin installed for this to work. + +=============================================================================== +gqlint *ale-graphql-gqlint* + +=============================================================================== +prettier *ale-graphql-prettier* + +See |ale-javascript-prettier| for information about the available options. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-groovy.txt b/doc/ale-groovy.txt new file mode 100644 index 00000000..bb8d45fd --- /dev/null +++ b/doc/ale-groovy.txt @@ -0,0 +1,49 @@ +=============================================================================== +ALE Groovy Integration *ale-groovy-options* + + +=============================================================================== +Integration Information + +Linting and fixing of Groovy files is enabled with the integration of +`npm-groovy-lint`. + + +=============================================================================== +npm-groovy-lint *ale-groovy-npm-groovy-lint* + + *ale-options.groovy_npmgroovylint_executable* + *g:ale_groovy_npmgroovylint_executable* + *b:ale_groovy_npmgroovylint_executable* +groovy_npmgroovylint_executable +g:ale_groovy_npmgroovylint_executable + Type: |String| + Default: `'npm-groovy-lint'` + + Location of the npm-groovy-lint binary file. + + *ale-options.groovy_npmgroovylint_options* + *g:ale_groovy_npmgroovylint_options* + *b:ale_groovy_npmgroovylint_options* +groovy_npmgroovylint_options +g:ale_groovy_npmgroovylint_options + Type: |String| + Default: `'--loglevel warning'` + + Additional npm-groovy-lint linter options. + + *ale-options.groovy_npmgroovylint_fix_options* + *g:ale_groovy_npmgroovylint_fix_options* + *b:ale_groovy_npmgroovylint_fix_options* +groovy_npmgroovylint_fix_options +g:ale_groovy_npmgroovylint_fix_options + Type: |String| + Default: `'--fix'` + + This variable can be used to configure fixing with npm-groovy-lint. It must + contain either `--fix` or `--format` for the fixer to work. See + `npm-groovy-lint --help` for more information on possible fix rules. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-hack.txt b/doc/ale-hack.txt new file mode 100644 index 00000000..3bf74bce --- /dev/null +++ b/doc/ale-hack.txt @@ -0,0 +1,61 @@ +=============================================================================== +ALE Hack Integration *ale-hack-options* + *ale-integration-hack* + + HHAST is disabled by default, as it executes code in the project root. + + Currently linters must be enabled globally. HHAST can be enabled in ftplugin + files like so: > + + let b:ale_linters = ['hack', 'hhast'] +< + Or in Lua: > + require("ale").setup.buffer({linters = {"hack", "hhast"}}) +< + +=============================================================================== +hack *ale-hack-hack* + + *ale-options.hack_hack_executable* + *g:ale_hack_hack_executable* + *b:ale_hack_hack_executable* +hack_hack_executable +g:ale_hack_hack_executable + Type: |String| + Default: `'hh_client'` + + This variable can be set to use a specific executable to interact with the + Hack typechecker. + + +=============================================================================== +hackfmt *ale-hack-hackfmt* + + *ale-options.hack_hackfmt_options* + *g:ale_hack_hackfmt_options* + *b:ale_hack_hackfmt_options* +hack_hackfmt_options +g:ale_hack_hackfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the hackfmt fixer. + + +=============================================================================== +hhast *ale-hack-hhast* + + *ale-options.hack_hhast_executable* + *g:ale_hack_hhast_executable* + *b:ale_hack_hhast_executable* +hack_hhast_executable +g:ale_hack_hhast_executable + Type: |String| + Default: `'vendor/bin/hhast-lint'` + + This variable can be set to use a specific executable to interact with the + Hack typechecker. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-handlebars.txt b/doc/ale-handlebars.txt new file mode 100644 index 00000000..d258b40f --- /dev/null +++ b/doc/ale-handlebars.txt @@ -0,0 +1,43 @@ +=============================================================================== +ALE Handlebars Integration *ale-handlebars-options* + + +=============================================================================== +djlint *ale-handlebars-djlint* + +See |ale-html-djlint| + + +=============================================================================== +prettier *ale-handlebars-prettier* + +See |ale-javascript-prettier| for information about the available options. +Uses glimmer parser by default. + + +=============================================================================== +ember-template-lint *ale-handlebars-embertemplatelint* + + *ale-options.handlebars_embertemplatelint_executable* + *g:ale_handlebars_embertemplatelint_executable* + *b:ale_handlebars_embertemplatelint_executable* +handlebars_embertemplatelint_executable +g:ale_handlebars_embertemplatelint_executable + Type: |String| + Default: `'ember-template-lint'` + + See |ale-integrations-local-executables| + + *ale-options.handlebars_embertemplatelint_use_global* + *g:ale_handlebars_embertemplatelint_use_global* + *b:ale_handlebars_embertemplatelint_use_global* +handlebars_embertemplatelint_use_global +g:ale_handlebars_embertemplatelint_use_global + Type: |Number| + 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-haskell.txt b/doc/ale-haskell.txt new file mode 100644 index 00000000..030d15b3 --- /dev/null +++ b/doc/ale-haskell.txt @@ -0,0 +1,305 @@ +=============================================================================== +ALE Haskell Integration *ale-haskell-options* + + +=============================================================================== +brittany *ale-haskell-brittany* + + *ale-options.haskell_brittany_executable* + *g:ale_haskell_brittany_executable* + *b:ale_haskell_brittany_executable* +haskell_brittany_executable +g:ale_haskell_brittany_executable + Type: |String| + Default: `'brittany'` + + This variable can be changed to use a different executable for brittany. + + +=============================================================================== +cspell *ale-haskell-cspell* + +See |ale-cspell-options| + + +=============================================================================== +floskell *ale-haskell-floskell* + + *ale-options.haskell_floskell_executable* + *g:ale_haskell_floskell_executable* + *b:ale_haskell_floskell_executable* +haskell_floskell_executable +g:ale_haskell_floskell_executable + Type: |String| + Default: `'floskell'` + + This variable can be changed to use a different executable for floskell. + + +=============================================================================== +ghc *ale-haskell-ghc* + + *ale-options.haskell_ghc_options* + *g:ale_haskell_ghc_options* + *b:ale_haskell_ghc_options* +haskell_ghc_options +g:ale_haskell_ghc_options + Type: |String| + Default: `'-fno-code -v0'` + + This variable can be changed to modify flags given to ghc. + + +=============================================================================== +ghc-mod *ale-haskell-ghc-mod* + + *ale-options.haskell_ghc_mod_executable* + *g:ale_haskell_ghc_mod_executable* + *b:ale_haskell_ghc_mod_executable* +haskell_ghc_mod_executable +g:ale_haskell_ghc_mod_executable + Type: |String| + Default: `'ghc-mod'` + + This variable can be changed to use a different executable for ghc-mod. + + +=============================================================================== +cabal-ghc *ale-haskell-cabal-ghc* + + *ale-options.haskell_cabal_ghc_options* + *g:ale_haskell_cabal_ghc_options* + *b:ale_haskell_cabal_ghc_options* +haskell_cabal_ghc_options +g:ale_haskell_cabal_ghc_options + Type: |String| + Default: `'-fno-code -v0'` + + This variable can be changed to modify flags given to ghc through cabal + exec. + + +=============================================================================== +hdevtools *ale-haskell-hdevtools* + + *ale-options.haskell_hdevtools_executable* + *g:ale_haskell_hdevtools_executable* + *b:ale_haskell_hdevtools_executable* +haskell_hdevtools_executable +g:ale_haskell_hdevtools_executable + Type: |String| + Default: `'hdevtools'` + + This variable can be changed to use a different executable for hdevtools. + + *ale-options.haskell_hdevtools_options* + *g:ale_haskell_hdevtools_options* + *b:ale_haskell_hdevtools_options* +haskell_hdevtools_options +g:ale_haskell_hdevtools_options + Type: |String| + Default: `get(g:, 'hdevtools_options', '-g -Wall')` + + This variable can be changed to modify flags given to hdevtools. + + The hdevtools documentation recommends setting GHC options for `hdevtools` + with `g:hdevtools_options`. ALE will use the value of `g:hdevtools_options` + for the value of `g:ale_haskell_hdevtools_options` by default, so this + option can be respected and overridden specifically for ALE. + + +=============================================================================== +hfmt *ale-haskell-hfmt* + + *ale-options.haskell_hfmt_executable* + *g:ale_haskell_hfmt_executable* + *b:ale_haskell_hfmt_executable* +haskell_hfmt_executable +g:ale_haskell_hfmt_executable + Type: |String| + Default: `'hfmt'` + + This variable can be changed to use a different executable for hfmt. + + +=============================================================================== +hindent *ale-haskell-hindent* + + *ale-options.haskell_hindent_executable* + *g:ale_haskell_hindent_executable* + *b:ale_haskell_hindent_executable* +haskell_hindent_executable +g:ale_haskell_hindent_executable + Type: |String| + Default: `'hindent'` + + This variable can be changed to use a different executable for hindent. + + +=============================================================================== +hlint *ale-haskell-hlint* + + *ale-options.haskell_hlint_executable* + *g:ale_haskell_hlint_executable* + *b:ale_haskell_hlint_executable* +haskell_hlint_executable +g:ale_haskell_hlint_executable + Type: |String| + Default: `'hlint'` + + This variable can be changed to use a different executable for hlint. + + *ale-options.haskell_hlint_options* + *g:ale_haskell_hlint_options* + *b:ale_haskell_hlint_options* +haskell_hlint_options +g:ale_haskell_hlint_options + Type: |String| + Default: `''` + + This variable can be used to pass extra options to the underlying hlint + executable. + + +=============================================================================== +hls *ale-haskell-hls* + + *ale-options.haskell_hls_executable* + *g:ale_haskell_hls_executable* + *b:ale_haskell_hls_executable* +haskell_hls_executable +g:ale_haskell_hls_executable + Type: |String| + Default: `'haskell-language-server-wrapper'` + + This variable can be changed to use a different executable for the haskell + language server. + + *ale-options.haskell_hls_config* + *g:ale_haskell_hls_config* + *b:ale_haskell_hls_config* +haskell_hls_config +g:ale_haskell_hls_config + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for HLS. For example, to see more + completions: +> + let g:ale_haskell_hls_config = {'haskell': {'maxCompletions': 250}} +< + Refer to HLS documentation for possible settings: + https://haskell-language-server.readthedocs.io/en/latest/configuration.html#language-specific-server-options + + +=============================================================================== +stack-build *ale-haskell-stack-build* + + *ale-options.haskell_stack_build_options* + *g:ale_haskell_stack_build_options* + *b:ale_haskell_stack_build_options* +haskell_stack_build_options +g:ale_haskell_stack_build_options + Type: |String| + Default: `'--fast'` + + We default to using `'--fast'`. Since Stack generates binaries, your + programs will be slower unless you separately rebuild them outside of ALE. + + +=============================================================================== +stack-ghc *ale-haskell-stack-ghc* + + *ale-options.haskell_stack_ghc_options* + *g:ale_haskell_stack_ghc_options* + *b:ale_haskell_stack_ghc_options* +haskell_stack_ghc_options +g: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* + + *ale-options.haskell_stylish_haskell_executable* + *g:ale_haskell_stylish_haskell_executable* + *b:ale_haskell_stylish_haskell_executable* +haskell_stylish_haskell_executable +g:ale_haskell_stylish_haskell_executable + Type: |String| + Default: `'stylish-haskell'` + + This variable can be changed to use a different executable for stylish-haskell. + + +=============================================================================== +hie *ale-haskell-hie* + + *ale-options.haskell_hie_executable* + *g:ale_haskell_hie_executable* + *b:ale_haskell_hie_executable* +haskell_hie_executable +g:ale_haskell_hie_executable + Type: |String| + Default: `'hie'` + + This variable can be changed to use a different executable for the haskell + ide engine. i.e. `'hie-wrapper'` + + +=============================================================================== +ormolu *ale-haskell-ormolu* + + *ale-options.haskell_ormolu_executable* + *g:ale_haskell_ormolu_executable* + *b:ale_haskell_ormolu_executable* +haskell_ormolu_executable +g:ale_haskell_ormolu_executable + Type: |String| + Default: `'ormolu'` + + This variable can be changed to use a different executable for ormolu. + + *ale-options.haskell_ormolu_options* + *g:ale_haskell_ormolu_options* + *b:ale_haskell_ormolu_options* +haskell_ormolu_options +g:ale_haskell_ormolu_options + Type: |String| + Default: `''` + + This variable can be used to pass extra options to the underlying ormolu + executable. + + +=============================================================================== +fourmolu *ale-haskell-fourmolu* + + *ale-options.haskell_fourmolu_executable* + *g:ale_haskell_fourmolu_executable* + *b:ale_haskell_fourmolu_executable* +haskell_fourmolu_executable +g:ale_haskell_fourmolu_executable + Type: |String| + Default: `'fourmolu'` + + This variable can be changed to use a different executable for fourmolu. + + *ale-options.haskell_fourmolu_options* + *g:ale_haskell_fourmolu_options* + *b:ale_haskell_fourmolu_options* +haskell_fourmolu_options +g:ale_haskell_fourmolu_options + Type: |String| + Default: `''` + + This variable can be used to pass extra options to the underlying fourmolu + executable. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-hcl.txt b/doc/ale-hcl.txt new file mode 100644 index 00000000..71e1114e --- /dev/null +++ b/doc/ale-hcl.txt @@ -0,0 +1,16 @@ +=============================================================================== +ALE HCL Integration *ale-hcl-options* + + +=============================================================================== +packer-fmt *ale-hcl-packer-fmt* + +See |ale-packer-fmt-fixer| for information about the available options. + +=============================================================================== +terraform-fmt *ale-hcl-terraform-fmt* + +See |ale-terraform-fmt-fixer| for information about the available options. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-help.txt b/doc/ale-help.txt new file mode 100644 index 00000000..89872545 --- /dev/null +++ b/doc/ale-help.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Help Integration *ale-help-options* + + +=============================================================================== +cspell *ale-help-cspell* + +See |ale-cspell-options| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-html.txt b/doc/ale-html.txt new file mode 100644 index 00000000..a5b93d58 --- /dev/null +++ b/doc/ale-html.txt @@ -0,0 +1,289 @@ +=============================================================================== +ALE HTML Integration *ale-html-options* + + +=============================================================================== +angular *ale-html-angular* + +ALE supports language server features for Angular. You can install it via `npm`: > + + $ npm install --save-dev @angular/language-server +< +Angular 11 and up are supported. + + *ale-options.html_angular_executable* + *g:ale_html_angular_executable* + *b:ale_html_angular_executable* +html_angular_executable +g:ale_html_angular_executable + Type: |String| + Default: `'ngserver'` + + See |ale-integrations-local-executables| + + *ale-options.html_angular_use_global* + *g:ale_html_angular_use_global* + *b:ale_html_angular_use_global* +html_angular_use_global +g:ale_html_angular_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +cspell *ale-html-cspell* + +See |ale-cspell-options| + + +=============================================================================== +djlint *ale-html-djlint* + +`djlint` options for HTML are the same as the options for htmlangular, +htmldjango, jinja, handlebars, nunjucks and gotmplhtml. + + *ale-options.html_djlint_executable* + *g:ale_html_djlint_executable* + *b:ale_html_djlint_executable* +html_djlint_executable +g:ale_html_djlint_executable + Type: |String| + Default: `'djlint'` + + See |ale-integrations-local-executables| + + *ale-options.html_djlint_options* + *g:ale_html_djlint_options* + *b:ale_html_djlint_options* +html_djlint_options +g:ale_html_djlint_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to djlint. + + +=============================================================================== +fecs *ale-html-fecs* + +`fecs` options for HTML are the same as the options for JavaScript, and both +of them read `./.fecsrc` as the default configuration file. + +See: |ale-javascript-fecs|. + + +=============================================================================== +html-beautify *ale-html-beautify* + + *ale-options.html_beautify_executable* + *g:ale_html_beautify_executable* + *b:ale_html_beautify_executable* +html_beautify_executable +g:ale_html_beautify_executable + Type: |String| + Default: `'html-beautify'` + + See |ale-integrations-local-executables| + + *ale-options.html_beautify_options* + *g:ale_html_beautify_options* + *b:ale_html_beautify_options* +html_beautify_options +g:ale_html_beautify_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to html-beautify. + + *ale-options.html_beautify_use_global* + *g:ale_html_beautify_use_global* + *b:ale_html_beautify_use_global* +html_beautify_use_global +g:ale_html_beautify_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +htmlhint *ale-html-htmlhint* + + *ale-options.html_htmlhint_executable* + *g:ale_html_htmlhint_executable* + *b:ale_html_htmlhint_executable* +html_htmlhint_executable +g:ale_html_htmlhint_executable + Type: |String| + Default: `'htmlhint'` + + See |ale-integrations-local-executables| + + *ale-options.html_htmlhint_options* + *g:ale_html_htmlhint_options* + *b:ale_html_htmlhint_options* +html_htmlhint_options +g:ale_html_htmlhint_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to HTMLHint. + + *ale-options.html_htmlhint_use_global* + *g:ale_html_htmlhint_use_global* + *b:ale_html_htmlhint_use_global* +html_htmlhint_use_global +g:ale_html_htmlhint_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier *ale-html-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +rustywind *ale-html-rustywind* + + *ale-options.html_rustywind_executable* + *g:ale_html_rustywind_executable* + *b:ale_html_rustywind_executable* +html_rustywind_executable +g:ale_html_rustywind_executable + Type: |String| + Default: `'rustywind'` + + See |ale-integrations-local-executables| + + *ale-options.html_rustywind_options* + *g:ale_html_rustywind_options* + *b:ale_html_rustywind_options* +html_rustywind_options +g:ale_html_rustywind_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to rustywind. + + +=============================================================================== +stylelint *ale-html-stylelint* + + *ale-options.html_stylelint_executable* + *g:ale_html_stylelint_executable* + *b:ale_html_stylelint_executable* +html_stylelint_executable +g:ale_html_stylelint_executable + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + *ale-options.html_stylelint_options* + *g:ale_html_stylelint_options* + *b:ale_html_stylelint_options* +html_stylelint_options +g:ale_html_stylelint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + *ale-options.html_stylelint_use_global* + *g:ale_html_stylelint_use_global* + *b:ale_html_stylelint_use_global* +html_stylelint_use_global +g:ale_html_stylelint_use_global + Type: |String| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +tidy *ale-html-tidy* + +`tidy` is a console application which corrects and cleans up HTML and XML +documents by fixing markup errors and upgrading legacy code to modern +standards. + +Note: +`/usr/bin/tidy` on macOS (installed by default) is too old. It was released +on 31 Oct 2006. It does not consider modern HTML specs (HTML5) and shows +outdated warnings. So |ale| ignores `/usr/bin/tidy` on macOS. + +To use `tidy` on macOS, please install the latest version with Homebrew: +> + $ brew install tidy-html5 +< +`/usr/local/bin/tidy` is installed. + + +------------------------------------------------------------------------------- +Options + *ale-options.html_tidy_executable* + *g:ale_html_tidy_executable* + *b:ale_html_tidy_executable* +html_tidy_executable +g:ale_html_tidy_executable + Type: |String| + Default: `'tidy'` + + This variable can be changed to change the path to tidy. + + *ale-options.html_tidy_options* + *g:ale_html_tidy_options* + *b:ale_html_tidy_options* +html_tidy_options +g:ale_html_tidy_options + Type: |String| + Default: `'-q -e -language en'` + + This variable can be changed to change the arguments provided to the + executable. + + ALE will attempt to automatically detect the appropriate file encoding to + provide to html-tidy, and fall back to UTF-8 when encoding detection fails. + + The recognized file encodings are as follows: ascii, big5, cp1252 (win1252), + cp850 (ibm858), cp932 (shiftjis), iso-2022-jp (iso-2022), latin1, macroman + (mac), sjis (shiftjis), utf-16le, utf-16, utf-8 + + *ale-options.html_tidy_use_global* + *g:ale_html_tidy_use_global* +html_tidy_use_global +g:ale_html_tidy_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +vscodehtml *ale-html-vscode* + +Website: https://github.com/hrsh7th/vscode-langservers-extracted + + +------------------------------------------------------------------------------- +Installation + +Install VSCode html language server either globally or locally: > + + npm install -g vscode-langservers-extracted +< + +=============================================================================== +write-good *ale-html-write-good* + +See |ale-write-good-options| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-htmlangular.txt b/doc/ale-htmlangular.txt new file mode 100644 index 00000000..0027cfbd --- /dev/null +++ b/doc/ale-htmlangular.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE HTML Angular Template Integration *ale-htmlangular-options* + + + +=============================================================================== +djlint *ale-htmlangular-djlint* + +See |ale-html-djlint| + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-htmldjango.txt b/doc/ale-htmldjango.txt new file mode 100644 index 00000000..14431c8e --- /dev/null +++ b/doc/ale-htmldjango.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE HTML Django Template Integration *ale-htmldjango-options* + + + +=============================================================================== +djlint *ale-htmldjango-djlint* + +See |ale-html-djlint| + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-http.txt b/doc/ale-http.txt new file mode 100644 index 00000000..0be3e247 --- /dev/null +++ b/doc/ale-http.txt @@ -0,0 +1,20 @@ +=============================================================================== +ALE HTTP Integration *ale-http-options* + + +=============================================================================== +kulala_fmt *ale-http-kulala_fmt* + + *ale-options.http_kulala_fmt_executable* + *g:ale_http_kulala_fmt_executable* + *b:ale_http_kulala_fmt_executable* +http_kulala_fmt +g:ale_http_kulala_fmt + Type: |String| + Default: `'kulala_fmt'` + + Override the invoked kulala_fmt binary. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-hurl.txt b/doc/ale-hurl.txt new file mode 100644 index 00000000..16fffa18 --- /dev/null +++ b/doc/ale-hurl.txt @@ -0,0 +1,20 @@ +=============================================================================== +ALE Hurl Integration *ale-hurl-options* + + +=============================================================================== +hurlfmt *ale-hurl-hurlfmt* + + *ale-options.hurl_hurlfmt_executable* + *g:ale_hurl_hurlfmt_executable* + *b:ale_hurl_hurlfmt_executable* +hurl_hurlfmt_executable +g:ale_hurl_hurlfmt_executable + Type: |String| + Default: `'hurlfmt'` + + Override the invoked hurlfmt binary. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-idris.txt b/doc/ale-idris.txt new file mode 100644 index 00000000..b771a87d --- /dev/null +++ b/doc/ale-idris.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Idris Integration *ale-idris-options* + + +=============================================================================== +idris *ale-idris-idris* + + *ale-options.idris_idris_executable* + *g:ale_idris_idris_executable* + *b:ale_idris_idris_executable* +idris_idris_executable +g:ale_idris_idris_executable + Type: |String| + Default: `'idris'` + + This variable can be changed to change the path to idris. + + *ale-options.idris_idris_options* + *g:ale_idris_idris_options* + *b:ale_idris_idris_options* +idris_idris_options +g:ale_idris_idris_options + Type: |String| + Default: `'--total --warnpartial --warnreach --warnipkg'` + + This variable can be changed to modify flags given to idris. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-ink.txt b/doc/ale-ink.txt new file mode 100644 index 00000000..8a6c2ae8 --- /dev/null +++ b/doc/ale-ink.txt @@ -0,0 +1,46 @@ +=============================================================================== +ALE Ink Integration *ale-ink-options* + + +=============================================================================== +ink-language-server *ale-ink-language-server* + +Ink Language Server - https://github.com/ephraim/ink-language-server + + *ale-options.ink_ls_executable* + *g:ale_ink_ls_executable* + *b:ale_ink_ls_executable* +ink_ls_executable +g:ale_ink_ls_executable + Type: |String| + Default: `'ink-language-server'` + + Ink language server executable. + + *ale-options.ink_ls_initialization_options* + *g:ale_ink_ls_initialization_options* + *b:ale_ink_ls_initialization_options* +ink_ls_initialization_options +g:ale_ink_ls_initialization_options + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server at startup. For certain platforms and certain story + structures, the defaults will suffice. However, many projects will need to + change these settings - see the ink-language-server website for more + information. + + An example of setting non-default options: > + + let g:ale_ink_ls_initialization_options = { + \ 'ink': { + \ 'mainStoryPath': 'init.ink', + \ 'inklecateExecutablePath': '/usr/local/bin/inklecate', + \ 'runThroughMono': v:false, + \ }, + \} +< + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-inko.txt b/doc/ale-inko.txt new file mode 100644 index 00000000..4daa8361 --- /dev/null +++ b/doc/ale-inko.txt @@ -0,0 +1,21 @@ +=============================================================================== +ALE Inko Integration *ale-inko-options* + *ale-integration-inko* + + +=============================================================================== +inko *ale-inko-inko* + + *ale-options.inko_inko_executable* + *g:ale_inko_inko_executable* + *b:ale_inko_inko_executable* +inko_inko_executable +g:ale_inko_inko_executable + Type: |String| + Default: `'inko'` + + This variable can be modified to change the executable path for `inko`. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-ispc.txt b/doc/ale-ispc.txt new file mode 100644 index 00000000..423185c1 --- /dev/null +++ b/doc/ale-ispc.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE ISPC Integration *ale-ispc-options* + + +=============================================================================== +ispc *ale-ispc-ispc* + + *ale-options.ispc_ispc_executable* + *g:ale_ispc_ispc_executable* + *b:ale_ispc_ispc_executable* +ispc_ispc_executable +g:ale_ispc_ispc_executable + Type: |String| + Default: `'ispc'` + + This variable can be changed to use a different executable for ispc. + + *ale-options.ispc_ispc_options* + *g:ale_ispc_ispc_options* + *b:ale_ispc_ispc_options* +ispc_ispc_options +g:ale_ispc_ispc_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to ispc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-java.txt b/doc/ale-java.txt new file mode 100644 index 00000000..4065c291 --- /dev/null +++ b/doc/ale-java.txt @@ -0,0 +1,339 @@ +=============================================================================== +ALE Java Integration *ale-java-options* + + +=============================================================================== +checkstyle *ale-java-checkstyle* + + *ale-options.java_checkstyle_config* + *g:ale_java_checkstyle_config* + *b:ale_java_checkstyle_config* +java_checkstyle_config +g:ale_java_checkstyle_config + Type: |String| + Default: `'/google_checks.xml'` + + A path to a checkstyle configuration file. + + If a configuration file is specified with |g:ale_java_checkstyle_options|, + it will be preferred over this setting. + + The path to the configuration file can be an absolute path or a relative + path. ALE will search for the relative path in parent directories. + + *ale-options.java_checkstyle_executable* + *g:ale_java_checkstyle_executable* + *b:ale_java_checkstyle_executable* +java_checkstyle_executable +g:ale_java_checkstyle_executable + Type: |String| + Default: `'checkstyle'` + + This variable can be changed to modify the executable used for checkstyle. + + *ale-options.java_checkstyle_options* + *g:ale_java_checkstyle_options* + *b:ale_java_checkstyle_options* +java_checkstyle_options +g:ale_java_checkstyle_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to checkstyle. + + If a configuration file is specified with `-c`, it will be used instead of + configuration files set with |g:ale_java_checkstyle_config|. + + +=============================================================================== +clang-format *ale-java-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for Java. + + +=============================================================================== +cspell *ale-java-cspell* + +See |ale-cspell-options| + + +=============================================================================== +javac *ale-java-javac* + + *ale-options.java_javac_classpath* + *g:ale_java_javac_classpath* + *b:ale_java_javac_classpath* +java_javac_classpath +g:ale_java_javac_classpath + Type: |String| or |List| + Default: `''` + + This variable can be set to change the global classpath for Java. + + *ale-options.java_javac_executable* + *g:ale_java_javac_executable* + *b:ale_java_javac_executable* +java_javac_executable +g:ale_java_javac_executable + Type: |String| + Default: `'javac'` + + This variable can be set to change the executable path used for javac. + + *ale-options.java_javac_options* + *g:ale_java_javac_options* + *b:ale_java_javac_options* +java_javac_options +g:ale_java_javac_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to javac. + + *ale-options.java_javac_sourcepath* + *g:ale_java_javac_sourcepath* + *b:ale_java_javac_sourcepath* +java_javac_sourcepath +g:ale_java_javac_sourcepath + Type: |String| or |List| + Default: `''` + + This variable can set multiple source code paths, the source code path is a + relative path (relative to the project root directory). + + The source path can be set as a String with a system-dependent path + separator. Note that the Unix path separator is a colon (`:`), and on + Windows the path separator is a semicolon (`;`). > + + let g:ale_java_javac_sourcepath = 'build/gen/source/xx/main:build/gen/source' +< + The source path can be set as a List so ALE will add the appropriate path + separator for the host system automatically. > + + let g:ale_java_javac_sourcepath = [ + \ 'build/generated/source/querydsl/main', + \ 'target/generated-sources/source/querydsl/main', + \] +< + +=============================================================================== +google-java-format *ale-java-google-java-format* + + *ale-options.java_google_java_format_executable* + *g:ale_java_google_java_format_executable* + *b:ale_java_google_java_format_executable* +java_google_java_format_executable +g:ale_java_google_java_format_executable + Type: |String| + Default: `'google-java-format'` + + See |ale-integrations-local-executables| + + *ale-options.java_google_java_format_options* + *g:ale_java_google_java_format_options* + *b:ale_java_google_java_format_options* +java_google_java_format_options +g:ale_java_google_java_format_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options + + +=============================================================================== +pmd *ale-java-pmd* + + *ale-options.java_pmd_options* + *g:ale_java_pmd_options* + *b:ale_java_pmd_options* +java_pmd_options +g:ale_java_pmd_options + Type: |String| + Default: `'-R category/java/bestpractices'` + + This variable can be changed to modify flags given to PMD. Do not specify -f + and -d. They are added automatically. + + +=============================================================================== +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/java-language-server. + +Before building the language server you need to install pre-requisites: npm, +maven, and protobuf. You also need to have Java 13 and JAVA_HOME properly +set. + +After downloading the source code and installing all pre-requisites you can +build the language server with the included build.sh script: + + `scripts/build.sh` + +This will create launch scripts for Linux, Mac, and Windows in the dist folder +within the repo: + + - `lang_server_linux.sh` + - `lang_server_mac.sh` + - `lang_server_windows.sh` + +To let ALE use this language server you need to set the executable, as +documented below. + + *ale-options.java_javalsp_executable* + *g:ale_java_javalsp_executable* + *b:ale_java_javalsp_executable* +java_javalsp_executable +g:ale_java_javalsp_executable + Type: |String| + Default: `''` + + This variable must be set to the absolute path of the language server + launcher executable. For example: > + + let g:ale_java_javalsp_executable = '/java-language-server/dist/lang_server_linux.sh' +< + *ale-options.java_javalsp_config* + *g:ale_java_javalsp_config* + *b:ale_java_javalsp_config* +java_javalsp_config +g:ale_java_javalsp_config + Type: |Dictionary| + Default: `{}` + + The javalsp linter automatically detects external dependencies for Maven and + Gradle projects. In case the javalsp fails to detect some of them, you can + specify them configuring settings for the language server, such as in your + ftplugin file. > + + let b:ale_java_javalsp_config = { + \ 'java': { + \ 'externalDependencies': [ + \ 'junit:junit:jar:4.12:test', + \ 'junit:junit:4.1' + \ ], + \ 'classPath': [ + \ 'lib/some-dependency.jar', + \ '/android-sdk/platforms/android-28.jar', + \ ], + \ }, + \} +< + Or in Lua: > + + require("ale").setup.buffer({ + java_lsp_config = { + java = { + externalDependencies = { + "junit:junit:jar:4.12:test", + "junit:junit:4.1" + }, + classPath = { + "lib/some-dependency.jar", + "/android-sdk/platforms/android-28.jar", + }, + }, + } + }) +< + The Java language server will look for the dependencies you specify in + `externalDependencies` array in your Maven and Gradle caches ~/.m2 and + ~/.gradle. + + +=============================================================================== +eclipselsp *ale-java-eclipselsp* + +To enable Eclipse JDT 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 11. 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. + + *ale-options.java_eclipselsp_path* + *g:ale_java_eclipselsp_path* + *b:ale_java_eclipselsp_path* +java_eclipselsp_path +g:ale_java_eclipselsp_path + Type: |String| + 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/redhat.java-0.4x.0 in + Linux). + + *ale-options.java_eclipselsp_executable* + *g:ale_java_eclipselsp_executable* + *b:ale_java_eclipse_executable* +java_eclipselsp_executable +g:ale_java_eclipselsp_executable + Type: |String| + Default: `'java'` + + This variable can be set to change the executable path used for java. + + *ale-options.java_eclipselsp_config_path* + *g:ale_java_eclipselsp_config_path* + *b:ale_java_eclipse_config_path* +java_eclipselsp_config_path +g:ale_java_eclipselsp_config_path + Type: |String| + Default: `''` + + Set this variable to change the configuration directory path used by + eclipselsp (e.g. `$HOME/.jdtls` in Linux). By default ALE will attempt to + use the configuration within the installation directory. + + This setting is particularly useful when eclipselsp is installed in a + non-writable directory like `/usr/share/java/jdtls`, as is the case when + installed via system package. + + *ale-options.java_eclipselsp_workspace_path* + *g:ale_java_eclipselsp_workspace_path* + *b:ale_java_eclipselsp_workspace_path* +java_eclipselsp_workspace_path +g:ale_java_eclipselsp_workspace_path + Type: |String| + Default: `''` + + If you have Eclipse installed it is a good idea to set this variable to the + absolute path of the Eclipse workspace. If not set this value will be set to + the parent folder of the project root. + + *ale-options.java_eclipselsp_javaagent* + *g:ale_java_eclipselsp_javaagent* + *b:ale_java_eclipselsp_javaagent* +java_eclipselsp_javaagent +g:ale_java_eclipselsp_javaagent + Type: |String| + Default: `''` + + A variable to add java agent for annotation processing such as Lombok. + If you have multiple java agent files, use space to separate them. + For example: > + + let g:ale_java_eclipselsp_javaagent='/eclipse/lombok.jar /eclipse/jacoco.jar' +< + +=============================================================================== +uncrustify *ale-java-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-javascript.txt b/doc/ale-javascript.txt new file mode 100644 index 00000000..72e4efd9 --- /dev/null +++ b/doc/ale-javascript.txt @@ -0,0 +1,433 @@ +=============================================================================== +ALE JavaScript Integration *ale-javascript-options* + *ale-eslint-nested-configuration-files* + +For fixing files with ESLint, nested configuration files with `root: false` +are not supported. This is because ALE fixes files by writing the contents of +buffers to temporary files, and then explicitly sets the configuration file. +Configuration files which are set explicitly must be root configuration files. +If you are using nested configuration files, you should restructure your +project so your configuration files use `extends` instead. + +See the ESLint documentation here: +http://eslint.org/docs/user-guide/configuring#extending-configuration-files + +You should change the structure of your project from this: > + /path/foo/.eslintrc.js # root: true + /path/foo/bar/.eslintrc.js # root: false +< + +To this: > + /path/foo/.base-eslintrc.js # Base configuration here + /path/foo/.eslintrc.js # extends: ["/path/foo/.base-eslintrc.js"] + /path/foo/bar/.eslintrc.js # extends: ["/path/foo/.base-eslintrc.js"] +< + +=============================================================================== +biome *ale-javascript-biome* + +Check the docs over at |ale-typescript-biome|. + + +=============================================================================== +clang-format *ale-javascript-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for JavaScript. + + +=============================================================================== +cspell *ale-javascript-cspell* + +See |ale-cspell-options| + + +=============================================================================== +deno *ale-javascript-deno* + +Check the docs over at |ale-typescript-deno|. + + +=============================================================================== +dprint *ale-javascript-dprint* + +See |ale-dprint-options| and https://dprint.dev/plugins/typescript + + +=============================================================================== +eslint *ale-javascript-eslint* + + *ale-options.javascript_eslint_executable* + *g:ale_javascript_eslint_executable* + *b:ale_javascript_eslint_executable* +javascript_eslint_executable +g:ale_javascript_eslint_executable + Type: |String| + Default: `'eslint'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_eslint_options* + *g:ale_javascript_eslint_options* + *b:ale_javascript_eslint_options* +javascript_eslint_options +g:ale_javascript_eslint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to eslint. + + *ale-options.javascript_eslint_use_global* + *g:ale_javascript_eslint_use_global* + *b:ale_javascript_eslint_use_global* +javascript_eslint_use_global +g:ale_javascript_eslint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + + *ale-options.javascript_eslint_suppress_eslintignore* + *g:ale_javascript_eslint_suppress_eslintignore* + *b:ale_javascript_eslint_suppress_eslintignore* +javascript_eslint_suppress_eslintignore +g:ale_javascript_eslint_suppress_eslintignore + Type: |Number| + Default: `0` + + This variable can be set to `1` to disable warnings for files being ignored + by eslint. + + + *ale-options.javascript_eslint_suppress_missing_config* + *g:ale_javascript_eslint_suppress_missing_config* + *b:ale_javascript_eslint_suppress_missing_config* +javascript_eslint_suppress_missing_config +g:ale_javascript_eslint_suppress_missing_config + Type: |Number| + Default: `0` + + This variable can be set to `1` to disable errors for missing eslint + configuration files. + + When turning this option on, eslint will not report any problems when no + configuration files are found. + + +=============================================================================== +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. + + *ale-options.javascript_fecs_executable* + *g:ale_javascript_fecs_executable* + *b:ale_javascript_fecs_executable* +javascript_fecs_executable +g:ale_javascript_fecs_executable + Type: |String| + Default: `'fecs'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_fecs_use_global* + *g:ale_javascript_fecs_use_global* + *b:ale_javascript_fecs_use_global* +javascript_fecs_use_global +g:ale_javascript_fecs_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +flow *ale-javascript-flow* + + *ale-options.javascript_flow_executable* + *g:ale_javascript_flow_executable* + *b:ale_javascript_flow_executable* +javascript_flow_executable +g:ale_javascript_flow_executable + Type: |String| + Default: `'flow'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_flow_use_home_config* + *g:ale_javascript_flow_use_home_config* + *b:ale_javascript_flow_use_home_config* +javascript_flow_use_home_config +g:ale_javascript_flow_use_home_config + Type: |Number| + Default: `0` + + When set to `1`, ALE will allow Flow to be executed with configuration files + from your home directory. ALE will not run Flow with home directory + configuration files by default, as doing so can lead to Vim consuming all of + your RAM and CPU power. + + *ale-options.javascript_flow_use_global* + *g:ale_javascript_flow_use_global* + *b:ale_javascript_flow_use_global* +javascript_flow_use_global +g:ale_javascript_flow_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.javascript_flow_use_respect_pragma* + *g:ale_javascript_flow_use_respect_pragma* + *b:ale_javascript_flow_use_respect_pragma* +javascript_flow_use_respect_pragma +g:ale_javascript_flow_use_respect_pragma + Type: |Number| + Default: `1` + + By default, ALE will use the `--respect-pragma` option for `flow`, so only + files with the `@flow` pragma are checked by ALE. This option can be set to + `0` to disable that behavior, so all files can be checked by `flow`. + + +=============================================================================== +importjs *ale-javascript-importjs* + + *ale-options.javascript_importjs_executable* + *g:ale_javascript_importjs_executable* + *b:ale_javascript_importjs_executable* +javascript_importjs_executable +g:ale_javascript_importjs_executable + Type: |String| + Default: `'importjs'` + + +=============================================================================== +jscs *ale-javascript-jscs* + + *ale-options.javascript_jscs_executable* + *g:ale_javascript_jscs_executable* + *b:ale_javascript_jscs_executable* +javascript_jscs_executable +g:ale_javascript_jscs_executable + Type: |String| + Default: `'jscs'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_jscs_use_global* + *g:ale_javascript_jscs_use_global* + *b:ale_javascript_jscs_use_global* +javascript_jscs_use_global +g:ale_javascript_jscs_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +jshint *ale-javascript-jshint* + + *ale-options.javascript_jshint_executable* + *g:ale_javascript_jshint_executable* + *b:ale_javascript_jshint_executable* +javascript_jshint_executable +g:ale_javascript_jshint_executable + Type: |String| + Default: `'jshint'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_jshint_use_global* + *g:ale_javascript_jshint_use_global* + *b:ale_javascript_jshint_use_global* +javascript_jshint_use_global +g:ale_javascript_jshint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier *ale-javascript-prettier* + + *ale-options.javascript_prettier_executable* + *g:ale_javascript_prettier_executable* + *b:ale_javascript_prettier_executable* +javascript_prettier_executable +g:ale_javascript_prettier_executable + Type: |String| + Default: `'prettier'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_prettier_options* + *g:ale_javascript_prettier_options* + *b:ale_javascript_prettier_options* +javascript_prettier_options +g:ale_javascript_prettier_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to prettier. + + *ale-options.javascript_prettier_use_global* + *g:ale_javascript_prettier_use_global* + *b:ale_javascript_prettier_use_global* +javascript_prettier_use_global +g:ale_javascript_prettier_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier-eslint *ale-javascript-prettier-eslint* + + *ale-options.javascript_prettier_eslint_executable* + *g:ale_javascript_prettier_eslint_executable* + *b:ale_javascript_prettier_eslint_executable* +javascript_prettier_eslint_executable +g:ale_javascript_prettier_eslint_executable + Type: |String| + Default: `'prettier-eslint'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_prettier_eslint_options* + *g:ale_javascript_prettier_eslint_options* + *b:ale_javascript_prettier_eslint_options* +javascript_prettier_eslint_options +g:ale_javascript_prettier_eslint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to prettier-eslint. + + + *ale-options.javascript_prettier_eslint_use_global* + *g:ale_javascript_prettier_eslint_use_global* + *b:ale_javascript_prettier_eslint_use_global* +javascript_prettier_eslint_use_global +g:ale_javascript_prettier_eslint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier-standard *ale-javascript-prettier-standard* + + + *ale-options.javascript_prettier_standard_executable* + *g:ale_javascript_prettier_standard_executable* + *b:ale_javascript_prettier_standard_executable* +javascript_prettier_standard_executable +g:ale_javascript_prettier_standard_executable + Type: |String| + Default: `'prettier-standard'` + + See |ale-integrations-local-executables| + + + *ale-options.javascript_prettier_standard_options* + *g:ale_javascript_prettier_standard_options* + *b:ale_javascript_prettier_standard_options* +javascript_prettier_standard_options +g:ale_javascript_prettier_standard_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to prettier-standard. + + + *ale-options.javascript_prettier_standard_use_global* + *g:ale_javascript_prettier_standard_use_global* + *b:ale_javascript_prettier_standard_use_global* +javascript_prettier_standard_use_global +g:ale_javascript_prettier_standard_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +standard *ale-javascript-standard* + + *ale-options.javascript_standard_executable* + *g:ale_javascript_standard_executable* + *b:ale_javascript_standard_executable* +javascript_standard_executable +g:ale_javascript_standard_executable + Type: |String| + Default: `'standard'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_standard_options* + *g:ale_javascript_standard_options* + *b:ale_javascript_standard_options* +javascript_standard_options +g:ale_javascript_standard_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to standard. + + *ale-options.javascript_standard_use_global* + *g:ale_javascript_standard_use_global* + *b:ale_javascript_standard_use_global* +javascript_standard_use_global +g:ale_javascript_standard_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +xo *ale-javascript-xo* + + *ale-options.javascript_xo_executable* + *g:ale_javascript_xo_executable* + *b:ale_javascript_xo_executable* +javascript_xo_executable +g:ale_javascript_xo_executable + Type: |String| + Default: `'xo'` + + See |ale-integrations-local-executables| + + *ale-options.javascript_xo_options* + *g:ale_javascript_xo_options* + *b:ale_javascript_xo_options* +javascript_xo_options +g:ale_javascript_xo_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to xo. + + *ale-options.javascript_xo_use_global* + *g:ale_javascript_xo_use_global* + *b:ale_javascript_xo_use_global* +javascript_xo_use_global +g:ale_javascript_xo_use_global + Type: |Number| + 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-jinja.txt b/doc/ale-jinja.txt new file mode 100644 index 00000000..37769fe9 --- /dev/null +++ b/doc/ale-jinja.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Jinja Integration *ale-jinja-options* + + + +=============================================================================== +djlint *ale-jinja-djlint* + +See |ale-html-djlint| + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-json.txt b/doc/ale-json.txt new file mode 100644 index 00000000..1f72f832 --- /dev/null +++ b/doc/ale-json.txt @@ -0,0 +1,252 @@ +=============================================================================== +ALE JSON Integration *ale-json-options* + + +=============================================================================== +biome *ale-json-biome* + +Check the docs over at |ale-typescript-biome|. + + +=============================================================================== +clang-format *ale-json-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for JSON. + + +=============================================================================== +cspell *ale-json-cspell* + +See |ale-cspell-options| + + +=============================================================================== +dprint *ale-json-dprint* + +See |ale-dprint-options| and https://dprint.dev/plugins/json + + +=============================================================================== +eslint *ale-json-eslint* + +The `eslint` linter for JSON uses the JavaScript options for `eslint`; see: +|ale-javascript-eslint|. + +You will need a JSON ESLint plugin installed for this to work. + + +=============================================================================== +fixjson *ale-json-fixjson* + +fixjson is a JSON file fixer/formatter for humans using (relaxed) JSON5. +It provides: + +- Pretty-prints JSON input +- Fixes various failures while humans writing JSON + - Fixes trailing commas objects or arrays + - Fixes missing commas for elements of objects or arrays + - Adds quotes to keys in objects + - Newlines in strings + - Hex numbers + - Fixes single quotes to double quotes + +You can install it using npm: +> + $ npm install -g fixjson +< +ALE provides fixjson integration as a fixer. See |ale-fix|. + +------------------------------------------------------------------------------- +Options + *ale-options.json_fixjson_executable* + *g:ale_json_fixjson_executable* + *b:ale_json_fixjson_executable* +json_fixjson_executable +g:ale_json_fixjson_executable + Type: |String| + Default: `'fixjson'` + + The executable that will be run for fixjson. + + *ale-options.json_fixjson_options* + *g:ale_json_fixjson_options* + *b:ale_json_fixjson_options* +json_fixjson_options +g:ale_json_fixjson_options + Type: |String| + Default: `''` + + This variable can add extra options to the command executed for running + fixjson. + + *ale-options.json_fixjson_use_global* + *g:ale_json_fixjson_use_global* + *b:ale_json_fixjson_use_global* +json_fixjson_use_global +g:ale_json_fixjson_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +pytool *ale-json-pytool* + +Use python's json.tool module to reformat json. + + *ale-options.json_pytool_executable* + *g:ale_json_pytool_executable* + *b:ale_json_pytool_executable* +json_pytool_executable +g:ale_json_pytool_executable + Type: |String| + Default: `'python'` + + The python executable that run to use its json.tool module. This fixer + requires python 3, which includes the json module. + + *ale-options.json_pytool_options* + *g:ale_json_pytool_options* + *b:ale_json_pytool_options* +json_pytool_options +g:ale_json_pytool_options + Type: |String| + Default: `''` + + These options are passed to the json.tool module. Example: > + let g:ale_json_pytool_options = '--sort-keys --indent 2' +< See docs for all options: + https://docs.python.org/3/library/json.html#module-json.tool + + *ale-options.json_pytool_use_global* + *g:ale_json_pytool_use_global* + *b:ale_json_pytool_use_global* +json_pytool_use_global +g:ale_json_pytool_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +jsonlint *ale-json-jsonlint* + + *ale-options.json_jsonlint_executable* + *g:ale_json_jsonlint_executable* + *b:ale_json_jsonlint_executable* +json_jsonlint_executable +g:ale_json_jsonlint_executable + Type: |String| + Default: `'jsonlint'` + + The executable that will be run for jsonlint. + + *ale-options.json_jsonlint_use_global* + *g:ale_json_jsonlint_use_global* + *b:ale_json_jsonlint_use_global* +json_jsonlint_use_global +g:ale_json_jsonlint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +jq *ale-json-jq* + + *ale-options.json_jq_executable* + *g:ale_json_jq_executable* + *b:ale_json_jq_executable* +json_jq_executable +g:ale_json_jq_executable + Type: |String| + Default: `'jq'` + + This option can be changed to change the path for `jq`. + + + *ale-options.json_jq_options* + *g:ale_json_jq_options* + *b:ale_json_jq_options* +json_jq_options +g:ale_json_jq_options + Type: |String| + Default: `''` + + This option can be changed to pass extra options to `jq`. + + *ale-options.json_jq_filters* + *g:ale_json_jq_filters* + *b:ale_json_jq_filters* +json_jq_filters +g:ale_json_jq_filters + Type: |String| + Default: `'.'` + + This option can be changed to pass custom filters to `jq`. + + +=============================================================================== +prettier *ale-json-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +spectral *ale-json-spectral* + +Website: https://github.com/stoplightio/spectral + + +------------------------------------------------------------------------------- +Installation + +Install spectral either globally or locally: > + + npm install @stoplight/spectral -g # global + npm install @stoplight/spectral # local +< + +------------------------------------------------------------------------------- +Options + + *ale-options.json_spectral_executable* + *g:ale_json_spectral_executable* + *b:ale_json_spectral_executable* +json_spectral_executable +g:ale_json_spectral_executable + Type: |String| + Default: `'spectral'` + + This variable can be set to change the path to spectral. + + *ale-options.json_spectral_use_global* + *g:ale_json_spectral_use_global* + *b:ale_json_spectral_use_global* +json_spectral_use_global +g:ale_json_spectral_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +vscodejson *ale-json-vscode* + +Website: https://github.com/hrsh7th/vscode-langservers-extracted + +------------------------------------------------------------------------------- +Installation + +Install VSCode json language server either globally or locally: > + + npm install -g vscode-langservers-extracted +< + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-json5.txt b/doc/ale-json5.txt new file mode 100644 index 00000000..bafa60d1 --- /dev/null +++ b/doc/ale-json5.txt @@ -0,0 +1,15 @@ +=============================================================================== +ALE JSON5 Integration *ale-json5-options* + + +=============================================================================== +eslint *ale-json5-eslint* + +The `eslint` linter for JSON uses the JavaScript options for `eslint`; see: +|ale-javascript-eslint|. + +You will need a JSON5 ESLint plugin installed for this to work. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-jsonc.txt b/doc/ale-jsonc.txt new file mode 100644 index 00000000..b05fa6e6 --- /dev/null +++ b/doc/ale-jsonc.txt @@ -0,0 +1,21 @@ +=============================================================================== +ALE JSONC Integration *ale-jsonc-options* + + +=============================================================================== +biome *ale-jsonc-biome* + +Check the docs over at |ale-typescript-biome|. + + +=============================================================================== +eslint *ale-jsonc-eslint* + +The `eslint` linter for JSON uses the JavaScript options for `eslint`; see: +|ale-javascript-eslint|. + +You will need a JSONC ESLint plugin installed for this to work. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-jsonnet.txt b/doc/ale-jsonnet.txt new file mode 100644 index 00000000..8c4f4cbd --- /dev/null +++ b/doc/ale-jsonnet.txt @@ -0,0 +1,54 @@ +=============================================================================== +ALE Jsonnet Integration *ale-jsonnet-options* + + +=============================================================================== +jsonnetfmt *ale-jsonnet-jsonnetfmt* + + *ale-options.jsonnet_jsonnetfmt_executable* + *g:ale_jsonnet_jsonnetfmt_executable* + *b:ale_jsonnet_jsonnetfmt_executable* +jsonnet_jsonnetfmt_executable +g:ale_jsonnet_jsonnetfmt_executable + Type: |String| + Default: `'jsonnetfmt'` + + This option can be changed to change the path for `jsonnetfmt`. + + *ale-options.jsonnet_jsonnetfmt_options* + *g:ale_jsonnet_jsonnetfmt_options* + *b:ale_jsonnet_jsonnetfmt_options* +jsonnet_jsonnetfmt_options +g:ale_jsonnet_jsonnetfmt_options + Type: |String| + Default: `''` + + This option can be changed to pass extra options to `jsonnetfmt`. + + +=============================================================================== +jsonnet-lint *ale-jsonnet-jsonnet-lint* + + *ale-options.jsonnet_jsonnet_lint_executable* + *g:ale_jsonnet_jsonnet_lint_executable* + *b:ale_jsonnet_jsonnet_lint_executable* +jsonnet_jsonnet_lint_executable +g:ale_jsonnet_jsonnet_lint_executable + Type: |String| + Default: `'jsonnet-lint'` + + This option can be changed to change the path for `jsonnet-lint`. + + *ale-options.jsonnet_jsonnet_lint_options* + *g:ale_jsonnet_jsonnet_lint_options* + *b:ale_jsonnet_jsonnet_lint_options* +jsonnet_jsonnet_lint_options +g:ale_jsonnet_jsonnet_lint_options + Type: |String| + Default: `''` + + This option can be changed to pass extra options to `jsonnet-lint`. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-julia.txt b/doc/ale-julia.txt new file mode 100644 index 00000000..175037b1 --- /dev/null +++ b/doc/ale-julia.txt @@ -0,0 +1,23 @@ +=============================================================================== +ALE Julia Integration *ale-julia-options* + + +=============================================================================== +languageserver *ale-julia-languageserver* + +To enable Julia LSP linter you need to install the LanguageServer.jl package +within julia. + + *ale-options.julia_executable* + *g:ale_julia_executable* + *b:ale_julia_executable* +julia_executable +g:ale_julia_executable + Type: |String| + Default: `'julia'` + + Path to the julia exetuable. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-kotlin.txt b/doc/ale-kotlin.txt new file mode 100644 index 00000000..3409d741 --- /dev/null +++ b/doc/ale-kotlin.txt @@ -0,0 +1,157 @@ +=============================================================================== +ALE Kotlin Integration *ale-kotlin-options* + *ale-integration-kotlin* + +=============================================================================== +Integration Information + + Make sure your setup has support for the kotlin file type. A filetype plugin + can be found here: https://github.com/udalov/kotlin-vim + + + Note: Make sure you have a working kotlin compiler + + +=============================================================================== +kotlinc *ale-kotlin-kotlinc* + + *ale-options.kotlin_kotlinc_options* + *g:ale_kotlin_kotlinc_options* + *b:ale_kotlin_kotlinc_options* +kotlin_kotlinc_options +g:ale_kotlin_kotlinc_options + Type: |String| + Default: `''` + + Additional options to pass to the kotlin compiler + + *ale-options.kotlin_kotlinc_enable_config* + *g:ale_kotlin_kotlinc_enable_config* + *b:ale_kotlin_kotlinc_enable_config* +kotlin_kotlinc_enable_config +g:ale_kotlin_kotlinc_enable_config + Type: |Number| + Default: `0` + + Setting this variable to `1` tells the linter to load a configuration file. + This should be set in your vimrc + + *ale-options.kotlin_kotlinc_config_file* + *g:ale_kotlin_kotlinc_config_file* + *b:ale_kotlin_kotlinc_config_file* +kotlin_kotlinc_config_file +g:ale_kotlin_kotlinc_config_file + Type: |String| + Default: `'.ale_kotlin_kotlinc_config'` + + Filename of the configuration file. This should be set in your vimrc + + *ale-options.kotlin_kotlinc_classpath* + *g:ale_kotlin_kotlinc_classpath* + *b:ale_kotlin_kotlinc_classpath* +kotlin_kotlinc_classpath +g:ale_kotlin_kotlinc_classpath + Type: |String| + Default: `''` + + A string containing the paths (separated by the appropriate path separator) + of the source directories. + + *ale-options.kotlin_kotlinc_sourcepath* + *g:ale_kotlin_kotlinc_sourcepath* + *b:ale_kotlin_kotlinc_sourcepath* +kotlin_kotlinc_sourcepath +g:ale_kotlin_kotlinc_sourcepath + Type: |String| + Default: `''` + + A string containing the paths (separated by space) of the source + directories. + + *ale-options.kotlin_kotlinc_use_module_file* + *g:ale_kotlin_kotlinc_use_module_file* + *b:ale_kotlin_kotlinc_use_module_file* +kotlin_kotlinc_use_module_file +g:ale_kotlin_kotlinc_use_module_file + Type: |Number| + Default: `0` + + This option indicates whether the linter should use a module file. It is off + by default. + + *ale-options.kotlin_kotlinc_module_filename* + *g:ale_kotlin_kotlinc_module_filename* + *b:ale_kotlin_kotlinc_module_filename* +kotlin_kotlinc_module_filename +g:ale_kotlin_kotlinc_module_filename + Type: |String| + Default: `'module.xml'` + + The filename of the module file that the linter should pass to the kotlin + compiler. + + +=============================================================================== +ktlint *ale-kotlin-ktlint* + + *ale-options.kotlin_ktlint_executable* + *g:ale_kotlin_ktlint_executable* + *b:ale_kotlin_ktlint_executable* +kotlin_ktlint_executable +g:ale_kotlin_ktlint_executable + Type: |String| + Default: `''` + + The Ktlint executable. + + Posix-compliant shell scripts are the only executables that can be found on + Ktlint's github release page. If you are not on such a system, your best + bet will be to download the ktlint jar and set this option to something + similar to `'java -jar /path/to/ktlint.jar'` + + *ale-options.kotlin_ktlint_rulesets* + *g:ale_kotlin_ktlint_rulesets* + *b:ale_kotlin_ktlint_rulesets* +kotlin_ktlint_rulesets +g:ale_kotlin_ktlint_rulesets + Type: |List| + Default: `[]` + + 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-ruleset.jar', + 'com.ktlint.rulesets:mycustomrule:1.0.0'] +< + *ale-options.kotlin_ktlint_options* + *g:ale_kotlin_ktlint_options* + *b:ale_kotlin_ktlint_options* +kotlin_ktlint_options +g:ale_kotlin_ktlint_options + Type: |String| + Default: `''` + + Options to pass to ktlint for both linting and fixing. For example: > + + let g:ale_kotlin_ktlint_options = '--android' +< + +=============================================================================== +languageserver *ale-kotlin-languageserver* + + *ale-options.kotlin_languageserver_executable* + *g:ale_kotlin_languageserver_executable* + *b:ale_kotlin_languageserver_executable* +kotlin_languageserver_executable +g:ale_kotlin_languageserver_executable + Type: |String| + Default: `''` + + The kotlin-language-server executable. + + Executables are located inside the bin/ folder of the language server + release. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-latex.txt b/doc/ale-latex.txt new file mode 100644 index 00000000..b3029a5b --- /dev/null +++ b/doc/ale-latex.txt @@ -0,0 +1,21 @@ +=============================================================================== +ALE LaTeX Integration *ale-latex-options* + + +=============================================================================== +cspell *ale-latex-cspell* + +=============================================================================== +write-good *ale-latex-write-good* + +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-less.txt b/doc/ale-less.txt new file mode 100644 index 00000000..3c4eacd0 --- /dev/null +++ b/doc/ale-less.txt @@ -0,0 +1,80 @@ +=============================================================================== +ALE Less Integration *ale-less-options* + + +=============================================================================== +lessc *ale-less-lessc* + + *ale-options.less_lessc_executable* + *g:ale_less_lessc_executable* + *b:ale_less_lessc_executable* +less_lessc_executable +g:ale_less_lessc_executable + Type: |String| + Default: `'lessc'` + + See |ale-integrations-local-executables| + + *ale-options.less_lessc_options* + *g:ale_less_lessc_options* + *b:ale_less_lessc_options* +less_lessc_options +g:ale_less_lessc_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to lessc. + + *ale-options.less_lessc_use_global* + *g:ale_less_lessc_use_global* + *b:ale_less_lessc_use_global* +less_lessc_use_global +g:ale_less_lessc_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier *ale-less-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +stylelint *ale-less-stylelint* + + *ale-options.less_stylelint_executable* + *g:ale_less_stylelint_executable* + *b:ale_less_stylelint_executable* +less_stylelint_executable +g:ale_less_stylelint_executable + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + *ale-options.less_stylelint_options* + *g:ale_less_stylelint_options* + *b:ale_less_stylelint_options* +less_stylelint_options +g:ale_less_stylelint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + *ale-options.less_stylelint_use_global* + *g:ale_less_stylelint_use_global* + *b:ale_less_stylelint_use_global* +less_stylelint_use_global +g:ale_less_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-llvm.txt b/doc/ale-llvm.txt new file mode 100644 index 00000000..d30f4ff4 --- /dev/null +++ b/doc/ale-llvm.txt @@ -0,0 +1,21 @@ +=============================================================================== +ALE LLVM Integration *ale-llvm-options* + + +=============================================================================== +llc *ale-llvm-llc* + + *ale-options.llvm_llc_executable* + *g:ale_llvm_llc_executable* + *b:ale_llvm_llc_executable* +llvm_llc_executable +g:ale_llvm_llc_executable + Type: |String| + Default: `"llc"` + + The command to use for checking. This variable is useful when llc command + has suffix like "llc-5.0". + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-lua.txt b/doc/ale-lua.txt new file mode 100644 index 00000000..dea526cc --- /dev/null +++ b/doc/ale-lua.txt @@ -0,0 +1,175 @@ +=============================================================================== +ALE Lua Integration *ale-lua-options* + + +=============================================================================== +cspell *ale-lua-cspell* + +See |ale-cspell-options| + + +=============================================================================== +lua-format *ale-lua-lua-format* + + *ale-options.lua_lua_format_executable* + *g:ale_lua_lua_format_executable* + *b:ale_lua_lua_format_executable* +lua_lua_format_executable +g:ale_lua_lua_format_executable + Type: |String| + Default: `'lua-format'` + + This variable can be changed to change the path to lua-format. + + *ale-options.lua_lua_format_options* + *g:ale_lua_lua_format_options* + *b:ale_lua_lua_format_options* +lua_lua_format_options +g:ale_lua_lua_format_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to lua-format. + + +=============================================================================== +lua-language-server *ale-lua-lua-language-server* + *ale-lua-language-server* + + *ale-options.lua_language_server_executable* + *g:ale_lua_language_server_executable* + *b:ale_lua_language_server_executable* +lua_language_server_executable +g:ale_lua_language_server_executable + Type: |String| + Default: `'lua-language-server'` + + This variable can be changed to set the path to lua-language-server. + + If you have compiled the language server yourself in `/some/path`, the path + will be `'/some/path/bin/lua-language-server'`. + + *ale-options.lua_lua_language_server_config* + *g:ale_lua_lua_language_server_config* + *b:ale_lua_lua_language_server_config* +lua_lua_language_server_config +g:ale_lua_lua_language_server_config + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server. + + +=============================================================================== +luac *ale-lua-luac* + + *ale-options.lua_luac_executable* + *g:ale_lua_luac_executable* + *b:ale_lua_luac_executable* +lua_luac_executable +g:ale_lua_luac_executable + Type: |String| + Default: `'luac'` + + This variable can be changed to change the path to luac. + + +=============================================================================== +luacheck *ale-lua-luacheck* + + *ale-options.lua_luacheck_executable* + *g:ale_lua_luacheck_executable* + *b:ale_lua_luacheck_executable* +lua_luacheck_executable +g:ale_lua_luacheck_executable + Type: |String| + Default: `'luacheck'` + + This variable can be changed to change the path to luacheck. + + *ale-options.lua_luacheck_options* + *g:ale_lua_luacheck_options* + *b:ale_lua_luacheck_options* +lua_luacheck_options +g:ale_lua_luacheck_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to luacheck. + + +=============================================================================== +luafmt *ale-lua-luafmt* + + *ale-options.lua_luafmt_executable* + *g:ale_lua_luafmt_executable* + *b:ale_lua_luafmt_executable* +lua_luafmt_executable +g:ale_lua_luafmt_executable + Type: |String| + Default: `'luafmt'` + + This variable can be set to use a different executable for luafmt. + + *ale-options.lua_luafmt_options* + *g:ale_lua_luafmt_options* + *b:ale_lua_luafmt_options* +lua_luafmt_options +g:ale_lua_luafmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the luafmt fixer. + + +=============================================================================== +selene *ale-lua-selene* + + *ale-options.lua_selene_executable* + *g:ale_lua_selene_executable* + *b:ale_lua_selene_executable* +lua_selene_executable +g:ale_lua_selene_executable + Type: |String| + Default: `'selene'` + + This variable can be set to use a different executable for selene. + + *ale-options.lua_selene_options* + *g:ale_lua_selene_options* + *b:ale_lua_selene_options* +lua_selene_options +g:ale_lua_selene_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to selene. + + +=============================================================================== +stylua *ale-lua-stylua* + + *ale-options.lua_stylua_executable* + *g:ale_lua_stylua_executable* + *b:ale_lua_stylua_executable* +lua_stylua_executable +g:ale_lua_stylua_executable + Type: |String| + Default: `'stylua'` + + This variable can be set to use a different executable for stylua. + + *ale-options.lua_stylua_options* + *g:ale_lua_stylua_options* + *b:ale_lua_stylua_options* +lua_stylua_options +g:ale_lua_stylua_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the stylua fixer. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-make.txt b/doc/ale-make.txt new file mode 100644 index 00000000..6258c812 --- /dev/null +++ b/doc/ale-make.txt @@ -0,0 +1,22 @@ +=============================================================================== +ALE Make Integration *ale-make-options* + + +=============================================================================== +checkmake *ale-make-checkmake* + + *ale-options.make_checkmake_config* + *g:ale_make_checkmake_config* + *b:ale_make_checkmake_config* +make_checkmake_config +g:ale_make_checkmake_config + Type: |String| + Default: `''` + + This variable can be used to set the `--config` option of checkmake command. + if the value is empty, the checkmake command will not be invoked with the + option. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-markdown.txt b/doc/ale-markdown.txt new file mode 100644 index 00000000..1b26bc17 --- /dev/null +++ b/doc/ale-markdown.txt @@ -0,0 +1,229 @@ +=============================================================================== +ALE Markdown Integration *ale-markdown-options* + + +=============================================================================== +cspell *ale-markdown-cspell* + +See |ale-cspell-options| + + +=============================================================================== +dprint *ale-markdown-dprint* + +See |ale-dprint-options| and https://dprint.dev/plugins/markdown + + +=============================================================================== +markdownlint *ale-markdown-markdownlint* + + *ale-options.markdown_markdownlint_executable* + *g:ale_markdown_markdownlint_executable* + *b:ale_markdown_markdownlint_executable* +markdown_markdownlint_executable +g:ale_markdown_markdownlint_executable + Type: |String| + Default: `'markdownlint'` + + Override the invoked `markdownlint` binary. You can use other binaries such as + `markdownlint-cli2`. + + *ale-options.markdown_markdownlint_options* + *g:ale_markdown_markdownlint_options* + *b:ale_markdown_markdownlint_options* +markdown_markdownlint_options +g:ale_markdown_markdownlint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to markdownlint. + + +=============================================================================== +marksman *ale-markdown-marksman* + + *ale-options.markdown_marksman_executable* + *g:ale_markdown_marksman_executable* + *b:ale_markdown_marksman_executable* +markdown_marksman_executable +g:ale_markdown_marksman_executable + Type: |String| + Default: `'marksman'` + + Override the invoked `marksman` binary. + + +=============================================================================== +mdl *ale-markdown-mdl* + + *ale-options.markdown_mdl_executable* + *g:ale_markdown_mdl_executable* + *b:ale_markdown_mdl_executable* +markdown_mdl_executable +g:ale_markdown_mdl_executable + Type: |String| + Default: `'mdl'` + + Override the invoked mdl binary. This is useful for running mdl from + binstubs or a bundle. + + *ale-options.markdown_mdl_options* + *g:ale_markdown_mdl_options* + *b:ale_markdown_mdl_options* +markdown_mdl_options +g:ale_markdown_mdl_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to mdl. + + +=============================================================================== +pandoc *ale-markdown-pandoc* + + *ale-options.markdown_pandoc_executable* + *g:ale_markdown_pandoc_executable* + *b:ale_markdown_pandoc_executable* +markdown_pandoc_executable +g:ale_markdown_pandoc_executable + Type: |String| + Default: `'pandoc'` + + This variable can be set to specify where to find the pandoc executable + + *ale-options.markdown_pandoc_options* + *g:ale_markdown_pandoc_options* + *b:ale_markdown_pandoc_options* +markdown_pandoc_options +g:ale_markdown_pandoc_options + Type: |String| + Default: `'-f gfm -t gfm -s -'` + + This variable can be set to change the default options passed to pandoc + + +=============================================================================== +prettier *ale-markdown-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +pymarkdown *ale-markdown-pymarkdown* + + *ale-options.markdown_pymarkdown_executable* + *g:ale_markdown_pymarkdown_executable* + *b:ale_markdown_pymarkdown_executable* +markdown_pymarkdown_executable +g:ale_markdown_pymarkdown_executable + Type: |String| + Default: `'pymarkdown'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pymarkdown'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pymarkdown'`. + + *ale-options.markdown_pymarkdown_options* + *g:ale_markdown_pymarkdown_options* + *b:ale_markdown_pymarkdown_options* +markdown_pymarkdown_options +g:ale_markdown_pymarkdown_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + pymarkdown invocation. + + *ale-options.markdown_pymarkdown_use_global* + *g:ale_markdown_pymarkdown_use_global* + *b:ale_markdown_pymarkdown_use_global* +markdown_pymarkdown_use_global +g:ale_markdown_pymarkdown_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.markdown_pymarkdown_auto_pipenv* + *g:ale_markdown_pymarkdown_auto_pipenv* + *b:ale_markdown_pymarkdown_auto_pipenv* +markdown_pymarkdown_auto_pipenv +g:ale_markdown_pymarkdown_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. + + *ale-options.markdown_pymarkdown_auto_poetry* + *g:ale_markdown_pymarkdown_auto_poetry* + *b:ale_markdown_pymarkdown_auto_poetry* +markdown_pymarkdown_auto_poetry +g:ale_markdown_pymarkdown_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.markdown_pymarkdown_auto_uv* + *g:ale_markdown_pymarkdown_auto_uv* + *b:ale_markdown_pymarkdown_auto_uv* +markdown_pymarkdown_auto_uv +g:ale_markdown_pymarkdown_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +remark-lint *ale-markdown-remark-lint* + + *ale-options.markdown_remark_lint_executable* + *g:ale_markdown_remark_lint_executable* + *b:ale_markdown_remark_lint_executable* +markdown_remark_lint_executable +g:ale_markdown_remark_lint_executable + Type: |String| + Default: `'remark'` + + See |ale-integrations-local-executables| + + *ale-options.markdown_remark_lint_options* + *g:ale_markdown_remark_lint_options* + *b:ale_markdown_remark_lint_options* +markdown_remark_lint_options +g:ale_markdown_remark_lint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to remark-lint. + + *ale-options.markdown_remark_lint_use_global* + *g:ale_markdown_remark_lint_use_global* + *b:ale_markdown_remark_lint_use_global* +markdown_remark_lint_use_global +g:ale_markdown_remark_lint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +textlint *ale-markdown-textlint* + +See |ale-text-textlint| + + +=============================================================================== +write-good *ale-markdown-write-good* + +See |ale-write-good-options| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-mercury.txt b/doc/ale-mercury.txt new file mode 100644 index 00000000..66d8a79b --- /dev/null +++ b/doc/ale-mercury.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Mercury Integration *ale-mercury-options* + + +=============================================================================== +mmc *ale-mercury-mmc* + + *ale-options.mercury_mmc_executable* + *g:ale_mercury_mmc_executable* + *b:ale_mercury_mmc_executable* +mercury_mmc_executable +g:ale_mercury_mmc_executable + Type: |String| + Default: `'mmc'` + + This variable can be changed to use a different executable for mmc. + + *ale-options.mercury_mmc_options* + *g:ale_mercury_mmc_options* + *b:ale_mercury_mmc_options* +mercury_mmc_options +g:ale_mercury_mmc_options + Type: |String| + Default: `'--make --output-compile-error-lines 100'` + + This variable can be set to pass additional options to mmc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nasm.txt b/doc/ale-nasm.txt new file mode 100644 index 00000000..15a74dcc --- /dev/null +++ b/doc/ale-nasm.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE NASM Integration *ale-nasm-options* + + +=============================================================================== +nasm *ale-nasm-nasm* + + *ale-options.nasm_nasm_executable* + *g:ale_nasm_nasm_executable* + *b:ale_nasm_nasm_executable* +nasm_nasm_executable +g:ale_nasm_nasm_executable + Type: |String| + Default `'nasm'` + + This variable can be changed to use different executable for NASM. + + *ale-options.nasm_nasm_options* + *g:ale_nasm_nasm_options* + *b:ale_nasm_nasm_options* +nasm_nasm_options +g:ale_nasm_nasm_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to NASM. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nickel.txt b/doc/ale-nickel.txt new file mode 100644 index 00000000..e057158a --- /dev/null +++ b/doc/ale-nickel.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Nickel Integration *ale-nickel-options* + + +=============================================================================== +nickel_format *ale-nickel-nickel-format* + + *ale-options.nickel_nickel_format_executable* + *g:ale_nickel_nickel_format_executable* + *b:ale_nickel_nickel_format_executable* +nickel_nickel_format_executable +g:ale_nickel_nickel_format_executable + Type: |String| + Default: `'nickel'` + + This option can be changed to change the path for `nickel`. + + *ale-options.nickel_nickel_format_options* + *g:ale_nickel_nickel_format_options* + *b:ale_nickel_nickel_format_options* +nickel_nickel_format_options +g:ale_nickel_nickel_format_options + Type: |String| + Default: `''` + + This option can be changed to pass extra options to `'nickel format'` + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nim.txt b/doc/ale-nim.txt new file mode 100644 index 00000000..c089fab2 --- /dev/null +++ b/doc/ale-nim.txt @@ -0,0 +1,51 @@ +=============================================================================== +ALE Nim Integration *ale-nim-options* + + +=============================================================================== +nimcheck *ale-nim-nimcheck* + + ALE does not provide additional configuration options for `nimcheck` at this + point. + + +=============================================================================== +nimlsp *ale-nim-nimlsp* + + *ale-options.nim_nimlsp_nim_sources* + *g:ale_nim_nimlsp_nim_sources* + *b:ale_nim_nimlsp_nim_sources* +g:ale_nim_nimlsp_nim_sources + Type: |String| + Default: `''` + + Sets the path to Nim source repository as the first argument to `nimlsp` + command. + + +=============================================================================== +nimpretty *ale-nim-nimpretty* + + *ale-options.nim_nimpretty_executable* + *g:ale_nim_nimpretty_executable* + *b:ale_nim_nimpretty_executable* +nim_nimpretty_executable +g:ale_nim_nimpretty_executable + Type: |String| + Default: `'nimpretty'` + + This variable can be changed to use a different executable for nimpretty. + + *ale-options.nim_nimpretty_options* + *g:ale_nim_nimpretty_options* + *b:ale_nim_nimpretty_options* +nim_nimpretty_options +g:ale_nim_nimpretty_options + Type: |String| + Default: `'--maxLineLen:80'` + + This variable can be changed to modify flags given to nimpretty. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nix.txt b/doc/ale-nix.txt new file mode 100644 index 00000000..2d97b45c --- /dev/null +++ b/doc/ale-nix.txt @@ -0,0 +1,151 @@ +=============================================================================== +ALE Nix Integration *ale-nix-options* + + +=============================================================================== +alejandra *ale-nix-alejandra* + + *ale-options.nix_alejandra_executable* + *g:ale_nix_alejandra_executable* + *b:ale_nix_alejandra_executable* +nix_alejandra_executable +g:ale_nix_alejandra_executable + Type: |String| + Default: `'alejandra'` + + This variable sets the executable used for alejandra. + + *ale-options.nix_alejandra_options* + *g:ale_nix_alejandra_options* + *b:ale_nix_alejandra_options* +nix_alejandra_options +g:ale_nix_alejandra_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the alejandra fixer. + + +=============================================================================== +nixfmt *ale-nix-nixfmt* + + *ale-options.nix_nixfmt_executable* + *g:ale_nix_nixfmt_executable* + *b:ale_nix_nixfmt_executable* +nix_nixfmt_executable +g:ale_nix_nixfmt_executable + Type: |String| + Default: `'nixfmt'` + + This variable sets the executable used for nixfmt. + + *ale-options.nix_nixfmt_options* + *g:ale_nix_nixfmt_options* + *b:ale_nix_nixfmt_options* +nix_nixfmt_options +g:ale_nix_nixfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the nixfmt fixer. + + +=============================================================================== +nixpkgs-fmt *ale-nix-nixpkgs-fmt* + + *ale-options.nix_nixpkgsfmt_executable* + *g:ale_nix_nixpkgsfmt_executable* + *b:ale_nix_nixpkgsfmt_executable* +nix_nixpkgsfmt_executable +g:ale_nix_nixpkgsfmt_executable + Type: |String| + Default: `'nixpkgs-fmt'` + + This variable sets executable used for nixpkgs-fmt. + + *ale-options.nix_nixpkgsfmt_options* + *g:ale_nix_nixpkgsfmt_options* + *b:ale_nix_nixpkgsfmt_options* +nix_nixpkgsfmt_options +g:ale_nix_nixpkgsfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the nixpkgs-fmt + fixer. + + +=============================================================================== +statix *ale-nix-statix* + + *ale-options.nix_statix_check_executable* + *g:ale_nix_statix_check_executable* + *b:ale_nix_statix_check_executable* +nix_statix_check_executable +g:ale_nix_statix_check_executable + Type: |String| + Default: `'statix'` + + This variable sets the executable used for statix when running it as a + linter. + + *ale-options.nix_statix_check_options* + *g:ale_nix_statix_check_options* + *b:ale_nix_statix_check_options* +nix_statix_check_options +g:ale_nix_statix_check_options + Type: |String| + Default: `''` + + This variable can be used to pass additional options to statix when running + it as a linter. + + *ale-options.nix_statix_fix_executable* + *g:ale_nix_statix_fix_executable* + *b:ale_nix_fix_check_executable* +nix_statix_fix_executable +g:ale_nix_statix_fix_executable + Type: |String| + Default: `'statix'` + + This variable sets the executable used for statix when running it as a + fixer. + + *ale-options.nix_statix_fix_options* + *g:ale_nix_statix_fix_options* + *b:ale_nix_statix_fix_options* +nix_statix_fix_options +g:ale_nix_statix_fix_options + Type: |String| + Default: `''` + + This variable can be used to pass additional options to statix when running + it as a fixer. + + +=============================================================================== +deadnix *ale-nix-deadnix* + + *ale-options.nix_deadnix_executable* + *g:ale_nix_deadnix_executable* + *b:ale_nix_deadnix_executable* +nix_deadnix_executable +g:ale_nix_deadnix_executable + Type: |String| + Default: `'deadnix'` + + This variable sets the executable used for deadnix. + + *ale-options.nix_deadnix_options* + *g:ale_nix_deadnix_options* + *b:ale_nix_deadnix_options* +nix_deadnix_options +g:ale_nix_deadnix_options + Type: |String| + Default: `''` + + This variable can be used to pass additional options to deadnix. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nroff.txt b/doc/ale-nroff.txt new file mode 100644 index 00000000..62ec7896 --- /dev/null +++ b/doc/ale-nroff.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE nroff Integration *ale-nroff-options* + + +=============================================================================== +write-good *ale-nroff-write-good* + +See |ale-write-good-options| + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nunjucks.txt b/doc/ale-nunjucks.txt new file mode 100644 index 00000000..d0e1db26 --- /dev/null +++ b/doc/ale-nunjucks.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Nunjucks Integration *ale-nunjucks-options* + + + +=============================================================================== +djlint *ale-nunjucks-djlint* + +See |ale-html-djlint| + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-objc.txt b/doc/ale-objc.txt new file mode 100644 index 00000000..526def67 --- /dev/null +++ b/doc/ale-objc.txt @@ -0,0 +1,94 @@ +=============================================================================== +ALE Objective-C Integration *ale-objc-options* + + +=============================================================================== +ccls *ale-objc-ccls* + + *ale-options.objc_ccls_executable* + *g:ale_objc_ccls_executable* + *b:ale_objc_ccls_executable* +objc_ccls_executable +g:ale_objc_ccls_executable + Type: |String| + Default: `'ccls'` + + This variable can be changed to use a different executable for ccls. + + *ale-options.objc_ccls_init_options* + *g:ale_objc_ccls_init_options* + *b:ale_objc_ccls_init_options* +objc_ccls_init_options +g:ale_objc_ccls_init_options + Type: |Dictionary| + Default: `{}` + + This variable can be changed to customize ccls initialization options. + Example: > + + let g:ale_objc_ccls_init_options = { + \ 'cacheDirectory': '/tmp/ccls', + \ 'cacheFormat': 'binary', + \ 'diagnostics': { + \ 'onOpen': 0, + \ 'opChange': 1000, + \ }, + \} +< + Visit https://github.com/MaskRay/ccls/wiki/Initialization-options for all + available options and explanations. + + +=============================================================================== +clang *ale-objc-clang* + + *ale-options.objc_clang_options* + *g:ale_objc_clang_options* + *b:ale_objc_clang_options* +objc_clang_options +g:ale_objc_clang_options + Type: |String| + Default: `'-std=c11 -Wall'` + + This variable can be changed to modify flags given to clang. + + +=============================================================================== +clang-format *ale-objc-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for Objective-C. + + +=============================================================================== +clangd *ale-objc-clangd* + + *ale-options.objc_clangd_executable* + *g:ale_objc_clangd_executable* + *b:ale_objc_clangd_executable* +objc_clangd_executable +g:ale_objc_clangd_executable + Type: |String| + Default: `'clangd'` + + This variable can be changed to use a different executable for clangd. + + *ale-options.objc_clangd_options* + *g:ale_objc_clangd_options* + *b:ale_objc_clangd_options* +objc_clangd_options +g:ale_objc_clangd_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clangd. + + +=============================================================================== +uncrustify *ale-objc-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-objcpp.txt b/doc/ale-objcpp.txt new file mode 100644 index 00000000..97662778 --- /dev/null +++ b/doc/ale-objcpp.txt @@ -0,0 +1,50 @@ +=============================================================================== +ALE Objective-C++ Integration *ale-objcpp-options* + + +=============================================================================== +clang *ale-objcpp-clang* + + *ale-options.objcpp_clang_options* + *g:ale_objcpp_clang_options* + *b:ale_objcpp_clang_options* +objcpp_clang_options +g:ale_objcpp_clang_options + Type: |String| + Default: `'-std=c++14 -Wall'` + + This variable can be changed to modify flags given to clang. + + +=============================================================================== +clangd *ale-objcpp-clangd* + + *ale-options.objcpp_clangd_executable* + *g:ale_objcpp_clangd_executable* + *b:ale_objcpp_clangd_executable* +objcpp_clangd_executable +g:ale_objcpp_clangd_executable + Type: |String| + Default: `'clangd'` + + This variable can be changed to use a different executable for clangd. + + *ale-options.objcpp_clangd_options* + *g:ale_objcpp_clangd_options* + *b:ale_objcpp_clangd_options* +objcpp_clangd_options +g:ale_objcpp_clangd_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clangd. + + +=============================================================================== +uncrustify *ale-objcpp-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-ocaml.txt b/doc/ale-ocaml.txt new file mode 100644 index 00000000..97feb35f --- /dev/null +++ b/doc/ale-ocaml.txt @@ -0,0 +1,151 @@ +=============================================================================== +ALE OCaml Integration *ale-ocaml-options* + + +=============================================================================== +dune *ale-ocaml-dune* + + Dune is a build system for OCaml projects. The `dune format` command is + supported for automatically formatting `dune` and `dune-project` files. + + *ale-options.ocaml_dune_executable* + *g:ale_ocaml_dune_executable* + *b:ale_ocaml_dune_executable* +ocaml_dune_executable +g:ale_ocaml_dune_executable + Type: |String| + Default: `'dune'` + + This variable can be set to pass the path to dune. + + *ale-options.ocaml_dune_options* + *g:ale_ocaml_dune_options* + *b:ale_ocaml_dune_options* +ocaml_dune_options +g:ale_ocaml_dune_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the dune fixer. + + +=============================================================================== +merlin *ale-ocaml-merlin* + + To use merlin linter for OCaml source code you need to make sure Merlin for + Vim is correctly configured. See the corresponding Merlin wiki page for + detailed instructions + (https://github.com/the-lambda-church/merlin/wiki/vim-from-scratch). + + +=============================================================================== +ocamllsp *ale-ocaml-ocamllsp* + + The `ocaml-lsp-server` is the official OCaml implementation of the Language + Server Protocol. See the installation instructions: + https://github.com/ocaml/ocaml-lsp#installation + + *ale-options.ocaml_ocamllsp_use_opam* + *g:ale_ocaml_ocamllsp_use_opam* + *b:ale_ocaml_ocamllsp_use_opam* +ocaml_ocamllsp_use_opam +g:ale_ocaml_ocamllsp_use_opam + Type: |Number| + Default: `get(g:, 'ale_ocaml_ocamllsp_use_opam', 1)` + + This variable can be set to change whether or not opam is used to execute + the language server. + +=============================================================================== +ols *ale-ocaml-ols* + + The `ocaml-language-server` is the engine that powers OCaml and ReasonML + editor support using the Language Server Protocol. See the installation + instructions: + https://github.com/freebroccolo/ocaml-language-server#installation + + *ale-options.ocaml_ols_executable* + *g:ale_ocaml_ols_executable* + *b:ale_ocaml_ols_executable* +ocaml_ols_executable +g:ale_ocaml_ols_executable + Type: |String| + Default: `'ocaml-language-server'` + + This variable can be set to change the executable path for `ols`. + + *ale-options.ocaml_ols_use_global* + *g:ale_ocaml_ols_use_global* + *b:ale_ocaml_ols_use_global* +ocaml_ols_use_global +g:ale_ocaml_ols_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable can be set to `1` to always use the globally installed + executable. See also |ale-integrations-local-executables|. + + +=============================================================================== +ocamlformat *ale-ocaml-ocamlformat* + + *ale-options.ocaml_ocamlformat_executable* + *g:ale_ocaml_ocamlformat_executable* + *b:ale_ocaml_ocamlformat_executable* +ocaml_ocamlformat_executable +g:ale_ocaml_ocamlformat_executable + Type: |String| + Default: `'ocamlformat'` + + This variable can be set to pass the path of the ocamlformat fixer. + + *ale-options.ocaml_ocamlformat_options* + *g:ale_ocaml_ocamlformat_options* + *b:ale_ocaml_ocamlformat_options* +ocaml_ocamlformat_options +g:ale_ocaml_ocamlformat_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the ocamlformat fixer. + + +=============================================================================== +ocp-indent *ale-ocaml-ocp-indent* + + *ale-options.ocaml_ocp_indent_executable* + *g:ale_ocaml_ocp_indent_executable* + *b:ale_ocaml_ocp_indent_executable* +ocaml_ocp_indent_executable +g:ale_ocaml_ocp_indent_executable + Type: |String| + Default: `ocp-indent` + + This variable can be set to pass the path of the ocp-indent. + + *ale-options.ocaml_ocp_indent_options* + *g:ale_ocaml_ocp_indent_options* + *b:ale_ocaml_ocp_indent_options* +ocaml_ocp_indent_options +g:ale_ocaml_ocp_indent_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the ocp-indent. + + *ale-options.ocaml_ocp_indent_config* + *g:ale_ocaml_ocp_indent_config* + *b:ale_ocaml_ocp_indent_config* +ocaml_ocp_indent_config +g: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-odin.txt b/doc/ale-odin.txt new file mode 100644 index 00000000..70011728 --- /dev/null +++ b/doc/ale-odin.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Odin Integration *ale-odin-options* + *ale-integration-odin* + +=============================================================================== +ols *ale-odin-ols* + + *ale-options.odin_ols_executable* + *g:ale_odin_ols_executable* + *b:ale_odin_ols_executable* +odin_ols_executable +g:ale_odin_ols_executable + Type: |String| + Default: `'ols'` + + This variable can be modified to change the executable path for `ols`. + + *ale-options.odin_ols_config* + *g:ale_odin_ols_config* + *b:ale_odin_ols_config* +odin_ols_config +g:ale_odin_ols_config + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for ols. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-openapi.txt b/doc/ale-openapi.txt new file mode 100644 index 00000000..0b93288b --- /dev/null +++ b/doc/ale-openapi.txt @@ -0,0 +1,81 @@ +=============================================================================== +ALE OpenApi Integration *ale-openapi-options* + + +=============================================================================== +ibm_validator *ale-openapi-ibm-validator* + +Website: https://github.com/IBM/openapi-validator + + +------------------------------------------------------------------------------- +Installation + +Install ibm-openapi-validator either globally or locally: > + + npm install ibm-openapi-validator -g # global + npm install ibm-openapi-validator # local +< + +------------------------------------------------------------------------------- +Configuration + +OpenAPI files can be written in YAML or JSON so in order for ALE plugins to +work with these files we must set the buffer |filetype| to either |openapi.yaml| +or |openapi.json| respectively. This causes ALE to lint the file with linters +configured for openapi and yaml files or openapi and json files respectively. + +For example setting filetype to |openapi.yaml| on a buffer and the following +|g:ale_linters| configuration will enable linting of openapi files using both +|ibm_validator| and |yamlint|: + +> + let g:ale_linters = { + \ 'yaml': ['yamllint'], + \ 'openapi': ['ibm_validator'] + \} +< + +The following plugin will detect openapi files automatically and set the +filetype to |openapi.yaml| or |openapi.json|: + + https://github.com/hsanson/vim-openapi + + +------------------------------------------------------------------------------- +Options + *ale-options.openapi_ibm_validator_executable* + *g:ale_openapi_ibm_validator_executable* + *b:ale_openapi_ibm_validator_executable* +openapi_ibm_validator_executable +g:ale_openapi_ibm_validator_executable + Type: |String| + Default: `'lint-openapi'` + + This variable can be set to change the path to lint-openapi. + + *ale-options.openapi_ibm_validator_options* + *g:ale_openapi_ibm_validator_options* + *b:ale_openapi_ibm_validator_options* +openapi_ibm_validator_options +g:ale_openapi_ibm_validator_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to lint-openapi. + + +=============================================================================== +prettier *ale-openapi-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +yamllint *ale-openapi-yamllint* + +See |ale-yaml-yamllint| for information about the available options. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-openscad.txt b/doc/ale-openscad.txt new file mode 100644 index 00000000..adcc746c --- /dev/null +++ b/doc/ale-openscad.txt @@ -0,0 +1,54 @@ +=============================================================================== +ALE OpenSCAD Integration *ale-openscad-options* + + +=============================================================================== +sca2d *ale-openscad-sca2d* + + *ale-options.openscad_sca2d_executable* + *g:ale_openscad_sca2d_executable* + *b:ale_openscad_sca2d_executable* +openscad_sca2d_executable +g:ale_openscad_sca2d_executable + Type: |String| + Default: `'sca2d'` + + See |ale-integrations-local-executables| + + *ale-options.openscad_sca2d_options* + *g:ale_openscad_sca2d_options* + *b:ale_openscad_sca2d_options* +openscad_sca2d_options +g:ale_openscad_sca2d_options + Type: |String| + Default: `''` + + This variable can be set to pass options to sca2d. + + +=============================================================================== +scadformat *ale-openscad-scadformat* + + *ale-options.openscad_scadformat_executable* + *g:ale_openscad_scadformat_executable* + *b:ale_openscad_scadformat_executable* +openscad_scadformat_executable +g:ale_openscad_scadformat_executable + Type: |String| + Default: `'scadformat'` + + See |ale-integrations-local-executables| + + *ale-options.openscad_scadformat_options* + *g:ale_openscad_scadformat_options* + *b:ale_openscad_scadformat_options* +openscad_scadformat_options +g:ale_openscad_scadformat_options + Type: |String| + Default: `''` + + This variable can be set to pass options to scadformat. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-packer.txt b/doc/ale-packer.txt new file mode 100644 index 00000000..689a68b6 --- /dev/null +++ b/doc/ale-packer.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Packer Integration *ale-packer-options* + + +=============================================================================== +packer-fmt-fixer *ale-packer-fmt-fixer* + + *ale-options.packer_fmt_executable* + *g:ale_packer_fmt_executable* + *b:ale_packer_fmt_executable* +packer_fmt_executable +g:ale_packer_fmt_executable + Type: |String| + Default: `'packer'` + + This variable can be changed to use a different executable for packer. + + *ale-options.packer_fmt_options* + *g:ale_packer_fmt_options* + *b:ale_packer_fmt_options* +packer_fmt_options +g:ale_packer_fmt_options + Type: |String| + Default: `''` + + This variable can be set to change command lines options for `packer fmt` + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pascal.txt b/doc/ale-pascal.txt new file mode 100644 index 00000000..e9b7688b --- /dev/null +++ b/doc/ale-pascal.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Pascal Integration *ale-pascal-options* + + +=============================================================================== +ptop *ale-pascal-ptop* + + *ale-options.pascal_ptop_executable* + *g:ale_pascal_ptop_executable* + *b:ale_pascal_ptop_executable* +pascal_ptop_executable +g:ale_pascal_ptop_executable + Type: |String| + Default: `'ptop'` + + This variable can be changed to specify the ptop executable. + + *ale-options.pascal_ptop_options* + *g:ale_pascal_ptop_options* + *b:ale_pascal_ptop_options* +pascal_ptop_options +g:ale_pascal_ptop_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the ptop fixer. + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pawn.txt b/doc/ale-pawn.txt new file mode 100644 index 00000000..f836df97 --- /dev/null +++ b/doc/ale-pawn.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Pawn Integration *ale-pawn-options* + + +=============================================================================== +uncrustify *ale-pawn-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-perl.txt b/doc/ale-perl.txt new file mode 100644 index 00000000..0f7553c2 --- /dev/null +++ b/doc/ale-perl.txt @@ -0,0 +1,110 @@ +=============================================================================== +ALE Perl Integration *ale-perl-options* + +ALE offers a few ways to check Perl code. Checking code with `perl` is +disabled by default, as `perl` code cannot be checked without executing it. +Specifically, we use the `-c` flag to see if `perl` code compiles. This does +not execute all of the code in a file, but it does run `BEGIN` and `CHECK` +blocks. See `perl --help` and https://stackoverflow.com/a/12908487/406224 + +See |g:ale_linters|. + + +=============================================================================== +perl *ale-perl-perl* + + *ale-options.perl_perl_executable* + *g:ale_perl_perl_executable* + *b:ale_perl_perl_executable* +perl_perl_executable +g:ale_perl_perl_executable + Type: |String| + Default: `'perl'` + + This variable can be changed to modify the executable used for linting perl. + + *ale-options.perl_perl_options* + *g:ale_perl_perl_options* + *b:ale_perl_perl_options* +perl_perl_options +g:ale_perl_perl_options + Type: |String| + Default: `'-c -Mwarnings -Ilib'` + + This variable can be changed to alter the command-line arguments to the perl + invocation. + + +=============================================================================== +perlcritic *ale-perl-perlcritic* + + *ale-options.perl_perlcritic_executable* + *g:ale_perl_perlcritic_executable* + *b:ale_perl_perlcritic_executable* +perl_perlcritic_executable +g:ale_perl_perlcritic_executable + Type: |String| + Default: `'perlcritic'` + + This variable can be changed to modify the perlcritic executable used for + linting perl. + + *ale-options.perl_perlcritic_profile* + *g:ale_perl_perlcritic_profile* + *b:ale_perl_perlcritic_profile* +perl_perlcritic_profile +g:ale_perl_perlcritic_profile + Type: |String| + Default: `'.perlcriticrc'` + + This variable can be changed to modify the perlcritic profile used for + linting perl. The current directory is checked for the file, then the + parent directory, etc, until it finds one. If no matching file is found, no + profile is passed to perlcritic. + + Set to an empty string to disable passing a specific profile to perlcritic + with the `'--profile'` option. + + To prevent perlcritic from using any profile, set this variable to an empty + string and pass `'--no-profile'`to perlcritic via the + |g:ale_perl_perlcritic_options| variable. + + *ale-options.perl_perlcritic_options* + *g:ale_perl_perlcritic_options* + *b:ale_perl_perlcritic_options* +perl_perlcritic_options +g:ale_perl_perlcritic_options + Type: |String| + Default: `''` + + This variable can be changed to supply additional command-line arguments to + the perlcritic invocation. + + *ale-options.perl_perlcritic_showrules* + *g:ale_perl_perlcritic_showrules* +perl_perlcritic_showrules +g:ale_perl_perlcritic_showrules + Type: |Number| + Default: `0` + + Controls whether perlcritic rule names are shown after the error message. + Defaults to off to reduce length of message. + + +=============================================================================== +perltidy *ale-perl-perltidy* + + *ale-options.perl_perltidy_options* + *g:ale_perl_perltidy_options* + *b:ale_perl_perltidy_options* +perl_perltidy_options +g:ale_perl_perltidy_options + Type: |String| + Default: `''` + + This variable can be changed to alter the command-line arguments to + the perltidy invocation. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-perl6.txt b/doc/ale-perl6.txt new file mode 100644 index 00000000..be1cdf3a --- /dev/null +++ b/doc/ale-perl6.txt @@ -0,0 +1,49 @@ +=============================================================================== +ALE Perl6 Integration *ale-perl6-options* + +Checking code with `perl6` is disabled by default, as `perl6` code cannot be +checked without executing it. Specifically, we use the `-c` flag to see if +`perl6` code compiles. This does not execute all of the code in a file, but it +does run `BEGIN` and `CHECK` blocks. See `perl6 --help` + +Full support requires a perl6 implementation that supports the +PERL6_EXCEPTIONS_HANDLER environment variable and JSON error output, +which was specified in 6.d. Rakudo version 2018.08 is the first rakudo release +that supports this. See `perl6 --version` and +https://docs.perl6.org/programs/03-environment-variables. + +Without this variable, errors and warnings will appear at line 1, and can be +viewed with ALEDetail. This also serves as a fallback for errors and warnings +that do not trigger JSON output. + +See |g:ale_linters|. + + +=============================================================================== +perl6 *ale-perl6-perl6* + + *ale-options.perl6_perl6_executable* + *g:ale_perl6_perl6_executable* + *b:ale_perl6_perl6_executable* +perl6_perl6_executable +g:ale_perl6_perl6_executable + Type: |String| + Default: `'perl6'` + + This variable can be changed to modify the executable used for linting + perl6. + + *ale-options.perl6_perl6_options* + *g:ale_perl6_perl6_options* + *b:ale_perl6_perl6_options* +perl6_perl6_options +g:ale_perl6_perl6_options + Type: |String| + Default: `'-c -Ilib'` + + This variable can be changed to alter the command-line arguments to the + perl6 invocation. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-php.txt b/doc/ale-php.txt new file mode 100644 index 00000000..d12935bd --- /dev/null +++ b/doc/ale-php.txt @@ -0,0 +1,451 @@ +=============================================================================== +ALE PHP Integration *ale-php-options* + + +=============================================================================== +cspell *ale-php-cspell* + +See |ale-cspell-options| + + +=============================================================================== +langserver *ale-php-langserver* + + *ale-options.php_langserver_executable* + *g:ale_php_langserver_executable* + *b:ale_php_langserver_executable* +php_langserver_executable +g:ale_php_langserver_executable + Type: |String| + Default: `'php-language-server.php'` + + The variable can be set to configure the executable that will be used for + running the PHP language server. `vendor` directory executables will be + preferred instead of this setting if |g:ale_php_langserver_use_global| is `0`. + + See: |ale-integrations-local-executables| + + *ale-options.php_langserver_use_global* + *g:ale_php_langserver_use_global* + *b:ale_php_langserver_use_global* +php_langserver_use_global +g:ale_php_langserver_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable can be set to `1` to force the language server to be run with + the executable set for |g:ale_php_langserver_executable|. + + See: |ale-integrations-local-executables| + + +=============================================================================== +phan *ale-php-phan* + +WARNING: please use the phan_client linter if you have an configuration file +for your project because the phan will look into your entirely project and +ale will display in the current buffer warnings that may belong to other file. + + *ale-options.php_phan_minimum_severity* + *g:ale_php_phan_minimum_severity* + *b:ale_php_phan_minimum_severity* +php_phan_minimum_severity +g:ale_php_phan_minimum_severity + Type: |Number| + Default: `0` + + This variable defines the minimum severity level. + + *ale-options.php_phan_executable* + *g:ale_php_phan_executable* + *b:ale_php_phan_executable* +php_phan_executable +g:ale_php_phan_executable + Type: |String| + Default: `'phan'` + + This variable sets executable used for phan or phan_client. + + *ale-options.php_phan_use_client* + *g:ale_php_phan_use_client* + *b:ale_php_phan_use_client* +php_phan_use_client +g:ale_php_phan_use_client + Type: |Number| + Default: `get(g:, 'ale_php_phan_use_client', 0)` + + This variable can be set to 1 to use the phan_client with phan daemon mode + instead of the phan standalone. + + +=============================================================================== +phpcbf *ale-php-phpcbf* + + *ale-options.php_phpcbf_executable* + *g:ale_php_phpcbf_executable* + *b:ale_php_phpcbf_executable* +php_phpcbf_executable +g:ale_php_phpcbf_executable + Type: |String| + Default: `'phpcbf'` + + See |ale-integrations-local-executables| + + *ale-options.php_phpcbf_standard* + *g:ale_php_phpcbf_standard* + *b:ale_php_phpcbf_standard* +php_phpcbf_standard +g:ale_php_phpcbf_standard + Type: |String| + Default: `''` + + This variable can be set to specify the coding standard used by phpcbf. If no + coding standard is specified, phpcbf will default to fixing against the + PEAR coding standard, or the standard you have set as the default. + + *ale-options.php_phpcbf_use_global* + *g:ale_php_phpcbf_use_global* + *b:ale_php_phpcbf_use_global* +php_phpcbf_use_global +g:ale_php_phpcbf_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.php_phpcbf_options* + *g:ale_php_phpcbf_options* + *b:ale_php_phpcbf_options* +php_phpcbf_options +g:ale_php_phpcbf_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to php-cbf + + +=============================================================================== +phpcs *ale-php-phpcs* + + *ale-options.php_phpcs_executable* + *g:ale_php_phpcs_executable* + *b:ale_php_phpcs_executable* +php_phpcs_executable +g:ale_php_phpcs_executable + Type: |String| + Default: `'phpcs'` + + See |ale-integrations-local-executables| + + *ale-options.php_phpcs_standard* + *g:ale_php_phpcs_standard* + *b:ale_php_phpcs_standard* +php_phpcs_standard +g:ale_php_phpcs_standard + Type: |String| + Default: `''` + + This variable can be set to specify the coding standard used by phpcs. If no + coding standard is specified, phpcs will default to checking against the + PEAR coding standard, or the standard you have set as the default. + + *ale-options.php_phpcs_use_global* + *g:ale_php_phpcs_use_global* + *b:ale_php_phpcs_use_global* +php_phpcs_use_global +g:ale_php_phpcs_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.php_phpcs_options* + *g:ale_php_phpcs_options* + *b:ale_php_phpcs_options* +php_phpcs_options +g:ale_php_phpcs_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to php-cs + + +=============================================================================== +phpmd *ale-php-phpmd* + + *ale-options.php_phpmd_executable* + *g:ale_php_phpmd_executable* + *b:ale_php_phpmd_executable* +php_phpmd_executable +g:ale_php_phpmd_executable + Type: |String| + Default: `'phpmd'` + + This variable sets executable used for phpmd. + + *ale-options.php_phpmd_ruleset* + *g:ale_php_phpmd_ruleset* + *b:ale_php_phpmd_ruleset* +php_phpmd_ruleset +g:ale_php_phpmd_ruleset + Type: |String| + Default: `'cleancode,codesize,controversial,design,naming,unusedcode'` + + This variable controls the ruleset used by phpmd. Default is to use all of + the available phpmd rulesets + + +=============================================================================== +phpstan *ale-php-phpstan* + + *ale-options.php_phpstan_executable* + *g:ale_php_phpstan_executable* + *b:ale_php_phpstan_executable* +php_phpstan_executable +g:ale_php_phpstan_executable + Type: |String| + Default: `'phpstan'` + + This variable sets executable used for phpstan. + + *ale-options.php_phpstan_level* + *g:ale_php_phpstan_level* + *b:ale_php_phpstan_level* +php_phpstan_level +g:ale_php_phpstan_level + Type: |String| + Default: `''` + + This variable controls the rule levels. 0 is the loosest and 7 is the + strictest. If this option isn't set, the rule level will be controlled by + the configuration file. If no configuration file can be detected, `'7'` will + be used instead. + + *ale-options.php_phpstan_configuration* + *g:ale_php_phpstan_configuration* + *b:ale_php_phpstan_configuration* +php_phpstan_configuration +g:ale_php_phpstan_configuration + Type: |String| + Default: `''` + + This variable sets path to phpstan configuration file. + + *ale-options.php_phpstan_autoload* + *g:ale_php_phpstan_autoload* + *b:ale_php_phpstan_autoload* +php_phpstan_autoload +g:ale_php_phpstan_autoload + Type: |String| + Default: `''` + + This variable sets path to phpstan autoload file. + + *ale-options.php_phpstan_memory_limit* + *g:ale_php_phpstan_memory_limit* + *b:ale_php_phpstan_memory-limit* +php_phpstan_memory_limit +g:ale_php_phpstan_memory_limit + Type: |String| + Default: `''` + + This variable sets the memory limit for phpstan analysis. This is a string + in the same format as `php.ini` accepts, e.g. `128M`, `1G`. + + +=============================================================================== +psalm *ale-php-psalm* + + *ale-options.php_psalm_executable* + *g:ale_php_psalm_executable* + *b:ale_php_psalm_executable* +php_psalm_executable +g:ale_php_psalm_executable + Type: |String| + Default: `'psalm'` + + This variable sets the executable used for psalm. + + *ale-options.php_psalm_options* + *g:ale_php_psalm_options* + *b:ale_php_psalm_options* +php_psalm_options +g:ale_php_psalm_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to psalm. + + *ale-options.php_psalm_use_global* + *g:ale_php_psalm_use_global* + *b:ale_php_psalm_use_global* +php_psalm_use_global +g:ale_php_psalm_use_global + Type: |Boolean| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +php-cs-fixer *ale-php-php-cs-fixer* + + *ale-options.php_cs_fixer_executable* + *g:ale_php_cs_fixer_executable* + *b:ale_php_cs_fixer_executable* +php_cs_fixer_executable +g:ale_php_cs_fixer_executable + Type: |String| + Default: `'php-cs-fixer'` + + This variable sets executable used for php-cs-fixer. + + *ale-options.php_cs_fixer_options* + *g:ale_php_cs_fixer_options* + *b:ale_php_cs_fixer_options* +php_cs_fixer_options +g:ale_php_cs_fixer_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to php-cs-fixer. + + *ale-options.php_cs_fixer_use_global* + *g:ale_php_cs_fixer_use_global* + *b:ale_php_cs_fixer_use_global* +php_cs_fixer_use_global +g:ale_php_cs_fixer_use_global + Type: |Boolean| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +php *ale-php-php* + + *ale-options.php_php_executable* + *g:ale_php_php_executable* + *b:ale_php_php_executable* +php_php_executable +g:ale_php_php_executable + Type: |String| + Default: `'php'` + + This variable sets the executable used for php. + + +=============================================================================== +pint *ale-php-pint* + + *ale-options.php_pint_executable* + *g:ale_php_pint_executable* + *b:ale_php_pint_executable* +php_pint_executable +g:ale_php_pint_executable + Type: |String| + Default: `'pint'` + + This variable sets the executable used for pint. + + *ale-options.php_pint_options* + *g:ale_php_pint_options* + *b:ale_php_pint_options* +php_pint_options +g:ale_php_pint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to pint. + + *ale-options.php_pint_use_global* + *g:ale_php_pint_use_global* + *b:ale_php_pint_use_global* +php_pint_use_global +g:ale_php_pint_use_global + Type: |Boolean| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +tlint *ale-php-tlint* + + *ale-options.php_tlint_executable* + *g:ale_php_tlint_executable* + *b:ale_php_tlint_executable* +php_tlint_executable +g:ale_php_tlint_executable + Type: |String| + Default: `'tlint'` + + See |ale-integrations-local-executables| + + *ale-options.php_tlint_use_global* + *g:ale_php_tlint_use_global* + *b:ale_php_tlint_use_global* +php_tlint_use_global +g:ale_php_tlint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.php_tlint_options* + *g:ale_php_tlint_options* + *b:ale_php_tlint_options* +php_tlint_options +g:ale_php_tlint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to tlint + + +=============================================================================== +intelephense *ale-php-intelephense* + + *ale-options.php_intelephense_executable* + *g:ale_php_intelephense_executable* + *b:ale_php_intelephense_executable* +php_intelephense_executable +g:ale_php_intelephense_executable + Type: |String| + Default: `'intelephense'` + + The variable can be set to configure the executable that will be used for + running the intelephense language server. `node_modules` directory + executable will be preferred instead of this setting if + |g:ale_php_intelephense_use_global| is `0`. + + See: |ale-integrations-local-executables| + + *ale-options.php_intelephense_use_global* + *g:ale_php_intelephense_use_global* + *b:ale_php_intelephense_use_global* +php_intelephense_use_global +g:ale_php_intelephense_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable can be set to `1` to force the language server to be run with + the executable set for |g:ale_php_intelephense_executable|. + + See: |ale-integrations-local-executables| + + *ale-options.php_intelephense_config* + *g:ale_php_intelephense_config* + *b:ale_php_intelephense_config* +php_intelephense_config +g:ale_php_intelephense_config + Type: |Dictionary| + Default: `{}` + + The initialization options config specified by Intelephense. Refer to the + installation docs provided by intelephense (github.com/bmewburn/intelephense + -docs). + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-po.txt b/doc/ale-po.txt new file mode 100644 index 00000000..1e03b7bb --- /dev/null +++ b/doc/ale-po.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE PO Integration *ale-po-options* + + +=============================================================================== +write-good *ale-po-write-good* + +See |ale-write-good-options| + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pod.txt b/doc/ale-pod.txt new file mode 100644 index 00000000..c7cc0bbc --- /dev/null +++ b/doc/ale-pod.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Pod Integration *ale-pod-options* + + +=============================================================================== +write-good *ale-pod-write-good* + +See |ale-write-good-options| + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pony.txt b/doc/ale-pony.txt new file mode 100644 index 00000000..f634d007 --- /dev/null +++ b/doc/ale-pony.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Pony Integration *ale-pony-options* + + +=============================================================================== +ponyc *ale-pony-ponyc* + + *ale-options.pony_ponyc_executable* + *g:ale_pony_ponyc_executable* + *b:ale_pony_ponyc_executable* +pony_ponyc_executable +g:ale_pony_ponyc_executable + Type: |String| + Default: `'ponyc'` + + See |ale-integrations-local-executables| + + *ale-options.pony_ponyc_options* + *g:ale_pony_ponyc_options* + *b:ale_pony_ponyc_options* +pony_ponyc_options +g:ale_pony_ponyc_options + Type: |String| + Default: `'--pass paint'` + + This variable can be set to pass options to ponyc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-powershell.txt b/doc/ale-powershell.txt new file mode 100644 index 00000000..64f601ed --- /dev/null +++ b/doc/ale-powershell.txt @@ -0,0 +1,86 @@ +=============================================================================== +ALE PowerShell Integration *ale-powershell-options* + + +=============================================================================== +cspell *ale-powershell-cspell* + +See |ale-cspell-options| + + +=============================================================================== +powershell *ale-powershell-powershell* + + *ale-options.powershell_powershell_executable* + *g:ale_powershell_powershell_executable* + *b:ale_powershell_powershell_executable* +powershell_powershell_executable +g: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. + + +------------------------------------------------------------------------------- +Options + *ale-options.powershell_psscriptanalyzer_executable* + *g:ale_powershell_psscriptanalyzer_executable* + *b:ale_powershell_psscriptanalyzer_executable* +powershell_psscriptanalyzer_executable +g: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' +< + + *ale-options.powershell_psscriptanalyzer_module* + *g:ale_powershell_psscriptanalyzer_module* + *b:ale_powershell_psscriptanalyzer_module* +powershell_psscriptanalyzer_module +g:ale_powershell_psscriptanalyzer_module + Type: |String| + Default: `'psscriptanalyzer'` + + This variable sets the name of the psscriptanalyzer module. + for psscriptanalyzer invocation. + + *ale-options.powershell_psscriptanalyzer_exclusions* + *g:ale_powershell_psscriptanalyzer_exclusions* + *b:ale_powershell_psscriptanalyzer_exclusions* +powershell_psscriptanalyzer_exclusions +g: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-prolog.txt b/doc/ale-prolog.txt new file mode 100644 index 00000000..334ff723 --- /dev/null +++ b/doc/ale-prolog.txt @@ -0,0 +1,71 @@ +=============================================================================== +ALE Prolog Integration *ale-prolog-options* + + +=============================================================================== +swipl *ale-prolog-swipl* + + *ale-options.prolog_swipl_executable* + *g:ale_prolog_swipl_executable* + *b:ale_prolog_swipl_executable* +prolog_swipl_executable +g:ale_prolog_swipl_executable + Type: |String| + Default: `'swipl'` + + The executable that will be run for the `swipl` linter. + + *ale-options.prolog_swipl_load* + *g:ale_prolog_swipl_load* + *b:ale_prolog_swipl_load* +prolog_swipl_load +g:ale_prolog_swipl_load + Type: |String| + Default: `'current_prolog_flag(argv, [File]), load_files(File, [sandboxed(true)]), halt.'` + + The prolog goals that will be passed to |g:ale_prolog_swipl_executable| with `-g` option. + + It does: + 1. Takes the first command argument (current file path) + 2. Checks (syntactic / semantic) problems and output to stderr + + NOTE: `sandboxed(true)` prohibits executing some directives such as 'initialization main'. + + *ale-options.prolog_swipl_timeout* + *g:ale_prolog_swipl_timeout* + *b:ale_prolog_swipl_timeout* +prolog_swipl_timeout +g:ale_prolog_swipl_timeout + Type: |Number| + Default: `3` + + Timeout seconds to detect long-running linter. + It is done by setting SIGALRM. + See |g:ale_prolog_swipl_alarm| and |g:ale_prolog_swipl_alarm_handler|. + + *ale-options.prolog_swipl_alarm* + *g:ale_prolog_swipl_alarm* + *b:ale_prolog_swipl_alarm* +prolog_swipl_alarm +g:ale_prolog_swipl_alarm + Type: |String| + Default: `'alarm(%t, (%h), _, [])'` + + The prolog goals to be expected to set SIGALRM. + `%t` is replaced by |g:ale_prolog_swipl_timeout|. + `%h` is replaced by |g:ale_prolog_swipl_alarm_handler|. + + *ale-options.prolog_swipl_alarm_handler* + *g:ale_prolog_swipl_alarm_handler* + *b:ale_prolog_swipl_alarm_handler* +prolog_swipl_alarm_handler +g:ale_prolog_swipl_alarm_handler + Type: |String| + Default: `'writeln(user_error, "ERROR: Exceeded %t seconds, Please change g:prolog_swipl_timeout to modify the limit."), halt(1)'` + + The prolog goals to be expected that will be run on SIGALRM. + `%t` is replaced by |g:ale_prolog_swipl_timeout|. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-proto.txt b/doc/ale-proto.txt new file mode 100644 index 00000000..1f427f8f --- /dev/null +++ b/doc/ale-proto.txt @@ -0,0 +1,122 @@ +=============================================================================== +ALE Proto Integration *ale-proto-options* + + +=============================================================================== +Integration Information + +To enable `.proto` file linting, update |g:ale_linters| as appropriate: +> + " Enable linter for .proto files + let g:ale_linters = {'proto': ['buf-lint', 'protoc-gen-lint', 'protolint']} +< +To enable `.proto` file fixing, update |g:ale_fixers| as appropriate: +> + " Enable linter for .proto files + let b:ale_fixers = {'proto': ['buf-format', 'protolint']} +< + +=============================================================================== +buf-format *ale-proto-buf-format* + + The formatter uses `buf`, a fully-featured Protobuf compiler that doesn't depend + on `protoc`. Make sure the `buf` binary is available in the system path, or + set ale_proto_buf_format_executable. + + *ale-options.proto_buf_format_executable* + *g:ale_proto_buf_format_executable* +proto_buf_format_executable +g:ale_proto_buf_format_executable + Type: |String| + Default: `'buf'` + + This variable can be changed to modify the executable used for buf. + + +=============================================================================== +buf-lint *ale-proto-buf-lint* + + The linter uses `buf`, a fully-featured Protobuf compiler that doesn't depend + on `protoc`. Make sure the `buf` binary is available in the system path, or + set ale_proto_buf_lint_executable. + + *ale-options.proto_buf_lint_executable* + *g:ale_proto_buf_lint_executable* +proto_buf_lint_executable +g:ale_proto_buf_lint_executable + Type: |String| + Default: `'buf'` + + This variable can be changed to modify the executable used for buf. + + *ale-options.proto_buf_lint_config* + *g:ale_proto_buf_lint_config* +proto_buf_lint_config +g:ale_proto_buf_lint_config + Type: |String| + Default: `''` + + A path to a buf configuration file. + + The path to the configuration file can be an absolute path or a relative + path. ALE will search for the relative path in parent directories. + + +=============================================================================== +clang-format *ale-proto-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for Proto. + + +=============================================================================== +protoc-gen-lint *ale-proto-protoc-gen-lint* + + The linter is a plugin for the `protoc` binary. As long as the binary resides + in the system path, `protoc` will find it. + + *ale-options.proto_protoc_gen_lint_options* + *g:ale_proto_protoc_gen_lint_options* +proto_protoc_gen_lint_options +g:ale_proto_protoc_gen_lint_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to protoc. Note that the + directory of the linted file is always passed as an include path with '-I' + before any user-supplied options. + + +=============================================================================== +protolint *ale-proto-protolint* + + The linter is a pluggable tool that doesn't depend on the `protoc` binary. + This supports both linting and fixing. + Make sure the binary is available in the system path, or set + ale_proto_protolint_executable. + Note that the binary with v0.22.0 or above is supported. + + *ale-options.proto_protolint_executable* + *g:ale_proto_protolint_executable* +proto_protolint_executable +g:ale_proto_protolint_executable + Type: |String| + Default: `'protolint'` + + This variable can be changed to modify the executable used for protolint. + + *ale-options.proto_protolint_config* + *g:ale_proto_protolint_config* +proto_protolint_config +g:ale_proto_protolint_config + Type: |String| + Default: `''` + + A path to a protolint configuration file. + + The path to the configuration file can be an absolute path or a relative + path. ALE will search for the relative path in parent directories. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pug.txt b/doc/ale-pug.txt new file mode 100644 index 00000000..2d293a0e --- /dev/null +++ b/doc/ale-pug.txt @@ -0,0 +1,51 @@ +=============================================================================== +ALE Pug Integration *ale-pug-options* + + +=============================================================================== +puglint *ale-pug-puglint* + +The puglint linter will detect configuration files based on the path to the +filename automatically. Configuration files will be loaded in this order: + +1. `.pug-lintrc` +2. `.pug-lintrc.js` +3. `.pug-lintrc.json` +4. `package.json` + +You might need to create a configuration file for your project to get +meaningful results. + + *ale-options.pug_puglint_executable* + *g:ale_pug_puglint_executable* + *b:ale_pug_puglint_executable* +pug_puglint_executable +g:ale_pug_puglint_executable + Type: |String| + Default: `'pug-lint'` + + See |ale-integrations-local-executables| + + *ale-options.pug_puglint_options* + *g:ale_pug_puglint_options* + *b:ale_pug_puglint_options* +pug_puglint_options +g:ale_pug_puglint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to pug-lint. + + *ale-options.pug_puglint_use_global* + *g:ale_pug_puglint_use_global* + *b:ale_pug_puglint_use_global* +pug_puglint_use_global +g:ale_pug_puglint_use_global + Type: |Number| + 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-puppet.txt b/doc/ale-puppet.txt new file mode 100644 index 00000000..f4cf68bd --- /dev/null +++ b/doc/ale-puppet.txt @@ -0,0 +1,71 @@ +=============================================================================== +ALE Puppet Integration *ale-puppet-options* + + +=============================================================================== +puppet *ale-puppet-puppet* + + *ale-options.puppet_puppet_executable* + *g:ale_puppet_puppet_executable* + *b:ale_puppet_puppet_executable* +puppet_puppet_executable +g:ale_puppet_puppet_executable + Type: |String| + Default: `'puppet'` + + This variable can be changed to specify the executable used for puppet. + + *ale-options.puppet_puppet_options* + *g:ale_puppet_puppet_options* + *b:ale_puppet_puppet_options* +puppet_puppet_options +g:ale_puppet_puppet_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + puppet parser validate invocation. + + +=============================================================================== +puppetlint *ale-puppet-puppetlint* + + *ale-options.puppet_puppetlint_executable* + *g:ale_puppet_puppetlint_executable* + *b:ale_puppet_puppetlint_executable* +puppet_puppetlint_executable +g:ale_puppet_puppetlint_executable + Type: |String| + Default: `'puppet-lint'` + + This variable can be changed to specify the executable used for puppet-lint. + + *ale-options.puppet_puppetlint_options* + *g:ale_puppet_puppetlint_options* + *b:ale_puppet_puppetlint_options* +puppet_puppetlint_options +g:ale_puppet_puppetlint_options + Type: |String| + Default: `'--no-autoloader_layout-check'` + + This variable can be changed to add command-line arguments to the + puppet-lint invocation. + + +=============================================================================== +puppet-languageserver *ale-puppet-languageserver* + + *ale-options.puppet_languageserver_executable* + *g:ale_puppet_languageserver_executable* + *b:ale_puppet_languageserver_executable* +puppet_languageserver_executable +g:ale_puppet_languageserver_executable + type: |String| + Default: `'puppet-languageserver'` + + This variable can be used to specify the executable used for + puppet-languageserver. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-purescript.txt b/doc/ale-purescript.txt new file mode 100644 index 00000000..32f427f7 --- /dev/null +++ b/doc/ale-purescript.txt @@ -0,0 +1,93 @@ +=============================================================================== +ALE PureScript Integration *ale-purescript-options* + + +=============================================================================== +purescript-language-server *ale-purescript-language-server* + +PureScript Language Server + (https://github.com/nwolverson/purescript-language-server) + + *ale-options.purescript_ls_executable* + *g:ale_purescript_ls_executable* + *b:ale_purescript_ls_executable* +purescript_ls_executable +g:ale_purescript_ls_executable + Type: |String| + Default: `'purescript-language-server'` + + PureScript language server executable. + + *ale-options.purescript_ls_config* + *g:ale_purescript_ls_config* + *b:ale_purescript_ls_config* +purescript_ls_config +g:ale_purescript_ls_config + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server. For example, with a spago project: > + + let g:ale_purescript_ls_config = { + \ 'purescript': { + \ 'addSpagoSources': v:true, + \ 'addNpmPath': v:true, + \ 'buildCommand': 'spago --quiet build --purs-args --json-errors', + \ }, + \} +< + +=============================================================================== +purs-tidy *ale-purescript-tidy* + + *ale-options.purescript_tidy_executable* + *g:ale_purescript_tidy_executable* + *b:ale_purescript_tidy_executable* +purescript_tidy_executable +g:ale_purescript_tidy_executable + Type: |String| + Default: `'purs-tidy'` + + This variable can be changed to use a different executable for purs-tidy. + + *ale-options.purescript_tidy_use_global* + *g:ale_purescript_tidy_use_global* + *b:ale_purescript_tidy_use_global* +purescript_tidy_use_global +g:ale_purescript_tidy_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.purescript_tidy_options* + *g:ale_purescript_tidy_options* + *b:ale_purescript_tidy_options* +purescript_tidy_options +g:ale_purescript_tidy_options + Type: |String| + Default: `''` + + This variable can be set to pass in additional option to the 'purs-tidy' + executable. +> + let g:ale_purescript_options = '--indent 3' +< + +=============================================================================== +purty *ale-purescript-purty* + + *ale-options.purescript_purty_executable* + *g:ale_purescript_purty_executable* + *b:ale_purescript_purty_executable* +purescript_purty_executable +g:ale_purescript_purty_executable + Type: |String| + Default: `'purty'` + + This variable can be changed to use a different executable for purty. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-pyrex.txt b/doc/ale-pyrex.txt new file mode 100644 index 00000000..251ed70f --- /dev/null +++ b/doc/ale-pyrex.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Pyrex (Cython) Integration *ale-pyrex-options* + + +=============================================================================== +cython *ale-pyrex-cython* + + *ale-options.pyrex_cython_executable* + *g:ale_pyrex_cython_executable* + *b:ale_pyrex_cython_executable* +pyrex_cython_executable +g:ale_pyrex_cython_executable + Type: |String| + Default: `'cython'` + + This variable can be changed to use a different executable for cython. + + *ale-options.pyrex_cython_options* + *g:ale_pyrex_cython_options* + *b:ale_pyrex_cython_options* +pyrex_cython_options +g:ale_pyrex_cython_options + Type: |String| + Default: `'--warning-extra --warning-errors'` + + This variable can be changed to modify flags given to cython. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-python.txt b/doc/ale-python.txt new file mode 100644 index 00000000..d9494d08 --- /dev/null +++ b/doc/ale-python.txt @@ -0,0 +1,2258 @@ +=============================================================================== +ALE Python Integration *ale-python-options* + + *ale-options.python_auto_pipenv* + *g:ale_python_auto_pipenv* + *b:ale_python_auto_pipenv* +python_auto_pipenv +g:ale_python_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. + + *ale-options.python_auto_poetry* + *g:ale_python_auto_poetry* + *b:ale_python_auto_poetry* +python_auto_poetry +g:ale_python_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_auto_uv* + *g:ale_python_auto_uv* + *b:ale_python_auto_uv* +python_auto_uv +g:ale_python_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + *ale-options.python_auto_virtualenv* + *g:ale_python_auto_virtualenv* + *b:ale_python_auto_virtualenv* +python_auto_virtualenv +g:ale_python_auto_virtualenv + Type: |Number| + Default: `0` + + If set to `1`, ALE will automatically set environment variables for commands + such as `PATH` to attempt to make the experience of running Python linters + via virtualenv easier, without the need for another plugin or some + specialised setup. + + +=============================================================================== +ALE Python Project Root Behavior *ale-python-root* + +For some linters, ALE will search for a Python project root by looking at the +files in directories on or above where a file being checked is. ALE applies +the following methods, in order: + +1. Find the first directory containing a common Python configuration file. +2. If no configuration file can be found, use the first directory which does + not contain a readable file named `__init__.py`. + +ALE will look for configuration files with the following filenames. > + + MANIFEST.in + setup.cfg + pytest.ini + tox.ini + .pyre_configuration.local + mypy.ini + .mypy.ini + pycodestyle.cfg + .flake8 + .flake8rc + pylama.ini + pylintrc + .pylintrc + pyrightconfig.json + pyrightconfig.toml + Pipfile + Pipfile.lock + poetry.lock + pyproject.toml + .tool-versions +< + +The first directory containing any of the files named above will be used. + + +=============================================================================== +autoflake *ale-python-autoflake* + + *ale-options.python_autoflake_executable* + *g:ale_python_autoflake_executable* + *b:ale_python_autoflake_executable* +python_autoflake_executable +g:ale_python_autoflake_executable + Type: |String| + Default: `'autoflake'` + + See |ale-integrations-local-executables| + + *ale-options.python_autoflake_options* + *g:ale_python_autoflake_options* + *b:ale_python_autoflake_options* +python_autoflake_options +g:ale_python_autoflake_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to autoflake. + + *ale-options.python_autoflake_use_global* + *g:ale_python_autoflake_use_global* + *b:ale_python_autoflake_use_global* +python_autoflake_use_global +g:ale_python_autoflake_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_autoflake_auto_pipenv* + *g:ale_python_autoflake_auto_pipenv* + *b:ale_python_autoflake_auto_pipenv* +python_autoflake_auto_pipenv +g:ale_python_autoflake_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. + + *ale-options.python_autoflake_auto_poetry* + *g:ale_python_autoflake_auto_poetry* + *b:ale_python_autoflake_auto_poetry* +python_autoflake_auto_poetry +g:ale_python_autoflake_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_autoflake_auto_uv* + *g:ale_python_autoflake_auto_uv* + *b:ale_python_autoflake_auto_uv* +python_autoflake_auto_uv +g:ale_python_autoflake_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +autoimport *ale-python-autoimport* + + *ale-options.python_autoimport_executable* + *g:ale_python_autoimport_executable* + *b:ale_python_autoimport_executable* +python_autoimport_executable +g:ale_python_autoimport_executable + Type: |String| + Default: `'autoimport'` + + See |ale-integrations-local-executables| + + *ale-options.python_autoimport_options* + *g:ale_python_autoimport_options* + *b:ale_python_autoimport_options* +python_autoimport_options +g:ale_python_autoimport_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to autoimport. + + *ale-options.python_autoimport_use_global* + *g:ale_python_autoimport_use_global* + *b:ale_python_autoimport_use_global* +python_autoimport_use_global +g:ale_python_autoimport_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + + *ale-options.python_autoimport_auto_pipenv* + *g:ale_python_autoimport_auto_pipenv* + *b:ale_python_autoimport_auto_pipenv* +python_autoimport_auto_pipenv +g:ale_python_autoimport_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. + + *ale-options.python_autoimport_auto_poetry* + *g:ale_python_autoimport_auto_poetry* + *b:ale_python_autoimport_auto_poetry* +python_autoimport_auto_poetry +g:ale_python_autoimport_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_autoimport_auto_uv* + *g:ale_python_autoimport_auto_uv* + *b:ale_python_autoimport_auto_uv* +python_autoimport_auto_uv +g:ale_python_autoimport_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +autopep8 *ale-python-autopep8* + + *ale-options.python_autopep8_executable* + *g:ale_python_autopep8_executable* + *b:ale_python_autopep8_executable* +python_autopep8_executable +g:ale_python_autopep8_executable + Type: |String| + Default: `'autopep8'` + + See |ale-integrations-local-executables| + + *ale-options.python_autopep8_options* + *g:ale_python_autopep8_options* + *b:ale_python_autopep8_options* +python_autopep8_options +g:ale_python_autopep8_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to autopep8. + + *ale-options.python_autopep8_use_global* + *g:ale_python_autopep8_use_global* + *b:ale_python_autopep8_use_global* +python_autopep8_use_global +g:ale_python_autopep8_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_autopep8_auto_pipenv* + *g:ale_python_autopep8_auto_pipenv* + *b:ale_python_autopep8_auto_pipenv* +python_autopep8_auto_pipenv +g:ale_python_autopep8_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. + + *ale-options.python_autopep8_auto_poetry* + *g:ale_python_autopep8_auto_poetry* + *b:ale_python_autopep8_auto_poetry* +python_autopep8_auto_poetry +g:ale_python_autopep8_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_autopep8_auto_uv* + *g:ale_python_autopep8_auto_uv* + *b:ale_python_autopep8_auto_uv* +python_autopep8_auto_uv +g:ale_python_autopep8_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +bandit *ale-python-bandit* + + *ale-options.python_bandit_executable* + *g:ale_python_bandit_executable* + *b:ale_python_bandit_executable* +python_bandit_executable +g:ale_python_bandit_executable + Type: |String| + Default: `'bandit'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `bandit'`. + Set this to `'poetry'` to invoke `'poetry` `run` `bandit'`. + + *ale-options.python_bandit_options* + *g:ale_python_bandit_options* + *b:ale_python_bandit_options* +python_bandit_options +g:ale_python_bandit_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + bandit invocation. + + *ale-options.python_bandit_use_config* + *g:ale_python_bandit_use_config* + *b:ale_python_bandit_use_config* +python_bandit_use_config +g: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. + + *ale-options.python_bandit_use_global* + *g:ale_python_bandit_use_global* + *b:ale_python_bandit_use_global* +python_bandit_use_global +g:ale_python_bandit_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_bandit_auto_pipenv* + *g:ale_python_bandit_auto_pipenv* + *b:ale_python_bandit_auto_pipenv* +python_bandit_auto_pipenv +g: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. + + *ale-options.python_bandit_auto_poetry* + *g:ale_python_bandit_auto_poetry* + *b:ale_python_bandit_auto_poetry* +python_bandit_auto_poetry +g:ale_python_bandit_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_bandit_auto_uv* + *g:ale_python_bandit_auto_uv* + *b:ale_python_bandit_auto_uv* +python_bandit_auto_uv +g:ale_python_bandit_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +black *ale-python-black* + + *ale-options.python_black_executable* + *g:ale_python_black_executable* + *b:ale_python_black_executable* +python_black_executable +g:ale_python_black_executable + Type: |String| + Default: `'black'` + + See |ale-integrations-local-executables| + + *ale-options.python_black_options* + *g:ale_python_black_options* + *b:ale_python_black_options* +python_black_options +g:ale_python_black_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to black. + + *ale-options.python_black_use_global* + *g:ale_python_black_use_global* + *b:ale_python_black_use_global* +python_black_use_global +g:ale_python_black_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_black_auto_pipenv* + *g:ale_python_black_auto_pipenv* + *b:ale_python_black_auto_pipenv* +python_black_auto_pipenv +g: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. + + *ale-options.python_black_auto_poetry* + *g:ale_python_black_auto_poetry* + *b:ale_python_black_auto_poetry* +python_black_auto_poetry +g:ale_python_black_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_black_auto_uv* + *g:ale_python_black_auto_uv* + *b:ale_python_black_auto_uv* +python_black_auto_uv +g:ale_python_black_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + *ale-options.python_black_change_directory* + *g:ale_python_black_change_directory* + *b:ale_python_black_change_directory* +python_black_change_directory +g: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. + + +=============================================================================== +cspell *ale-python-cspell* + +See |ale-cspell-options| + + +=============================================================================== +flake8 *ale-python-flake8* + + *ale-options.python_flake8_change_directory* + *g:ale_python_flake8_change_directory* + *b:ale_python_flake8_change_directory* +python_flake8_change_directory +g:ale_python_flake8_change_directory + Type: |String| + Default: `'project'` + + If set to `project`, ALE will switch to the project root before checking file. + If set to `file`, ALE will first switch to the directory containing the + Python file being checked with `flake8` before checking it. + You can turn it off with `off` option if you want to control the directory + Python is executed from yourself. + + *ale-options.python_flake8_executable* + *g:ale_python_flake8_executable* + *b:ale_python_flake8_executable* +python_flake8_executable +g:ale_python_flake8_executable + Type: |String| + Default: `'flake8'` + + This variable can be changed to modify the executable used for flake8. Set + this to `'pipenv'` to invoke `'pipenv` `run` `flake8'`. Set this to + `'poetry'` to invoke `'poetry` `run` `flake8'`. + + *ale-options.python_flake8_options* + *g:ale_python_flake8_options* + *b:ale_python_flake8_options* +python_flake8_options +g:ale_python_flake8_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the flake8 + invocation. + + For example, to dynamically switch between programs targeting Python 2 and + Python 3, you may want to set > + + let g:ale_python_flake8_executable = 'python3' " or 'python' for Python 2 + let g:ale_python_flake8_options = '-m flake8' +< + after making sure it's installed for the appropriate Python versions (e.g. + `python3 -m pip install --user flake8`). + + *ale-options.python_flake8_use_global* + *g:ale_python_flake8_use_global* + *b:ale_python_flake8_use_global* +python_flake8_use_global +g:ale_python_flake8_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable controls whether or not ALE will search for flake8 in a + virtualenv directory first. If this variable is set to `1`, then ALE will + always use |g:ale_python_flake8_executable| for the executable path. + + Both variables can be set with `b:` buffer variables instead. + + *ale-options.python_flake8_auto_pipenv* + *g:ale_python_flake8_auto_pipenv* + *b:ale_python_flake8_auto_pipenv* +python_flake8_auto_pipenv +g:ale_python_flake8_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. + + *ale-options.python_flake8_auto_poetry* + *g:ale_python_flake8_auto_poetry* + *b:ale_python_flake8_auto_poetry* +python_flake8_auto_poetry +g:ale_python_flake8_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_flake8_auto_uv* + *g:ale_python_flake8_auto_uv* + *b:ale_python_flake8_auto_uv* +python_flake8_auto_uv +g:ale_python_flake8_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +flakehell *ale-python-flakehell* + + *ale-options.python_flakehell_change_directory* + *g:ale_python_flakehell_change_directory* + *b:ale_python_flakehell_change_directory* +python_flakehell_change_directory +g:ale_python_flakehell_change_directory + Type: |String| + Default: `project` + + If set to `project`, ALE will switch to the project root before checking file. + If set to `file`, ALE will switch to directory the Python file being + checked with `flakehell` is in before checking it. + You can turn it off with `off` option if you want to control the directory + Python is executed from yourself. + + *ale-options.python_flakehell_executable* + *g:ale_python_flakehell_executable* + *b:ale_python_flakehell_executable* +python_flakehell_executable +g:ale_python_flakehell_executable + Type: |String| + Default: `'flakehell'` + + This variable can be changed to modify the executable used for flakehell. Set + this to `'pipenv'` to invoke `'pipenv` `run` `flakehell'`. Set this to + `'poetry'` to invoke `'poetry` `run` `flakehell'`. Set this to `'python'` to + invoke `'python` `-m` `flakehell'`. + + *ale-options.python_flakehell_options* + *g:ale_python_flakehell_options* + *b:ale_python_flakehell_options* +python_flakehell_options +g:ale_python_flakehell_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the flakehell + lint invocation. + + *ale-options.python_flakehell_use_global* + *g:ale_python_flakehell_use_global* + *b:ale_python_flakehell_use_global* +python_flakehell_use_global +g:ale_python_flakehell_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable controls whether or not ALE will search for flakehell in a + virtualenv directory first. If this variable is set to `1`, then ALE will + always use |g:ale_python_flakehell_executable| for the executable path. + + Both variables can be set with `b:` buffer variables instead. + + *ale-options.python_flakehell_auto_pipenv* + *g:ale_python_flakehell_auto_pipenv* + *b:ale_python_flakehell_auto_pipenv* +python_flakehell_auto_pipenv +g:ale_python_flakehell_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. + + *ale-options.python_flakehell_auto_poetry* + *g:ale_python_flakehell_auto_poetry* + *b:ale_python_flakehell_auto_poetry* +python_flakehell_auto_poetry +g:ale_python_flakehell_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_flakehell_auto_uv* + *g:ale_python_flakehell_auto_uv* + *b:ale_python_flakehell_auto_uv* +python_flakehell_auto_uv +g:ale_python_flakehell_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +isort *ale-python-isort* + + *ale-options.python_isort_executable* + *g:ale_python_isort_executable* + *b:ale_python_isort_executable* +python_isort_executable +g:ale_python_isort_executable + Type: |String| + Default: `'isort'` + + See |ale-integrations-local-executables| + + *ale-options.python_isort_options* + *g:ale_python_isort_options* + *b:ale_python_isort_options* +python_isort_options +g:ale_python_isort_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to isort. + + *ale-options.python_isort_use_global* + *g:ale_python_isort_use_global* + *b:ale_python_isort_use_global* +python_isort_use_global +g:ale_python_isort_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_isort_auto_pipenv* + *g:ale_python_isort_auto_pipenv* + *b:ale_python_isort_auto_pipenv* +python_isort_auto_pipenv +g:ale_python_isort_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. + + *ale-options.python_isort_auto_poetry* + *g:ale_python_isort_auto_poetry* + *b:ale_python_isort_auto_poetry* +python_isort_auto_poetry +g:ale_python_isort_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_isort_auto_uv* + *g:ale_python_isort_auto_uv* + *b:ale_python_isort_auto_uv* +python_isort_auto_uv +g:ale_python_isort_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +mypy *ale-python-mypy* + +The minimum supported version of mypy that ALE supports is v0.4.4. This is +the first version containing the `--shadow-file` option ALE needs to be able +to check for errors while you type. + +`mypy` will be run from a detected project root, per |ale-python-root|. + + *ale-options.python_mypy_auto_pipenv* + *g:ale_python_mypy_auto_pipenv* + *b:ale_python_mypy_auto_pipenv* +python_mypy_auto_pipenv +g:ale_python_mypy_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. + + *ale-options.python_mypy_auto_poetry* + *g:ale_python_mypy_auto_poetry* + *b:ale_python_mypy_auto_poetry* +python_mypy_auto_poetry +g:ale_python_mypy_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_mypy_auto_uv* + *g:ale_python_mypy_auto_uv* + *b:ale_python_mypy_auto_uv* +python_mypy_auto_uv +g:ale_python_mypy_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + *ale-options.python_mypy_executable* + *g:ale_python_mypy_executable* + *b:ale_python_mypy_executable* +python_mypy_executable +g:ale_python_mypy_executable + Type: |String| + Default: `'mypy'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `mypy'`. + Set this to `'poetry'` to invoke `'poetry` `run` `mypy'`. + + *ale-options.python_mypy_ignore_invalid_syntax* + *g:ale_python_mypy_ignore_invalid_syntax* + *b:ale_python_mypy_ignore_invalid_syntax* +python_mypy_ignore_invalid_syntax +g:ale_python_mypy_ignore_invalid_syntax + Type: |Number| + Default: `0` + + When set to `1`, syntax error messages for mypy will be ignored. This option + can be used when running other Python linters which check for syntax errors, + as mypy can take a while to finish executing. + + *ale-options.python_mypy_options* + *g:ale_python_mypy_options* + *b:ale_python_mypy_options* +python_mypy_options +g:ale_python_mypy_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the mypy + invocation. + + *ale-options.python_mypy_show_notes* + *g:ale_python_mypy_show_notes* + *b:ale_python_mypy_show_notes* +python_mypy_show_notes +g:ale_python_mypy_show_notes + Type: |Number| + Default: `1` + + If enabled, notes on lines will be displayed as 'I' (info) messages. + + *ale-options.python_mypy_use_global* + *g:ale_python_mypy_use_global* + *b:ale_python_mypy_use_global* +python_mypy_use_global +g:ale_python_mypy_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +prospector *ale-python-prospector* + + *ale-options.python_prospector_executable* + *g:ale_python_prospector_executable* + *b:ale_python_prospector_executable* +python_prospector_executable +g:ale_python_prospector_executable + Type: |String| + Default: `'prospector'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `prospector'`. + Set this to `'poetry'` to invoke `'poetry` `run` `prospector'`. + + *ale-options.python_prospector_options* + *g:ale_python_prospector_options* + *b:ale_python_prospector_options* +python_prospector_options +g:ale_python_prospector_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the prospector + invocation. + + For example, to dynamically switch between programs targeting Python 2 and + Python 3, you may want to set > + + let g:ale_python_prospector_executable = 'python3' + " or 'python' for Python 2 + let g:ale_python_prospector_options = '--rcfile /path/to/.prospector.yaml' + " The virtualenv detection needs to be disabled. + let g:ale_python_prospector_use_global = 0 + + after making sure it's installed for the appropriate Python versions (e.g. + `python3 -m pip install --user prospector`). +< + *ale-options.python_prospector_use_global* + *g:ale_python_prospector_use_global* + *b:ale_python_prospector_use_global* +python_prospector_use_global +g:ale_python_prospector_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_prospector_auto_pipenv* + *g:ale_python_prospector_auto_pipenv* + *b:ale_python_prospector_auto_pipenv* +python_prospector_auto_pipenv +g:ale_python_prospector_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. + + *ale-options.python_prospector_auto_poetry* + *g:ale_python_prospector_auto_poetry* + *b:ale_python_prospector_auto_poetry* +python_prospector_auto_poetry +g:ale_python_prospector_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_prospector_auto_uv* + *g:ale_python_prospector_auto_uv* + *b:ale_python_prospector_auto_uv* +python_prospector_auto_uv +g:ale_python_prospector_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pycln *ale-python-pycln* + + *ale-options.python_pycln_change_directory* + *g:ale_python_pycln_change_directory* + *b:ale_python_pycln_change_directory* +python_pycln_change_directory +g:ale_python_pycln_change_directory + Type: |Number| + Default: `1` + + If set to `1`, `pycln` will be run from a detected project root, per + |ale-python-root|. if set to `0` or no project root detected, + `pycln` will be run from the buffer's directory. + + *ale-options.python_pycln_executable* + *g:ale_python_pycln_executable* + *b:ale_python_pycln_executable* +python_pycln_executable +g:ale_python_pycln_executable + Type: |String| + Default: `'pycln'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pycln'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pycln'`. + + *ale-options.python_pycln_options* + *g:ale_python_pycln_options* + *b:ale_python_pycln_options* +python_pycln_options +g:ale_python_pycln_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the pycln + invocation. + + For example, to select/enable and/or disable some error codes, + you may want to set the following: > + + let g:ale_python_pycln_options = '--expand-stars' +< + *ale-options.python_pycln_config_file* + *g:ale_python_pycln_config_file* + *b:ale_python_pycln_config_file* +python_pycln_config_file +g:ale_python_pycln_config_file + Type: |String| + Default: `''` + + Use this variable to set the configuration file. + If `'--config' ` is found in the |g:ale_python_pycln_options|, then that + option value will override the value in this variable. + + *ale-options.python_pycln_use_global* + *g:ale_python_pycln_use_global* + *b:ale_python_pycln_use_global* +python_pycln_use_global +g:ale_python_pycln_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_pycln_auto_pipenv* + *g:ale_python_pycln_auto_pipenv* + *b:ale_python_pycln_auto_pipenv* +python_pycln_auto_pipenv +g:ale_python_pycln_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. + + *ale-options.python_pycln_auto_poetry* + *g:ale_python_pycln_auto_poetry* + *b:ale_python_pycln_auto_poetry* +python_pycln_auto_poetry +g:ale_python_pycln_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pycln_auto_uv* + *g:ale_python_pycln_auto_uv* + *b:ale_python_pycln_auto_uv* +python_pycln_auto_uv +g:ale_python_pycln_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pycodestyle *ale-python-pycodestyle* + + *ale-options.python_pycodestyle_executable* + *g:ale_python_pycodestyle_executable* + *b:ale_python_pycodestyle_executable* +python_pycodestyle_executable +g:ale_python_pycodestyle_executable + Type: |String| + Default: `'pycodestyle'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pycodestyle'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pycodestyle'`. + + *ale-options.python_pycodestyle_options* + *g:ale_python_pycodestyle_options* + *b:ale_python_pycodestyle_options* +python_pycodestyle_options +g:ale_python_pycodestyle_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + pycodestyle invocation. + + *ale-options.python_pycodestyle_use_global* + *g:ale_python_pycodestyle_use_global* + *b:ale_python_pycodestyle_use_global* +python_pycodestyle_use_global +g:ale_python_pycodestyle_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_pycodestyle_auto_pipenv* + *g:ale_python_pycodestyle_auto_pipenv* + *b:ale_python_pycodestyle_auto_pipenv* +python_pycodestyle_auto_pipenv +g:ale_python_pycodestyle_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. + + *ale-options.python_pycodestyle_auto_poetry* + *g:ale_python_pycodestyle_auto_poetry* + *b:ale_python_pycodestyle_auto_poetry* +python_pycodestyle_auto_poetry +g:ale_python_pycodestyle_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pycodestyle_auto_uv* + *g:ale_python_pycodestyle_auto_uv* + *b:ale_python_pycodestyle_auto_uv* +python_pycodestyle_auto_uv +g:ale_python_pycodestyle_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pydocstyle *ale-python-pydocstyle* + + *ale-options.python_pydocstyle_executable* + *g:ale_python_pydocstyle_executable* + *b:ale_python_pydocstyle_executable* +python_pydocstyle_executable +g:ale_python_pydocstyle_executable + Type: |String| + Default: `'pydocstyle'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pydocstyle'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pydocstyle'`. + + *ale-options.python_pydocstyle_options* + *g:ale_python_pydocstyle_options* + *b:ale_python_pydocstyle_options* +python_pydocstyle_options +g:ale_python_pydocstyle_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + pydocstyle invocation. + + *ale-options.python_pydocstyle_use_global* + *g:ale_python_pydocstyle_use_global* + *b:ale_python_pydocstyle_use_global* +python_pydocstyle_use_global +g:ale_python_pydocstyle_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_pydocstyle_auto_pipenv* + *g:ale_python_pydocstyle_auto_pipenv* + *b:ale_python_pydocstyle_auto_pipenv* +python_pydocstyle_auto_pipenv +g:ale_python_pydocstyle_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. + + *ale-options.python_pydocstyle_auto_poetry* + *g:ale_python_pydocstyle_auto_poetry* + *b:ale_python_pydocstyle_auto_poetry* +python_pydocstyle_auto_poetry +g:ale_python_pydocstyle_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pydocstyle_auto_uv* + *g:ale_python_pydocstyle_auto_uv* + *b:ale_python_pydocstyle_auto_uv* +python_pydocstyle_auto_uv +g:ale_python_pydocstyle_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pyflakes *ale-python-pyflakes* + + *ale-options.python_pyflakes_executable* + *g:ale_python_pyflakes_executable* + *b:ale_python_pyflakes_executable* +python_pyflakes_executable +g:ale_python_pyflakes_executable + Type: |String| + Default: `'pyflakes'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pyflakes'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pyflakes'`. + + *ale-options.python_pyflakes_auto_pipenv* + *g:ale_python_pyflakes_auto_pipenv* + *b:ale_python_pyflakes_auto_pipenv* +python_pyflakes_auto_pipenv +g:ale_python_pyflakes_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. + + *ale-options.python_pyflakes_auto_poetry* + *g:ale_python_pyflakes_auto_poetry* + *b:ale_python_pyflakes_auto_poetry* +python_pyflakes_auto_poetry +g:ale_python_pyflakes_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pyflakes_auto_uv* + *g:ale_python_pyflakes_auto_uv* + *b:ale_python_pyflakes_auto_uv* +python_pyflakes_auto_uv +g:ale_python_pyflakes_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pyflyby *ale-python-pyflyby* + + *ale-options.python_pyflyby_executable* + *g:ale_python_pyflyby_executable* + *b:ale_python_pyflyby_executable* +python_pyflyby_executable +g:ale_python_pyflyby_executable + Type: |String| + Default: `'tidy-imports'` + + See |ale-integrations-local-executables| + + *ale-options.python_pyflyby_options* + *g:ale_python_pyflyby_options* + *b:ale_python_pyflyby_options* +python_pyflyby_options +g:ale_python_pyflyby_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the pyflyby + tidy-imports invocation. + + *ale-options.python_pyflyby_use_global* + *g:ale_python_pyflyby_use_global* + *b:ale_python_pyflyby_use_global* +python_pyflyby_use_global +g:ale_python_pyflyby_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_pyflyby_auto_pipenv* + *g:ale_python_pyflyby_auto_pipenv* + *b:ale_python_pyflyby_auto_pipenv* +python_pyflyby_auto_pipenv +g:ale_python_pyflyby_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. + + *ale-options.python_pyflyby_auto_poetry* + *g:ale_python_pyflyby_auto_poetry* + *b:ale_python_pyflyby_auto_poetry* +python_pyflyby_auto_poetry +g:ale_python_pyflyby_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pyflyby_auto_uv* + *g:ale_python_pyflyby_auto_uv* + *b:ale_python_pyflyby_auto_uv* +python_pyflyby_auto_uv +g:ale_python_pyflyby_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pylama *ale-python-pylama* + + *ale-options.python_pylama_change_directory* + *g:ale_python_pylama_change_directory* + *b:ale_python_pylama_change_directory* +python_pylama_change_directory +g: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. + + *ale-options.python_pylama_executable* + *g:ale_python_pylama_executable* + *b:ale_python_pylama_executable* +python_pylama_executable +g: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'`. Set this to + `'poetry'` to invoke `'poetry` `run` `pylama'`. + + *ale-options.python_pylama_options* + *g:ale_python_pylama_options* + *b:ale_python_pylama_options* +python_pylama_options +g:ale_python_pylama_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the pylama + invocation. + + *ale-options.python_pylama_use_global* + *g:ale_python_pylama_use_global* + *b:ale_python_pylama_use_global* +python_pylama_use_global +g: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. + + *ale-options.python_pylama_auto_pipenv* + *g:ale_python_pylama_auto_pipenv* + *b:ale_python_pylama_auto_pipenv* +python_pylama_auto_pipenv +g: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. + + *ale-options.python_pylama_auto_poetry* + *g:ale_python_pylama_auto_poetry* + *b:ale_python_pylama_auto_poetry* +python_pylama_auto_poetry +g:ale_python_pylama_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pylama_auto_uv* + *g:ale_python_pylama_auto_uv* + *b:ale_python_pylama_auto_uv* +python_pylama_auto_uv +g:ale_python_pylama_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pylint *ale-python-pylint* + + *ale-options.python_pylint_change_directory* + *g:ale_python_pylint_change_directory* + *b:ale_python_pylint_change_directory* +python_pylint_change_directory +g:ale_python_pylint_change_directory + Type: |Number| + Default: `1` + + 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. + + *ale-options.python_pylint_executable* + *g:ale_python_pylint_executable* + *b:ale_python_pylint_executable* +python_pylint_executable +g:ale_python_pylint_executable + Type: |String| + Default: `'pylint'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pylint'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pylint'`. + + *ale-options.python_pylint_options* + *g:ale_python_pylint_options* + *b:ale_python_pylint_options* +python_pylint_options +g:ale_python_pylint_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the pylint + invocation. + + For example, to dynamically switch between programs targeting Python 2 and + Python 3, you may want to set > + + let g:ale_python_pylint_executable = 'python3' " or 'python' for Python 2 + let g:ale_python_pylint_options = '--rcfile /path/to/pylint.rc' + " The virtualenv detection needs to be disabled. + let g:ale_python_pylint_use_global = 0 + + after making sure it's installed for the appropriate Python versions (e.g. + `python3 -m pip install --user pylint`). +< + *ale-options.python_pylint_use_global* + *g:ale_python_pylint_use_global* + *b:ale_python_pylint_use_global* +python_pylint_use_global +g:ale_python_pylint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_pylint_auto_pipenv* + *g:ale_python_pylint_auto_pipenv* + *b:ale_python_pylint_auto_pipenv* +python_pylint_auto_pipenv +g:ale_python_pylint_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. + + *ale-options.python_pylint_auto_poetry* + *g:ale_python_pylint_auto_poetry* + *b:ale_python_pylint_auto_poetry* +python_pylint_auto_poetry +g:ale_python_pylint_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pylint_auto_uv* + *g:ale_python_pylint_auto_uv* + *b:ale_python_pylint_auto_uv* +python_pylint_auto_uv +g:ale_python_pylint_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + *ale-options.python_pylint_use_msg_id* + *g:ale_python_pylint_use_msg_id* + *b:ale_python_pylint_use_msg_id* +python_pylint_use_msg_id +g:ale_python_pylint_use_msg_id + Type: |Number| + Default: `0` + + Use message for output (e.g. I0011) instead of symbolic name of the message + (e.g. locally-disabled). + + +=============================================================================== +pylsp *ale-python-pylsp* + +`pylsp` will be run from a detected project root, per |ale-python-root|. + + *ale-options.python_pylsp_executable* + *g:ale_python_pylsp_executable* + *b:ale_python_pylsp_executable* +python_pylsp_executable +g:ale_python_pylsp_executable + Type: |String| + Default: `'pylsp'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pylsp'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pyls'`. + + *ale-options.python_pylsp_use_global* + *g:ale_python_pylsp_use_global* + *b:ale_python_pylsp_use_global* +python_pylsp_use_global +g:ale_python_pylsp_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_pylsp_auto_pipenv* + *g:ale_python_pylsp_auto_pipenv* + *b:ale_python_pylsp_auto_pipenv* +python_pylsp_auto_pipenv +g:ale_python_pylsp_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. + + *ale-options.python_pylsp_auto_poetry* + *g:ale_python_pylsp_auto_poetry* + *b:ale_python_pylsp_auto_poetry* +python_pylsp_auto_poetry +g:ale_python_pylsp_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pylsp_auto_uv* + *g:ale_python_pylsp_auto_uv* + *b:ale_python_pylsp_auto_uv* +python_pylsp_auto_uv +g:ale_python_pylsp_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + *ale-options.python_pylsp_config* + *g:ale_python_pylsp_config* + *b:ale_python_pylsp_config* +python_pylsp_config +g:ale_python_pylsp_config + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for pylsp. For example, to disable + the pycodestyle linter: > + + let g:ale_python_pylsp_config = { + \ 'pylsp': { + \ 'plugins': { + \ 'pycodestyle': { + \ 'enabled': v:false + \ } + \ } + \ }, + \} +< + *ale-options.python_pylsp_options* + *g:ale_python_pylsp_options* + *b:ale_python_pylsp_options* +python_pylsp_options +g:ale_python_pylsp_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the pylsp + invocation. Note that this is not the same thing as ale_python_pylsp_config, + which allows configuration of how pylsp functions; this is intended to + provide flexibility in how the pylsp command is invoked. + + For example, if you had installed `pylsp` but your `pylsp` executable was not + on your `PATH` for some reason, an alternative way to run the pylsp server + would be: + let g:ale_python_pylsp_executable = 'python3' + let g:ale_python_pylsp_options = '-m pylsp' + + An example strategy for installing `pylsp`: + `python3 -m pip install --user pylsp` + + +=============================================================================== +pyre *ale-python-pyre* + +`pyre` will be run from a detected project root, per |ale-python-root|. + + *ale-options.python_pyre_executable* + *g:ale_python_pyre_executable* + *b:ale_python_pyre_executable* +python_pyre_executable +g:ale_python_pyre_executable + Type: |String| + Default: `'pyre'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pyre'`. + Set this to `'poetry'` to invoke `'poetry` `run` `pyre'`. + + *ale-options.python_pyre_use_global* + *g:ale_python_pyre_use_global* + *b:ale_python_pyre_use_global* +python_pyre_use_global +g:ale_python_pyre_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_pyre_auto_pipenv* + *g:ale_python_pyre_auto_pipenv* + *b:ale_python_pyre_auto_pipenv* +python_pyre_auto_pipenv +g:ale_python_pyre_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. + + *ale-options.python_pyre_auto_poetry* + *g:ale_python_pyre_auto_poetry* + *b:ale_python_pyre_auto_poetry* +python_pyre_auto_poetry +g:ale_python_pyre_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pyre_auto_uv* + *g:ale_python_pyre_auto_uv* + *b:ale_python_pyre_auto_uv* +python_pyre_auto_uv +g:ale_python_pyre_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +pyright *ale-python-pyright* + +The `pyright` linter requires a recent version of `pyright` which includes +the `pyright-langserver` executable. You can install `pyright` on your system +through `npm` with `sudo npm install -g pyright` or similar. + +Refer to their README for installation instructions: +https://github.com/Microsoft/pyright + +`pyright` needs to know the path to your Python executable and probably a +virtualenv to run. ALE will try to detect these automatically. +See |g:ale_python_pyright_config|. + + *ale-options.python_pyright_executable* + *g:ale_python_pyright_executable* + *b:ale_python_pyright_executable* +python_pyright_executable +g:ale_python_pyright_executable + Type: |String| + Default: `'pyright-langserver'` + + The executable for running `pyright`, which is typically installed globally. + + *ale-options.python_pyright_config* + *g:ale_python_pyright_config* + *b:ale_python_pyright_config* +python_pyright_config +g:ale_python_pyright_config + Type: |Dictionary| + Default: `{}` + + Settings for configuring the `pyright` language server. + + See pyright's documentation for a full list of options: + https://github.com/microsoft/pyright/blob/master/docs/settings.md + + ALE will automatically try to set defaults for `venvPath` and `pythonPath` + so your project can automatically be checked with the right libraries. + You can override these settings with whatever you want in your ftplugin + file like so: > + + let b:ale_python_pyright_config = { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': '/other/dir', + \ }, + \} +< + If `venvPath` is set, but `pythonPath` is not, + ALE will use `venvPath . '/bin/python'` or similar as `pythonPath`. + + A commonly used setting for `pyright` is disabling language services + apart from type checking and "hover" (|ale-hover|), you can set this + setting like so, or use whatever other settings you want: > + + let b:ale_python_pyright_config = { + \ 'pyright': { + \ 'disableLanguageServices': v:true, + \ }, + \} +< + *ale-options.python_pyright_auto_pipenv* + *g:ale_python_pyright_auto_pipenv* + *b:ale_python_pyright_auto_pipenv* +python_pyright_auto_pipenv +g:ale_python_pyright_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. + + *ale-options.python_pyright_auto_poetry* + *g:ale_python_pyright_auto_poetry* + *b:ale_python_pyright_auto_poetry* +python_pyright_auto_poetry +g:ale_python_pyright_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_pyright_auto_uv* + *g:ale_python_pyright_auto_uv* + *b:ale_python_pyright_auto_uv* +python_pyright_auto_uv +g:ale_python_pyright_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +refurb *ale-python-refurb* + + *ale-options.python_refurb_change_directory* + *g:ale_python_refurb_change_directory* + *b:ale_python_refurb_change_directory* +python_refurb_change_directory +g:ale_python_refurb_change_directory + Type: |Number| + Default: `1` + + If set to `1`, `refurb` will be run from a detected project root, per + |ale-python-root|. if set to `0` or no project root detected, + `refurb` will be run from the buffer's directory. + + *ale-options.python_refurb_executable* + *g:ale_python_refurb_executable* + *b:ale_python_refurb_executable* +python_refurb_executable +g:ale_python_refurb_executable + Type: |String| + Default: `'refurb'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `refurb'`. + Set this to `'poetry'` to invoke `'poetry` `run` `refurb'`. + + *ale-options.python_refurb_options* + *g:ale_python_refurb_options* + *b:ale_python_refurb_options* +python_refurb_options +g:ale_python_refurb_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the refurb + invocation. + + For example, to select/enable and/or disable some error codes, + you may want to set > + + let g:ale_python_refurb_options = '--ignore 100' +< + *ale-options.python_refurb_use_global* + *g:ale_python_refurb_use_global* + *b:ale_python_refurb_use_global* +python_refurb_use_global +g:ale_python_refurb_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_refurb_auto_pipenv* + *g:ale_python_refurb_auto_pipenv* + *b:ale_python_refurb_auto_pipenv* +python_refurb_auto_pipenv +g:ale_python_refurb_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. + + *ale-options.python_refurb_auto_poetry* + *g:ale_python_refurb_auto_poetry* + *b:ale_python_refurb_auto_poetry* +python_refurb_auto_poetry +g:ale_python_refurb_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_refurb_auto_uv* + *g:ale_python_refurb_auto_uv* + *b:ale_python_refurb_auto_uv* +python_refurb_auto_uv +g:ale_python_refurb_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +reorder-python-imports *ale-python-reorder_python_imports* + + *ale-options.python_reorder_python_imports_executable* + *g:ale_python_reorder_python_imports_executable* + *b:ale_python_reorder_python_imports_executable* +python_reorder_python_imports_executable +g:ale_python_reorder_python_imports_executable + Type: |String| + Default: `'reorder-python-imports'` + + See |ale-integrations-local-executables| + + *ale-options.python_reorder_python_imports_options* + *g:ale_python_reorder_python_imports_options* + *b:ale_python_reorder_python_imports_options* +python_reorder_python_imports_options +g:ale_python_reorder_python_imports_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to reorder-python-imports. + + *ale-options.python_reorder_python_imports_use_global* + *g:ale_python_reorder_python_imports_use_global* + *b:ale_python_reorder_python_imports_use_global* +python_reorder_python_imports_use_global +g:ale_python_reorder_python_imports_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_reorder_python_imports_auto_pipenv* + *g:ale_python_reorder_python_imports_auto_pipenv* + *b:ale_python_reorder_python_imports_auto_pipenv* +python_reorder_python_imports_auto_pipenv +g:ale_python_reorder_python_imports_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. + + *ale-options.python_reorder_python_imports_auto_poetry* + *g:ale_python_reorder_python_imports_auto_poetry* + *b:ale_python_reorder_python_imports_auto_poetry* +python_reorder_python_imports_auto_poetry +g:ale_python_reorder_python_imports_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_reorder_python_imports_auto_uv* + *g:ale_python_reorder_python_imports_auto_uv* + *b:ale_python_reorder_python_imports_auto_uv* +python_reorder_python_imports_auto_uv +g:ale_python_reorder_python_imports_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +ruff *ale-python-ruff* + + *ale-options.python_ruff_change_directory* + *g:ale_python_ruff_change_directory* + *b:ale_python_ruff_change_directory* +python_ruff_change_directory +g:ale_python_ruff_change_directory + Type: |Number| + Default: `1` + + If set to `1`, `ruff` will be run from a detected project root, per + |ale-python-root|. if set to `0` or no project root detected, + `ruff` will be run from the buffer's directory. + + *ale-options.python_ruff_executable* + *g:ale_python_ruff_executable* + *b:ale_python_ruff_executable* +python_ruff_executable +g:ale_python_ruff_executable + Type: |String| + Default: `'ruff'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `ruff'`. + Set this to `'poetry'` to invoke `'poetry` `run` `ruff'`. + + *ale-options.python_ruff_options* + *g:ale_python_ruff_options* + *b:ale_python_ruff_options* +python_ruff_options +g:ale_python_ruff_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the ruff + invocation. + + For example, to select/enable and/or disable some error codes, + you may want to set: > + + let g:ale_python_ruff_options = '--ignore F401' +< + *ale-options.python_ruff_use_global* + *g:ale_python_ruff_use_global* + *b:ale_python_ruff_use_global* +python_ruff_use_global +g:ale_python_ruff_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_ruff_auto_pipenv* + *g:ale_python_ruff_auto_pipenv* + *b:ale_python_ruff_auto_pipenv* +python_ruff_auto_pipenv +g:ale_python_ruff_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. + + *ale-options.python_ruff_auto_poetry* + *g:ale_python_ruff_auto_poetry* + *b:ale_python_ruff_auto_poetry* +python_ruff_auto_poetry +g:ale_python_ruff_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_ruff_auto_uv* + *g:ale_python_ruff_auto_uv* + *b:ale_python_ruff_auto_uv* +python_ruff_auto_uv +g:ale_python_ruff_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +ruff-format *ale-python-ruff-format* + + *ale-options.python_ruff_format_change_directory* + *g:ale_python_ruff_format_change_directory* + *b:ale_python_ruff_format_change_directory* +python_ruff_format_change_directory +g:ale_python_ruff_format_change_directory + Type: |Number| + Default: `1` + + If set to `1`, `ruff` will be run from a detected project root, per + |ale-python-root|. if set to `0` or no project root detected, + `ruff` will be run from the buffer's directory. + + *ale-options.python_ruff_format_executable* + *g:ale_python_ruff_format_executable* + *b:ale_python_ruff_format_executable* +python_ruff_format_executable +g:ale_python_ruff_format_executable + Type: |String| + Default: `'ruff'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `ruff'`. + Set this to `'poetry'` to invoke `'poetry` `run` `ruff'`. + + *ale-options.python_ruff_format_options* + *g:ale_python_ruff_format_options* + *b:ale_python_ruff_format_options* +python_ruff_format_options +g:ale_python_ruff_format_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the ruff + invocation. + + For example, to select/enable and/or disable some error codes, + you may want to set > + + let g:ale_python_ruff_format_options = '--ignore F401' +< + *ale-options.python_ruff_format_use_global* + *g:ale_python_ruff_format_use_global* + *b:ale_python_ruff_format_use_global* +python_ruff_format_use_global +g:ale_python_ruff_format_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_ruff_format_auto_pipenv* + *g:ale_python_ruff_format_auto_pipenv* + *b:ale_python_ruff_format_auto_pipenv* +python_ruff_format_auto_pipenv +g:ale_python_ruff_format_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. + + *ale-options.python_ruff_format_auto_poetry* + *g:ale_python_ruff_format_auto_poetry* + *b:ale_python_ruff_format_auto_poetry* +python_ruff_format_auto_poetry +g:ale_python_ruff_format_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_ruff_format_auto_uv* + *g:ale_python_ruff_format_auto_uv* + *b:ale_python_ruff_format_auto_uv* +python_ruff_format_auto_uv +g:ale_python_ruff_format_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +unimport *ale-python-unimport* + +`unimport` will be run from a detected project root, per |ale-python-root|. + + *ale-options.python_unimport_auto_pipenv* + *g:ale_python_unimport_auto_pipenv* + *b:ale_python_unimport_auto_pipenv* +python_unimport_auto_pipenv +g:ale_python_unimport_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. + + *ale-options.python_unimport_auto_poetry* + *g:ale_python_unimport_auto_poetry* + *b:ale_python_unimport_auto_poetry* +python_unimport_auto_poetry +g:ale_python_unimport_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_unimport_auto_uv* + *g:ale_python_unimport_auto_uv* + *b:ale_python_unimport_auto_uv* +python_unimport_auto_uv +g:ale_python_unimport_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + *ale-options.python_unimport_executable* + *g:ale_python_unimport_executable* + *b:ale_python_unimport_executable* +python_unimport_executable +g:ale_python_unimport_executable + Type: |String| + Default: `'unimport'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `unimport'`. + Set this to `'poetry'` to invoke `'poetry` `run` `unimport'`. + + *ale-options.python_unimport_options* + *g:ale_python_unimport_options* + *b:ale_python_unimport_options* +python_unimport_options +g:ale_python_unimport_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the unimport + invocation. + + *ale-options.python_unimport_use_global* + *g:ale_python_unimport_use_global* + *b:ale_python_unimport_use_global* +python_unimport_use_global +g:ale_python_unimport_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +vulture *ale-python-vulture* + + *ale-options.python_vulture_change_directory* + *g:ale_python_vulture_change_directory* + *b:ale_python_vulture_change_directory* +python_vulture_change_directory +g:ale_python_vulture_change_directory + Type: |Number| + Default: `1` + + If set to `1`, ALE will switch to the directory the Python file being + checked with `vulture` is in before checking it and check the whole project + directory instead of checking only the file opened in the current buffer. + This helps `vulture` to know the context and avoid false-negative results. + + *ale-options.python_vulture_executable* + *g:ale_python_vulture_executable* + *b:ale_python_vulture_executable* +python_vulture_executable +g:ale_python_vulture_executable + Type: |String| + Default: `'vulture'` + + See |ale-integrations-local-executables| + + *ale-options.python_vulture_options* + *g:ale_python_vulture_options* + *b:ale_python_vulture_options* +python_vulture_options +g:ale_python_vulture_options + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the vulture + invocation. + + *ale-options.python_vulture_use_global* + *g:ale_python_vulture_use_global* + *b:ale_python_vulture_use_global* +python_vulture_use_global +g:ale_python_vulture_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_vulture_auto_pipenv* + *g:ale_python_vulture_auto_pipenv* + *b:ale_python_vulture_auto_pipenv* +python_vulture_auto_pipenv +g:ale_python_vulture_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. + + *ale-options.python_vulture_auto_poetry* + *g:ale_python_vulture_auto_poetry* + *b:ale_python_vulture_auto_poetry* +python_vulture_auto_poetry +g:ale_python_vulture_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_vulture_auto_uv* + *g:ale_python_vulture_auto_uv* + *b:ale_python_vulture_auto_uv* +python_vulture_auto_uv +g:ale_python_vulture_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== +yapf *ale-python-yapf* + + *ale-options.python_yapf_executable* + *g:ale_python_yapf_executable* + *b:ale_python_yapf_executable* +python_yapf_executable +g:ale_python_yapf_executable + Type: |String| + Default: `'yapf'` + + See |ale-integrations-local-executables| + + *ale-options.python_yapf_use_global* + *g:ale_python_yapf_use_global* + *b:ale_python_yapf_use_global* +python_yapf_use_global +g:ale_python_yapf_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_yapf_auto_pipenv* + *g:ale_python_yapf_auto_pipenv* + *b:ale_python_yapf_auto_pipenv* +python_yapf_auto_pipenv +g:ale_python_yapf_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. + + *ale-options.python_yapf_auto_poetry* + *g:ale_python_yapf_auto_poetry* + *b:ale_python_yapf_auto_poetry* +python_yapf_auto_poetry +g:ale_python_yapf_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_yapf_auto_uv* + *g:ale_python_yapf_auto_uv* + *b:ale_python_yapf_auto_uv* +python_yapf_auto_uv +g:ale_python_yapf_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-qml.txt b/doc/ale-qml.txt new file mode 100644 index 00000000..fa5116a2 --- /dev/null +++ b/doc/ale-qml.txt @@ -0,0 +1,20 @@ +=============================================================================== +ALE QML Integration *ale-qml-options* + + +=============================================================================== +qmlfmt *ale-qml-qmlfmt* + + *ale-options.qml_qmlfmt_executable* + *g:ale_qml_qmlfmt_executable* + *b:ale_qml_qmlfmt_executable* +qml_qmlfmt_executable +g:ale_qml_qmlfmt_executable + Type: |String| + Default: `'qmlfmt'` + + This variable can be set to change the path to qmlfmt. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-r.txt b/doc/ale-r.txt new file mode 100644 index 00000000..3610500e --- /dev/null +++ b/doc/ale-r.txt @@ -0,0 +1,81 @@ +=============================================================================== +ALE R Integration *ale-r-options* + + +=============================================================================== +languageserver *ale-r-languageserver* + + *ale-options.r_languageserver_cmd* + *g:ale_r_languageserver_cmd* + *b:ale_r_languageserver_cmd* +r_languageserver_cmd +g:ale_r_languageserver_cmd + Type: |String| + Default: `'languageserver::run()'` + + This option can be configured to change the execution command for + languageserver. + + See the languageserver documentation for more options. + + *ale-options.r_languageserver_config* + *g:ale_r_languageserver_config* + *b:ale_r_languageserver_config* +r_languageserver_config +g:ale_r_languageserver_config + Type: |Dictionary| + Default: `{}` + + This option can be configured to change settings for languageserver. See the + languageserver documentation for more information. + + +=============================================================================== +lintr *ale-r-lintr* + + *ale-options.r_lintr_options* + *g:ale_r_lintr_options* + *b:ale_r_lintr_options* +r_lintr_options +g:ale_r_lintr_options + Type: |String| + Default: `'lintr::with_defaults()'` + + This option can be configured to change the options for lintr. + + The value of this option will be run with `eval` for the `lintr::lint` + options. Consult the lintr documentation for more information. + + *ale-options.r_lintr_lint_package* + *g:ale_r_lintr_lint_package* + *b:ale_r_lintr_lint_package* +r_lintr_lint_package +g:ale_r_lintr_lint_package + Type: |Number| + Default: `0` + + When set to `1`, the file will be checked with `lintr::lint_package` instead + of `lintr::lint`. This prevents erroneous namespace warnings when linting + package files. + + +=============================================================================== +styler *ale-r-styler* + + *ale-options.r_styler_options* + *g:ale_r_styler_options* + *b:ale_r_styler_options* +r_styler_options +g: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-racket.txt b/doc/ale-racket.txt new file mode 100644 index 00000000..6188b04f --- /dev/null +++ b/doc/ale-racket.txt @@ -0,0 +1,49 @@ +=============================================================================== +ALE Racket Integration *ale-racket-options* + + +=============================================================================== +racket_langserver *ale-racket-langserver* + +1. Install racket-langserver as described here: + https://github.com/jeapostrophe/racket-langserver +2. Have `racket` available in the `$PATH` environment variable, currently there + is no way to specify path to custom location of `racket`. +3. set `racket_langserver` as a linter for `racket` like: > + let g:ale_linters['racket'] += ['racket_langserver'] + +You should be able to see linter results and use LSP features of `ALE` like +`ALEGoToDefinition` with `racket-langserver`. + + +=============================================================================== +raco_fmt *ale-racket-raco-fmt* + + *ale-options.racket_raco_fmt_executable* + *g:ale_racket_raco_fmt_executable* + *b:ale_racket_raco_fmt_executable* +racket_raco_fmt_executable +g:ale_racket_raco_fmt_executable + Type: |String| + Default: `'raco'` + + If the `raco` excutable is not in the `$PATH` environment variable, or you + prefer to use one installed in a custom location, set this option to the + path to the specific `raco` executable. + + *ale-options.racket_raco_fmt_options* + *g:ale_racket_raco_fmt_options* + *b:ale_racket_raco_fmt_options* +racket_raco_fmt_options +g:ale_racket_raco_fmt_options + Type: |String| + Default: `''` + + Use this variable to pass command-line flags/parameters to `raco_fmt` + + For example, set the page width limit to 40 > + let g:ale_racket_raco_fmt_options = '--width 40' + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-reasonml.txt b/doc/ale-reasonml.txt new file mode 100644 index 00000000..2b6c231c --- /dev/null +++ b/doc/ale-reasonml.txt @@ -0,0 +1,90 @@ +=============================================================================== +ALE ReasonML Integration *ale-reasonml-options* + + +=============================================================================== +merlin *ale-reasonml-merlin* + +To use merlin linter for ReasonML source code you need to make sure Merlin for +Vim is correctly configured. See the corresponding Merlin wiki page for +detailed instructions: +https://github.com/the-lambda-church/merlin/wiki/vim-from-scratch + +=============================================================================== +ols *ale-reasonml-ols* + +The `ocaml-language-server` is the engine that powers OCaml and ReasonML +editor support using the Language Server Protocol. See the installation +instructions: +https://github.com/freebroccolo/ocaml-language-server#installation + + +------------------------------------------------------------------------------- +Options + *ale-options.reason_ols_executable* + *g:ale_reason_ols_executable* + *b:ale_reason_ols_executable* +reason_ols_executable +g:ale_reason_ols_executable + Type: |String| + Default: `'ocaml-language-server'` + + This variable can be set to change the executable path for `ols`. + + *ale-options.reason_ols_use_global* + *g:ale_reason_ols_use_global* + *b:ale_reason_ols_use_global* +reason_ols_use_global +g:ale_reason_ols_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable can be set to `1` to always use the globally installed + executable. See also |ale-integrations-local-executables|. + + +=============================================================================== +reason-language-server *ale-reasonml-language-server* + +Note: You must set an executable - there is no 'default' install location. +Go to https://github.com/jaredly/reason-language-server and download the +latest release. You can place it anywhere, but ensure you set the executable +path. + + *ale-options.reason_ls_executable* + *g:ale_reason_ls_executable* + *b:ale_reason_ls_executable* +reason_ls_executable +g:ale_reason_ls_executable + Type: |String| + + This variable defines the standard location of the language server + executable. This must be set. + + +=============================================================================== +refmt *ale-reasonml-refmt* + + *ale-options.reasonml_refmt_executable* + *g:ale_reasonml_refmt_executable* + *b:ale_reasonml_refmt_executable* +reasonml_refmt_executable +g:ale_reasonml_refmt_executable + Type: |String| + Default: `'refmt'` + + This variable can be set to pass the path of the refmt fixer. + + *ale-options.reasonml_refmt_options* + *g:ale_reasonml_refmt_options* + *b:ale_reasonml_refmt_options* +reasonml_refmt_options +g:ale_reasonml_refmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the refmt fixer. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-rego.txt b/doc/ale-rego.txt new file mode 100644 index 00000000..454b9241 --- /dev/null +++ b/doc/ale-rego.txt @@ -0,0 +1,58 @@ +=============================================================================== +ALE Rego Integration *ale-rego-options* + + +=============================================================================== +cspell *ale-rego-cspell* + +See |ale-cspell-options| + + +=============================================================================== +opacheck *ale-rego-opa-check* + + *ale-options.rego_opacheck_executable* + *g:ale_rego_opacheck_executable* + *b:ale_rego_opacheck_executable* +rego_opacheck_executable +g:ale_rego_opacheck_executable + Type: |String| + Default: `'opa'` + + This variable can be changed to use a different executable for opa. + + *ale-options.rego_opacheck_options* + *g:rego_opacheck_options* + *b:rego_opacheck_options* +rego_opacheck_options +g:ale_rego_opacheck_options + Type: |String| + Default: `''` + + This variable can be changed to pass custom CLI flags to opa check. + + +=============================================================================== +opafmt *ale-rego-opa-fmt-fixer* + + *ale-options.opa_fmt_executable* + *g:ale_opa_fmt_executable* + *b:ale_opa_fmt_executable* +opa_fmt_executable +g:ale_opa_fmt_executable + Type: |String| + Default: `'opa'` + + This variable can be changed to use a different executable for opa. + + *ale-options.opa_fmt_options* + *g:ale_opa_fmt_options* + *b:ale_opa_fmt_options* +opa_fmt_options +g:ale_opa_fmt_options + Type: |String| + Default: `''` + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-rest.txt b/doc/ale-rest.txt new file mode 100644 index 00000000..df49ee68 --- /dev/null +++ b/doc/ale-rest.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE REST Integration *ale-rest-options* + + +=============================================================================== +kulala_fmt *ale-rest-kulala_fmt* + +See |ale-http-kulala_fmt| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-restructuredtext.txt b/doc/ale-restructuredtext.txt new file mode 100644 index 00000000..7af62133 --- /dev/null +++ b/doc/ale-restructuredtext.txt @@ -0,0 +1,33 @@ +=============================================================================== +ALE reStructuredText Integration *ale-restructuredtext-options* + + +=============================================================================== +cspell *ale-restructuredtext-cspell* + +See |ale-cspell-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| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-robot.txt b/doc/ale-robot.txt new file mode 100644 index 00000000..470fb2c5 --- /dev/null +++ b/doc/ale-robot.txt @@ -0,0 +1,18 @@ +=============================================================================== +ALE Robot Integration *ale-robot-options* + + +=============================================================================== +rflint *ale-robot-rflint* + + *ale-options.robot_rflint_executable* + *g:ale_robot_rflint_executable* + *b:ale_robot_rflint_executable* +robot_rflint_executable +g:ale_robot_rflint_executable + Type: |String| + Default: `'rflint'` + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-ruby.txt b/doc/ale-ruby.txt new file mode 100644 index 00000000..517317bd --- /dev/null +++ b/doc/ale-ruby.txt @@ -0,0 +1,346 @@ +=============================================================================== +ALE Ruby Integration *ale-ruby-options* + + +=============================================================================== +brakeman *ale-ruby-brakeman* + + *ale-options.ruby_brakeman_executable* + *g:ale_ruby_brakeman_executable* + *b:ale_ruby_brakeman_executable* +ruby_brakeman_executable +g:ale_ruby_brakeman_executable + Type: |String| + Default: `'brakeman'` + + Override the invoked brakeman binary. Set this to `'bundle'` to invoke + `'bundle` `exec` brakeman'. + + *ale-options.ruby_brakeman_options* + *g:ale_ruby_brakeman_options* + *b:ale_ruby_brakeman_options* +ruby_brakeman_options +g:ale_ruby_brakeman_options + Type: |String| + Default: `''` + + The contents of this variable will be passed through to brakeman. + + +=============================================================================== +cspell *ale-ruby-cspell* + +See |ale-cspell-options| + + +=============================================================================== +debride *ale-ruby-debride* + + *ale-options.ruby_debride_executable* + *g:ale_ruby_debride_executable* + *b:ale_ruby_debride_executable* +ruby_debride_executable +g:ale_ruby_debride_executable + Type: |String| + Default: `'debride'` + + Override the invoked debride binary. Set this to `'bundle'` to invoke + `'bundle` `exec` debride'. + + *ale-options.ruby_debride_options* + *g:ale_ruby_debride_options* + *b:ale_ruby_debride_options* +ruby_debride_options +g:ale_ruby_debride_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to debride. + + +=============================================================================== +packwerk *ale-ruby-packwerk* + + *ale-options.ruby_packwerk_executable* + *g:ale_ruby_packwerk_executable* + *b:ale_ruby_packwerk_executable* +ruby_packwerk_executable +g:ale_ruby_packwerk_executable + Type: |String| + Default: `'packwerk'` + + Override the invoked packwerk binary. Set this to `'bundle'` to invoke + `'bundle` `exec` packwerk'. + + *ale-options.ruby_packwerk_options* + *g:ale_ruby_packwerk_options* + *b:ale_ruby_packwerk_options* +ruby_packwerk_options +g:ale_ruby_packwerk_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to packwerk. + + +=============================================================================== +prettier *ale-ruby-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +rails_best_practices *ale-ruby-rails_best_practices* + + *ale-options.ruby_rails_best_practices_executable* + *g:ale_ruby_rails_best_practices_executable* + *b:ale_ruby_rails_best_practices_executable* +ruby_rails_best_practices_executable +g:ale_ruby_rails_best_practices_executable + Type: |String| + Default: `'rails_best_practices'` + + Override the invoked rails_best_practices binary. Set this to `'bundle'` to + invoke `'bundle` `exec` rails_best_practices'. + + + *ale-options.ruby_rails_best_practices_options* + *g:ale_ruby_rails_best_practices_options* + *b:ale_ruby_rails_best_practices_options* +ruby_rails_best_practices_options +g:ale_ruby_rails_best_practices_options + Type: |String| + Default: `''` + + The contents of this variable will be passed through to rails_best_practices. + + +=============================================================================== +reek *ale-ruby-reek* + + *ale-options.ruby_reek_executable* + *g:ale_ruby_reek_executable* + *b:ale_ruby_reek_executable* +ruby_reek_executable +g:ale_ruby_reek_executable + Type: |String| + Default: `'reek'` + + Override the invoked reek binary. Set this to `'bundle'` to invoke + `'bundle` `exec` reek'. + + *ale-options.ruby_reek_show_context* + *g:ale_ruby_reek_show_context* + *b:ale_ruby_reek_show_context* +ruby_reek_show_context +g:ale_ruby_reek_show_context + Type: |Number| + Default: `0` + + Controls whether context is included in the linter message. Defaults to off + because context is usually obvious while viewing a file. + + *ale-options.ruby_reek_show_wiki_link* + *g:ale_ruby_reek_show_wiki_link* + *b:ale_ruby_reek_show_wiki_link* +ruby_reek_show_wiki_link +g:ale_ruby_reek_show_wiki_link + Type: |Number| + Default: `0` + + Controls whether linter messages contain a link to an explanatory wiki page + for the type of code smell. Defaults to off to improve readability. + + +=============================================================================== +rubocop *ale-ruby-rubocop* + + *ale-options.ruby_rubocop_executable* + *g:ale_ruby_rubocop_executable* + *b:ale_ruby_rubocop_executable* +ruby_rubocop_executable +g:ale_ruby_rubocop_executable + Type: |String| + Default: `'rubocop'` + + Override the invoked rubocop binary. Set this to `'bundle'` to invoke + `'bundle` `exec` rubocop'. + + *ale-options.ruby_rubocop_options* + *g:ale_ruby_rubocop_options* + *b:ale_ruby_rubocop_options* +ruby_rubocop_options +g:ale_ruby_rubocop_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to rubocop. + + *ale-options.ruby_rubocop_auto_correct_all* + *g:ale_ruby_rubocop_auto_correct_all* + *b:ale_ruby_rubocop_auto_correct_all* +ruby_rubocop_auto_correct_all +g:ale_ruby_rubocop_auto_correct_all + Type: |Number| + Default: `0` + + This variable can be changed to make rubocop to correct all offenses (unsafe). + + +=============================================================================== +ruby *ale-ruby-ruby* + + *ale-options.ruby_ruby_executable* + *g:ale_ruby_ruby_executable* + *b:ale_ruby_ruby_executable* +ruby_ruby_executable +g:ale_ruby_ruby_executable + Type: |String| + Default: `'ruby'` + + This variable can be changed to use a different executable for ruby. + + +=============================================================================== +rufo *ale-ruby-rufo* + + *ale-options.ruby_rufo_executable* + *g:ale_ruby_rufo_executable* + *b:ale_ruby_rufo_executable* +ruby_rufo_executable +g:ale_ruby_rufo_executable + Type: |String| + Default: `'rufo'` + + Override the invoked rufo binary. This is useful for running rufo from + binstubs or a bundle. + + +=============================================================================== +solargraph *ale-ruby-solargraph* + + *ale-options.ruby_solargraph_executable* + *g:ale_ruby_solargraph_executable* + *b:ale_ruby_solargraph_executable* +ruby_solargraph_executable +g:ale_ruby_solargraph_executable + Type: |String| + Default: `'solargraph'` + + Override the invoked solargraph binary. This is useful for running solargraph + from binstubs or a bundle. + + +=============================================================================== +sorbet *ale-ruby-sorbet* + + *ale-options.ruby_sorbet_executable* + *g:ale_ruby_sorbet_executable* + *b:ale_ruby_sorbet_executable* +ruby_sorbet_executable +g:ale_ruby_sorbet_executable + Type: |String| + Default: `'srb'` + + Override the invoked sorbet binary. Set this to `'bundle'` to invoke + `'bundle` `exec` srb'. + + *ale-options.ruby_sorbet_options* + *g:ale_ruby_sorbet_options* + *b:ale_ruby_sorbet_options* +ruby_sorbet_options +g:ale_ruby_sorbet_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to sorbet. + + *ale-options.ruby_sorbet_enable_watchman* + *g:ale_ruby_sorbet_enable_watchman* + *b:ale_ruby_sorbet_enable_watchman* +ruby_sorbet_enable_watchman +g:ale_ruby_sorbet_enable_watchman + Type: |Number| + Default: `0` + + Whether or not to use watchman to let the LSP server to know about changes + to files from outside of vim. Defaults to disable watchman because it + requires watchman to be installed separately from sorbet. + + +=============================================================================== +standardrb *ale-ruby-standardrb* + + *ale-options.ruby_standardrb_executable* + *g:ale_ruby_standardrb_executable* + *b:ale_ruby_standardrb_executable* +ruby_standardrb_executable +g:ale_ruby_standardrb_executable + Type: |String| + Default: `'standardrb'` + + Override the invoked standardrb binary. Set this to `'bundle'` to invoke + `'bundle` `exec` standardrb'. + + *ale-options.ruby_standardrb_options* + *g:ale_ruby_standardrb_options* + *b:ale_ruby_standardrb_options* +ruby_standardrb_options +g:ale_ruby_standardrb_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to standardrb. + + +=============================================================================== +syntax_tree *ale-ruby-syntax_tree* + + *ale-options.ruby_syntax_tree_executable* + *g:ale_ruby_syntax_tree_executable* + *b:ale_ruby_syntax_tree_executable* +ruby_syntax_tree_executable +g:ale_ruby_syntax_tree_executable + Type: |String| + Default: `'stree'` + + Override the invoked SyntaxTree binary. Set this to `'bundle'` to invoke + `'bundle` `exec` stree'. + + *ale-options.ruby_syntax_tree_options* + *g:ale_ruby_syntax_tree_options* + *b:ale_ruby_syntax_tree_options* +ruby_syntax_tree_options +g:ale_ruby_syntax_tree_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to SyntaxTree. + + +=============================================================================== +rubyfmt *ale-ruby-rubyfmt* + + *ale-options.ruby_rubyfmt_executable* + *g:ale_ruby_rubyfmt_executable* + *b:ale_ruby_rubyfmt_executable* +ruby_rubyfmt_executable +g:ale_ruby_rubyfmt_executable + Type: |String| + Default: `'rubyfmt'` + + This option can be changed to change the path for `rubyfmt`. + + *ale-options.ruby_rubyfmt_options* + *g:ale_ruby_rubyfmt_options* + *b:ale_ruby_rubyfmt_options* +ruby_rubyfmt_options +g:ale_ruby_rubyfmt_options + Type: |String| + Default: `''` + + This option can be changed to pass extra options to `'rubyfmt'`. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-rust.txt b/doc/ale-rust.txt new file mode 100644 index 00000000..f75716fa --- /dev/null +++ b/doc/ale-rust.txt @@ -0,0 +1,369 @@ +=============================================================================== +ALE Rust Integration *ale-rust-options* + *ale-integration-rust* + +=============================================================================== +Integration Information + + If Vim does not detect the Rust file type out-of-the-box, you need the runtime + files for Rust distributed in Vim >=8.0.0501 or upstream: + https://github.com/rust-lang/rust.vim + + Note that there are several possible linters and fixers for Rust files: + + 1. rustc -- The Rust compiler is used to check the currently edited file. + So, if your project consists of multiple files, you will get some errors + when you use e.g. a struct which is defined in another file. You can use + |g:ale_rust_ignore_error_codes| to ignore some of these errors. + 2. cargo -- If your project is managed by Cargo, the whole project is + checked. That means that all errors are properly shown, but cargo can + only operate on the files written on disk, so errors will not be reported + while you type. + 3. rls -- If you have `rls` installed, you might prefer using this linter + over cargo. rls implements the Language Server Protocol for incremental + compilation of Rust code, and can check Rust files while you type. `rls` + requires Rust files to be contained in Cargo projects. + 4. analyzer -- If you have rust-analyzer installed, you might prefer using + this linter over cargo and rls. rust-analyzer also implements the + Language Server Protocol for incremental compilation of Rust code, and is + the next iteration of rls. rust-analyzer either requires Rust files to be + contained in Cargo projects or requires the project to be described in + the rust-project.json format: + https://rust-analyzer.github.io/manual.html#non-cargo-based-projects + 5. rustfmt -- If you have `rustfmt` installed, you can use it as a fixer to + consistently reformat your Rust code. + + Only cargo and rust-analyze are enabled by default. To switch to using rustc + instead of cargo, configure |b:ale_linters| in your ftplugin file + appropriately: > + + " See the help text for the option for more information. + let b:ale_linters = ['analyzer', 'rustc'] +< + Or in Lua: > + + require("ale").setup.buffer({linters = {"analyzer", "rustc"}}) +< + Also note that rustc 1.18. or later is needed. + + +=============================================================================== +analyzer *ale-rust-analyzer* + + *ale-options.rust_analyzer_executable* + *g:ale_rust_analyzer_executable* + *b:ale_rust_analyzer_executable* +rust_analyzer_executable +g:ale_rust_analyzer_executable + Type: |String| + Default: `'rust-analyzer'` + + This variable can be modified to change the executable path for + `rust-analyzer`. + + *ale-options.rust_analyzer_config* + *g:ale_rust_analyzer_config* + *b:ale_rust_analyzer_config* +rust_analyzer_config +g:ale_rust_analyzer_config + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for rust-analyzer. Keys of the + dictionary are components of configuration keys. For example: > + + let g:ale_rust_analyzer_config = { + \ 'server': { + \ 'extraEnv': { 'RUSTUP_TOOLCHAIN': 'stable' }, + \ } + \} +< + corresponds to `rust-analyzer.server.extraEnv = { 'RUSTUP_TOOLCHAIN': 'stable' }` + + For available configuration parameters, see the `rust-analyzer` manual: + + https://rust-analyzer.github.io/manual.html#configuration + + +=============================================================================== +cargo *ale-rust-cargo* + + *ale-options.rust_cargo_use_check* + *g:ale_rust_cargo_use_check* + *b:ale_rust_cargo_use_check* +rust_cargo_use_check +g:ale_rust_cargo_use_check + Type: |Number| + Default: `1` + + When set to `1`, this option will cause ALE to use `cargo check` instead of + `cargo build` . `cargo check` is supported since version 1.16.0 of Rust. + + ALE will never use `cargo check` when the version of `cargo` is less than + 0.17.0. + + *ale-options.rust_cargo_check_all_targets* + *g:ale_rust_cargo_check_all_targets* + *b:ale_rust_cargo_check_all_targets* +rust_cargo_check_all_targets +g:ale_rust_cargo_check_all_targets + Type: |Number| + Default: `0` + + When set to `1`, ALE will set the `--all-targets` option when `cargo check` + is used. See |g:ale_rust_cargo_use_check|, + + *ale-options.rust_cargo_check_tests* + *g:ale_rust_cargo_check_tests* + *b:ale_rust_cargo_check_tests* +rust_cargo_check_tests +g:ale_rust_cargo_check_tests + Type: |Number| + Default: `0` + + When set to `1`, ALE will set the `--tests` option when `cargo check` + is used. This allows for linting of tests which are normally excluded. + See |g:ale_rust_cargo_use_check|, + + *ale-options.rust_cargo_check_examples* + *g:ale_rust_cargo_check_examples* + *b:ale_rust_cargo_check_examples* +rust_cargo_check_examples +g:ale_rust_cargo_check_examples + Type: |Number| + Default: `0` + + When set to `1`, ALE will set the `--examples` option when `cargo check` + is used. This allows for linting of examples which are normally excluded. + See |g:ale_rust_cargo_use_check|, + + *ale-options.rust_cargo_default_feature_behavior* + *g:ale_rust_cargo_default_feature_behavior* + *b:ale_rust_cargo_default_feature_behavior* +rust_cargo_default_feature_behavior +g:ale_rust_cargo_default_feature_behavior + Type: |String| + Default: `default` + + When set to `none`, ALE will set the `--no-default-features` option when + invoking `cargo`. Only the features specified in + |g:ale_rust_cargo_include_features| will be included when performing the + lint check. + + When set to `default`, ALE will instruct `cargo` to build all default + features specified in the project's `Cargo.toml` file, in addition to + including any additional features defined in + |g:ale_rust_cargo_include_features|. + + When set to `all`, ALE will set the `--all-features` option when + invoking `cargo`, which will include all features defined in the project's + `Cargo.toml` file when performing the lint check. + + *ale-options.rust_cargo_include_features* + *g:ale_rust_cargo_include_features* + *b:ale_rust_cargo_include_features* +rust_cargo_include_features +g:ale_rust_cargo_include_features + Type: |String| + Default: `''` + + When defined, ALE will set the `--features` option when invoking `cargo` to + perform the lint check. See |g:ale_rust_cargo_default_feature_behavior|. + + *ale-options.rust_cargo_avoid_whole_workspace* + *g:ale_rust_cargo_avoid_whole_workspace* + *b:ale_rust_cargo_avoid_whole_workspace* +rust_cargo_avoid_whole_workspace +g:ale_rust_cargo_avoid_whole_workspace + Type: |Number| + Default: `1` + + When set to 1, and ALE is used to edit a crate that is part of a Cargo + workspace, avoid building the entire workspace by invoking `cargo` directly + in the crate's directory. Otherwise, behave as usual. + + *ale-options.rust_cargo_use_clippy* + *g:ale_rust_cargo_use_clippy* + *b:ale_rust_cargo_use_clippy* +rust_cargo_use_clippy +g:ale_rust_cargo_use_clippy + Type: |Number| + Default: `0` + + When set to 1, `cargo clippy` will be used instead of `cargo check` or + `cargo build` as linter. + For details of `cargo clippy`, please visit the following link: + + https://github.com/rust-lang-nursery/rust-clippy + + Since `cargo clippy` is optional toolchain, it's safer to check whether + `cargo-clippy` is executable as follows: +> + let g:ale_rust_cargo_use_clippy = executable('cargo-clippy') +< + *ale-options.rust_cargo_clippy_options* + *g:ale_rust_cargo_clippy_options* + *b:ale_rust_cargo_clippy_options* +rust_cargo_clippy_options +g:ale_rust_cargo_clippy_options + Type: |String| + Default: `''` + + When `cargo clippy` is used, this value will be added to a command line to run + it. This variable is useful when you want to add some extra options which + only `cargo clippy` supports (e.g. `--deny`). + + *ale-options.rust_cargo_target_dir* + *g:ale_rust_cargo_target_dir* + *b:ale_rust_cargo_target_dir* +rust_cargo_target_dir +g:ale_rust_cargo_target_dir + Type: |String| + Default: `''` + + Use a custom target directory when running the commands for ALE. This can + help to avoid "waiting for file lock on build directory" messages when + running `cargo` commands manually while ALE is performing its checks. + + +=============================================================================== +cspell *ale-rust-cspell* + +See |ale-cspell-options| + + +=============================================================================== +rls *ale-rust-rls* + + *ale-options.rust_rls_executable* + *g:ale_rust_rls_executable* + *b:ale_rust_rls_executable* +rust_rls_executable +g:ale_rust_rls_executable + Type: |String| + Default: `'rls'` + + This variable can be modified to change the executable path for `rls`. + + *ale-options.rust_rls_toolchain* + *g:ale_rust_rls_toolchain* + *b:ale_rust_rls_toolchain* +rust_rls_toolchain +g:ale_rust_rls_toolchain + Type: |String| + Default: `''` + + This option can be set to change the toolchain used for `rls`. Possible + 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. + + *ale-options.rust_rls_config* + *g:ale_rust_rls_config* + *b:ale_rust_rls_config* +rust_rls_config +g:ale_rust_rls_config + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for rls. For example, to force + using clippy as linter in your ftplugin file: > + + let b:ale_rust_rls_config = { + \ 'rust': { + \ 'clippy_preference': 'on' + \ }, + \} +< + Or in Lua: > + + require("ale").setup.buffer({ + rust_rls_config = { + rust = { + clippy_preference = "on", + }, + }, + }) +< + +=============================================================================== +rustc *ale-rust-rustc* + + *ale-options.rust_rustc_options* + *g:ale_rust_rustc_options* + *b:ale_rust_rustc_options* +rust_rustc_options +g:ale_rust_rustc_options + Type: |String| + Default: `'--emit=mir -o /dev/null'` + + The variable can be used to change the options passed to `rustc`. + + Users of nightly builds of Rust might want to use `-Z no-codegen` instead. + Be careful when setting the options, as running `rustc` could execute code + or generate binary files. + + *ale-options.rust_ignore_error_codes* + *g:ale_rust_ignore_error_codes* + *b:ale_rust_ignore_error_codes* +rust_ignore_error_codes +g:ale_rust_ignore_error_codes + Type: |List| of |String|s + Default: `[]` + + This variable can contain error codes which will be ignored. For example, to + ignore most errors regarding failed imports, put this in your .vimrc > + + let g:ale_rust_ignore_error_codes = ['E0432', 'E0433'] +< + *ale-options.rust_ignore_secondary_spans* + *g:ale_rust_ignore_secondary_spans* + *b:ale_rust_ignore_secondary_spans* +rust_ignore_secondary_spans +g:ale_rust_ignore_secondary_spans + Type: |Number| + Default: `0` + + When set to 1, instructs the Rust error reporting to ignore secondary spans. + The problem with secondary spans is that they sometimes appear in error + messages before the main cause of the error, for example: > + + 1 src/main.rs|98 col 5 error| this function takes 4 parameters but 5 + parameters were supplied: defined here + 2 src/main.rs|430 col 32 error| this function takes 4 parameters but 5 + parameters were supplied: expected 4 parameters +< + This is due to the sorting by line numbers. With this option set to 1, + the 'defined here' span will not be presented. + + +=============================================================================== +rustfmt *ale-rust-rustfmt* + + *ale-options.rust_rustfmt_options* + *g:ale_rust_rustfmt_options* + *b:ale_rust_rustfmt_options* +rust_rustfmt_options +g:ale_rust_rustfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the rustfmt fixer. + + *ale-options.rust_rustfmt_executable* + *g:ale_rust_rustfmt_executable* + *b:ale_rust_rustfmt_executable* +rust_rustfmt_executable +g:ale_rust_rustfmt_executable + Type: |String| + Default: `'rustfmt'` + + This variable can be modified to change the executable path for `rustfmt`. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-salt.tmt b/doc/ale-salt.tmt new file mode 100644 index 00000000..73bafdf8 --- /dev/null +++ b/doc/ale-salt.tmt @@ -0,0 +1,47 @@ +=============================================================================== +ALE SALT Integration *ale-salt-options* + +=============================================================================== +salt-lint *ale-salt-salt-lint* + +Website: https://github.com/warpnet/salt-lint + + +------------------------------------------------------------------------------- +Installation + +Install salt-lint in your a virtualenv directory, locally, or globally: > + + pip install salt-lint # After activating virtualenv + pip install --user salt-lint # Install to ~/.local/bin + sudo pip install salt-lint # Install globally +< +See |g:ale_virtualenv_dir_names| for configuring how ALE searches for +virtualenv directories. + + +------------------------------------------------------------------------------- +Options + *ale-options.salt_salt_lint_executable* + *g:ale_salt_salt_lint_executable* + *b:ale_salt_salt_lint_executable* +salt_salt_lint_executable +g:ale_salt_salt_lint_executable + Type: |String| + Default: `'salt-lint'` + + This variable can be set to change the path to salt-lint. + + *ale-options.salt_salt_lint_options* + *g:ale_salt_salt_lint_options* + *b:ale_salt_salt_lint_options* +salt_salt_lint_options +g:ale_salt_salt_lint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to salt-lint. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-sass.txt b/doc/ale-sass.txt new file mode 100644 index 00000000..edb7b349 --- /dev/null +++ b/doc/ale-sass.txt @@ -0,0 +1,36 @@ +=============================================================================== +ALE Sass Integration *ale-sass-options* + + +=============================================================================== +sasslint *ale-sass-sasslint* + +See |ale-scss-sasslint| for information about the available options. + + +=============================================================================== +stylelint *ale-sass-stylelint* + + *ale-options.sass_stylelint_executable* + *g:ale_sass_stylelint_executable* + *b:ale_sass_stylelint_executable* +sass_stylelint_executable +g:ale_sass_stylelint_executable + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + *ale-options.sass_stylelint_use_global* + *g:ale_sass_stylelint_use_global* + *b:ale_sass_stylelint_use_global* +sass_stylelint_use_global +g:ale_sass_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-scala.txt b/doc/ale-scala.txt new file mode 100644 index 00000000..4c188880 --- /dev/null +++ b/doc/ale-scala.txt @@ -0,0 +1,146 @@ +=============================================================================== +ALE Scala Integration *ale-scala-options* + + +=============================================================================== +cspell *ale-scala-cspell* + +See |ale-cspell-options| + + +=============================================================================== +metals *ale-scala-metals* + +`metals` requires either an SBT project, a Mill project, or a running Bloop +server. + + *ale-options.scala_metals_executable* + *g:ale_scala_metals_executable* + *b:ale_scala_metals_executable* +scala_metals_executable +g:ale_scala_metals_executable + Type: |String| + Default: `'metals-vim'` + + Override the invoked `metals` binary. + + *ale-options.scala_metals_project_root* + *g:ale_scala_metals_project_root* + *b:ale_scala_metals_project_root* +scala_metals_project_root +g:ale_scala_metals_project_root + Type: |String| + Default: `''` + + By default the project root is found by searching upwards for `build.sbt`, + `build.sc`, `.bloop` or `.metals`. If the project root is elsewhere, you + can override the project root directory. + + +=============================================================================== +sbtserver *ale-scala-sbtserver* + +`sbtserver` requires a running ^1.1.x sbt shell to connect to. It will attempt +to connect via TCP to the address defined in `g:ale_scala_sbtserver_address`. +As `sbt` defaults to listening via unix sockets, place these settings into +your `~/.sbt/1.0/global.sbt` to ensure that ale will always attempt to connect +to the right socket: + +`serverConnectionType := ConnectionType.Tcp` and `serverPort := 4273` + + *ale-options.scala_sbtserver_address* + *g:ale_scala_sbtserver_address* + *b:ale_scala_sbtserver_address* +scala_sbtserver_address +g:ale_scala_sbtserver_address + Type: |String| + Default: `'127.0.0.1:4273'` + + By default the address is found by parsing `active.json`, however, reading a + file is a blocking operation which should be avoided in ale. The easy way + around this is to configure sbt to always connect to the same port, which + the instructions above describe. + + *ale-options.scala_sbtserver_project_root* + *g:ale_scala_sbtserver_project_root* + *b:ale_scala_sbtserver_project_root* +scala_sbtserver_project_root +g:ale_scala_sbtserver_project_root + Type: |String| + Default: `''` + + By default the project root is found by searching upwards for `build.sbt`. + If the project root is elsewhere, you can override the project root + directory. + + +=============================================================================== +scalafmt *ale-scala-scalafmt* + +If Nailgun is used, override `g:ale_scala_scalafmt_executable` like so: > + + let g:ale_scala_scalafmt_executable = 'ng' +< + *ale-options.scala_scalafmt_executable* + *g:ale_scala_scalafmt_executable* + *b:ale_scala_scalafmt_executable* +scala_scalafmt_executable +g:ale_scala_scalafmt_executable + Type: |String| + Default: `'scalafmt'` + + Override the invoked `scalafmt` binary. This is useful for running `scalafmt` + with Nailgun. + + *ale-options.scala_scalafmt_options* + *g:ale_scala_scalafmt_options* + *b:ale_scala_scalafmt_options* +scala_scalafmt_options +g:ale_scala_scalafmt_options + Type: |String| + Default: `''` + + A string containing additional options to pass to `'scalafmt'`, or + `'ng scalafmt'` if Nailgun is used. + + +=============================================================================== +scalastyle *ale-scala-scalastyle* + +`scalastyle` requires a configuration file for a project to run. When no +configuration file can be found, ALE will report a problem saying that a +configuration file is required at line 1. + +To disable `scalastyle` globally, use |g:ale_linters| like so: > + + let g:ale_linters = {'scala': ['scalac']} " Enable only scalac instead +< +See |g:ale_linters| for more information on disabling linters. + + *ale-options.scala_scalastyle_config* + *g:ale_scala_scalastyle_config* + *b:ale_scala_scalastyle_config* +scala_scalastyle_config +g:ale_scala_scalastyle_config + Type: |String| + Default: `''` + + A string containing the location of a global fallback configuration file. + + By default, ALE will look for a configuration file named + `scalastyle_config.xml` or `scalastyle-config.xml` in the current file's + directory or parent directories. + + *ale-options.scala_scalastyle_options* + *g:ale_scala_scalastyle_options* + *b:ale_scala_scalastyle_options* +scala_scalastyle_options +g:ale_scala_scalastyle_options + Type: |String| + Default: `''` + + A string containing additional options to pass to scalastyle. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-scss.txt b/doc/ale-scss.txt new file mode 100644 index 00000000..40ab2d2b --- /dev/null +++ b/doc/ale-scss.txt @@ -0,0 +1,80 @@ +=============================================================================== +ALE SCSS Integration *ale-scss-options* + + +=============================================================================== +prettier *ale-scss-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +sasslint *ale-scss-sasslint* + + *ale-options.scss_sasslint_executable* + *g:ale_scss_sasslint_executable* + *b:ale_scss_sasslint_executable* +scss_sasslint_executable +g:ale_scss_sasslint_executable + Type: |String| + Default: `'sass-lint'` + + See |ale-integrations-local-executables| + + *ale-options.scss_sasslint_options* + *g:ale_scss_sasslint_options* + *b:ale_scss_sasslint_options* +scss_sasslint_options +g:ale_scss_sasslint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to sass-lint. + + *ale-options.scss_sasslint_use_global* + *g:ale_scss_sasslint_use_global* + *b:ale_scss_sasslint_use_global* +scss_sasslint_use_global +g:ale_scss_sasslint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +stylelint *ale-scss-stylelint* + + *ale-options.scss_stylelint_executable* + *g:ale_scss_stylelint_executable* + *b:ale_scss_stylelint_executable* +scss_stylelint_executable +g:ale_scss_stylelint_executable + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + *ale-options.scss_stylelint_options* + *g:ale_scss_stylelint_options* + *b:ale_scss_stylelint_options* +scss_stylelint_options +g:ale_scss_stylelint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + *ale-options.scss_stylelint_use_global* + *g:ale_scss_stylelint_use_global* + *b:ale_scss_stylelint_use_global* +scss_stylelint_use_global +g:ale_scss_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-sh.txt b/doc/ale-sh.txt new file mode 100644 index 00000000..cc2e4245 --- /dev/null +++ b/doc/ale-sh.txt @@ -0,0 +1,169 @@ +=============================================================================== +ALE Shell Integration *ale-sh-options* + + +=============================================================================== +bashate *ale-sh-bashate* + + *ale-options.sh_bashate_executable* + *g:ale_sh_bashate_executable* + *b:ale_sh_bashate_executable* +sh_bashate_executable +g:ale_sh_bashate_executable + Type: |String| + Default: `'bashate'` + + This variable sets executable used for bashate. + + *ale-options.sh_bashate_options* + *g:ale_sh_bashate_options* + *b:ale_sh_bashate_options* +sh_bashate_options +g:ale_sh_bashate_options + Type: |String| + Default: `''` + + With this variable we are able to pass extra arguments for bashate. For + example to ignore the indentation rule: > + + let g:ale_sh_bashate_options = '-i E003' +< + +=============================================================================== +cspell *ale-sh-cspell* + +See |ale-cspell-options| + + +=============================================================================== +sh-language-server *ale-sh-language-server* + + *ale-options.sh_language_server_executable* + *g:ale_sh_language_server_executable* + *b:ale_sh_language_server_executable* +sh_language_server_executable +g:ale_sh_language_server_executable + Type: |String| + Default: `'bash-language-server'` + + See |ale-integrations-local-executables| + + *ale-options.sh_language_server_use_global* + *g:ale_sh_language_server_use_global* + *b:ale_sh_language_server_use_global* +sh_language_server_use_global +g:ale_sh_language_server_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +shell *ale-sh-shell* + + *ale-options.sh_shell_default_shell* + *g:ale_sh_shell_default_shell* + *b:ale_sh_shell_default_shell* +sh_shell_default_shell +g:ale_sh_shell_default_shell + Type: |String| + Default: The current shell (`$SHELL`). Falls back to `'bash'` if that cannot be + read or if the current shell is `'fish'`. + + When ALE runs the linter for shells with the `-n` flag, it will attempt to + read the shell from the shebang (`#!`) line from the shell script to + determine the shell program to run. When this detection fails, this variable + will be used instead. + + +=============================================================================== +shellcheck *ale-sh-shellcheck* + + *ale-options.sh_shellcheck_executable* + *g:ale_sh_shellcheck_executable* + *b:ale_sh_shellcheck_executable* +sh_shellcheck_executable +g:ale_sh_shellcheck_executable + Type: |String| + Default: `'shellcheck'` + + This variable sets executable used for shellcheck. + + *ale-options.sh_shellcheck_options* + *g:ale_sh_shellcheck_options* + *b:ale_sh_shellcheck_options* +sh_shellcheck_options +g:ale_sh_shellcheck_options + Type: |String| + Default: `''` + + With this variable we are able to pass extra arguments for shellcheck + for shellcheck invocation. + + For example, if we want shellcheck to follow external sources (`see SC1091`) + we can set the variable as such: > + + let g:ale_sh_shellcheck_options = '-x' +< + *ale-options.sh_shellcheck_change_directory* + *g:ale_sh_shellcheck_change_directory* + *b:ale_sh_shellcheck_change_directory* +sh_shellcheck_change_directory +g: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. + + *ale-options.sh_shellcheck_dialect* + *g:ale_sh_shellcheck_dialect* + *b:ale_sh_shellcheck_dialect* +sh_shellcheck_dialect +g: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|). + + *ale-options.sh_shellcheck_exclusions* + *g:ale_sh_shellcheck_exclusions* + *b:ale_sh_shellcheck_exclusions* +sh_shellcheck_exclusions +g:ale_sh_shellcheck_exclusions + Type: |String| + Default: `''` + + Set this variable to exclude test(s) for shellcheck (-e/--exclude option). + To exclude more than one option, separate them with commas. + + For example, to ignore some warnings that aren't applicable to files that + will be sourced by other scripts, use the buffer-local variant: > + + autocmd BufEnter PKGBUILD,.env + \ let b:ale_sh_shellcheck_exclusions = 'SC2034,SC2154,SC2164' +< + +=============================================================================== +shfmt *ale-sh-shfmt* + + *ale-options.sh_shfmt_options* + *g:ale_sh_shfmt_options* + *b:ale_sh_shfmt_options* +sh_shfmt_options +g:ale_sh_shfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the shfmt fixer. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-sml.txt b/doc/ale-sml.txt new file mode 100644 index 00000000..a54fa5a7 --- /dev/null +++ b/doc/ale-sml.txt @@ -0,0 +1,39 @@ +=============================================================================== +ALE SML Integration *ale-sml-options* + +=============================================================================== +smlnj *ale-sml-smlnj* + *ale-sml-smlnj-cm* + +There are two SML/NJ powered checkers: + +- one using Compilation Manager that works on whole projects, but requires you + to save before errors show up +- one using the SML/NJ REPL that works as you change the text, but might fail + if your project can only be built with CM. + +We dynamically select which one to use based whether we find a `*.cm` file at +or above the directory of the file being checked. Only one checker (`smlnj`, +`smlnj-cm`) will be enabled at a time. + +------------------------------------------------------------------------------- +Options + *ale-options.sml_smlnj_cm_file* + *g:ale_sml_smlnj_cm_file* + *b:ale_sml_smlnj_cm_file* +sml_smlnj_cm_file +g:ale_sml_smlnj_cm_file + Type: |String| + Default: `'*.cm'` + + By default, ALE will look for a `*.cm` file in your current directory, + searching upwards. It stops when it finds at least one `*.cm` file (taking + the first file if there are more than one). + + Change this option (in the buffer or global scope) to control how ALE finds + CM files. For example, to always search for a CM file named `sandbox.cm`: > + + let g:ale_sml_smlnj_cm_file = 'sandbox.cm' +< +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-solidity.txt b/doc/ale-solidity.txt new file mode 100644 index 00000000..7b9e5a7a --- /dev/null +++ b/doc/ale-solidity.txt @@ -0,0 +1,58 @@ +=============================================================================== +ALE Solidity Integration *ale-solidity-options* + + +=============================================================================== +solc *ale-solidity-solc* + + *ale-options.solidity_solc_executable* + *g:ale_solidity_solc_executable* + *b:ale_solidity_solc_executable* +solidity_solc_executable +g:ale_solidity_solc_executable + Type: |String| + Default: `'solc'` + + Override the invoked solc binary. For truffle/hardhat binaries. + + *ale-options.solidity_solc_options* + *g:ale_solidity_solc_options* + *b:ale_solidity_solc_options* +solidity_solc_options +g:ale_solidity_solc_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to solc. + + +=============================================================================== +solhint *ale-solidity-solhint* + + Solhint should work out-of-the-box. You can further configure it using a + `.solihint.json` file. See https://github.com/protofire/solhint for more + information. + + +=============================================================================== +solium *ale-solidity-solium* + + Use of Solium linter for Solidity source code requires a .soliumrc.json + file in project root. This file can be generated by running `solium --init`. + See the corresponding solium usage for detailed instructions + (https://github.com/duaraghav8/Solium#usage). + + +=============================================================================== +forge *ale-solidity-forge* + + `forge fmt` is not a linter, only a formatter. It should be used only as a + fixer. + + `forge fmt` should work out-of-the-box. You can further configure it using + `foundry.toml`. See the corresponding documentation for detailed + instructions (https://book.getfoundry.sh/reference/config/formatter). + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-spec.txt b/doc/ale-spec.txt new file mode 100644 index 00000000..bcacba2e --- /dev/null +++ b/doc/ale-spec.txt @@ -0,0 +1,48 @@ +=============================================================================== +ALE Spec Integration *ale-spec-options* + *ale-integration-spec* + +=============================================================================== +Integration Information + + The rpmlint linter is disabled by default, because running rpmlint can + result in the execution of code embedded in the spec file and rpmlint makes + no distinction between checks which are safe to run on untrusted files and + those which are not. + + Currently linters must be enabled globally. The rpmlint linter can be + enabled with: +> + let g:ale_linters = {'spec': ['rpmlint']} +< + +=============================================================================== +rpmlint *ale-spec-rpmlint* + + *ale-options.spec_rpmlint_executable* + *g:ale_spec_rpmlint_executable* + *b:ale_spec_rpmlint_executable* +spec_rpmlint_executable +g:ale_spec_rpmlint_executable + Type: |String| + Default: `'rpmlint'` + + This variable sets executable used for rpmlint. + + *ale-options.spec_rpmlint_options* + *g:ale_spec_rpmlint_options* + *b:ale_spec_rpmlint_options* +spec_rpmlint_options +g:ale_spec_rpmlint_options + Type: |String| + Default: `''` + + Set this to pass extra arguments to rpmlint. + + For example, to instruct rpmlint to use a specific configuration file: +> + let g:ale_spec_rpmlint_options = '-f custom.cf' +< + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-sql.txt b/doc/ale-sql.txt new file mode 100644 index 00000000..8988e7a2 --- /dev/null +++ b/doc/ale-sql.txt @@ -0,0 +1,109 @@ +=============================================================================== +ALE SQL Integration *ale-sql-options* + + +=============================================================================== +dprint *ale-sql-dprint* + +See |ale-dprint-options| +and https://github.com/dprint/dprint-plugin-sql/releases + + +=============================================================================== +pgformatter *ale-sql-pgformatter* + + *ale-options.sql_pgformatter_executable* + *g:ale_sql_pgformatter_executable* + *b:ale_sql_pgformatter_executable* +sql_pgformatter_executable +g:ale_sql_pgformatter_executable + Type: |String| + Default: `'pg_format'` + + This variable sets executable used for pgformatter. + + *ale-options.sql_pgformatter_options* + *g:ale_sql_pgformatter_options* + *b:ale_sql_pgformatter_options* +sql_pgformatter_options +g:ale_sql_pgformatter_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the pgformatter fixer. + + +=============================================================================== +sqlfluff *ale-sql-sqlfluff* + + *ale-options.sql_sqlfluff_executable* + *g:ale_sql_sqlfluff_executable* + *b:ale_sql_sqlfluff_executable* +sql_sqlfluff_executable +g:ale_sql_sqlfluff_executable + Type: |String| + Default: `'sqlfluff'` + + This variable sets executable used for sqlfluff. + + *ale-options.sql_sqlfluff_options* + *g:ale_sql_sqlfluff_options* + *b:ale_sql_sqlfluff_options* +sql_sqlfluff_options +g:ale_sql_sqlfluff_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the sqlfluff linter. + +=============================================================================== +sqlfmt *ale-sql-sqlfmt* + + *ale-options.sql_sqlfmt_executable* + *g:ale_sql_sqlfmt_executable* + *b:ale_sql_sqlfmt_executable* +sql_sqlfmt_executable +g:ale_sql_sqlfmt_executable + Type: |String| + Default: `'sqlfmt'` + + This variable sets executable used for sqlfmt. + + *ale-options.sql_sqlfmt_options* + *g:ale_sql_sqlfmt_options* + *b:ale_sql_sqlfmt_options* +sql_sqlfmt_options +g:ale_sql_sqlfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the sqlfmt fixer. + At this time only the -u flag is available to format with upper-case. + + +=============================================================================== +sqlformat *ale-sql-sqlformat* + + *ale-options.sql_sqlformat_executable* + *g:ale_sql_sqlformat_executable* + *b:ale_sql_sqlformat_executable* +sql_sqlformat_executable +g:ale_sql_sqlformat_executable + Type: |String| + Default: `'sqlformat'` + + This variable sets executable used for sqlformat. + + *ale-options.sql_sqlformat_options* + *g:ale_sql_sqlformat_options* + *b:ale_sql_sqlformat_options* +sql_sqlformat_options +g:ale_sql_sqlformat_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the sqlformat fixer. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-stylus.txt b/doc/ale-stylus.txt new file mode 100644 index 00000000..5ba53c4f --- /dev/null +++ b/doc/ale-stylus.txt @@ -0,0 +1,40 @@ +=============================================================================== +ALE Stylus Integration *ale-stylus-options* + + +=============================================================================== +stylelint *ale-stylus-stylelint* + + *ale-options.stylus_stylelint_executable* + *g:ale_stylus_stylelint_executable* + *b:ale_stylus_stylelint_executable* +stylus_stylelint_executable +g:ale_stylus_stylelint_executable + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + *ale-options.stylus_stylelint_options* + *g:ale_stylus_stylelint_options* + *b:ale_stylus_stylelint_options* +stylus_stylelint_options +g:ale_stylus_stylelint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + *ale-options.stylus_stylelint_use_global* + *g:ale_stylus_stylelint_use_global* + *b:ale_stylus_stylelint_use_global* +stylus_stylelint_use_global +g:ale_stylus_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-sugarss.txt b/doc/ale-sugarss.txt new file mode 100644 index 00000000..9909dc98 --- /dev/null +++ b/doc/ale-sugarss.txt @@ -0,0 +1,40 @@ +=============================================================================== +ALE SugarSS Integration *ale-sugarss-options* + + +=============================================================================== +stylelint *ale-sugarss-stylelint* + + *ale-options.sugarss_stylelint_executable* + *g:ale_sugarss_stylelint_executable* + *b:ale_sugarss_stylelint_executable* +sugarss_stylelint_executable +g:ale_sugarss_stylelint_executable + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + *ale-options.sugarss_stylelint_options* + *g:ale_sugarss_stylelint_options* + *b:ale_sugarss_stylelint_options* +sugarss_stylelint_options +g:ale_sugarss_stylelint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + *ale-options.sugarss_stylelint_use_global* + *g:ale_sugarss_stylelint_use_global* + *b:ale_sugarss_stylelint_use_global* +sugarss_stylelint_use_global +g: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..ee225b72 --- /dev/null +++ b/doc/ale-supported-languages-and-tools.txt @@ -0,0 +1,769 @@ +*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 + * `ada_language_server` + * `cspell` + * `gcc` + * `gnatpp` +* Ansible + * `ansible-language-server` + * `ansible-lint`!! +* API Blueprint + * `drafter` +* APKBUILD + * `apkbuild-fixer` + * `apkbuild-lint` + * `secfixes-check` +* AsciiDoc + * `alex` + * `cspell` + * `languagetool`!! + * `proselint` + * `redpen` + * `textlint` + * `vale` + * `write-good` +* ASM + * `gcc` + * `llvm-mc` +* Astro + * `eslint` + * `prettier` +* AVRA + * `avra` +* Awk + * `gawk` +* Bash + * `bashate` + * `cspell` + * `language-server` + * `shell` (-n flag) + * `shellcheck` + * `shfmt` +* Bats + * `shellcheck` +* Bazel + * `buildifier` +* BibTeX + * `bibclean` +* Bicep + * `bicep` +* BitBake + * `oelint-adv` +* Bourne Shell + * `shell` (-n flag) + * `shellcheck` + * `shfmt` +* C + * `astyle` + * `ccls` + * `clang` (`cc`) + * `clang-format` + * `clangcheck`!! + * `clangd` + * `clangtidy`!! + * `cppcheck` + * `cpplint`!! + * `cquery` + * `cspell` + * `flawfinder` + * `gcc` (`cc`) + * `uncrustify` +* C# + * `clang-format` + * `csc`!! + * `cspell` + * `dotnet-format` + * `mcs` + * `mcsc`!! + * `uncrustify` +* C++ (filetype cpp) + * `astyle` + * `ccls` + * `clang` (`cc`) + * `clang-format` + * `clangcheck`!! + * `clangd` + * `clangtidy`!! + * `clazy`!! + * `cppcheck` + * `cpplint`!! + * `cquery` + * `cspell` + * `flawfinder` + * `gcc` (`cc`) + * `uncrustify` +* C3 + * `c3lsp` +* Cairo + * `scarb`!! + * `starknet` +* Chef + * `cookstyle` + * `foodcritic`!! +* Clojure + * `clj-kondo` + * `cljfmt` + * `joker` +* CloudFormation + * `cfn-python-lint` +* CMake + * `cmake-format` + * `cmake-lint` + * `cmakelint` +* CoffeeScript + * `coffee` + * `coffeelint` +* Crystal + * `ameba`!! + * `crystal`!! +* CSS + * `VSCode CSS language server` + * `cspell` + * `css-beautify` + * `csslint` + * `fecs` + * `prettier` + * `stylelint` +* Cucumber + * `cucumber` +* CUDA + * `clang-format` + * `clangd` + * `nvcc`!! +* Cypher + * `cypher-lint` +* Cython (pyrex filetype) + * `cython` +* D + * `dfmt` + * `dls` + * `dmd` + * `uncrustify` +* Dafny + * `dafny`!! +* Dart + * `analysis_server` + * `dart-analyze`!! + * `dart-format`!! + * `dartfmt`!! + * `language_server` +* desktop + * `desktop-file-validate` +* Dhall + * `dhall-format` + * `dhall-freeze` + * `dhall-lint` +* Dockerfile + * `dockerfile_lint` + * `dockerlinter` + * `dprint` + * `hadolint` +* Elixir + * `credo` + * `cspell` + * `dialyxir` + * `dogma`!! + * `elixir-ls` + * `lexical` + * `mix`!! +* Elm + * `elm-format` + * `elm-ls` + * `elm-make` +* Erb + * `erb` + * `erb-formatter` + * `erblint` + * `erubi` + * `erubis` + * `htmlbeautifier` + * `ruumba` +* Erlang + * `SyntaxErl` + * `dialyzer`!! + * `elvis`!! + * `erlang-mode` (The Erlang mode for Emacs) + * `erlang_ls` + * `erlc` + * `erlfmt` +* Fish + * `fish` (-n flag) + * `fish_indent` +* Fortran + * `gcc` + * `language_server` +* Fountain + * `proselint` +* FusionScript + * `fusion-lint` +* Git Commit Messages + * `gitlint` +* Gleam + * `gleam_format` + * `gleamlsp` +* GLSL + * `glslang` + * `glslls` +* Go + * `bingo` + * `cspell` + * `go build`!! + * `go mod`!! + * `go vet`!! + * `gofmt` + * `gofumpt` + * `goimports` + * `golangci-lint`!! + * `golangserver` + * `golines` + * `gopls` + * `gosimple`!! + * `gotype`!! + * `revive`!! + * `staticcheck`!! +* Go HTML Templates + * djlint +* GraphQL + * `eslint` + * `gqlint` + * `prettier` +* Groovy + * `npm-groovy-lint` +* Hack + * `hack` + * `hackfmt` + * `hhast` +* Haml + * `haml-lint` +* Handlebars + * djlint + * `ember-template-lint` +* Haskell + * `brittany` + * `cabal-ghc` + * `cspell` + * `floskell` + * `fourmolu` + * `ghc` + * `ghc-mod` + * `hdevtools` + * `hfmt` + * `hie` + * `hindent` + * `hlint` + * `hls` + * `ormolu` + * `stack-build`!! + * `stack-ghc` + * `stylish-haskell` +* HCL + * `packer-fmt` + * `terraform-fmt` +* HTML + * `VSCode HTML language server` + * `alex` + * `angular` + * `cspell` + * djlint + * `eslint` + * `fecs` + * `html-beautify` + * `htmlhint` + * `prettier` + * `proselint` + * `rustywind` + * `tidy` + * `write-good` +* HTML Angular + * djlint +* HTML Django + * djlint +* HTTP + * kulala_fmt +* Hurl + * `hurlfmt` +* Idris + * `idris` +* Ink + * `ink-language-server` +* Inko + * `inko` !! +* ISPC + * `ispc`!! +* Java + * `PMD` + * `checkstyle`!! + * `clang-format` + * `cspell` + * `eclipselsp` + * `google-java-format` + * `javac` + * `javalsp` + * `uncrustify` +* JavaScript + * `biome` + * `clang-format` + * `cspell` + * `deno` + * `dprint` + * `eslint` + * `fecs` + * `flow` + * `jscs` + * `jshint` + * `prettier` + * `prettier-eslint` + * `prettier-standard` + * `standard` + * `tsserver` + * `xo` +* Jinja + * djlint +* JSON + * `VSCode JSON language server` + * `biome` + * `clang-format` + * `cspell` + * `dprint` + * `eslint` + * `fixjson` + * `jq` + * `json.tool` + * `jsonlint` + * `prettier` + * `spectral` +* JSON5 + * `eslint` +* JSONC + * `biome` + * `eslint` +* Jsonnet + * `jsonnet-lint` + * `jsonnetfmt` +* Julia + * `languageserver` +* Kotlin + * `kotlinc`!! + * `ktlint` + * `languageserver` +* LaTeX (tex) + * `alex` + * `chktex` + * `cspell` + * `lacheck` + * `proselint` + * `redpen` + * `texlab` + * `textlint` + * `vale` + * `write-good` +* Less + * `lessc` + * `prettier` + * `stylelint` +* LLVM + * `llc` +* Lua + * `cspell` + * `lua-format` + * `lua-language-server` + * `luac` + * `luacheck` + * `luafmt` + * `selene` + * `stylua` +* Mail + * `alex` + * `languagetool`!! + * `proselint` + * `vale` +* Make + * `checkmake` +* Markdown + * `alex` + * `cspell` + * `languagetool`!! + * `markdownlint`!! + * `marksman` + * `mdl` + * `pandoc` + * `prettier` + * `proselint` + * `pymarkdown` + * `redpen` + * `remark-lint` + * `textlint` + * `vale` + * `write-good` +* MATLAB + * `mlint` +* Mercury + * `mmc`!! +* NASM + * `nasm`!! +* Nickel + * `nickel_format` +* Nim + * `nim check`!! + * `nimlsp` + * `nimpretty` +* nix + * `alejandra` + * `deadnix` + * `nix-instantiate` + * `nixfmt` + * `nixpkgs-fmt` + * `rnix-lsp` + * `statix` +* nroff + * `alex` + * `proselint` + * `write-good` +* Nunjucks + * djlint +* Objective-C + * `ccls` + * `clang` + * `clang-format` + * `clangd` + * `uncrustify` +* Objective-C++ + * `clang` + * `clangd` + * `uncrustify` +* OCaml + * `dune` + * `merlin` (see |ale-ocaml-merlin|) + * `ocamlformat` + * `ocamllsp` + * `ocp-indent` + * `ols` +* Odin + * `ols` +* OpenApi + * `ibm_validator` + * `prettier` + * `yamllint` +* OpenSCAD + * `SCA2D` + * `scadformat` +* Packer + * `packer-fmt-fixer` +* Pascal + * `ptop` +* Pawn + * `uncrustify` +* Perl + * `perl -c` + * `perl-critic` + * `perltidy` +* Perl6 + * `perl6 -c` +* PHP + * `cspell` + * `intelephense` + * `langserver` + * `phan` + * `php -l` + * `php-cs-fixer` + * `phpactor` + * `phpcbf` + * `phpcs` + * `phpmd` + * `phpstan` + * `pint` + * `psalm`!! + * `tlint` +* PO + * `alex` + * `msgfmt` + * `proselint` + * `write-good` +* Pod + * `alex` + * `proselint` + * `write-good` +* Pony + * `ponyc` +* PowerShell + * `cspell` + * `powershell` + * `psscriptanalyzer` +* Prolog + * `swipl` +* proto + * `buf-format`!! + * `buf-lint`!! + * `clang-format` + * `protoc-gen-lint`!! + * `protolint`!! +* Pug + * `pug-lint` +* Puppet + * `languageserver` + * `puppet` + * `puppet-lint` +* PureScript + * `purescript-language-server` + * `purs-tidy` + * `purty` +* Python + * `autoflake`!! + * `autoimport` + * `autopep8` + * `bandit` + * `black` + * `cspell` + * `flake8` + * `flakehell` + * `isort` + * `mypy` + * `prospector`!! + * `pycln` + * `pycodestyle` + * `pydocstyle` + * `pyflakes` + * `pyflyby` + * `pylama`!! + * `pylint`!! + * `pylsp` + * `pyre` + * `pyright` + * `refurb` + * `reorder-python-imports` + * ruff + * ruff-format + * `unimport` + * `vulture`!! + * `yapf` +* QML + * `qmlfmt` + * `qmllint` +* R + * `languageserver` + * `lintr` + * `styler` +* Racket + * `racket-langserver` + * `raco` + * `raco_fmt` +* Re:VIEW + * `redpen` +* ReasonML + * `merlin` + * `ols` + * `reason-language-server` + * `refmt` +* Rego + * `cspell` + * `opacheck` + * `opafmt` +* REST + * kulala_fmt +* reStructuredText + * `alex` + * `cspell` + * `proselint` + * `redpen` + * `rstcheck` + * `textlint` + * `vale` + * `write-good` +* Robot + * `rflint` +* RPM spec + * `rpmlint` +* Ruby + * `brakeman`!! + * `cspell` + * `debride` + * `packwerk`!! + * `prettier` + * `rails_best_practices`!! + * `reek` + * `rubocop` + * `ruby` + * `rubyfmt` + * `rufo` + * `solargraph` + * `sorbet` + * `standardrb` + * `steep` + * `syntax_tree` +* Rust + * `cargo`!! + * `cspell` + * `rls` + * `rust-analyzer` + * `rustc` (see |ale-integration-rust|) + * `rustfmt` +* Salt + * `salt-lint` +* Sass + * `sass-lint` + * `stylelint` +* Scala + * `cspell` + * `fsc` + * `metals` + * `sbtserver` + * `scalac` + * `scalafmt` + * `scalastyle` +* SCSS + * `prettier` + * `sass-lint` + * `scss-lint` + * `stylelint` +* Slim + * `slim-lint` +* SML + * `smlnj` +* Solidity + * `forge` + * `solc` + * `solhint` + * `solium` +* SQL + * `dprint` + * `pgformatter` + * `sql-lint` + * `sqlfluff` + * `sqlfmt` + * `sqlformat` + * `sqlint` +* Stylus + * `stylelint` +* SugarSS + * `stylelint` +* Svelte + * `prettier` + * `svelteserver` +* Swift + * Apple `swift-format` + * `cspell` + * `sourcekit-lsp` + * `swiftformat` + * `swiftlint` +* systemd + * `systemd-analyze`!! +* Tcl + * `nagelfar`!! +* Terraform + * `checkov` + * `terraform` + * `terraform-fmt-fixer` + * `terraform-ls` + * `terraform-lsp` + * `tflint` + * `tfsec` +* Texinfo + * `alex` + * `cspell` + * `proselint` + * `write-good` +* Text^ + * `alex` + * `cspell` + * `languagetool`!! + * `proselint` + * `redpen` + * `textlint` + * `vale` + * `write-good` +* Thrift + * `thrift` + * `thriftcheck` +* TOML + * `dprint` +* TypeScript + * `biome` + * `cspell` + * `deno` + * `dprint` + * `eslint` + * `fecs` + * `prettier` + * `standard` + * `tslint` + * `tsserver` + * `typecheck` +* Typst + * `typstyle` +* V + * `v`!! + * `vfmt` +* VALA + * `uncrustify` + * `vala_lint`!! +* Verilog + * `hdl-checker` + * `iverilog` + * slang + * `verilator` + * `vlog` + * `xvlog` + * `yosys`!! +* VHDL + * `ghdl` + * `vcom` + * `xvhdl` +* Vim + * `vimls` + * `vint` +* Vim help^ + * `alex` + * `proselint` + * `write-good` +* Vue + * `cspell` + * `prettier` + * `vls` + * `volar` +* WGSL + * `naga` +* XHTML + * `alex` + * `cspell` + * `proselint` + * `write-good` +* XML + * `xmllint` +* YAML + * `actionlint` + * `circleci`!! + * `gitlablint` + * `prettier` + * `spectral` + * `swaglint` + * `yaml-language-server` + * `yamlfix` + * `yamlfmt` + * `yamllint` + * `yq` +* YANG + * `yang-lsp` +* Yara + * `yls` +* Zeek + * `zeek`!! +* Zig + * `zigfmt` + * `zlint` + * `zls` + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-svelte.txt b/doc/ale-svelte.txt new file mode 100644 index 00000000..ccdc7a07 --- /dev/null +++ b/doc/ale-svelte.txt @@ -0,0 +1,36 @@ +=============================================================================== +ALE Svelte Integration *ale-svelte-options* + + +=============================================================================== +prettier *ale-svelte-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +svelteserver *ale-svelte-svelteserver* + + *ale-options.svelte_svelteserver_executable* + *g:ale_svelte_svelteserver_executable* + *b:ale_svelte_svelteserver_executable* +svelte_svelteserver_executable +g:ale_svelte_svelteserver_executable + Type: |String| + Default: `'svelteserver'` + + See |ale-integrations-local-executables| + + *ale-options.svelte_svelteserver_use_global* + *g:ale_svelte_svelteserver_use_global* + *b:ale_svelte_svelteserver_use_global* +svelte_svelteserver_use_global +g:ale_svelte_svelteserver_use_global + Type: |Number| + 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-swift.txt b/doc/ale-swift.txt new file mode 100644 index 00000000..d1b615c2 --- /dev/null +++ b/doc/ale-swift.txt @@ -0,0 +1,71 @@ +=============================================================================== +ALE Swift Integration *ale-swift-options* + + +=============================================================================== +apple-swift-format *ale-swift-apple-swift-format* + +There are 3 options to enable linting and fixing with Apple's swift-format: + +1. Install the local executable in your path, as described here: + https://github.com/apple/swift-format +2. Install the executable via your OS package manager, for instance via + Homebrew with `brew install swift-format` +3. Your Swift project has a dependency on the swift-format package, so it can + be run with `swift run swift-format lint ...` In this case, you need to set + a variable, see |g:ale_swift_appleswiftformat_use_swiftpm|. + +Additionally, ALE tries to locate and use the nearest existing `.swift-format` +configuration file. + + *ale-options.swift_appleswiftformat_executable* + *g:ale_swift_appleswiftformat_executable* + *b:ale_swift_appleswiftformat_executable* +swift_appleswiftformat_executable +g:ale_swift_appleswiftformat_executable + Type: |String| + Default: `'swift-format'` + + This variable can be modified to change the executable path for + `swift-format`. + + *ale-options.swift_appleswiftformat_use_swiftpm* + *g:ale_swift_appleswiftformat_use_swiftpm* + *b:ale_swift_appleswiftformat_use_swiftpm* +swift_appleswiftformat_use_swiftpm +g:ale_swift_appleswiftformat_use_swiftpm + Type: |Number| + Default: `0` + + When set to `1`, this option will cause ALE to use + `swift run swift-format lint ...` instead of the global executable. Use this + option if your Swift project has a dependency on the swift-format package. + + See |ale-integrations-local-executables| + + +=============================================================================== +cspell *ale-swift-cspell* + +See |ale-cspell-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 + + *ale-options.sourcekit_lsp_executable* + *g:ale_sourcekit_lsp_executable* + *b:ale_sourcekit_lsp_executable* +sourcekit_lsp_executable +g: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-systemd.txt b/doc/ale-systemd.txt new file mode 100644 index 00000000..13c7037f --- /dev/null +++ b/doc/ale-systemd.txt @@ -0,0 +1,14 @@ +=============================================================================== +ALE systemd Integration *ale-systemd-options* + + +=============================================================================== +systemd-analyze *ale-systemd-analyze* + +ALE supports checking user systemd units with `systemd-analyze --user verify` +Checks will only work with user unit files in their proper location. There +aren't any options, and checks can only run after saving the file. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-tcl.txt b/doc/ale-tcl.txt new file mode 100644 index 00000000..25110a8f --- /dev/null +++ b/doc/ale-tcl.txt @@ -0,0 +1,30 @@ +=============================================================================== +ALE Tcl Integration *ale-tcl-options* + + +=============================================================================== +nagelfar *ale-tcl-nagelfar* + + *ale-options.tcl_nagelfar_executable* + *g:ale_tcl_nagelfar_executable* + *b:ale_tcl_nagelfar_executable* +tcl_nagelfar_executable +g:ale_tcl_nagelfar_executable + Type: |String| + Default: `'nagelfar.tcl'` + + This variable can be changed to change the path to nagelfar. + + *ale-options.tcl_nagelfar_options* + *g:ale_tcl_nagelfar_options* + *b:ale_tcl_nagelfar_options* +tcl_nagelfar_options +g:ale_tcl_nagelfar_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to nagelfar. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-terraform.txt b/doc/ale-terraform.txt new file mode 100644 index 00000000..ae8a7bc5 --- /dev/null +++ b/doc/ale-terraform.txt @@ -0,0 +1,167 @@ +=============================================================================== +ALE Terraform Integration *ale-terraform-options* + + +=============================================================================== +checkov *ale-terraform-checkov* + + *ale-options.terraform_checkov_executable* + *g:ale_terraform_checkov_executable* + *b:ale_terraform_checkov_executable* +terraform_checkov_executable +g:ale_terraform_checkov_executable + Type: |String| + Default: `'checkov'` + + This variable can be changed to use a different executable for checkov. + + *ale-options.terraform_checkov_options* + *g:ale_terraform_checkov_options* + *b:ale_terraform_checkov_options* +terraform_checkov_options +g:ale_terraform_checkov_options + Type: |String| + Default: `''` + + This variable can be changed to set additional options for checkov. + + +=============================================================================== +terraform-fmt-fixer *ale-terraform-fmt-fixer* + + *ale-options.terraform_fmt_executable* + *g:ale_terraform_fmt_executable* + *b:ale_terraform_fmt_executable* +terraform_fmt_executable +g:ale_terraform_fmt_executable + Type: |String| + Default: `'terraform'` + + This variable can be changed to use a different executable for terraform. + + *ale-options.terraform_fmt_options* + *g:ale_terraform_fmt_options* + *b:ale_terraform_fmt_options* +terraform_fmt_options +g:ale_terraform_fmt_options + Type: |String| + Default: `''` + + +=============================================================================== +terraform *ale-terraform-terraform* + + *ale-options.terraform_terraform_executable* + *g:ale_terraform_terraform_executable* + *b:ale_terraform_terraform_executable* +terraform_terraform_executable +g:ale_terraform_terraform_executable + Type: |String| + Default: `'terraform'` + + This variable can be changed to use a different executable for terraform. + + +=============================================================================== +terraform-ls *ale-terraform-terraform-ls* + +Official terraform language server. More stable than *terraform-lsp* but +currently has less features. + + *ale-options.terraform_ls_executable* + *g:ale_terraform_ls_executable* + *b:ale_terraform_ls_executable* +terraform_ls_executable +g:ale_terraform_ls_executable + Type: |String| + Default: `'terraform-ls'` + + This variable can be changed to use a different executable for terraform-ls. + + *ale-options.terraform_ls_options* + *g:ale_terraform_ls_options* + *b:ale_terraform_ls_options* +terraform_ls_options +g:ale_terraform_ls_options + Type: |String| + Default: `''` + + This variable can be changed to pass custom CLI flags to terraform-ls. + + +=============================================================================== +terraform-lsp *ale-terraform-terraform-lsp* + + *ale-options.terraform_langserver_executable* + *g:ale_terraform_langserver_executable* + *b:ale_terraform_langserver_executable* +terraform_langserver_executable +g:ale_terraform_langserver_executable + Type: |String| + Default: `'terraform-lsp'` + + This variable can be changed to use a different executable for terraform-lsp. + + *ale-options.terraform_langserver_options* + *g:ale_terraform_langserver_options* + *b:ale_terraform_langserver_options* +terraform_langserver_options +g:ale_terraform_langserver_options + Type: |String| + Default: `''` + + This variable can be changed to pass custom CLI flags to terraform-lsp. + + +=============================================================================== +tflint *ale-terraform-tflint* + + *ale-options.terraform_tflint_executable* + *g:ale_terraform_tflint_executable* + *b:ale_terraform_tflint_executable* +terraform_tflint_executable +g:ale_terraform_tflint_executable + Type: |String| + Default: `'tflint'` + + This variable can be changed to use a different executable for tflint. + + *ale-options.terraform_tflint_options* + *g:ale_terraform_tflint_options* + *b:ale_terraform_tflint_options* +terraform_tflint_options +g:ale_terraform_tflint_options + Type: |String| + Default: `'-f json'` + + This variable can be changed to pass different options to tflint. Ale does + expect json output from tflint, so if you change this, you'll probably want + to include '-f json' in your new value. + + +=============================================================================== +tfsec *ale-terraform-tfsec* + + *ale-options.terraform_tfsec_executable* + *g:ale_terraform_tfsec_executable* + *b:ale_terraform_tfsec_executable* +terraform_tfsec_executable +g:ale_terraform_tfsec_executable + Type: |String| + Default: `'tfsec'` + + This variable can be changed to use a different executable for tfsec. + + *ale-options.terraform_tfsec_options* + *g:ale_terraform_tfsec_options* + *b:ale_terraform_tfsec_options* +terraform_tfsec_options +g:ale_terraform_tfsec_options + Type: |String| + Default: `''` + + This variable can be changed to pass custom CLI flags to tfsec. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-tex.txt b/doc/ale-tex.txt new file mode 100644 index 00000000..effaee9b --- /dev/null +++ b/doc/ale-tex.txt @@ -0,0 +1,115 @@ +=============================================================================== +ALE TeX Integration *ale-tex-options* + + +=============================================================================== +chktex *ale-tex-chktex* + + *ale-options.tex_chktex_executable* + *g:ale_tex_chktex_executable* + *b:ale_tex_chktex_executable* +tex_chktex_executable +g:ale_tex_chktex_executable + Type: |String| + Default: `'chktex'` + + This variable can be changed to change the path to chktex. + + *ale-options.tex_chktex_options* + *g:ale_tex_chktex_options* + *b:ale_tex_chktex_options* +tex_chktex_options +g:ale_tex_chktex_options + Type: |String| + Default: `'-I'` + + This variable can be changed to modify flags given to chktex. + + +=============================================================================== +cspell *ale-tex-cspell* + +See |ale-cspell-options| + + +=============================================================================== +lacheck *ale-tex-lacheck* + + *ale-options.lacheck_executable* + *g:ale_lacheck_executable* + *b:ale_lacheck_executable* +lacheck_executable +g:ale_lacheck_executable + Type: |String| + Default: `'lacheck'` + + This variable can be changed to change the path to lacheck. + + +=============================================================================== +latexindent *ale-tex-latexindent* + + *ale-options.tex_latexindent_executable* + *g:ale_tex_latexindent_executable* + *b:ale_tex_latexindent_executable* +tex_latexindent_executable +g:ale_tex_latexindent_executable + Type: |String| + Default: `'latexindent'` + + This variable can be changed to change the path to latexindent. + + *ale-options.tex_latexindent_options* + *g:ale_tex_latexindent_options* + *b:ale_tex_latexindent_options* +tex_latexindent_options +g:ale_tex_latexindent_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to latexindent. + + +=============================================================================== +texlab *ale-tex-texlab* + + *ale-options.tex_texlab_executable* + *g:ale_tex_texlab_executable* + *b:ale_tex_texlab_executable* +tex_texlab_executable +g:ale_tex_texlab_executable + Type: |String| + Default: `'texlab'` + + This variable can be changed to change the path to texlab. + + *ale-options.tex_texlab_options* + *g:ale_tex_texlab_options* + *b:ale_tex_texlab_options* +tex_texlab_options +g:ale_tex_texlab_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to texlab command. + + *ale-options.tex_texlab_config* + *g:ale_tex_texlab_config* + *b:ale_tex_texlab_config* +tex_texlab_config +g:ale_tex_texlab_config + Type: |Dictionary| + Default: `{}` + + Dictionary containing LSP configuration settings used to initialize texlab + language server. Refer to texlab documentation for possible settings: + + https://github.com/latex-lsp/texlab/blob/master/docs/options.md + + For example to set build onSave initialization setting: > + + let g:ale_tex_texlab_config = {"build":{"onSave":v:true}} +< + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-texinfo.txt b/doc/ale-texinfo.txt new file mode 100644 index 00000000..53d42b69 --- /dev/null +++ b/doc/ale-texinfo.txt @@ -0,0 +1,18 @@ +=============================================================================== +ALE Texinfo Integration *ale-texinfo-options* + + +=============================================================================== +cspell *ale-texinfo-cspell* + +See |ale-cspell-options| + + +=============================================================================== +write-good *ale-texinfo-write-good* + +See |ale-write-good-options| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-text.txt b/doc/ale-text.txt new file mode 100644 index 00000000..da2d9b49 --- /dev/null +++ b/doc/ale-text.txt @@ -0,0 +1,55 @@ +=============================================================================== +ALE Text Integration *ale-text-options* + + +============================================================================== +cspell *ale-text-cspell* + +See |ale-cspell-options| + + +=============================================================================== +textlint *ale-text-textlint* + +The options for the textlint linter are global because it does not make +sense to have them specified on a per-language basis. + + *ale-options.textlint_executable* + *g:ale_textlint_executable* + *b:ale_textlint_executable* +textlint_executable +g:ale_textlint_executable + Type: |String| + Default: `'textlint'` + + See |ale-integrations-local-executables| + + *ale-options.textlint_options* + *g:ale_textlint_options* + *b:ale_textlint_options* +textlint_options +g:ale_textlint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to textlint. + + *ale-options.textlint_use_global* + *g:ale_textlint_use_global* + *b:ale_textlint_use_global* +textlint_use_global +g:ale_textlint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +write-good *ale-text-write-good* + +See |ale-write-good-options| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-thrift.txt b/doc/ale-thrift.txt new file mode 100644 index 00000000..d4ca549f --- /dev/null +++ b/doc/ale-thrift.txt @@ -0,0 +1,84 @@ +=============================================================================== +ALE Thrift Integration *ale-thrift-options* + + +=============================================================================== +thrift *ale-thrift-thrift* + +The `thrift` linter works by compiling the buffer's contents and reporting any +errors reported by the parser and the configured code generator(s). + + +------------------------------------------------------------------------------- +Options + *ale-options.thrift_thrift_executable* + *g:ale_thrift_thrift_executable* + *b:ale_thrift_thrift_executable* +thrift_thrift_executable +g:ale_thrift_thrift_executable + Type: |String| + Default: `'thrift'` + + See |ale-integrations-local-executables| + + *ale-options.thrift_thrift_generators* + *g:ale_thrift_thrift_generators* + *b:ale_thrift_thrift_generators* +thrift_thrift_generators +g:ale_thrift_thrift_generators + Type: |List| + Default: `['cpp']` + + This list must contain one or more named code generators. Generator options + can be included as part of each string, e.g. `['py:dynamic']`. + + *ale-options.thrift_thrift_includes* + *g:ale_thrift_thrift_includes* + *b:ale_thrift_thrift_includes* +thrift_thrift_includes +g:ale_thrift_thrift_includes + Type: |List| + Default: `['.']` + + This list contains paths that will be searched for thrift `include` + directives. + + *ale-options.thrift_thrift_options* + *g:ale_thrift_thrift_options* + *b:ale_thrift_thrift_options* +thrift_thrift_options +g:ale_thrift_thrift_options + Type: |String| + Default: `'-strict'` + + This variable can be changed to customize the additional command-line + arguments that are passed to the thrift compiler. + + +=============================================================================== +thriftcheck *ale-thrift-thriftcheck* + + *ale-options.thrift_thriftcheck_executable* + *g:ale_thrift_thriftcheck_executable* + *b:ale_thrift_thriftcheck_executable* +thrift_thriftcheck_executable +g:ale_thrift_thriftcheck_executable + Type: |String| + Default: `'thriftcheck'` + + See |ale-integrations-local-executables| + + *ale-options.thrift_thriftcheck_options* + *g:ale_thrift_thriftcheck_options* + *b:ale_thrift_thriftcheck_options* +thrift_thriftcheck_options +g:ale_thrift_thriftcheck_options + Type: |String| + Default: `''` + + This variable can be changed to customize the additional command-line + arguments that are passed to thriftcheck. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-toml.txt b/doc/ale-toml.txt new file mode 100644 index 00000000..222a91f4 --- /dev/null +++ b/doc/ale-toml.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE TOML Integration *ale-toml-options* + + +=============================================================================== +dprint *ale-toml-dprint* + +See |ale-dprint-options| and https://dprint.dev/plugins/toml + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-typescript.txt b/doc/ale-typescript.txt new file mode 100644 index 00000000..47d8c4bf --- /dev/null +++ b/doc/ale-typescript.txt @@ -0,0 +1,332 @@ +=============================================================================== +ALE TypeScript Integration *ale-typescript-options* + + +=============================================================================== +biome *ale-typescript-biome* + + *ale-options.biome_executable* + *g:ale_biome_executable* + *b:ale_biome_executable* +biome_executable +g:ale_biome_executable + Type: |String| + Default: `'biome'` + + *ale-options.biome_options* + *g:ale_biome_options* + *b:ale_biome_options* +biome_options +g:ale_biome_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to `biome check` when + applying fixes. + + *ale-options.biome_use_global* + *g:ale_biome_use_global* + *b:ale_biome_use_global* +biome_use_global +g:ale_biome_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.biome_fixer_apply_unsafe* + *g:ale_biome_fixer_apply_unsafe* + *b:ale_biome_fixer_apply_unsafe* +biome_fixer_apply_unsafe +g:ale_biome_fixer_apply_unsafe + Type: |Number| + Default: `0` + + If set to `1`, biome will apply unsafe fixes along with safe fixes. + + *ale-options.biome_lsp_project_root* + *g:ale_biome_lsp_project_root* + *b:ale_biome_lsp_project_root* +biome_lsp_project_root +g:ale_biome_lsp_project_root + Type: |String| + Default: `''` + + If this variable is left unset, ALE will try to find the project root by + executing the following steps in the given order: + + 1. Find an ancestor directory containing a biome.json. + 2. Find an ancestor directory containing a biome.jsonc. + 3. Find an ancestor directory containing a package.json. + 4. Find an ancestor directory containing a .git folder. + 5. Use the directory of the current buffer (if the buffer was opened from + a file). + + +=============================================================================== +cspell *ale-typescript-cspell* + +See |ale-cspell-options| + + +=============================================================================== +deno *ale-typescript-deno* + +Starting from version 1.6.0, Deno comes with its own language server. Earlier +versions are not supported. + + +------------------------------------------------------------------------------- +Options + *ale-options.deno_executable* + *g:ale_deno_executable* + *b:ale_deno_executable* +deno_executable +g:ale_deno_executable + Type: |String| + Default: `'deno'` + + *ale-options.deno_lsp_project_root* + *g:ale_deno_lsp_project_root* + *b:ale_deno_lsp_project_root* +deno_lsp_project_root +g:ale_deno_lsp_project_root + Type: |String| + Default: `''` + + If this variable is left unset, ALE will try to find the project root by + executing the following steps in the given order: + + 1. Find an ancestor directory containing a tsconfig.json. + 2. Find an ancestor directory containing a .git folder. + 3. Use the directory of the current buffer (if the buffer was opened from + a file). + + *ale-options.deno_unstable* + *g:ale_deno_unstable* + *b:ale_deno_unstable* +deno_unstable +g:ale_deno_unstable + Type: |Number| + Default: `0` + + Enable or disable unstable Deno features and APIs. + + *ale-options.deno_import_map* + *g:ale_deno_import_map* + *b:ale_deno_import_map* +deno_import_map +g:ale_deno_import_map + Type: |String| + Default: `'import_map.json'` + + Specify the import map filename to load url maps in a deno project. + + +=============================================================================== +dprint *ale-typescript-dprint* + +See |ale-dprint-options| and https://dprint.dev/plugins/typescript + + +=============================================================================== +eslint *ale-typescript-eslint* + +Because of how TypeScript compiles code to JavaScript and how interrelated +the two languages are, the `eslint` linter for TypeScript uses the JavaScript +options for `eslint` too. See: |ale-javascript-eslint|. + + +=============================================================================== +prettier *ale-typescript-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +standard *ale-typescript-standard* + + *ale-options.typescript_standard_executable* + *g:ale_typescript_standard_executable* + *b:ale_typescript_standard_executable* +typescript_standard_executable +g:ale_typescript_standard_executable + Type: |String| + Default: `'standard'` + + See |ale-integrations-local-executables| + + *ale-options.typescript_standard_options* + *g:ale_typescript_standard_options* + *b:ale_typescript_standard_options* +typescript_standard_options +g:ale_typescript_standard_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to standard. + + *ale-options.typescript_standard_use_global* + *g:ale_typescript_standard_use_global* + *b:ale_typescript_standard_use_global* +typescript_standard_use_global +g:ale_typescript_standard_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +tslint *ale-typescript-tslint* + +This linter isn't recommended, because TSLint can't be used for checking for +problems while you type. You should probably use the tsserver plugin instead. +tsserver plugins are described here: +https://github.com/Microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin + +Follow the instructions on the plugin website for installing it: +https://github.com/Microsoft/typescript-tslint-plugin + +Then disable TSLint in your typescript ftplugin file. > + let b:ale_linters_ignore = ['tslint'] +< +Or in Lua: > + require("ale").setup.buffer({linters_ignore={"tslint"}}) +< + +------------------------------------------------------------------------------- +Options + *ale-options.typescript_tslint_executable* + *g:ale_typescript_tslint_executable* + *b:ale_typescript_tslint_executable* +typescript_tslint_executable +g:ale_typescript_tslint_executable + Type: |String| + Default: `'tslint'` + + See |ale-integrations-local-executables| + + *ale-options.typescript_tslint_config_path* + *g:ale_typescript_tslint_config_path* + *b:ale_typescript_tslint_config_path* +typescript_tslint_config_path +g:ale_typescript_tslint_config_path + Type: |String| + Default: `''` + + ALE will first discover the tslint.json path in an ancestor directory. If no + such path exists, this variable will be used instead. + + *ale-options.typescript_tslint_ignore_empty_files* + *g:ale_typescript_tslint_ignore_empty_files* + *b:ale_typescript_tslint_ignore_empty_files* +typescript_tslint_ignore_empty_files +g:ale_typescript_tslint_ignore_empty_files + Type: |Number| + Default: `0` + + When set to `1`, ALE will not report any problems for empty files with + TSLint. ALE will still execute TSLint for the files, but ignore any problems + reported. This stops ALE from complaining about newly created files, + and files where lines have been added and then removed. + + *ale-options.typescript_tslint_rules_dir* + *g:ale_typescript_tslint_rules_dir* + *b:ale_typescript_tslint_rules_dir* +typescript_tslint_rules_dir +g:ale_typescript_tslint_rules_dir + Type: |String| + Default: `''` + + If this variable is set, ALE will use it as the rules directory for tslint. + + *ale-options.typescript_tslint_use_global* + *g:ale_typescript_tslint_use_global* + *b:ale_typescript_tslint_use_global* +typescript_tslint_use_global +g:ale_typescript_tslint_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +tsserver *ale-typescript-tsserver* + + *ale-options.typescript_tsserver_executable* + *g:ale_typescript_tsserver_executable* + *b:ale_typescript_tsserver_executable* +typescript_tsserver_executable +g:ale_typescript_tsserver_executable + Type: |String| + Default: `'tsserver'` + + ALE will first discover the tsserver path in an ancestor node_modules + directory. If no such path exists, this variable will be used instead. + + If you wish to use only a globally installed version of tsserver, set + |g:ale_typescript_tsserver_use_global| to `1`. + + *ale-options.typescript_tsserver_config_path* + *g:ale_typescript_tsserver_config_path* + *b:ale_typescript_tsserver_config_path* +typescript_tsserver_config_path +g:ale_typescript_tsserver_config_path + Type: |String| + Default: `''` + + ALE will first discover the tsserver.json path in an ancestor directory. If + no such path exists, this variable will be used instead. + + *ale-options.typescript_tsserver_use_global* + *g:ale_typescript_tsserver_use_global* + *b:ale_typescript_tsserver_use_global* +typescript_tsserver_use_global +g:ale_typescript_tsserver_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + This variable controls whether or not ALE will search for a local path for + tsserver first. If this variable is set to `1`, then ALE will always use the + global version of tsserver, in preference to locally installed versions of + tsserver in node_modules. + + +=============================================================================== +xo *ale-typescript-xo* + + *ale-options.typescript_xo_executable* + *g:ale_typescript_xo_executable* + *b:ale_typescript_xo_executable* +typescript_xo_executable +g:ale_typescript_xo_executable + Type: |String| + Default: `'xo'` + + See |ale-integrations-local-executables| + + *ale-options.typescript_xo_options* + *g:ale_typescript_xo_options* + *b:ale_typescript_xo_options* +typescript_xo_options +g:ale_typescript_xo_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to xo. + + *ale-options.typescript_xo_use_global* + *g:ale_typescript_xo_use_global* + *b:ale_typescript_xo_use_global* +typescript_xo_use_global +g:ale_typescript_xo_use_global + Type: |Number| + 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-typst.html b/doc/ale-typst.html new file mode 100644 index 00000000..013f3f0c --- /dev/null +++ b/doc/ale-typst.html @@ -0,0 +1,29 @@ +=============================================================================== +ALE Typst Integration *ale-typst-options* + +=============================================================================== +typstyle *ale-typst-typstyle* + + *ale-options.typst_typstyle_executable* + *g:ale_typst_typstyle_executable* + *b:ale_typst_typstyle_executable* +typst_typstyle_executable +g:ale_typst_typstyle_executable + Type: |String| + Default: `'typstyle'` + + See |ale-integrations-local-executables| + + *ale-options.typst_typstyle_options* + *g:ale_typst_typstyle_options* + *b:ale_typst_typstyle_options* +typst_typstyle_options +g:ale_typst_typstyle_options + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to typstyle. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-v.txt b/doc/ale-v.txt new file mode 100644 index 00000000..eafbc09e --- /dev/null +++ b/doc/ale-v.txt @@ -0,0 +1,53 @@ +=============================================================================== +ALE V Integration *ale-v-options* + + +=============================================================================== +Integration Information + +`v` is V's build tool. `vfmt` (called as `v fmt` from the same +executable that does the builds) is the autoformatter/fixer. + + *ale-options.v_v_executable* + *g:ale_v_v_executable* + *b:ale_v_v_executable* +v_v_executable +g:ale_v_v_executable + Type: |String| + Default: `'v'` + + The executable that will be run for the `v` linter and the `vfmt` fixer. + + +=============================================================================== +v *ale-v-v* + + *ale-options.v_v_options* + *g:ale_v_v_options* + *b:ale_v_v_options* +v_v_options +g:ale_v_v_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the v linter. + They are injected directly after "v .". + + +=============================================================================== +vfmt *ale-v-vfmt* + + *ale-options.v_vfmt_options* + *g:ale_v_vfmt_options* + *b:ale_v_vfmt_options* +v_vfmt_options +g:ale_v_vfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the vfmt fixer. + They are injected directly after "v fmt". + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-vala.txt b/doc/ale-vala.txt new file mode 100644 index 00000000..6912793c --- /dev/null +++ b/doc/ale-vala.txt @@ -0,0 +1,36 @@ +=============================================================================== +ALE VALA Integration *ale-vala-options* + + +=============================================================================== +uncrustify *ale-vala-uncrustify* + +See |ale-c-uncrustify| for information about the available options. + + +=============================================================================== +Vala-Lint *ale-vala-vala-lint* + + *ale-options.vala_vala_lint_executable* + *g:ale_vala_vala_lint_executable* + *b:ale_vala_vala_lint_executable* +g:ale_vala_vala_lint_executable + Type: |String| + Default: `'io.elementary.vala-lint'` + + This variable can be set to specify a Vala-Lint executable file. + + *ale-options.vala_vala_lint_config_filename* + *g:ale_vala_vala_lint_config_filename* + *b:ale_vala_vala_lint_config_filename* +g:ale_vala_vala_lint_config_filename + Type: |String| + Default: `'vala-lint.conf'` + + This variable can be set to specify a Vala-Lint config filename. When a file + with the specified name was not found or this variable was set to empty, + Vala-Lint will be executed without specifying a config filename. + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-verilog.txt b/doc/ale-verilog.txt new file mode 100644 index 00000000..481b51cd --- /dev/null +++ b/doc/ale-verilog.txt @@ -0,0 +1,176 @@ +=============================================================================== +ALE Verilog/SystemVerilog Integration *ale-verilog-options* + + +=============================================================================== +ALE can use seven different linters for Verilog HDL: + + HDL Checker + Using `hdl_checker --lsp` + + iverilog: + Using `iverilog -t null -Wall` + + slang: + Using `slang -Weverything` + + verilator + Using `verilator --lint-only -Wall` + + ModelSim/Questa + Using `vlog -quiet -lint` + + Vivado + Using `xvlog` + + Yosys + Using `yosys -Q -T -p 'read_verilog'` + +By default, both 'verilog' and 'systemverilog' filetypes are checked. + +You can limit 'systemverilog' files to be checked using only 'verilator' by +defining 'g:ale_linters' variable: > + + au FileType systemverilog + \ let g:ale_linters = {'systemverilog' : ['verilator'],} +< + +=============================================================================== +General notes + +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. + +HDL Checker is an alternative for some of the issues described above. It wraps +around ghdl, Vivado and ModelSim/Questa and, when using the latter, it can +handle mixed language (VHDL, Verilog, SystemVerilog) designs. + +=============================================================================== +hdl-checker *ale-verilog-hdl-checker* + +See |ale-vhdl-hdl-checker| + + +=============================================================================== +iverilog *ale-verilog-iverilog* + + No additional options + +=============================================================================== +slang *ale-verilog-slang* + + *ale-options.verilog_slang_option* + *g:ale_verilog_slang_option* + *b:ale_verilog_slang_options* +verilog_slang_option +g:ale_verilog_slang_option + Type: |String| + Default: `''` + + This variable can be changed to modify 'slang' command arguments. + + +=============================================================================== +verilator *ale-verilog-verilator* + + *ale-options.verilog_verilator_options* + *g:ale_verilog_verilator_options* + *b:ale_verilog_verilator_options* +verilog_verilator_options +g:ale_verilog_verilator_options + Type: |String| + Default: `''` + + This variable can be changed to modify 'verilator' command arguments. + + 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* + + *ale-options.verilog_vlog_executable* + *g:ale_verilog_vlog_executable* + *b:ale_verilog_vlog_executable* +verilog_vlog_executable +g:ale_verilog_vlog_executable + Type: |String| + Default: `'vlog'` + + This variable can be changed to the path to the 'vlog' executable. + + *ale-options.verilog_vlog_options* + *g:ale_verilog_vlog_options* + *b:ale_verilog_vlog_options* +verilog_vlog_options +g: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* + + *ale-options.verilog_xvlog_executable* + *g:ale_verilog_xvlog_executable* + *b:ale_verilog_xvlog_executable* +verilog_xvlog_executable +g:ale_verilog_xvlog_executable + Type: |String| + Default: `'xvlog'` + + This variable can be changed to the path to the 'xvlog' executable. + + *ale-options.verilog_xvlog_options* + *g:ale_verilog_xvlog_options* + *b:ale_verilog_xvlog_options* +verilog_xvlog_options +g:ale_verilog_xvlog_options + Type: |String| + Default: `''` + + This variable can be changed to modify the flags/options passed to 'xvlog'. + + +=============================================================================== +yosys *ale-verilog-yosys* + + *ale-options.verilog_yosys_executable* + *g:ale_verilog_yosys_executable* + *b:ale_verilog_yosys_executable* +verilog_yosys_executable +g:ale_verilog_yosys_executable + Type: |String| + Default: `'yosys'` + + This variable can be changed to the path to the 'yosys' executable. + + *ale-options.verilog_yosys_options* + *g:ale_verilog_yosys_options* + *b:ale_verilog_yosys_options* +verilog_yosys_options +g:ale_verilog_yosys_options + Type: |String| + Default: `'-Q -T -p ''read_verilog %s'''` + + This variable can be changed to modify the flags/options passed to 'yosys'. + By default, Yosys is an interactive program. To obtain linting functionality, + the `'read_verilog'` command is used. + + +=============================================================================== + 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..6b9bd183 --- /dev/null +++ b/doc/ale-vhdl.txt @@ -0,0 +1,177 @@ +=============================================================================== +ALE VHDL Integration *ale-vhdl-options* + + +=============================================================================== +ALE can use four different linters for VHDL: + + ghdl: + Using `ghdl --std=08` + + ModelSim/Questa + Using `vcom -2008 -quiet -lint` + + Vivado + Using `xvhdl --2008` + + HDL Checker + Using `hdl_checker --lsp` + +=============================================================================== +General notes + +ghdl, ModelSim/Questa and Vivado 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. + +HDL Checker is an alternative for some of the issues described above. It wraps +around ghdl, Vivado and ModelSim/Questa and, when using the latter, it can +handle mixed language (VHDL, Verilog, SystemVerilog) designs. + +=============================================================================== +ghdl *ale-vhdl-ghdl* + + *ale-options.vhdl_ghdl_executable* + *g:ale_vhdl_ghdl_executable* + *b:ale_vhdl_ghdl_executable* +vhdl_ghdl_executable +g:ale_vhdl_ghdl_executable + Type: |String| + Default: `'ghdl'` + + This variable can be changed to the path to the 'ghdl' executable. + + *ale-options.vhdl_ghdl_options* + *g:ale_vhdl_ghdl_options* + *b:ale_vhdl_ghdl_options* +vhdl_ghdl_options +g:ale_vhdl_ghdl_options + Type: |String| + Default: `'--std=08'` + + This variable can be changed to modify the flags/options passed to 'ghdl'. + + +=============================================================================== +hdl-checker *ale-vhdl-hdl-checker* + +HDL Checker is a wrapper for VHDL/Verilg/SystemVerilog tools that aims to +reduce the boilerplate code needed to set things up. It can automatically +infer libraries for VHDL sources, determine the compilation order and provide +some static checks. + +You can install it using pip: +> + $ pip install hdl-checker + +`hdl-checker` will be run from a detected project root, determined by the +following methods, in order: + +1. Find the first directory containing a configuration file (see + |g:ale_hdl_checker_config_file|) +2. If no configuration file can be found, find the first directory containing + a folder named `'.git' +3. If no such folder is found, use the directory of the current buffer + + *ale-options.hdl_checker_executable* + *g:ale_hdl_checker_executable* + *b:ale_hdl_checker_executable* +hdl_checker_executable +g:ale_hdl_checker_executable + Type: |String| + Default: `'hdl_checker'` + + This variable can be changed to the path to the 'hdl_checker' executable. + + *ale-options.hdl_checker_options* + *g:ale_hdl_checker_options* + *b:ale_hdl_checker_options* +hdl_checker_options +g:ale_hdl_checker_options + Type: |String| + Default: `''` + + This variable can be changed to modify the flags/options passed to the + 'hdl_checker' server startup command. + + *ale-options.hdl_checker_config_file* + *g:ale_hdl_checker_config_file* + *b:ale_hdl_checker_config_file* +hdl_checker_config_file +g:ale_hdl_checker_config_file + Type: |String| + Default: `'.hdl_checker.config'` (Unix), + `'_hdl_checker.config'` (Windows) + + This variable can be changed to modify the config file HDL Checker will try + to look for. It will also affect how the project's root directory is + determined (see |ale-vhdl-hdl-checker|). + + More info on the configuration file format can be found at: + https://github.com/suoto/hdl_checker/wiki/Setting-up-a-project + + +=============================================================================== +vcom *ale-vhdl-vcom* + + *ale-options.vhdl_vcom_executable* + *g:ale_vhdl_vcom_executable* + *b:ale_vhdl_vcom_executable* +vhdl_vcom_executable +g:ale_vhdl_vcom_executable + Type: |String| + Default: `'vcom'` + + This variable can be changed to the path to the 'vcom' executable. + + *ale-options.vhdl_vcom_options* + *g:ale_vhdl_vcom_options* + *b:ale_vhdl_vcom_options* +vhdl_vcom_options +g: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* + + *ale-options.vhdl_xvhdl_executable* + *g:ale_vhdl_xvhdl_executable* + *b:ale_vhdl_xvhdl_executable* +vhdl_xvhdl_executable +g:ale_vhdl_xvhdl_executable + Type: |String| + Default: `'xvhdl'` + + This variable can be changed to the path to the 'xvhdl' executable. + + *ale-options.vhdl_xvhdl_options* + *g:ale_vhdl_xvhdl_options* + *b:ale_vhdl_xvhdl_options* +vhdl_xvhdl_options +g: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-vim-help.txt b/doc/ale-vim-help.txt new file mode 100644 index 00000000..3cbe20d5 --- /dev/null +++ b/doc/ale-vim-help.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Vim help Integration *ale-vim-help-options* + + +=============================================================================== +write-good *ale-vim-help-write-good* + +See |ale-write-good-options| + + +=============================================================================== +vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-vim.txt b/doc/ale-vim.txt new file mode 100644 index 00000000..aa2045ac --- /dev/null +++ b/doc/ale-vim.txt @@ -0,0 +1,93 @@ +=============================================================================== +ALE Vim Integration *ale-vim-options* + + +=============================================================================== +vimls *ale-vim-vimls* + + The `vim-language-server` is the engine that powers VimL editor support + using the Language Server Protocol. See the installation instructions: + https://github.com/iamcco/vim-language-server#install + + *ale-options.vim_vimls_executable* + *g:ale_vim_vimls_executable* + *b:ale_vim_vimls_executable* +vim_vimls_executable +g:ale_vim_vimls_executable + Type: |String| + Default: `'vim-language-server'` + + This option can be set to change the executable path for vimls. + + *ale-options.vim_vimls_config* + *g:ale_vim_vimls_config* + *b:ale_vim_vimls_config* +vim_vimls_config +g:ale_vim_vimls_config + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server. For example: > + + let g:ale_vim_vimls_config = { + \ 'vim': { + \ 'iskeyword': '@,48-57,_,192-255,-#', + \ 'vimruntime': '', + \ 'runtimepath': '', + \ 'diagnostic': {'enable': v:true}, + \ 'indexes': { + \ 'runtimepath': v:true, + \ 'gap': 100, + \ 'count': 3, + \ 'projectRootPatterns': ['.git', 'autoload', 'plugin'], + \ }, + \ 'suggest': { + \ 'fromVimruntime': v:true, + \ 'fromRuntimepath': v:false, + \ }, + \ }, + \} +< + Consult the vim-language-server documentation for more information about + settings. + + *ale-options.vim_vimls_use_global* + *g:ale_vim_vimls_use_global* + *b:ale_vim_vimls_use_global* +vim_vimls_use_global +g:ale_vim_vimls_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +vint *ale-vim-vint* + + *ale-options.vim_vint_executable* + *g:ale_vim_vint_executable* + *b:ale_vim_vint_executable* +vim_vint_executable +g:ale_vim_vint_executable + Type: |String| + Default: `'vint'` + + This option can be set to change the executable path for Vint. + + *ale-options.vim_vint_show_style_issues* + *g:ale_vim_vint_show_style_issues* + *b:ale_vim_vint_show_style_issues* +vim_vint_show_style_issues +g:ale_vim_vint_show_style_issues + Type: |Number| + Default: `1` + + This variable will enable/disable style issues for Vint. When this option + is disabled, only warnings and errors which are not purely style issues + will be reported. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-vue.txt b/doc/ale-vue.txt new file mode 100644 index 00000000..864991ec --- /dev/null +++ b/doc/ale-vue.txt @@ -0,0 +1,82 @@ +=============================================================================== +ALE Vue Integration *ale-vue-options* + + +=============================================================================== +cspell *ale-vue-cspell* + +See |ale-cspell-options| + + +=============================================================================== +prettier *ale-vue-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +vls *ale-vue-vls* + + *ale-options.vue_vls_executable* + *g:ale_vue_vls_executable* + *b:ale_vue_vls_executable* +vue_vls_executable +g:ale_vue_vls_executable + Type: |String| + Default: `'vls'` + + See |ale-integrations-local-executables| + + *ale-options.vue_vls_use_global* + *g:ale_vue_vls_use_global* + *b:ale_vue_vls_use_global* +vue_vls_use_global +g:ale_vue_vls_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +volar *ale-vue-volar* + + It is required to have typescript installed in your project as your dev + dependency: `npm i -D typescript` + + *ale-options.vue_volar_executable* + *g:ale_vue_volar_executable* + *b:ale_vue_volar_executable* +vue_volar_executable +g:ale_vue_volar_executable + Type: |String| + Default: `'vue-language-server'` + + See |ale-integrations-local-executables| + + *ale-options.vue_volar_use_global* + *g:ale_vue_volar_use_global* + *b:ale_vue_volar_use_global* +vue_volar_use_global +g:ale_vue_volar_use_global + Type: |Number| + Default: `1` + + See |ale-integrations-local-executables| + + *ale-options.vue_volar_init_options* + *g:ale_vue_volar_init_options* + *b:ale_vue_volar_init_options* +vue_volar_init_options +g:ale_vue_volar_init_options + Type: |Dictionary| + Default: `{'typescript': 'tsdk': ''}` + + This option can be configured to set the initialization options for volar. + + ALE will automatically replace `tsdk` with local detected path to the + typescript library. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-wgsl.txt b/doc/ale-wgsl.txt new file mode 100644 index 00000000..339ce975 --- /dev/null +++ b/doc/ale-wgsl.txt @@ -0,0 +1,20 @@ +=============================================================================== +ALE WGSL Integration *ale-wgsl-options* + + +=============================================================================== +naga *ale-wgsl-naga* + + *ale-options.wgsl_naga_executable* + *g:ale_wgsl_naga_executable* + *b:ale_wgsl_naga_executable* +wgsl_naga_executable +g:ale_wgsl_naga_executable + Type: |String| + Default: `'naga'` + + The executable that will be run for the `naga` linter. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-xhtml.txt b/doc/ale-xhtml.txt new file mode 100644 index 00000000..10ca5b82 --- /dev/null +++ b/doc/ale-xhtml.txt @@ -0,0 +1,18 @@ +=============================================================================== +ALE XHTML Integration *ale-xhtml-options* + + +=============================================================================== +cspell *ale-xhtml-cspell* + +See |ale-cspell-options| + + +=============================================================================== +write-good *ale-xhtml-write-good* + +See |ale-write-good-options| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-xml.txt b/doc/ale-xml.txt new file mode 100644 index 00000000..7c0941c5 --- /dev/null +++ b/doc/ale-xml.txt @@ -0,0 +1,40 @@ +=============================================================================== +ALE XML Integration *ale-xml-options* + + +=============================================================================== +xmllint *ale-xml-xmllint* + + *ale-options.xml_xmllint_executable* + *g:ale_xml_xmllint_executable* + *b:ale_xml_xmllint_executable* +xml_xmllint_executable +g:ale_xml_xmllint_executable + Type: |String| + Default: `'xmllint'` + + This variable can be set to change the path to xmllint. + + *ale-options.xml_xmllint_options* + *g:ale_xml_xmllint_options* + *b:ale_xml_xmllint_options* +xml_xmllint_options +g:ale_xml_xmllint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to xmllint. + + *ale-options.xml_xmllint_indentsize* + *g:ale_xml_xmllint_indentsize* + *b:ale_xml_xmllint_indentsize* +xml_xmllint_indentsize +g:ale_xml_xmllint_indentsize + Type: |Number| + Default: `2` + + This variable can be sent to specify the amount of spaces used for + indentation. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-yaml.txt b/doc/ale-yaml.txt new file mode 100644 index 00000000..9f8a681d --- /dev/null +++ b/doc/ale-yaml.txt @@ -0,0 +1,474 @@ +=============================================================================== +ALE YAML Integration *ale-yaml-options* + + +=============================================================================== +actionlint *ale-yaml-actionlint* + +Website: https://github.com/rhysd/actionlint + + +Installation +------------------------------------------------------------------------------- + +See installation guide: https://github.com/rhysd/actionlint#quick-start + +This linter is disabled by default and must be enabled by setting `g:ale_linters`. +To enable it only for Github Action YAML files a configuration like this is +better: + +> + au BufRead,BufNewFile */.github/*/*.y{,a}ml + \ let b:ale_linters = {'yaml': ['actionlint']} +< + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_actionlint_executable* + *g:ale_yaml_actionlint_executable* + *b:ale_yaml_actionlint_executable* +yaml_actionlint_executable +g:ale_yaml_actionlint_executable + Type: |String| + Default: `'actionlint'` + + This variable can be set to change the path to actionlint. + + *ale-options.yaml_actionlint_options* + *g:ale_yaml_actionlint_options* + *b:ale_yaml_actionlint_options* +yaml_actionlint_options +g:ale_yaml_actionlint_options + Type: |String| + Default: `''` + + This variable can be set to add extra options to actionlint executable. + + For example, to disable running `shellcheck` and `pyflakes` external commands, + you may want to set: +> + let g:ale_yaml_actionlint_options = '-shellcheck= -pyflakes=' +< + Please note that passing `-format` as option is not supported at the moment. + + +=============================================================================== +circleci *ale-yaml-circleci* + +Website: https://circleci.com/docs/2.0/local-cli + + +Installation +------------------------------------------------------------------------------- + +Follow the instructions on the website, and make sure to test that you can +validate configuration files with: > + + circleci config validate - < .circleci/config.yml +< + +As long as the validator runs correctly, you should be able to see errors when +you save the configuration file. The validator doesn't run as you type because +it sends network requests, and running too often would overload the circleci +servers. + + +=============================================================================== +prettier *ale-yaml-prettier* + +Website: https://github.com/prettier/prettier + + +Installation +------------------------------------------------------------------------------- + +Install prettier either globally or locally: > + + npm install prettier -g # global + npm install prettier # local +< + +=============================================================================== +spectral *ale-yaml-spectral* + +Website: https://github.com/stoplightio/spectral + + +Installation +------------------------------------------------------------------------------- + +Install spectral either globally or locally: > + + npm install @stoplight/spectral -g # global + npm install @stoplight/spectral # local +< + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_spectral_executable* + *g:ale_yaml_spectral_executable* + *b:ale_yaml_spectral_executable* +yaml_spectral_executable +g:ale_yaml_spectral_executable + Type: |String| + Default: `'spectral'` + + This variable can be set to change the path to spectral. + + *ale-options.yaml_spectral_use_global* + *g:ale_yaml_spectral_use_global* + *b:ale_yaml_spectral_use_global* +yaml_spectral_use_global +g:ale_yaml_spectral_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +swaglint *ale-yaml-swaglint* + +Website: https://github.com/byCedric/swaglint + + +Installation +------------------------------------------------------------------------------- + +Install swaglint either globally or locally: > + + npm install swaglint -g # global + npm install swaglint # local +< + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_swaglint_executable* + *g:ale_yaml_swaglint_executable* + *b:ale_yaml_swaglint_executable* +yaml_swaglint_executable +g:ale_yaml_swaglint_executable + Type: |String| + Default: `'swaglint'` + + This variable can be set to change the path to swaglint. + + *ale-options.yaml_swaglint_use_global* + *g:ale_yaml_swaglint_use_global* + *b:ale_yaml_swaglint_use_global* +yaml_swaglint_use_global +g:ale_yaml_swaglint_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +yaml-language-server *ale-yaml-language-server* + +Website: https://github.com/redhat-developer/yaml-language-server + + +Installation +------------------------------------------------------------------------------- + +Install yaml-language-server either globally or locally: > + + npm install yaml-language-server -g # global + npm install yaml-language-server # local + + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_ls_executable* + *g:ale_yaml_ls_executable* + *b:ale_yaml_ls_executable* +yaml_ls_executable +g:ale_yaml_ls_executable + Type: |String| + Default: `'yaml-language-server'` + + This variable can be set to change the path to yaml-language-server. + + *ale-options.yaml_ls_config* + *g:ale_yaml_ls_config* + *b:ale_yaml_ls_config* +yaml_ls_config +g:ale_yaml_ls_config + Type: |Dictionary| + Default: `{}` + + A Dictionary for settings to pass to the language server. For example, to + enable the schema store, you can do use the following in your yaml ftplugin + file: > + + let b:ale_yaml_ls_config = { + \ 'yaml': { + \ 'schemaStore': { + \ 'enable': v:true, + \ }, + \ }, + \} +< + Or in Lua: > + + require("ale").setup.buffer({ + yaml_ls_config = { + yaml = { + schemaStore = { + enable = true, + }, + }, + }, + }) +< + Consult the yaml-language-server documentation for more information about + settings. + + *ale-options.yaml_ls_use_global* + *g:ale_yaml_ls_use_global* + *b:ale_yaml_ls_use_global* +yaml_ls_use_global +g:ale_yaml_ls_use_global + Type: |String| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +yamlfix *ale-yaml-yamlfix* + +Website: https://lyz-code.github.io/yamlfix + + +Installation +------------------------------------------------------------------------------- + +Install yamlfix: > + + pip install yamlfix +< + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_yamlfix_executable* + *g:ale_yaml_yamlfix_executable* + *b:ale_yaml_yamlfix_executable* +yaml_yamlfix_executable +g:ale_yaml_yamlfix_executable + Type: |String| + Default: `'yamlfix'` + + See |ale-integrations-local-executables| + + *ale-options.yaml_yamlfix_options* + *g:ale_yaml_yamlfix_options* + *b:ale_yaml_yamlfix_options* +yaml_yamlfix_options +g:ale_yaml_yamlfix_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to yamlfix. + + *ale-options.yaml_yamlfix_use_global* + *g:ale_yaml_yamlfix_use_global* + *b:ale_yaml_yamlfix_use_global* +yaml_yamlfix_use_global +g:ale_yaml_yamlfix_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +yamlfmt *ale-yaml-yamlfmt* + +Website: https://github.com/google/yamlfmt + + +------------------------------------------------------------------------------- +Installation + +Install yamlfmt: + + See the website. + + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_yamlfmt_executable* + *g:ale_yaml_yamlfmt_executable* + *b:ale_yaml_yamlfmt_executable* +yaml_yamlfmt_executable +g:ale_yaml_yamlfmt_executable + Type: |String| + Default: `'yamlfmt'` + + See |ale-integrations-local-executables| + + *ale-options.yaml_yamlfmt_options* + *g:ale_yaml_yamlfmt_options* + *b:ale_yaml_yamlfmt_options* +yaml_yamlfmt_options +g:ale_yaml_yamlfmt_options + Type: |String| + Default: `''` + + This variable can be set to pass extra options to yamlfmt. + + *ale-options.yaml_yamlfmt_use_global* + *g:ale_yaml_yamlfmt_use_global* + *b:ale_yaml_yamlfmt_use_global* +yaml_yamlfmt_use_global +g:ale_yaml_yamlfmt_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== +yamllint *ale-yaml-yamllint* + +Website: https://github.com/adrienverge/yamllint + + +------------------------------------------------------------------------------- +Installation + +Install yamllint in your a virtualenv directory, locally, or globally: > + + pip install yamllint # After activating virtualenv + pip install --user yamllint # Install to ~/.local/bin + sudo pip install yamllint # Install globally + +See |g:ale_virtualenv_dir_names| for configuring how ALE searches for +virtualenv directories. + + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_yamllint_executable* + *g:ale_yaml_yamllint_executable* + *b:ale_yaml_yamllint_executable* +yaml_yamllint_executable +g:ale_yaml_yamllint_executable + Type: |String| + Default: `'yamllint'` + + This variable can be set to change the path to yamllint. + + *ale-options.yaml_yamllint_options* + *g:ale_yaml_yamllint_options* + *b:ale_yaml_yamllint_options* +yaml_yamllint_options +g:ale_yaml_yamllint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to yamllint. + + +=============================================================================== +gitlablint *ale-yaml-gitlablint* + +Website: https://github.com/elijah-roberts/gitlab-lint + + +------------------------------------------------------------------------------- +Installation + +Install yamllint in your a virtualenv directory, locally, or globally: > + + pip3 install gitlab_lint # After activating virtualenv + pip3 install --user gitlab_lint # Install to ~/.local/bin + sudo pip3 install gitlab_lint # Install globally + +See |g:ale_virtualenv_dir_names| for configuring how ALE searches for +virtualenv directories. + +Is recommended to use |g:ale_pattern_options| to enable this linter so it only +applies to 'gitlab-ci.yml' files and not all yaml files: +> + let g:ale_pattern_options = { + \ '.gitlab-ci\.yml$': { + \ 'ale_linters': ['gitlablint'], + \ }, + \} +< + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_gitlablint_executable* + *g:ale_yaml_gitlablint_executable* + *b:ale_yaml_gitlablint_executable* +yaml_gitlablint_executable +g:ale_yaml_gitlablint_executable + Type: |String| + Default: `'gll'` + + This variable can be set to change the path to gll. + + *ale-options.yaml_gitlablint_options* + *g:ale_yaml_gitlablint_options* + *b:ale_yaml_gitlablint_options* +yaml_gitlablint_options +g:ale_yaml_gitlablint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to gll. + + +=============================================================================== +yq *ale-yaml-yq* + +Website: https://github.com/mikefarah/yq + + +------------------------------------------------------------------------------- +Installation + +Install yq: > + + wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - | tar xz && mv ${BINARY} /usr/bin/yq + + +------------------------------------------------------------------------------- +Options + *ale-options.yaml_yq_executable* + *g:ale_yaml_yq_executable* + *b:ale_yaml_yq_executable* +yaml_yq_executable +g:ale_yaml_yq_executable + Type: |String| + Default: `'yq'` + + This variable can be set to change the path to yq. + + *ale-options.yaml_yq_options* + *g:ale_yaml_yq_options* + *b:ale_yaml_yq_options* +yaml_yq_options +g:ale_yaml_yq_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to yq. + + *ale-options.yaml_yq_filters* + *g:ale_yaml_yq_filters* + *b:ale_yaml_yq_filters* +yaml_yq_filters +g:ale_yaml_yq_filters + Type: |String| + Default: `'.'` + + This option can be changed to pass additional filters to yq + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-yang.txt b/doc/ale-yang.txt new file mode 100644 index 00000000..493a6b17 --- /dev/null +++ b/doc/ale-yang.txt @@ -0,0 +1,20 @@ +=============================================================================== +ALE YANG Integration *ale-yang-options* + + +=============================================================================== +yang-lsp *ale-yang-lsp* + + *ale-options.yang_lsp_executable* + *g:ale_yang_lsp_executable* + *b:ale_yang_lsp_executable* +yang_lsp_executable +g:ale_yang_lsp_executable + Type: |String| + Default: `'yang-language-server'` + + This variable can be changed to use a different executable for yang-lsp. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-yara.txt b/doc/ale-yara.txt new file mode 100644 index 00000000..8a1f9081 --- /dev/null +++ b/doc/ale-yara.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Yara Integration *ale-yara-options* + *ale-integration-yara* + +=============================================================================== +Integration Information + + Currently, the only supported linter for yara is yls. + + +=============================================================================== +yls *ale-yara-yls* + + *ale-options.yara_yls_executable* + *g:ale_yara_yls_executable* + *b:ale_yara_yls_executable* +yara_yls_executable +g:ale_yara_yls_executable + Type: |String| + Default: `'yls'` + + This variable can be modified to change the executable path for `yls`. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-zeek.txt b/doc/ale-zeek.txt new file mode 100644 index 00000000..09e3c304 --- /dev/null +++ b/doc/ale-zeek.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE Zeek Integration *ale-zeek-options* + *ale-integration-zeek* + +=============================================================================== +Integration Information + + Currently, the only supported linter for Zeek is zeek. + +=============================================================================== +zeek *ale-zeek-zeek* + + *ale-options.zeek_zeek_executable* + *g:ale_zeek_zeek_executable* + *b:ale_zeek_zeek_executable* +zeek_zeek_executable +g:ale_zeek_zeek_executable + Type: |String| + Default: `'zeek'` + + This variable can be modified to change the executable path for `zeek`. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: + diff --git a/doc/ale-zig.txt b/doc/ale-zig.txt new file mode 100644 index 00000000..8194c4bb --- /dev/null +++ b/doc/ale-zig.txt @@ -0,0 +1,70 @@ +=============================================================================== +ALE Zig Integration *ale-zig-options* + *ale-integration-zig* + +=============================================================================== +Integration Information + + The following linters are supported for Zig: + + * zlint (https://github.com/DonIsaac/zlint) + * zls (https://github.com/zigtools/zls) + + +=============================================================================== +zigfmt *ale-zig-zigfmt* + + *ale-options.zig_zigfmt_executable* + *g:ale_zig_zigfmt_executable* + *b:ale_zig_zigfmt_executable* +zig_zigfmt_executable +g:ale_zig_zigfmt_executable + Type: |String| + Default: `'zig'` + + The executable that will be run for the `zig fmt` fixer. + + +=============================================================================== +zlint *ale-zig-zlint* + + *ale-options.zig_zlint_executable* + *g:ale_zig_zlint_executable* + *b:ale_zig_zlint_executable* +zig_zlint_executable +g:ale_zig_zlint_executable + Type: |String| + Default: `'zlint'` + + This variable can be modified to change the executable path for `zlint`. + + +=============================================================================== +zls *ale-zig-zls* + + *ale-options.zig_zls_executable* + *g:ale_zig_zls_executable* + *b:ale_zig_zls_executable* +zig_zls_executable +g:ale_zig_zls_executable + Type: |String| + Default: `'zls'` + + This variable can be modified to change the executable path for `zls`. + + *ale-options.zig_zls_config* + *g:ale_zig_zls_config* + *b:ale_zig_zls_config* +zig_zls_config +g:ale_zig_zls_config + Type: |Dictionary| + Default: `{}` + + WARNING: As of writing, zls does not support receiving configuration + from the client. This variable is a PLACEHOLDER until it does. + + Dictionary with configuration settings for zls. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale.txt b/doc/ale.txt new file mode 100644 index 00000000..36b551d4 --- /dev/null +++ b/doc/ale.txt @@ -0,0 +1,5355 @@ +*ale.txt* Plugin to lint and fix files asynchronously +*ale* + +ALE - Asynchronous Lint Engine + +=============================================================================== +CONTENTS *ale-contents* + + 1. Introduction.........................|ale-introduction| + 2. Supported Languages & Tools..........|ale-support| + 3. Linting..............................|ale-lint| + 3.1 Linting On Other Machines.........|ale-lint-other-machines| + 3.2 Adding Language Servers...........|ale-lint-language-servers| + 3.3 Other Sources.....................|ale-lint-other-sources| + 4. Fixing Problems......................|ale-fix| + 5. Language Server Protocol Support.....|ale-lsp| + 5.1 LSP Neovim Integration............|ale-lsp-neovim| + 5.2 Completion........................|ale-completion| + 5.3 Go To Definition..................|ale-go-to-definition| + 5.4 Go To Type Definition.............|ale-go-to-type-definition| + 5.5 Go To Implementation..............|ale-go-to-implementation| + 5.6 Find References...................|ale-find-references| + 5.7 Hovering..........................|ale-hover| + 5.8 Symbol Search.....................|ale-symbol-search| + 5.9 Refactoring: Rename, Actions......|ale-refactor| + 6. Global Options.......................|ale-options| + 6.1 Highlights........................|ale-highlights| + 7. Linter/Fixer Options.................|ale-integration-options| + 7.1 Options for alex..................|ale-alex-options| + 7.2 Options for cspell................|ale-cspell-options| + 7.3 Options for languagetool..........|ale-languagetool-options| + 7.4 Options for write-good............|ale-write-good-options| + 7.5 Other Linter/Fixer Options........|ale-other-integration-options| + 8. Commands/Keybinds....................|ale-commands| + 9. API..................................|ale-api| + 10. Special Thanks......................|ale-special-thanks| + 11. Contact.............................|ale-contact| + + +=============================================================================== +1. Introduction *ale-introduction* + +ALE provides the means to run linters asynchronously in Vim in a variety of +languages and tools. ALE sends the contents of buffers to linter programs +using the |job-control| features available in Vim 8 and NeoVim. For Vim 8, +Vim must be compiled with the |+job| and |+channel| and |+timers| features +as a minimum. + +ALE supports the following key features for linting: + +1. Running linters when text is changed. +2. Running linters when files are opened. +3. Running linters when files are saved. (When a global flag is set.) +4. Populating the |location-list| with warning and errors. +5. Setting |signs| with warnings and errors for error markers. +6. Using `:echo` to show error messages when the cursor moves. +7. Setting syntax highlights for errors. + +ALE can fix problems with files with the `:ALEFix` command, using the same job +control functionality used for checking for problems. Try using the +`:ALEFixSuggest` command for browsing tools that can be used to fix problems +for the current buffer. + +If you are interested in contributing to the development of ALE, read the +developer documentation. See |ale-development| + +For configuring ALE in Neovim, you can use the |ale.setup| function to +configure ALE globally in `init.vim`. > + + require("ale").setup({ + completion_enabled = true, + maximum_file_size = 1024 * 1024, + warn_about_trailing_whitespace = false, + }) +< +In |ftplugin| files you can customise behavior for different filetypes by +using the |ale.setup.buffer| function. > + + -- In ftplugin/python.lua in &runtimepath + require("ale").setup.buffer({ + linters = {"ruff", "pyright"}, + fixers = {"ruff"} + }) +< +Buffer local settings override global settings for that buffer. + + +=============================================================================== +2. Supported Languages & Tools *ale-support* + +ALE supports a wide variety of languages and tools. See |ale-supported-list| +for the full list. + + +=============================================================================== +3. Linting *ale-lint* + +ALE's primary focus is on checking for problems with your code with various +programs via some Vim code for integrating with those programs, referred to +as 'linters.' ALE supports a wide array of programs for linting by default, +but additional programs can be added easily by defining files in |runtimepath| +with the filename pattern `ale_linters//.vim`. For more +information on defining new linters, see the extensive documentation +for |ale#linter#Define()|. + +Without any configuration, ALE will attempt to check all of the code for every +file you open in Vim with all available tools by default. To see what ALE +is doing, and what options have been set, try using the `:ALEInfo` command. + +Most of the linters ALE runs will check the Vim buffer you are editing instead +of the file on disk. This allows you to check your code for errors before you +have even saved your changes. ALE will check your code in the following +circumstances, which can be configured with the associated options. + +* When you modify a buffer - |g:ale_lint_on_text_changed| +* On leaving insert mode - |g:ale_lint_on_insert_leave| +* When you open a new or modified buffer - |g:ale_lint_on_enter| +* When you save a buffer - |g:ale_lint_on_save| +* When the filetype changes for a buffer - |g:ale_lint_on_filetype_changed| +* If ALE is used to check code manually - |:ALELint| + + *ale-lint-settings-on-startup* + +It is worth reading the documentation for every option. You should configure +which events ALE will use before ALE is loaded, so it can optimize which +autocmd commands to run. You can force autocmd commands to be reloaded with +`:ALEDisable | ALEEnable` + +This also applies to the autocmd commands used for |g:ale_echo_cursor|. + + *ale-lint-file-linters* + +Some programs must be run against files which have been saved to disk, and +simply do not support reading temporary files or stdin, either of which are +required for ALE to be able to check for errors as you type. The programs +which behave this way are documented in the lists and tables of supported +programs. ALE will only lint files with these programs in the following +circumstances. + +* When you open a new or modified buffer - |g:ale_lint_on_enter| +* When you save a buffer - |g:ale_lint_on_save| +* When the filetype changes for a buffer - |g:ale_lint_on_filetype_changed| +* If ALE is used to check code manually - |:ALELint| + +ALE will report problems with your code in the following ways, listed with +their relevant options. + +* Via Neovim diagnostics (On in Neovim 0.7+) - |g:ale_use_neovim_diagnostics_api| +* By updating loclist (On by default) - |g:ale_set_loclist| +* By updating quickfix (Off by default) - |g:ale_set_quickfix| +* By setting error highlights - |g:ale_set_highlights| +* By creating signs in the sign column - |g:ale_set_signs| +* By echoing messages based on your cursor - |g:ale_echo_cursor| +* By showing virtual text at your cursor - |g:ale_virtualtext_cursor| +* By previewing details at your cursor - |g:ale_cursor_detail| +* By showing balloons for your mouse cursor - |g:ale_set_balloons| + +Please consult the documentation for each option, which can reveal some other +ways of tweaking the behavior of each way of displaying problems. You can +disable or enable whichever options you prefer. + +Most settings can be configured for each buffer. (|b:| instead of |g:|), +including disabling ALE for certain buffers with |b:ale_enabled|. The +|g:ale_pattern_options| setting can be used to configure files differently +based on regular expressions for filenames. For configuring entire projects, +the buffer-local options can be used with external plugins for reading Vim +project configuration files. Buffer-local settings can also be used in +ftplugin files for different filetypes. + +ALE offers several options for controlling which linters are run. + +* Selecting linters to run. - |g:ale_linters| +* Aliasing filetypes for linters - |g:ale_linter_aliases| +* Only running linters you asked for. - |g:ale_linters_explicit| +* Disabling only a subset of linters. - |g:ale_linters_ignore| +* Disabling LSP linters and `tsserver`. - |g:ale_disable_lsp| + +You can stop ALE any currently running linters with the `:ALELintStop` command. +Any existing problems will be kept. + + +------------------------------------------------------------------------------- +3.1 Linting On Other Machines *ale-lint-other-machines* + +ALE offers support for running linters or fixers on files you are editing +locally on other machines, so long as the other machine has access to the file +you are editing. This could be a linter or fixer run inside of a Docker image, +running in a virtual machine, running on a remote server, etc. + +In order to run tools on other machines, you will need to configure your tools +to run via scripts that execute commands on those machines, such as by setting +the ALE `_executable` options for those tools to a path for a script to run, +or by using |g:ale_command_wrapper| to specify a script to wrap all commands +that are run by ALE, before they are executed. For tools that ALE runs where +ALE looks for locally installed executables first, you may need to set the +`_use_global` options for those tools to `1`, or you can set +|g:ale_use_global_executables| to `1` before ALE is loaded to only use global +executables for all tools. + +In order for ALE to properly lint or fix files which are running on another +file system, you must provide ALE with |List|s of strings for mapping paths to +and from your local file system and the remote file system, such as the file +system of your Docker container. See |g:ale_filename_mappings| for all of the +different ways these filename mappings can be configured. + +For example, you might configure `pylint` to run via Docker by creating a +script like so. > + + #!/usr/bin/env bash + + exec docker run -i --rm -v "$(pwd):/data" cytopia/pylint "$@" +< +You will want to run Docker commands with `-i` in order to read from stdin. + +With the above script in mind, you might configure ALE to lint your Python +project with `pylint` by providing the path to the script to execute, and +mappings which describe how to change between the two file systems in your +`python.vim` |ftplugin| file, like so: > + + if expand('%:p') =~# '^/home/w0rp/git/test-pylint/' + let b:ale_linters = ['pylint'] + let b:ale_python_pylint_use_global = 1 + " This is the path to the script above. + let b:ale_python_pylint_executable = '/home/w0rp/git/test-pylint/pylint.sh' + " /data matches the path in Docker. + let b:ale_filename_mappings = { + \ 'pylint': [ + \ ['/home/w0rp/git/test-pylint', '/data'], + \ ], + \} + endif +< +You might consider using a Vim plugin for loading Vim configuration files +specific to each project, if you have a lot of projects to manage. + + +------------------------------------------------------------------------------- +3.2 Adding Language Servers *ale-lint-language-servers* + +ALE comes with many default configurations for language servers, so they can +be detected and run automatically. ALE can connect to other language servers +by defining a new linter for a filetype. New linters can be defined in |vimrc|, +in plugin files, or `ale_linters` directories in 'runtimepath'. + +See |ale-linter-loading-behavior| for more information on loading linters. + +A minimal configuration for a language server linter might look so. > + + call ale#linter#Define('filetype_here', { + \ 'name': 'any_name_you_want', + \ 'lsp': 'stdio', + \ 'executable': '/path/to/executable', + \ 'command': '%e run', + \ 'project_root': '/path/to/root_of_project', + \}) +< +For language servers that use a TCP or named pipe socket connection, you +should define the address to connect to instead. > + + call ale#linter#Define('filetype_here', { + \ 'name': 'any_name_you_want', + \ 'lsp': 'socket', + \ 'address': 'servername:1234', + \ 'project_root': '/path/to/root_of_project', + \}) +< +Most of the options for a language server can be replaced with a |Funcref| for +a function accepting a buffer number for dynamically computing values such as +the executable path, the project path, the server address, etc, most of which +can also be determined based on executing some other asynchronous task. See +|ale#command#Run()| for computing linter options based on asynchronous +results. + +See |ale#linter#Define()| for a detailed explanation of all of the options for +configuring linters. + + +------------------------------------------------------------------------------- +3.3 Other Sources *ale-lint-other-sources* + +Problems for a buffer can be taken from other sources and rendered by ALE. +This allows ALE to be used in combination with other plugins which also want +to display any problems they might find with a buffer. ALE's API includes the +following components for making this possible. + +* |ale#other_source#StartChecking()| - Tell ALE that a buffer is being checked. +* |ale#other_source#ShowResults()| - Show results from another source. +* |ALEWantResults| - A signal for when ALE wants results. + +Other resources can provide results for ALE to display at any time, following +ALE's loclist format. (See |ale-loclist-format|) For example: > + + " Tell ALE to show some results. + " This function can be called at any time. + call ale#other_source#ShowResults(bufnr(''), 'some-linter-name', [ + \ {'text': 'Something went wrong', 'lnum': 13}, + \]) +< +Other sources should use a unique name for identifying themselves. A single +linter name can be used for all problems from another source, or a series of +unique linter names can be used. Results can be cleared for that source by +providing an empty List. + +|ale#other_source#StartChecking()| should be called whenever another source +starts checking a buffer, so other tools can know that a buffer is being +checked by some plugin. The |ALEWantResults| autocmd event can be used to +start checking a buffer for problems every time that ALE does. When +|ALEWantResults| is signaled, |g:ale_want_results_buffer| will be set to the +number of the buffer that ALE wants to check. +|ale#other_source#StartChecking()| should be called synchronously, and other +sources should perform their checks on a buffer in the background +asynchronously, so they don't interrupt editing. + +|ale#other_source#ShowResults()| must not be called synchronously before +ALE's engine executes its code after the |ALEWantResults| event runs. If +there are immediate results to provide to ALE, a 0 millisecond timer with +|timer_start()| can be set instead up to call |ale#other_source#ShowResults()| +after ALE has first executed its engine code for its own sources. + +A plugin might integrate its own checks with ALE like so: > + + augroup SomeGroupName + autocmd! + autocmd User ALEWantResults call Hook(g:ale_want_results_buffer) + augroup END + + function! DoBackgroundWork(buffer) abort + " Start some work in the background here. + " ... + " Then call WorkDone(a:buffer, results) + endfunction + + function! Hook(buffer) abort + " Tell ALE we're going to check this buffer. + call ale#other_source#StartChecking(a:buffer, 'some-name') + call DoBackgroundWork(a:buffer) + endfunction + + function! WorkDone(buffer, results) abort + " Send results to ALE after they have been collected. + call ale#other_source#ShowResults(a:buffer, 'some-name', a:results) + endfunction +< + +=============================================================================== +4. Fixing Problems *ale-fix* + +ALE can fix problems with files with the `:ALEFix` command. `:ALEFix` +accepts names of fixers to be applied as arguments. Alternatively, +when no arguments are provided, the variable |g:ale_fixers| will be +read for getting a |List| of commands for filetypes, split on `.`, and +the functions named in |g:ale_fixers| will be executed for fixing the +errors. + +The `:ALEFixSuggest` command can be used to suggest tools that be used to +fix problems for the current buffer. + +The values for `g:ale_fixers` can be a list of |String|, |Funcref|, or +|lambda| values. String values must either name a function, or a short name +for a function set in the ALE fixer registry. + +Each function for fixing errors must accept either one argument `(buffer)` or +two arguments `(buffer, lines)`, representing the buffer being fixed and the +lines to fix. The functions must return either `0`, for changing nothing, a +|List| for new lines to set, 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 +is desired. This is required to avoid unnecessary copying of the lines of +the buffers being checked. + +When a |Dictionary| is returned for an `:ALEFix` callback, the following keys +are supported for running the commands. + + `cwd` An optional |String| for setting the working directory + for the command. + + If not set, or `v:null`, the `cwd` of the last command + that spawn this one will be used. + + `command` A |String| for the command to run. This key is required. + + When `%t` is included in a command string, a temporary + file will be created, containing the lines from the file + after previous adjustment have been done. + + See |ale-command-format-strings| for formatting options. + + `read_temporary_file` When set to `1`, ALE will read the contents of the + temporary file created for `%t`. This option can be used + for commands which need to modify some file on disk in + order to fix files. + + `process_with` An optional callback for post-processing. + + The callback must accept arguments `(bufnr, output)`: + the buffer number undergoing fixing and the fixer's + output as a |List| of |String|s. It must return a |List| + of |String|s that will be the new contents of the + buffer. + + This callback is useful to remove excess lines from the + command's output or apply additional changes to the + output. + + + `read_buffer` An optional key for disabling reading the buffer. + + When set to `0`, ALE will not pipe the buffer's data + into the command via stdin. This option is ignored and + the buffer is not read when `read_temporary_file` is + `1`. + + This option defaults to `1`. + + *ale-fix-configuration* + +Synchronous functions and asynchronous jobs will be run in a sequence for +fixing files, and can be combined. For example: +> + let g:ale_fixers = { + \ 'javascript': [ + \ 'DoSomething', + \ 'eslint', + \ {buffer, lines -> filter(lines, 'v:val !=~ ''^\s*//''')}, + \ ], + \} + + ALEFix +< +The above example will call a function called `DoSomething` which could act +upon some lines immediately, then run `eslint` from the ALE registry, and +then call a lambda function which will remove every single line comment +from the file. + +For buffer-local settings, such as in |g:ale_pattern_options| or in ftplugin +files, a |List| may be used for configuring the fixers instead. +> + " Same as the above, only a List can be used instead of a Dictionary. + let b:ale_fixers = [ + \ 'DoSomething', + \ 'eslint', + \ {buffer, lines -> filter(lines, 'v:val !=~ ''^\s*//''')}, + \] + + ALEFix +< +For convenience, a plug mapping is defined for `:ALEFix`, so you can set up a +keybind easily for fixing files. > + + " Bind F8 to fixing problems with ALE + nmap (ale_fix) +< +Files can be fixed automatically with the following options, which are all off +by default. + +|g:ale_fix_on_save| - Fix files when they are saved. + +Fixers can be disabled on save with |g:ale_fix_on_save_ignore|. They will +still be run when you manually run `:ALEFix`. + +Fixers can be run on another machines, just like linters, such as fixers run +from a Docker container, running in a virtual machine, running a remote +server, etc. See |ale-lint-other-machines|. + + +=============================================================================== +5. Language Server Protocol Support *ale-lsp* + +ALE integrates with Language Server Protocol (LSP) 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. + +If you want to use another plugin for LSP features and tsserver, you can use +the |g:ale_disable_lsp| setting to disable ALE's own LSP integrations, or +ignore particular linters with |g:ale_linters_ignore|. In ALE's default +configuration ALE will attempt to avoid conflicting with `nvim-lspconfig`. + +ALE will integrate with Neovim's LSP client by default in Neovim 0.8+. This +functionality can be controlled with the |g:ale_use_neovim_lsp_api| setting. +See |ale-lsp-neovim| below for information about ALE's integration with +Neovim's LSP client. + +If for any reason you want to stop a language server ALE starts, such as when +a project configuration has significantly changed, or new files have been +added the language server isn't aware of, use either `:ALEStopLSP` or +`:ALEStopAllLSPs` to stop the server until ALE automatically starts it again. + + +------------------------------------------------------------------------------- +5.1 LSP Neovim Integration *ale-lsp-neovim* + +In Neovim 0.8+ ALE will integrate with Neovim's native LSP client by default, +unless disabled by setting |g:ale_use_neovim_lsp_api| to `0`. All built in +functionality for Neovim's LSP client should work as expected, and this +ensures ALE integrates well with other plugins that rely on Neovim's LSP +client. + +NOTE: Neovim versions below `0.11.0` do not support socket connections to +language servers when the `address` defined in ALE uses a hostname instead of +an IP address. To work around this, configure language clients with an IP +address instead of a hostname, or revert back to ALE's custom LSP client. + +See |lsp| for information on Neovim's built in LSP client. + +For diagnostics, for computing problems to show via ALE, ALE overrides the +diagnostics handler for the LSP client launched by ALE, so all of the +functionality in ALE will work as expected. By default ALE will send +diagnostics back to Neovim's diagnostics API, which can be configured with the +|g:ale_use_neovim_diagnostics_api| setting. This ensures that all of the +functionality ALE adds on top for diagnostics will function, and that problems +from linters that don't use LSP can be combined with LSP servers. See the +diagram below. > + + +-------------------+ + | Language Server | (Sends diagnostics) + +-------------------+ + | + +-------------------+ + | Neovim LSP Client | (Receives diagnostics) + +-------------------+ + | + +-------------------+ + | ALE Processing | (Intercepts and processes diagnostics) + +-------------------+ + | + +-------------------+ + | Diagnostic engine | (Either Neovim's diagnostics or ALE's custom code) + +-------------------+ + | + +-------------------+ + | Neovim | (User sees formatted diagnostics) + +-------------------+ +< +For LSP functionality executed via ALE's own functions, commands, and +keybinds, ALE will intercept requests and handle them in an entirely custom +way, ensuring ALE functionality should work largely the same between +different Vim versions. See the diagram below. > + + +-------------------+ + | Neovim | (User triggers LSP request via ALE) + +-------------------+ + | + +-------------------+ + | ALE | (ALE sends request to Neovim client) + +-------------------+ + | + +-------------------+ + | Neovim LSP Client | (Forwards request to language server) + +-------------------+ + | + +-------------------+ + | Language Server | (Processes request and sends response) + +-------------------+ + | + +-------------------+ + | Neovim LSP Client | (Receives response) + +-------------------+ + | + +-------------------+ + | ALE | (ALE Handles "raw" LSP response) + +-------------------+ + | + +-------------------+ + | Neovim | (User sees result) + +-------------------+ +< +For LSP functionality built-in to Neovim, such as the |gd| keybind for jumping +to a definition, Neovim will bypass ALE entirely, ensuring that ALE does not +interfere with LSP functionality as expected by built-in Neovim tools or other +plugins. See the diagram below. > + + +-------------------+ + | Neovim | (User triggers LSP request) + +-------------------+ + | + +-------------------+ + | Neovim LSP Client | (Directly handles the request) + +-------------------+ + | + +-------------------+ + | Language Server | (Processes request and sends response) + +-------------------+ + | + +-------------------+ + | Neovim LSP Client | (Receives response and shows result) + +-------------------+ + | + +-------------------+ + | Neovim | (User sees result) + +-------------------+ +< + +------------------------------------------------------------------------------- +5.2 Completion *ale-completion* + +In Neovim 0.8+ ALE's integration with its native LSP client will make it +possible to use other plugins that rely on Neovim's LSP client as a basis. +`nvim-cmp` is recommended as a completion plugin worth trying in Neovim. +See: https://github.com/hrsh7th/nvim-cmp + +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. + + *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-asyncomplete-integration* + +ALE additionally integrates with asyncomplete.vim for offering automatic +completion data. ALE's asyncomplete source requires registration with +defaults provided by the |asyncomplete#sources#ale#get_source_options| function > + + " Use ALE's function for asyncomplete defaults + " Provide your own overrides here. + au User asyncomplete_setup call asyncomplete#register_source( + \ asyncomplete#sources#ale#get_source_options({ + \ 'priority': 10, + \ }) + \) +> +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. ALE's own completion implementation can be enabled by setting +|g:ale_completion_enabled| to `true` or `1`. This setting must be set to +`true` or `1` before ALE is loaded. The delay for completion can be configured +with |g:ale_completion_delay|. This setting should not be enabled if you wish +to use ALE as a completion source for other plugins. + +ALE automatic completion will not work when 'paste' is active. Only set +'paste' when you are copy and pasting text into your buffers. + +ALE automatic completion will interfere with default insert completion with +`CTRL-N` and so on (|compl-vim|). You can write your own keybinds and a +function in your |vimrc| file to force insert completion instead, like so: > + + function! SmartInsertCompletion() abort + " Use the default CTRL-N in completion menus + if pumvisible() + return "\" + endif + + " Exit and re-enter insert mode, and use insert completion + return "\a\" + endfunction + + inoremap =SmartInsertCompletion() +< +ALE provides an 'omnifunc' function |ale#completion#OmniFunc| for triggering +completion manually with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O| > + + " Use ALE's function for omnicompletion. + set omnifunc=ale#completion#OmniFunc +< + *ale-completion-fallback* + +You can write your own completion function and fallback on other methods of +completion by checking if there are no results that ALE can determine. For +example, for Python code, you could fall back on the `python3complete` +function. > + + function! TestCompletionFunc(findstart, base) abort + let l:result = ale#completion#OmniFunc(a:findstart, a:base) + + " Check if ALE couldn't find anything. + if (a:findstart && l:result is -3) + \|| (!a:findstart && empty(l:result)) + " Defer to another omnifunc if ALE couldn't find anything. + return python3complete#Complete(a:findstart, a:base) + endif + + return l:result + endfunction + + set omnifunc=TestCompletionFunc +< +See |complete-functions| for documentation on how to write completion +functions. + +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|. + +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 supports automatic imports from external modules. This behavior can be +disabled by setting the |g:ale_completion_autoimport| variable to `0`. +Disabling automatic imports can drop some or all completion items from +some LSP servers (e.g. eclipselsp). + +You can manually request imports for symbols at the cursor with the +`:ALEImport` command. The word at the cursor must be an exact match for some +potential completion result which includes additional text to insert into the +current buffer, which ALE will assume is code for an import line. This command +can be useful when your code already contains something you need to import. + +You can execute other commands whenever ALE inserts some completion text with +the |ALECompletePost| event. + +When working with TypeScript files, ALE can remove warnings from your +completions by setting the |g:ale_completion_tsserver_remove_warnings| +variable to 1. + + *ale-completion-completeopt-bug* + +ALE Automatic completion implementation replaces |completeopt| before opening +the omnicomplete menu with . 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 +< +Or alternatively, if you want to show documentation in popups: > + + set completeopt=menu,menuone,popup,noselect,noinsert +< + *ale-symbols* + +ALE provides a set of basic completion symbols. If you want to replace those +symbols with others, you can set the variable |g:ale_completion_symbols| with +a mapping of the type of completion to the symbol or other string that you +would like to use. An example here shows the available options for symbols > + + let g:ale_completion_symbols = { + \ 'text': '', + \ 'method': '', + \ 'function': '', + \ 'constructor': '', + \ 'field': '', + \ 'variable': '', + \ 'class': '', + \ 'interface': '', + \ 'module': '', + \ 'property': '', + \ 'unit': 'unit', + \ 'value': 'val', + \ 'enum': '', + \ 'keyword': 'keyword', + \ 'snippet': '', + \ 'color': 'color', + \ 'file': '', + \ 'reference': 'ref', + \ 'folder': '', + \ 'enum member': '', + \ 'constant': '', + \ 'struct': '', + \ 'event': 'event', + \ 'operator': '', + \ 'type_parameter': 'type param', + \ '': 'v' + \ } +< + +------------------------------------------------------------------------------- +5.3 Go To Definition *ale-go-to-definition* + +ALE supports jumping to the files and locations where symbols are defined +through any enabled LSP linters. The locations ALE will jump to depend on the +information returned by LSP servers. The `:ALEGoToDefinition` command will jump +to the definition of symbols under the cursor. See the documentation for the +command for configuring how the location will be displayed. + +ALE will update Vim's |tagstack| automatically unless |g:ale_update_tagstack| is +set to `0`. + + +------------------------------------------------------------------------------- +5.4 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 `:ALEGoToTypeDefinition` +command will jump to the definition of symbols under the cursor. See the +documentation for the command for configuring how the location will be +displayed. + + +------------------------------------------------------------------------------- +5.5 Go To Implementation *ale-go-to-implementation* + +ALE supports jumping to the files and locations where symbols are implemented +through any enabled LSP linters. The locations ALE will jump to depend on the +information returned by LSP servers. The `:ALEGoToImplementation` command will +jump to the implementation of symbols under the cursor. See the documentation +for the command for configuring how the location will be displayed. + + +------------------------------------------------------------------------------- +5.6 Find References *ale-find-references* + +ALE supports finding references for symbols though any enabled LSP linters +with the `:ALEFindReferences` command. See the documentation for the command +for a full list of options. + + +------------------------------------------------------------------------------- +5.7 Hovering *ale-hover* + +ALE supports "hover" information for printing brief information about symbols +at the cursor taken from LSP linters. The following commands are supported: + +`:ALEHover` - Print information about the symbol at the cursor. + +Truncated information will be displayed when the cursor rests on a symbol by +default, as long as there are no problems on the same line. You can disable +this behavior by setting |g:ale_hover_cursor| to `0`. + +If |g:ale_set_balloons| is set to `true` or `1` and your version of Vim +supports the |balloon_show()| function, then "hover" information also show up +when you move the mouse over a symbol in a buffer. Diagnostic information will +take priority over hover information for balloons. If a line contains a +problem, that problem will be displayed in a balloon instead of hover +information. + +Hover information can be displayed in the preview window instead by setting +|g:ale_hover_to_preview| to `true` or `1`. + +When using Neovim or Vim with |popupwin|, if |g:ale_hover_to_floating_preview| +or |g:ale_floating_preview| is set to `true` or `1`, the hover information +will show in a floating window. The borders of the floating preview window can +be customized by setting |g:ale_floating_window_border|. + +For Vim 8.1+ terminals, mouse hovering is disabled by default. Enabling +|balloonexpr| commands in terminals can cause scrolling issues in terminals, +so ALE will not attempt to show balloons unless |g:ale_set_balloons| is set to +`true` or `1` before ALE is loaded. + +For enabling mouse support in terminals, you may have to change your mouse +settings. For example: > + + " Example mouse settings. + " You will need to try different settings, depending on your terminal. + set mouse=a + set ttymouse=xterm +< + +Documentation for symbols at the cursor can be retrieved using the +`:ALEDocumentation` command. This command is only available for `tsserver`. + + +------------------------------------------------------------------------------- +5.8 Symbol Search *ale-symbol-search* + +ALE supports searching for workspace symbols via LSP linters with the +`:ALESymbolSearch` command. See the documentation for the command +for a full list of options. + + +------------------------------------------------------------------------------- +5.9 Refactoring: Rename, Actions *ale-refactor* + +ALE supports renaming symbols in code such as variables or class names with +the `:ALERename` command. + +`:ALEFileRename` will rename file and fix import paths (tsserver only). + +`:ALECodeAction` will execute actions on the cursor or applied to a visual +range selection, such as automatically fixing errors. + +Actions will appear in the right click mouse menu by default for GUI versions +of Vim, unless disabled by setting |g:ale_popup_menu_enabled| to `0`. + +Make sure to set your Vim to move the cursor position whenever you right +click, and enable the mouse menu: > + + set mouse=a + set mousemodel=popup_setpos +< +You may wish to remove some other menu items you don't want to see: > + + silent! aunmenu PopUp.Select\ Word + silent! aunmenu PopUp.Select\ Sentence + silent! aunmenu PopUp.Select\ Paragraph + silent! aunmenu PopUp.Select\ Line + silent! aunmenu PopUp.Select\ Block + silent! aunmenu PopUp.Select\ Blockwise + silent! aunmenu PopUp.Select\ All +< + +=============================================================================== +6. Global Options *ale-options* + +Options documented here can be configured either Vim variables, or via the +|ale.setup| and |ale.setup.buffer| functions in Lua. When configuring via +the Lua functions in Lua scripts, ALE will bridge types to Vim script in the +following ways. + +1. Strings, numbers, booleans, and `nil` will be represented exactly. +2. Tables with no or only number keys will become a |List| in Vim. +3. Keys other than strings and numbers in tables cannot be represented. +4. Tables with special |metatable| properties cannot be represented. +5. Options accepting functions as values will automatically have Vim functions + created that bridge the function calls to and from Lua code. + + *g:airline#extensions#ale#enabled* +g:airline#extensions#ale#enabled + Type: |Number| + Default: `1` + + Enables or disables the |airline|'s native extension for ale, which displays + warnings and errors in the status line, prefixed by + |airline#extensions#ale#error_symbol| and + |airline#extensions#ale#warning_symbol|. + + *ale-options.cache_executable_check_failures* + *g:ale_cache_executable_check_failures* +cache_executable_check_failures +g:ale_cache_executable_check_failures + Type: |Boolean| or |Number| + Default: `nil` + + When set to `true` or `1`, ALE will cache failing executable checks for + linters. By default, only executable checks which succeed will be cached. + + When this option is set to `true` or `1`, Vim will have to be restarted + after new executables are installed for ALE to be able to run linters for + those executables. + + *ale-options.change_sign_column_color* + *g:ale_change_sign_column_color* +change_sign_column_color +g:ale_change_sign_column_color + Type: |Boolean| or |Number| + Default: `false` + + When set to `true` or `1`, this option will set different highlights for the + sign column itself when ALE reports problems with a file. This option can be + combined with |g:ale_sign_column_always|. + + ALE uses the following highlight groups for highlighting the sign column: + + `:ALESignColumnWithErrors` - Links to `Error` by default. + `:ALESignColumnWithoutErrors` - Uses the value for `SignColumn` by default. + + The sign column color can only be changed globally in Vim. The sign column + might produce unexpected results if editing different files in split + windows. + + *ale-options.close_preview_on_insert* + *g:ale_close_preview_on_insert* +close_preview_on_insert +g:ale_close_preview_on_insert + Type: |Boolean| or |Number| + Default: `false` + + When this option is set to `true` or `1`, ALE's |preview-window| will be + automatically closed upon entering Insert Mode. This option can be used in + combination with |g:ale_cursor_detail| for automatically displaying the + preview window on problem lines, and automatically closing it again when + editing text. + + This setting must be set to `true` or `1` before ALE is loaded for this + behavior to be enabled. See |ale-lint-settings-on-startup|. + + *ale-options.command_wrapper* + *g:ale_command_wrapper* + *b:ale_command_wrapper* +command_wrapper +g:ale_command_wrapper + Type: |String| + Default: `''` + + An option for wrapping all commands that ALE runs, for linters, fixers, + and LSP commands. This option can be set globally, or for specific buffers. + + This option can be used to apply nice to all commands. For example: > + + " Prefix all commands with nice. + let g:ale_command_wrapper = 'nice -n5' +< + Use the `:ALEInfo` command to view the commands that are run. All of the + arguments for commands will be put on the end of the wrapped command by + default. A `%*` marker can be used to spread the arguments in the wrapped + command. > + + " Has the same effect as the above. + let g:ale_command_wrapper = 'nice -n5 %*' +< + For passing all of the arguments for a command as one argument to a wrapper, + `%@` can be used instead. > + + " Will result in say: /bin/bash -c 'other-wrapper -c "some command" -x' + let g:ale_command_wrapper = 'other-wrapper -c %@ -x' +< + For commands including `&&` or `;`, only the last command in the list will + be passed to the wrapper. `&&` is most commonly used in ALE to change the + working directory before running a command. + + *ale-options.completion_delay* + *g:ale_completion_delay* +completion_delay +g:ale_completion_delay + Type: |Number| + Default: `100` + + The number of milliseconds before ALE will send a request to a language + server for completions after you have finished typing. + + See |ale-completion| + + *ale-options.completion_enabled* + *g:ale_completion_enabled* + *b:ale_completion_enabled* +completion_enabled +g:ale_completion_enabled + Type: |Boolean| or |Number| + Default: `false` + + When this option is set to `true` or `1`, completion support will be enabled. + + This setting must be set to `true` or `1` before ALE is loaded for this behavior + to be enabled. + + This setting should not be enabled if you wish to use ALE as a completion + source for other completion plugins. + + ALE automatic completion will not work when 'paste' is active. Only set + 'paste' when you are copy and pasting text into your buffers. + + A buffer-local version of this setting `b:ale_completion_enabled` can be set + to `0` to disable ALE's automatic completion support for a single buffer. + ALE's completion support must be enabled globally to be enabled locally. + + See |ale-completion| + + *ale-options.completion_tsserver_remove_warnings* + *g:ale_completion_tsserver_remove_warnings* +completion_tsserver_remove_warnings +g:ale_completion_tsserver_remove_warnings + Type: |Boolean| or |Number| + Default: `false` + + When this option is set to `false` or `0`, ALE will return all completion + items from its built in completion engine, including those that are a + warning. Warnings can be excluded from completed items by setting it to + `true` or `1`. + + *ale-options.completion_autoimport* + *g:ale_completion_autoimport* +completion_autoimport +g:ale_completion_autoimport + Type: |Boolean| or |Number| + Default: `true` + + When this option is set to `1`, ALE will try to automatically import + completion results from external modules. It can be disabled by setting it + to `0`. Some LSP servers include auto imports on every completion item so + disabling automatic imports may drop some or all completion items returned + by it (e.g. eclipselsp). + + *ale-options.completion_excluded_words* + *g:ale_completion_excluded_words* + *b:ale_completion_excluded_words* +completion_excluded_words +g:ale_completion_excluded_words + Type: |List| + Default: `[]` + + This option can be set to a list of |String| values for "words" to exclude + from completion results, as in the words for |complete-items|. The strings + will be matched exactly in a case-sensitive manner. (|==#|) + + This setting can be configured in ftplugin files with buffer variables, so + that different lists can be used for different filetypes. For example: > + + " In ~/.vim/ftplugin/typescript.vim + + " Don't suggest `it` or `describe` so we can use snippets for those words. + let b:ale_completion_excluded_words = ['it', 'describe'] +< + *ale-options.completion_symbols* + *g:ale_completion_symbols* +completion_symbols +g:ale_completion_symbols + Type: |Dictionary| + Default: See `autoload/ale/completion.vim` + + A mapping from completion types to symbols for completions. See + |ale-symbols| for more information. + + By default, this mapping only uses built in Vim completion kinds, but it can + be updated to use any Unicode character for the completion kind. For + example: > + let g:ale_completion_symbols = { + \ 'text': '', + \ 'method': '', + \ 'function': '', + \ 'constructor': '', + \ 'field': '', + \ 'variable': '', + \ 'class': '', + \ 'interface': '', + \ 'module': '', + \ 'property': '', + \ 'unit': 'v', + \ 'value': 'v', + \ 'enum': 't', + \ 'keyword': 'v', + \ 'snippet': 'v', + \ 'color': 'v', + \ 'file': 'v', + \ 'reference': 'v', + \ 'folder': 'v', + \ 'enum_member': 'm', + \ 'constant': 'm', + \ 'struct': 't', + \ 'event': 'v', + \ 'operator': 'f', + \ 'type_parameter': 'p', + \ '': 'v' + \ }) +< + *ale-options.completion_max_suggestions* + *g:ale_completion_max_suggestions* +completion_max_suggestions +g:ale_completion_max_suggestions + Type: |Number| + Default: `50` + + The maximum number of items ALE will suggest in completion menus for + automatic completion. + + Setting this number higher will require more processing time, and may + suggest too much noise. Setting this number lower will require less + processing time, but some suggestions will not be included, so you might not + be able to see the suggestions you want. + + Adjust this option as needed, depending on the complexity of your codebase + and your available processing power. + + *ale-options.cursor_detail* + *g:ale_cursor_detail* +cursor_detail +g:ale_cursor_detail + Type: |Number| + Default: `false` + + When this option is set to `true` or `1`, ALE's |preview-window| will be + automatically opened when the cursor moves onto lines with problems. ALE + will search for problems using the same logic that |g:ale_echo_cursor| uses. + The preview window will be closed automatically when you move away from the + line. + + Messages are only displayed after a short delay. See |g:ale_echo_delay|. + + The preview window is opened without stealing focus, which means your cursor + will stay in the same buffer as it currently is. + + The preview window can be closed automatically upon entering Insert mode + by setting |g:ale_close_preview_on_insert| to `true` or `1`. + + Either this setting or |g:ale_echo_cursor| must be set to `true` or `1` + before ALE is loaded for messages to be displayed. + See |ale-lint-settings-on-startup|. + + *ale-options.default_navigation* + *g:ale_default_navigation* + *b:ale_default_navigation* +default_navigation +g:ale_default_navigation + Type: |String| + Default: `'buffer'` + + The default method for navigating away from the current buffer to another + buffer, such as for `:ALEFindReferences` or `:ALEGoToDefinition`. + + *ale-options.detail_to_floating_preview* + *g:ale_detail_to_floating_preview* + *b:ale_detail_to_floating_preview* +detail_to_floating_preview +g:ale_detail_to_floating_preview + Type: |Number| + Default: `0` + + When this option is set to `1`, Neovim or Vim with |popupwin| will use a + floating window for ALEDetail output. + + *ale-options.disable_lsp* + *g:ale_disable_lsp* + *b:ale_disable_lsp* +disable_lsp +g:ale_disable_lsp + Type: |Boolean| OR |Number| OR |String| + Default: `'auto'` + + When this option is set to `'auto'`, ALE will automatically disable linters + that it detects as having already been configured with the `nvim-lspconfig` + plugin. When this option is set to `true` or `1`, ALE ignores all linters + powered by LSP, and also `tsserver`. + + Any linters that are disabled will also not be usable for LSP functionality + other than just linting. + + Please see also |ale-lsp|. + + *ale-options.echo_cursor* + *g:ale_echo_cursor* +echo_cursor +g:ale_echo_cursor + Type: |Number| + Default: `1` + + When this option is set to `1`, a truncated message will be echoed when a + cursor is near a warning or error. ALE will attempt to find the warning or + error at a column nearest to the cursor when the cursor is resting on a line + which contains a warning or error. This option can be set to `0` to disable + this behavior. + + Messages are only displayed after a short delay. See |g:ale_echo_delay|. + + The format of the message can be customized with |g:ale_echo_msg_format|. + + Either this setting or |g:ale_cursor_detail| must be set to `true` or `1` + before ALE is loaded for messages to be displayed. See + |ale-lint-settings-on-startup|. + + *ale-options.echo_delay* + *g:ale_echo_delay* + *b:ale_echo_delay* +echo_delay +g:ale_echo_delay + Type: |Number| + Default: `10` + + Given any integer, this option controls the number of milliseconds before + ALE will echo or preview a message for a problem near the cursor. + + The value can be increased to decrease the amount of processing ALE will do + for files displaying a large number of problems. + + *ale-options.echo_msg_error_str* + *g:ale_echo_msg_error_str* +echo_msg_error_str +g:ale_echo_msg_error_str + Type: |String| + Default: `'Error'` + + The string used for `%severity%` for errors. See |g:ale_echo_msg_format| + + *ale-options.echo_msg_format* + *g:ale_echo_msg_format* + *b:ale_echo_msg_format* +echo_msg_format +g:ale_echo_msg_format + Type: |String| + Default: `'%code: %%s'` + + This variable defines a message format for echoed messages. The following + sequences of characters will be replaced. + + `%s` - replaced with the text for the problem + `%...code...% `- replaced with the error code + `%linter%` - replaced with the name of the linter + `%severity%` - replaced with the severity of the problem (e.g. `Error`) + `%type%` - replaced with the type of the problem (e.g. `E`) + + The strings for `%severity%` can be configured with the following options. + + |g:ale_echo_msg_error_str| - Defaults to `'Error'` + |g:ale_echo_msg_info_str| - Defaults to `'Info'` + |g:ale_echo_msg_warning_str| - Defaults to `'Warning'` + + `%code%` is replaced with the error code, and replaced with an empty string + when there is no error code. Any extra characters between the percent signs + will be printed when an error code is present. For example, a message like + `(error code): message` will be printed for `'%(code): %%s'` and simply the + message will be printed when there is no code. + + |g:ale_echo_cursor| needs to be set to 1 for messages to be displayed. + + The echo message format can also be configured separately for each buffer, + so different formats can be used for different languages. (Say in ftplugin + files.) + + *ale-options.echo_msg_info_str* + *g:ale_echo_msg_info_str* +echo_msg_info_str +g:ale_echo_msg_info_str + Type: |String| + Default: `'Info'` + + The string used for `%severity%` for info. See |g:ale_echo_msg_format| + + *ale-options.echo_msg_log_str* + *g:ale_echo_msg_log_str* +echo_msg_log_str +g:ale_echo_msg_log_str + Type: |String| + Default: `'Log'` + + The string used for `%severity%` for log, used only for handling LSP show + message requests. See |g:ale_lsp_show_message_format| + + *ale-options.echo_msg_warning_str* + *g:ale_echo_msg_warning_str* +echo_msg_warning_str +g:ale_echo_msg_warning_str + Type: |String| + Default: `'Warning'` + + The string used for `%severity%` for warnings. See |g:ale_echo_msg_format| + + *ale-options.enabled* + *g:ale_enabled* + *b:ale_enabled* +enabled +g:ale_enabled + Type: |Number| + Default: `1` + + When set to `0`, this option will completely disable ALE, such that no + error checking will be performed, etc. ALE can be toggled on and off with + the `:ALEToggle` command, which changes this option. + + ALE can be disabled in each buffer by setting `let b:ale_enabled = 0` + Disabling ALE based on filename patterns can be accomplished by setting + a regular expression for |g:ale_pattern_options|. For example: > + + " Disable linting for all minified JS files. + let g:ale_pattern_options = {'\.min.js$': {'ale_enabled': 0}} +< + See |g:ale_pattern_options| for more information on that option. + + *ale-options.exclude_highlights* + *g:ale_exclude_highlights* + *b:ale_exclude_highlights* +exclude_highlights +g:ale_exclude_highlights + Type: |List| + Default: `[]` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + A list of regular expressions for matching against highlight messages to + remove. For example: > + + " Do not highlight messages matching strings like these. + let b:ale_exclude_highlights = ['line too long', 'foo.*bar'] +< + See also: |g:ale_set_highlights| + + *ale-options.fixers* + *g:ale_fixers* + *b:ale_fixers* +fixers +g:ale_fixers + Type: |Dictionary| + Default: `{}` + + A mapping from filetypes to |List| values for functions for fixing errors. + See |ale-fix| for more information. + + This variable can be overridden with variables in each buffer. + `b:ale_fixers` can be set to a |List| of callbacks instead, which can be + more convenient. + + A special `'*'` key be used as a wildcard filetype for configuring fixers + for every other type of file. For example: > + + " Fix Python files with 'bar'. + " Don't fix 'html' files. + " Fix everything else with 'foo'. + let g:ale_fixers = {'python': ['bar'], 'html': [], '*': ['foo']} +< + *ale-options.fix_on_save* + *g:ale_fix_on_save* + *b:ale_fix_on_save* +fix_on_save +g:ale_fix_on_save + Type: |Number| + Default: `0` + + When set to 1, ALE will fix files when they are saved. + + If |g:ale_lint_on_save| is set to 1, files will be checked with linters + after files are fixed, only when the buffer is open, or re-opened. Changes + to the file will be saved to the file on disk. + + Files will not be fixed on `:wq`, so you should check your code before + closing a buffer. + + 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. + + *ale-options.fix_on_save_ignore* + *g:ale_fix_on_save_ignore* + *b:ale_fix_on_save_ignore* +fix_on_save_ignore +g: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] +< + *ale-options.floating_preview* + *g:ale_floating_preview* +floating_preview +g:ale_floating_preview + Type: |Number| + Default: `0` + + When set to `1`, Neovim or Vim with |popupwin| will use a floating window + for ale's preview window. + This is equivalent to setting |g:ale_hover_to_floating_preview| and + |g:ale_detail_to_floating_preview| to `1`. + + *ale-options.floating_preview_popup_opts* + *g:ale_floating_preview_popup_opts* +floating_preview_popup_opts +g:ale_floating_preview_popup_opts + Type: |String| or |Dictionary| + Default: `''` + + Either a dictionary of options or the string name of a function that returns + a dictionary of options. This will be used as an argument to |popup_create| + for Vim users or |nvim_open_win| for NeoVim users. In either case, the + resulting dictionary is merged with ALE defaults rather than explicitly + overriding them. This only takes effect if |g:ale_floating_preview| is + enabled. + + NOTE: for Vim users see |popup_create-arguments|, for NeoVim users see + |nvim_open_win| for argument details + + For example, to enhance popups with a title: > + + function! CustomOpts() abort + let [l:info, l:loc] = ale#util#FindItemAtCursor(bufnr('')) + return {'title': ' ALE: ' . (l:loc.linter_name) . ' '} + endfunction + + let g:ale_floating_preview_popup_opts = 'g:CustomOpts' +< + *ale-options.floating_window_border* + *g:ale_floating_window_border* +floating_window_border +g:ale_floating_window_border + Type: |List| + Default: `['|', '-', '+', '+', '+', '+', '|', '-']` + + When set to `[]`, window borders are disabled. The elements in the list set + the characters for the left side, top, top-left corner, top-right + corner, bottom-right corner, bottom-left corner, right side, and bottom of + the floating window, respectively. + + If the terminal supports Unicode, you might try setting the value to + ` ['│', '─', '╭', '╮', '╯', '╰', '│', '─']`, to make it look nicer. + + NOTE: For compatibility with previous versions, if the list does not have + elements for the right side and bottom, the left side and top will be used + instead. + + *ale-options.history_enabled* + *g:ale_history_enabled* +history_enabled +g:ale_history_enabled + Type: |Number| + Default: `1` + + When set to `1`, ALE will remember the last few commands which were run + for every buffer which is open. This information can be viewed with the + `:ALEInfo` command. The size of the buffer can be controlled with the + |g:ale_max_buffer_history_size| option. + + This option can be disabled if storing a command history is not desired. + + *ale-options.history_log_output* + *g:ale_history_log_output* +history_log_output +g:ale_history_log_output + Type: |Number| + Default: `1` + + When set to `1`, ALE will store the output of commands which have completed + successfully in the command history, and the output will be displayed when + using `:ALEInfo`. + + |g:ale_history_enabled| must be set to `1` for this output to be stored or + printed. + + Some memory will be consumed by this option. It is very useful for figuring + out what went wrong with linters, and for bug reports. Turn this option off + if you want to save on some memory usage. + + *ale-options.hover_cursor* + *g:ale_hover_cursor* +hover_cursor +g:ale_hover_cursor + Type: |Number| + Default: `1` + + If set to `1`, ALE will show truncated information in the echo line about + the symbol at the cursor automatically when the |CursorHold| event is fired. + The delay before requesting hover information is based on 'updatetime', as + with all |CursorHold| events. + + If there's a problem on the line where the cursor is resting, ALE will not + show any hover information. + + See |ale-hover| for more information on hover information. + + This setting must be set to `1` before ALE is loaded for this behavior + to be enabled. See |ale-lint-settings-on-startup|. + + *ale-options.hover_to_preview* + *g:ale_hover_to_preview* + *b:ale_hover_to_preview* +hover_to_preview +g:ale_hover_to_preview + Type: |Number| + Default: `0` + + If set to `1`, hover messages will be displayed in the preview window, + instead of in balloons or the message line. + + *ale-options.hover_to_floating_preview* + *g:ale_hover_to_floating_preview* + *b:ale_hover_to_floating_preview* +hover_to_floating_preview +g:ale_hover_to_floating_preview + Type: |Number| + Default: `0` + + If set to `1`, Neovim or Vim with |popupwin| will use floating windows for + hover messages. + + *ale-options.info_default_mode* + *g:ale_info_default_mode* + *b:ale_info_default_mode* +info_default_mode +g:ale_info_default_mode + Type: |String| + Default: `'preview'` + + Changes the default mode used for `:ALEInfo`. See documentation for `:ALEInfo` + for more information. + + *ale-options.keep_list_window_open* + *g:ale_keep_list_window_open* + *b:ale_keep_list_window_open* +keep_list_window_open +g:ale_keep_list_window_open + Type: |Number| + Default: `0` + + When set to `1`, this option will keep the loclist or quickfix windows event + after all warnings/errors have been removed for files. By default the + loclist or quickfix windows will be closed automatically when there are no + warnings or errors. + + See |g:ale_open_list| + + *ale-options.list_window_size* + *g:ale_list_window_size* + *b:ale_list_window_size* +list_window_size +g:ale_list_window_size + Type: |Number| + Default: `10` + + This number configures the number of lines to set for the height of windows + opened automatically for ALE problems. The default of `10` matches the Vim + default height. + + See |g:ale_open_list| for information on automatically opening windows + for quickfix or the loclist. + + *ale-options.lint_delay* + *g:ale_lint_delay* + *b:ale_lint_delay* +lint_delay +g:ale_lint_delay + Type: |Number| + Default: `200` + + This variable controls the milliseconds delay after which the linters will + be run after text is changed. This option is only meaningful with the + |g:ale_lint_on_text_changed| variable set to `always`, `insert`, or `normal`. + + A buffer-local option, `b:ale_lint_delay`, can be set to change the delay + for different buffers, such as in |ftplugin| files. + + *ale-options.lint_on_enter* + *g:ale_lint_on_enter* +lint_on_enter +g:ale_lint_on_enter + Type: |Number| + Default: `1` + + When this option is set to `1`, the |BufWinEnter| event will be used to + apply linters when buffers are first opened. If this is not desired, this + variable can be set to `0` in your vimrc file to disable this behavior. + + The |FileChangedShellPost| and |BufEnter| events will be used to check if + files have been changed outside of Vim. If a file is changed outside of + Vim, it will be checked when it is next opened. + + You should set this setting once before ALE is loaded, and restart Vim if + you want to change your preferences. See |ale-lint-settings-on-startup|. + + *ale-options.lint_on_filetype_changed* + *g:ale_lint_on_filetype_changed* +lint_on_filetype_changed +g:ale_lint_on_filetype_changed + Type: |Number| + Default: `1` + + This option will cause ALE to run when the filetype for a file is changed + after a buffer has first been loaded. A short delay will be used before + linting will be done, so the filetype can be changed quickly several times + in a row, but resulting in only one lint cycle. + + You should set this setting once before ALE is loaded, and restart Vim if + you want to change your preferences. See |ale-lint-settings-on-startup|. + + *ale-options.lint_on_save* + *g:ale_lint_on_save* +lint_on_save +g:ale_lint_on_save + Type: |Number| + Default: `1` + + This option will make ALE run the linters whenever a file is saved when it + it set to `1` in your vimrc file. This option can be used in combination + with the |g:ale_lint_on_enter| and |g:ale_lint_on_text_changed| options to + make ALE only check files after that have been saved, if that is what is + desired. + + *ale-options.lint_on_text_changed* + *g:ale_lint_on_text_changed* +lint_on_text_changed +g:ale_lint_on_text_changed + Type: |Boolean| or |Number| or |String| + Default: `'normal'` + + This option controls how ALE will check your files as you make changes. + The following values can be used. + + `'always'`, `'1'`, `true`, or `1` - Check buffers on |TextChanged| or |TextChangedI|. + `'normal'` - Check buffers only on |TextChanged|. + `'insert'` - Check buffers only on |TextChangedI|. + `'never'`, `'0'`, `false`, or `0` - Never check buffers on changes. + + ALE will check buffers after a short delay, with a timer which resets on + each change. The delay can be configured by adjusting the |g:ale_lint_delay| + variable. + *ale-linting-interrupts-mapping* + + Due to a bug in Vim, ALE can interrupt mappings with pending key presses, + per |timeoutlen|. If this happens, follow the advice for enabling + |g:ale_lint_on_insert_leave| below, and set this option to `'normal'`, or + disable it entirely. + + You should set this setting once before ALE is loaded, and restart Vim if + you want to change your preferences. See |ale-lint-settings-on-startup|. + + *ale-options.lint_on_insert_leave* + *g:ale_lint_on_insert_leave* + *b:ale_lint_on_insert_leave* +lint_on_insert_leave +g:ale_lint_on_insert_leave + Type: |Boolean| or |Number| + Default: `true` + + When set to `1` in your vimrc file, this option will cause ALE to run + linters when you leave insert mode. + + ALE will not lint files when you escape insert mode with |CTRL-C| by + default. You can make ALE lint files with this option when you use |CTRL-C| + with the following mapping. > + + " Make using Ctrl+C do the same as Escape, to trigger autocmd commands + inoremap +< + A buffer-local version of this setting `b:ale_lint_on_insert_leave` can be + set to `0` to disable linting when leaving insert mode. The setting must + be enabled globally to be enabled locally. + + You should set this setting once before ALE is loaded, and restart Vim if + you want to change your preferences. See |ale-lint-settings-on-startup|. + + *ale-options.linter_aliases* + *g:ale_linter_aliases* + *b:ale_linter_aliases* +linter_aliases +g:ale_linter_aliases + Type: |Dictionary| or |List| or |String| + Default: `{}` + + The |g:ale_linter_aliases| option can be used to set aliases from one + filetype to another. A given filetype can be mapped to use the linters + run for another given filetype. + + This |Dictionary| will be merged with a default dictionary containing the + following values: > + + { + \ 'Dockerfile': 'dockerfile', + \ 'csh': 'sh', + \ 'javascriptreact': ['javascript', 'jsx'], + \ 'plaintex': 'tex', + \ 'ps1': 'powershell', + \ 'rmarkdown': 'r', + \ 'rmd': 'r', + \ 'systemverilog': 'verilog', + \ 'typescriptreact': ['typescript', 'tsx'], + \ 'vader': ['vim', 'vader'], + \ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'], + \ 'vimwiki': 'markdown', + \ 'vue': ['vue', 'javascript'], + \ 'xsd': ['xsd', 'xml'], + \ 'xslt': ['xslt', 'xml'], + \ 'zsh': 'sh', + \} +< + For example, if you wish to map a new filetype `'foobar'` to run the `'php'` + linters, you could set the following: > + + let g:ale_linter_aliases = {'foobar': 'php'} +< + Or in Lua: > + + require("ale").setup({linter_aliases = {foobar = "php"}}) +< + When combined with the |g:ale_linters| option, the original filetype + (`'foobar'`) will be used for determining which linters to run, + not the aliased type (`'php'`). This allows an aliased type to run a + different set of linters from the type it is being mapped to. + + Passing a list of filetypes is also supported. Say you want to lint + javascript and css embedded in HTML (using linters that support that). + You could alias `html` like so: > + + `let g:ale_linter_aliases = {'html': ['html', 'javascript', 'css']}` +< + Or in Lua: > + + require("ale").setup({linter_aliases = {html = {"html", "javascript", "css"}}) +< + Note that `html` itself was included as an alias. That is because aliases + will override the original linters for the aliased filetype. + + Linter aliases can be configured in each buffer with buffer-local variables. + ALE will first look for aliases for filetypes in the `b:ale_linter_aliases` + variable, then `g:ale_linter_aliases`, and then a default Dictionary. + + `b:ale_linter_aliases` can be set to a |List| or a |String|, to tell ALE to + load the linters for specific filetypes for a given buffer. > + + let b:ale_linter_aliases = ['html', 'javascript', 'css'] + " OR, Alias a filetype to only a single filetype with a String. + let b:ale_linter_aliases = 'javascript' +< + Or in Lua: > + + require("ale").setup.buffer({linter_aliases = {"html", "javascript", "css"}}) + -- OR, Alias a filetype to only a single filetype with a String. + require("ale").setup.buffer({linter_aliases = "javascript"}) +< + No linters will be loaded when the buffer's filetype is empty. + + *ale-options.filename_mappings* + *g:ale_filename_mappings* + *b:ale_filename_mappings* +filename_mappings +g:ale_filename_mappings + Type: |Dictionary| or |List| + Default: `{}` + + Either a |Dictionary| mapping a linter or fixer name, as displayed in + `:ALEInfo`, to a |List| of two-item |List|s for filename mappings, or just a + |List| of two-item |List|s. When given some paths to files, the value of + this setting will be used to convert filenames on a local file system to + filenames on some remote file system, such as paths in a Docker image, + virtual machine, or network drive. + + For example: > + + let g:ale_filename_mappings = { + \ 'pylint': [ + \ ['/home/john/proj', '/data'], + \ ], + \} +< + Or in Lua: > + + require("ale").setup({ + filename_mappings = { + pylint = { + {"/home/john/proj", "/data"}, + }, + }, + }) +< + With the above configuration, a filename such as `/home/john/proj/foo.py` + will be provided to the linter/fixer as `/data/foo.py`, and paths parsed + from linter results such as `/data/foo.py` will be converted back to + `/home/john/proj/foo.py`. + + You can use `*` as to apply a |List| of filename mappings to all other + linters or fixers not otherwise matched. > + + " Use one List of paths for pylint. + " Use another List of paths for everything else. + let g:ale_filename_mappings = { + \ 'pylint': [ + \ ['/home/john/proj', '/data'], + \ ], + \ '*': [ + \ ['/home/john/proj', '/other-data'], + \ ], + \} +< + If you just want every single linter or fixer to use the same filename + mapping, you can just use a |List|. > + + " Same as above, but for ALL linters and fixers. + let g:ale_filename_mappings = [ + \ ['/home/john/proj', '/data'], + \] +< + Or in Lua: > + + require("ale").setup({ + filename_mappings = { + {"/home/john/proj", "/data"}, + }, + }) +< + You can provide many such filename paths for multiple projects. Paths are + matched by checking if the start of a file path matches the given strings, + in a case-sensitive manner. Earlier entries in the |List| will be tried + before later entries when mapping to a given file system. + + Buffer-local options can be set to the same values to override the global + options, such as in |ftplugin| files. + + NOTE: Only fixers registered with a short name can support filename mapping + by their fixer names. See |ale-fix|. Filename mappings set for all tools by + using only a |List| for the setting will also be applied to fixers not in + the registry. + + NOTE: In order for this filename mapping to work correctly, linters and + fixers must exclusively determine paths to files to lint or fix via ALE + command formatting as per |ale-command-format-strings|, and paths parsed + from linter files must be provided in `filename` keys if a linter returns + results for more than one file at a time, as per |ale-loclist-format|. If + you discover a linter or fixer which does not behave properly, please report + it as an issue. + + If you are running a linter or fixer through Docker or another remote file + system, you may have to mount your temporary directory, which you can + discover with the following command: > + + :echo fnamemodify(tempname(), ':h:h') +< + You should provide a mapping from this temporary directory to whatever you + mount this directory to in Docker, or whatever remote file system you are + working with. + + You can inspect the filename mappings ALE will use with the + |ale#GetFilenameMappings()| function. + + *ale-options.linters* + *g:ale_linters* + *b:ale_linters* +linters +g:ale_linters + Type: |Dictionary| or |List| + Default: `{}` + + The |g:ale_linters| option sets a |Dictionary| mapping a filetype to a + |List| of linter programs to be run when checking particular filetypes. + + This |Dictionary| will be merged with a default dictionary containing the + following values: > + + { + \ 'apkbuild': ['apkbuild_lint', 'secfixes_check'], + \ 'csh': ['shell'], + \ 'elixir': ['credo', 'dialyxir', 'dogma'], + \ 'go': ['gofmt', 'golangci-lint', 'gopls', 'govet'], + \ 'groovy': ['npm-groovy-lint'], + \ 'hack': ['hack'], + \ 'help': [], + \ 'inko': ['inko'], + \ 'json': ['jsonlint', 'spectral'], + \ 'json': ['jsonlint', 'spectral', 'vscodejson'], + \ 'json5': [], + \ 'jsonc': [], + \ 'perl': ['perlcritic'], + \ 'perl6': [], + \ 'python': ['flake8', 'mypy', 'pylint', 'pyright', 'ruff'], + \ 'rust': ['analyzer', 'cargo'], + \ 'spec': [], + \ 'text': [], + \ 'vader': ['vimls'], + \ 'vue': ['eslint', 'vls'], + \ 'zsh': ['shell'], + \ 'v': ['v'], + \ 'yaml': ['actionlint', 'spectral', 'yaml-language-server', 'yamllint'], + \} +< + This option can be used to enable only a particular set of linters for a + file. For example, you can enable only `eslint` for JavaScript files: > + + let g:ale_linters = {'javascript': ['eslint']} +< + Or in Lua: > + + require("ale").setup({linters = {javascript = {"eslint"}}}) +< + If you want to disable all linters for a particular filetype, you can pass + an empty list of linters as the value: > + + let g:ale_linters = {'javascript': []} +< + Or in Lua: > + + require("ale").setup({linters = {javascript = {}}}) +< + All linters will be run for unspecified filetypes. All available linters can + be enabled explicitly for a given filetype by passing the string `'all'`, + instead of a List. > + + let g:ale_linters = {'c': 'all'} +< + Or in Lua: > + + require("ale").setup({linters = {c = "all"}}) +< + Linters can be configured in each buffer with buffer-local variables. ALE + will first look for linters for filetypes in the `b:ale_linters` variable, + then `g:ale_linters`, and then the default Dictionary mentioned above. + + `b:ale_linters` can be set to a List, or the string `'all'`. When linters + for two different filetypes share the same name, the first linter loaded + will be used. Any ambiguity can be resolved by using a Dictionary specifying + which linter to run for which filetype instead. > + + " Use ESLint for the buffer if the filetype includes 'javascript'. + let b:ale_linters = {'javascript': ['eslint'], 'html': ['tidy']} + " Use a List for the same setting. This will work in most cases. + let b:ale_linters = ['eslint', 'tidy'] + " Disable all linters for the buffer. + let b:ale_linters = [] + " Explicitly enable all available linters for the filetype. + let b:ale_linters = 'all' +< + In Lua: > + + require("ale").setup.buffer({ + linters = {javascript = {"eslint"}, html = {"tidy"}}, + }) + require("ale").setup.buffer({linters = {"eslint", "tidy"}}) + require("ale").setup.buffer({linters = {}}) + require("ale").setup.buffer({linters = "all"}) +< + ALE can be configured to disable all linters unless otherwise specified with + `g:ale_enabled` or `b:ale_enabled` with the option |g:ale_linters_explicit|. + + *ale-options.linters_explicit* + *g:ale_linters_explicit* +linters_explicit +g:ale_linters_explicit + Type: |Boolean| or |Number| + Default: `false` + + When set to `true` or `1`, only the linters from |g:ale_linters| and + |b:ale_linters| will be enabled. The default behavior for ALE is to enable + as many linters as possible, unless otherwise specified. + + *ale-options.linters_ignore* + *g:ale_linters_ignore* + *b:ale_linters_ignore* +linters_ignore +g:ale_linters_ignore + Type: |Dictionary| or |List| + Default: `{}` + + Linters to ignore. Commands for ignored linters will not be run, and + diagnostics for LSP linters will be ignored. (See |ale-lsp|) + + This setting can be set to a |Dictionary| mapping filetypes to linter names, + just like |g:ale_linters|, to list linters to ignore. Ignore lists will be + applied after everything else. > + + " Select flake8 and pylint, and ignore pylint, so only flake8 is run. + let g:ale_linters = {'python': ['flake8', 'pylint']} + let g:ale_linters_ignore = {'python': ['pylint']} +< + Or in Lua: > + + require("ale").setup({ + linters = {"python": {"flake8", "pylint"}}, + linters_ignore = {"python": {"pylint"}}, + }) +< + This setting can be set to simply a |List| of linter names, which is + especially more convenient when using the setting in ftplugin files for + particular buffers. > + + " The same as above, in a ftplugin/python.vim. + let b:ale_linters = ['flake8', 'pylint'] + let b:ale_linters_ignore = ['pylint'] +< + Or in Lua: > + + require("ale").setup.buffer({ + linters = {"flake8", "pylint"}, + linters_ignore = {"pylint"}, + }) +< + *ale-options.list_vertical* + *g:ale_list_vertical* + *b:ale_list_vertical* +list_vertical +g:ale_list_vertical + Type: |Boolean| or |Number| + Default: `false` + + When set to `true` or `1`, this will cause ALE to open any windows (loclist + or quickfix) vertically instead of horizontally (|vert| |lopen|) or (|vert| + |copen|) + + *ale-options.loclist_msg_format* + *g:ale_loclist_msg_format* + *b:ale_loclist_msg_format* +loclist_msg_format +g:ale_loclist_msg_format + Type: |String| + Default: `g:ale_echo_msg_format` (`echo_msg_format`) + + This option is the same as |g:ale_echo_msg_format|, but for formatting the + message used for the loclist and the quickfix list. + + The strings for configuring `%severity%` are also used for this option. + + *ale-options.lsp_show_message_format* + *g:ale_lsp_show_message_format* +lsp_show_message_format +g:ale_lsp_show_message_format + Type: |String| + Default: `'%severity%:%linter%: %s'` + + This variable defines the format that messages received from an LSP will + have when echoed. The following sequences of characters will be replaced. + + `%s` - replaced with the message text + `%linter%` - replaced with the name of the linter + `%severity%` - replaced with the severity of the message + + The strings for `%severity%` levels "error", "info" and "warning" are shared + with |g:ale_echo_msg_format|. Severity "log" is unique to + |g:ale_lsp_show_message_format| and it can be configured via + + |g:ale_echo_msg_log_str| - Defaults to `'Log'` + + Please note that |g:ale_lsp_show_message_format| *can not* be configured + separately for each buffer like |g:ale_echo_msg_format| can. + + *ale-options.lsp_show_message_severity* + *g:ale_lsp_show_message_severity* +lsp_show_message_severity +g:ale_lsp_show_message_severity + Type: |String| + Default: `'error'` + + This variable defines the minimum severity level an LSP message needs to be + displayed. Messages below this level are discarded; please note that + messages with `Log` severity level are always discarded. + + Possible values follow the LSP spec `MessageType` definition: + + `'error'` - Displays only errors. + `'warning'` - Displays errors and warnings. + `'information'` - Displays errors, warnings and infos + `'log'` - Same as `'information'` + `'disabled'` - Doesn't display any information at all. + + *ale-options.lsp_suggestions* + *g:ale_lsp_suggestions* +lsp_suggestions +g:ale_lsp_suggestions + Type: |Boolean| or |Number| + Default: `false` + + If set to `true` or ``1`, show suggestions from LSP servers or tsserver, in + addition to warnings and errors. + + *ale-options.max_buffer_history_size* + *g:ale_max_buffer_history_size* +max_buffer_history_size +g:ale_max_buffer_history_size + Type: |Number| + Default: `20` + + This setting controls the maximum number of commands which will be stored in + the command history used for `:ALEInfo`. Command history will be rotated in + a FIFO manner. If set to a number <= 0, then the history will be + continuously set to an empty |List|. + + History can be disabled completely with |g:ale_history_enabled|. + + *ale-options.max_signs* + *g:ale_max_signs* + *b:ale_max_signs* +max_signs +g:ale_max_signs + Type: |Number| + Default: `-1` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + When set to any positive integer, ALE will not render any more than the + given number of signs for any one buffer. + + When set to `0`, no signs will be set, but sign processing will still be + done, so existing signs can be removed. + + When set to any other value, no limit will be imposed on the number of signs + set. + + For disabling sign processing, see |g:ale_set_signs|. + + *ale-options.maximum_file_size* + *g:ale_maximum_file_size* + *b:ale_maximum_file_size* +maximum_file_size +g:ale_maximum_file_size + Type: |Number| + Default: `nil` + + A maximum file size in bytes for ALE to check. If set to any positive + number, ALE will skip checking files larger than the given size. + + *ale-options.open_list* + *g:ale_open_list* + *b:ale_open_list* +open_list +g:ale_open_list + Type: |Boolean| |Number| or |String| + Default: `false` + + When set to `true` or `1`, this will cause ALE to automatically open a + window for the loclist (|lopen|) or for the quickfix list instead if + |g:ale_set_quickfix| is `true` or `1`. (|copen|) + + When set to any higher numerical value, ALE will only open the window when + the number of warnings or errors are at least that many. + + When set to `'on_save'`, ALE will only open the loclist after buffers have + been saved. The list will be opened some time after buffers are saved and + any linter for a buffer returns results. + + The window will be kept open until all warnings or errors are cleared, + including those not set by ALE, unless |g:ale_keep_list_window_open| is set + to `true` or `1`, in which case the window will be kept open when no + problems are found. + + The window size can be configured with |g:ale_list_window_size|. + + Windows can be opened vertically with |g:ale_list_vertical|. + + If you want to close the loclist window automatically when the buffer is + closed, you can set up the following |autocmd| command: > + + augroup CloseLoclistWindowGroup + autocmd! + autocmd QuitPre * if empty(&buftype) | lclose | endif + augroup END +< + *g:ale_pattern_options* +g:ale_pattern_options + Type: |Dictionary| + Default: `nil` + + NOTE: This option is not available through |ale.setup| in Lua as the options + named here would require separate translation to the equivalent Vim options. + You should instead use conditions in ftplugin files to configure options + based on filename patterns. + + This option maps regular expression patterns to |Dictionary| values for + buffer variables. This option can be set to automatically configure + different settings for different files. For example: > + + " Use just ESLint for linting and fixing files which end in '.foo.js' + let g:ale_pattern_options = { + \ '\.foo\.js$': { + \ 'ale_linters': ['eslint'], + \ 'ale_fixers': ['eslint'], + \ }, + \} +< + See |b:ale_linters| and |b:ale_fixers| for information for those options. + + Filenames are matched with |match()|, and patterns depend on the |magic| + setting, unless prefixed with the special escape sequences like `'\v'`, etc. + The patterns can match any part of a filename. The absolute path of the + filename will be used for matching, taken from `expand('%:p')`. + + The options for every match for the filename will be applied, with the + pattern keys sorted in alphabetical order. Options for `'zebra'` will + override the options for `'alpha'` for a filename `alpha-zebra`. + + *ale-options.pattern_options_enabled* + *g:ale_pattern_options_enabled* +pattern_options_enabled +g:ale_pattern_options_enabled + Type: |Boolean| or |Number| + Default: `nil` + + This option can be used for disabling pattern options. If set to `0`, ALE + will not set buffer variables per |g:ale_pattern_options|. + + *ale-options.popup_menu_enabled* + *g:ale_popup_menu_enabled* +popup_menu_enabled +g:ale_popup_menu_enabled + Type: |Boolean| or |Number| + Default: `has('gui_running')` + + When this option is set to `true` or `1`, ALE will show code actions and + rename capabilities in the right click mouse menu when there's a LSP server + or tsserver available. See |ale-refactor|. + + This feature is only supported in GUI versions of Vim. + + This setting must be set to `true` or `1` before ALE is loaded for this + behavior to be enabled. See |ale-lint-settings-on-startup|. + + *ale-options.rename_tsserver_find_in_comments* + *g:ale_rename_tsserver_find_in_comments* +rename_tsserver_find_in_comments +g:ale_rename_tsserver_find_in_comments + Type: |Boolean| or |Number| + Default: `false` + + If set to `true` or `1`, this option will tell tsserver to find and replace + text in comments when calling `:ALERename`. + + *ale-options.rename_tsserver_find_in_strings* + *g:ale_rename_tsserver_find_in_strings* +rename_tsserver_find_in_strings +g:ale_rename_tsserver_find_in_strings + Type: |Boolean| or |Number| + Default: `false` + + If set to `true` or `1`, this option will tell tsserver to find and replace + text in strings when calling `:ALERename`. + + *ale-options.root* + *g:ale_root* + *b:ale_root* +root +g:ale_root + Type: |Dictionary| or |String| + Default: `{}` + + This option is used to determine the project root for a 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, and the linter is an + LSP linter, it will not run. + + *ale-options.save_hidden* + *g:ale_save_hidden* +save_hidden +g:ale_save_hidden + Type: |Boolean| or |Number| + Default: `false` + + When set to `true` or `1`, save buffers when 'hidden' is set when applying + code actions or rename operations, such as through `:ALERename` or + `:ALEOrganizeImports`. + + *ale-options.set_balloons* + *g:ale_set_balloons* + *b:ale_set_balloons* +set_balloons +g:ale_set_balloons + Type: |Boolean| or |Number| or |String| + Default: `has('balloon_eval') && has('gui_running')` + + When this option is set to `true` or `1`, balloon messages will be displayed + for problems or hover information if available. + + Problems nearest to the line the mouse cursor is over will be displayed. If + there are no problems to show, and one of the linters is an LSP linter + supporting "Hover" information, per |ale-hover|, then brief information + about the symbol under the cursor will be displayed in a balloon. + + This option can be set to `'hover'` to only enable balloons for hover + message, so diagnostics are never shown in balloons. You may wish to + configure use this setting only in GUI Vim like so: > + + let g:ale_set_balloons = has('gui_running') ? 'hover' : 0 +< + Balloons can be enabled for terminal versions of Vim that support balloons, + but some versions of Vim will produce strange mouse behavior when balloons + are enabled. To configure balloons for your terminal, you should first + configure your |ttymouse| setting, and then consider setting + `g:ale_set_balloons` to `1` before ALE is loaded. + + `b:ale_set_balloons` can be set to `0` to disable balloons for a buffer. + Balloons cannot be enabled for a specific buffer when not initially enabled + globally. + + Balloons will not be shown when |g:ale_enabled| or |b:ale_enabled| is not + `true` or `1`. + + *ale-options.set_balloons_legacy_echo* + *g:ale_set_balloons_legacy_echo* + *b:ale_set_balloons_legacy_echo* +set_balloons_legacy_echo +g:ale_set_balloons_legacy_echo + Type: |Boolean| or |Number| + Default: `nil` + + If set to `true` or `1`, moving your mouse over documents in Vim will make + ALE ask `tsserver` or `LSP` servers for information about the symbol where + the mouse cursor is, and print that information into Vim's echo line. This + is an option for supporting older versions of Vim which do not properly + support balloons in an asynchronous manner. + + If your version of Vim supports the |balloon_show| function, then this + option does nothing meaningful. + + *ale-options.set_highlights* + *g:ale_set_highlights* +set_highlights +g:ale_set_highlights + Type: |Boolean| or |Number| + Default: `has('syntax')` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + ALE's highlight groups will not be used when setting highlights through + Neovim's diagnostics API. See |diagnostic-highlights| for how to configure + Neovim diagnostic highlighting. + + When this option is set to `true` or `1`, highlights will be presented. + + ALE will use the following highlight groups for problems: + + ALEError items with `'type': 'E'` |hl-ALEError| + ALEWarning items with `'type': 'W'` |hl-ALEWarning| + ALEInfo items with `'type': 'I'` |hl-ALEInfo| + ALEStyleError items with `'type': 'E'` and + `'sub_type': 'style'` |hl-ALEStyleError| + ALEStyleWarning items with `'type': 'W'` and + `'sub_type': 'style'` |hl-ALEStyleWarning| + + When |g:ale_set_signs| is not set to `true` or `1`, the following highlights + for entire lines will be set. + + ALEErrorLine all items with `'type': 'E'` |hl-ALEErrorLine| + ALEWarningLine all items with `'type': 'W'` |hl-ALEWarningLine| + ALEInfoLine all items with `'type': 'I'` |hl-ALEInfoLine| + + Vim can only highlight the characters up to the last column in a buffer for + match highlights, whereas the line highlights when signs are enabled will + run to the edge of the screen. + + Highlights can be excluded with the |g:ale_exclude_highlights| option. + + *ale-options.set_loclist* + *g:ale_set_loclist* +set_loclist +g:ale_set_loclist + Type: |Boolean| or |Number| + Default: `true` + + When this option is set to `true` or `1`, the |location-list| will be + populated with any warnings and errors which are found by ALE. This feature + can be used to implement jumping between errors through typical use of + `:lnext` and `:lprev`. + + *ale-options.set_quickfix* + *g:ale_set_quickfix* +set_quickfix +g:ale_set_quickfix + Type: |Boolean| or |Number| + Default: `false` + + When this option is set to `true` or `1`, the |quickfix| list will be + populated with any problems which are found by ALE, instead of the + |location-list|. The loclist will never be populated when this option is + enabled. + + Problems from every buffer ALE has checked will be included in the quickfix + list, which can be checked with `:copen`. Problems will be de-duplicated. + + This feature should not be used in combination with tools for searching for + matches and commands like `:cfdo`, as ALE will replace the quickfix list + pretty frequently. If you wish to use such tools, you should populate the + loclist or use `:ALEPopulateQuickfix` instead. + + *ale-options.set_signs* + *g:ale_set_signs* +set_signs +g:ale_set_signs + Type: |Boolean| or |Number| + Default: `has('signs')` + + When this option is set to `true` or `1`, the |sign| column will be + populated with signs marking where problems appear in the file. + + When |g:ale_use_neovim_diagnostics_api| is `1`, the only other setting that + will be respected for signs is |g:ale_sign_priority|. ALE's highlight groups + will and other sign settings will not apply when setting signs through + Neovim's diagnostics API. See |diagnostic-signs| for how to configure signs + in Neovim. + + ALE will use the following highlight groups for problems: + + ALEErrorSign items with `'type': 'E'` |hl-ALEErrorSign| + ALEWarningSign items with `'type': 'W'` |hl-ALEWarningSign| + ALEInfoSign items with `'type': 'I'` |hl-ALEInfoSign| + ALEStyleErrorSign items with `'type': 'E'` and + `'sub_type': 'style'` |hl-ALEStyleErrorSign| + ALEStyleWarningSign items with `'type': 'W'` and + `'sub_type': 'style'` |hl-ALEStyleWarningSign| + + In addition to the style of the signs, the style of lines where signs appear + can be configured with the following highlights: + + ALEErrorLine all items with `'type': 'E'` |hl-ALEErrorLine| + ALEWarningLine all items with `'type': 'W'` |hl-ALEWarningLine| + ALEInfoLine all items with `'type': 'I'` |hl-ALEInfoLine| + + With Neovim 0.3.2 or higher, ALE can use the `numhl` option to highlight the + 'number' column. It uses the following highlight groups. + + ALEErrorSignLineNr items with `'type': 'E'` |hl-ALEErrorSignLineNr| + ALEWarningSignLineNr items with `'type': 'W'` |hl-ALEWarningSignLineNr| + ALEInfoSignLineNr items with `'type': 'I'` |hl-ALEInfoSignLineNr| + ALEStyleErrorSignLineNr items with `'type': 'E'` and + `'sub_type': 'style'` |hl-ALEStyleErrorSignLineNr| + ALEStyleWarningSignLineNr items with `'type': 'W'` and + `'sub_type': 'style'` |hl-ALEStyleWarningSignLineNr| + + To enable line number highlighting |g:ale_sign_highlight_linenrs| must be + set to `true` or `1` before ALE is loaded. + + The markers for the highlights can be customized with the following options: + + |g:ale_sign_error| + |g:ale_sign_warning| + |g:ale_sign_info| + |g:ale_sign_style_error| + |g:ale_sign_style_warning| + + When multiple problems exist on the same line, the signs will take + precedence in the order above, from highest to lowest. + + To limit the number of signs ALE will set, see |g:ale_max_signs|. + + *ale-options.sign_priority* + *g:ale_sign_priority* +sign_priority +g:ale_sign_priority + Type: |Number| + Default: `30` + + From Neovim 0.4.0 and Vim 8.1, ALE can set sign priority to all signs. The + larger this value is, the higher priority ALE signs have over other plugin + signs. See |sign-priority| for further details on how priority works. + + *ale-options.shell* + *g:ale_shell* + *b:ale_shell* +shell +g:ale_shell + Type: |String| + Default: `nil` + + 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. + + *ale-options.shell_arguments* + *g:ale_shell_arguments* + *b:ale_shell_arguments* +shell_arguments +g:ale_shell_arguments + Type: |String| + Default: `nil` + + 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. + + *ale-options.sign_column_always* + *g:ale_sign_column_always* +sign_column_always +g:ale_sign_column_always + Type: |Boolean| or |Number| + Default: `v:false` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + By default, the sign gutter will disappear when all warnings and errors have + been fixed for a file. When this option is set to `1`, the sign column will + remain open. This can be preferable if you don't want the text in your file + to move around as you edit a file. + + *ale-options.sign_error* + *g:ale_sign_error* +sign_error +g:ale_sign_error + Type: |String| + Default: `'E'` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + The sign for errors in the sign gutter. + + *ale-options.sign_info* + *g:ale_sign_info* +sign_info +g:ale_sign_info + Type: |String| + Default: `'I'` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + The sign for "info" markers in the sign gutter. + + *ale-options.sign_style_error* + *g:ale_sign_style_error* +sign_style_error +g:ale_sign_style_error + Type: |String| + Default: `g:ale_sign_error` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + The sign for style errors in the sign gutter. + + *ale-options.sign_style_warning* + *g:ale_sign_style_warning* +sign_style_warning +g:ale_sign_style_warning + Type: |String| + Default: `g:ale_sign_warning` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + The sign for style warnings in the sign gutter. + + *ale-options.sign_offset* + *g:ale_sign_offset* +sign_offset +g:ale_sign_offset + Type: |Number| + Default: `1000000` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + This variable controls offset from which numeric IDs will be generated for + new signs. Signs cannot share the same ID values, so when two Vim plugins + set signs at the same time, the IDs have to be configured such that they do + not conflict with one another. If the IDs used by ALE are found to conflict + with some other plugin, this offset value can be changed, and hopefully both + plugins will work together. See |sign-place| for more information on how + signs are set. + + *ale-options.sign_warning* + *g:ale_sign_warning* +sign_warning +g:ale_sign_warning + Type: |String| + Default: `'W'` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + The sign for warnings in the sign gutter. + + *ale-options.sign_highlight_linenrs* + *g:ale_sign_highlight_linenrs* +sign_highlight_linenrs +g:ale_sign_highlight_linenrs + Type: |Boolean| or |Number| + Default: `false` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + When set to `true` or `1`, this option enables highlighting problems on the + 'number' column in Vim versions that support `numhl` highlights. This option + must be configured before ALE is loaded. + + *ale-options.update_tagstack* + *g:ale_update_tagstack* + *b:ale_update_tagstack* +update_tagstack +g:ale_update_tagstack + Type: |Boolean| or |Number| + Default: `v:true` + + When set to `true` or `1`, ALE will update Vims |tagstack| automatically + when jumping to a location through ALE's commands, so users can jump back to + where they came from. + *ale-options.type_map* + *g:ale_type_map* + *b:ale_type_map* +type_map +g:ale_type_map + Type: |Dictionary| + Default: `{}` + + This option can be set re-map problem types for linters. Each key in the + |Dictionary| should be the name of a linter, and each value must be a + |Dictionary| mapping problem types from one type to another. The following + types are supported: + + `'E'` - `{'type': 'E'}` + `'ES'` - `{'type': 'E', 'sub_type': 'style'}` + `'W'` - `{'type': 'W'}` + `'WS'` - `{'type': 'W', 'sub_type': 'style'}` + `'I'` - `{'type': 'I'}` + + For example, if you want to turn flake8 errors into warnings, you can write + the following: > + + let g:ale_type_map = {'flake8': {'ES': 'WS', 'E': 'W'}} +< + Or in Lua: > + + require("ale").setup(type_map = {flake8 = {ES = "WS", E = "W"}}) +< + If you wanted to turn style errors and warnings into regular errors and + warnings, you can write the following: > + + let g:ale_type_map = {'flake8': {'ES': 'E', 'WS': 'W'}} +< + Or in Lua: > + + require("ale").setup(type_map = {flake8 = {ES = "E", WS = "W"}}) +< + Type maps can be set per-buffer with `b:ale_type_map`, or in Lua with + |ale.setup.buffer|. + + *ale-options.use_global_executables* + *g:ale_use_global_executables* +use_global_executables +g:ale_use_global_executables + Type: |Number| + Default: `nil` + + This option can be set to change the default for all `_use_global` options. + This option must be set before ALE is loaded, preferably in a vimrc file. + + See |ale-integrations-local-executables| for more information on those + options. + + *ale-options.use_neovim_diagnostics_api* + *g:ale_use_neovim_diagnostics_api* +use_neovim_diagnostics_api +g:ale_use_neovim_diagnostics_api + Type: |Boolean| or |Number| + Default: `has('nvim-0.7')` + + If set to `true` or `1`, disable ALE's standard UI, and instead send all + linter output to Neovim's diagnostics API. This allows you to collect + problems using ALE and other plugins together all in one place. Many + options for configuring how problems appear on the screen will not apply + when the API is enabled. + + This option requires Neovim 0.7+, as that version introduces the diagnostics + API. + + *ale-options.use_neovim_lsp_api* + *g:ale_use_neovim_lsp_api* +use_neovim_lsp_api +g:ale_use_neovim_lsp_api + Type: |Boolean| or |Number| + Default: `has('nvim-0.8')` + + If set to `true` or `1`, ALE will use Neovim's native LSP client API for LSP + functionality. This makes it possible to use Neovim's built in LSP commands + and keybinds, and improves integration with other Neovim plugins that + integrate with Neovim's LSP client. + + See |ale-lsp-neovim| for more information about ALE's integration with + Neovim's LSP client. + + This option requires Neovim 0.8+. + + *ale-options.virtualtext_cursor* + *g:ale_virtualtext_cursor* +virtualtext_cursor +g:ale_virtualtext_cursor + Type: |Number| or |String| + Default: `'all'` (if supported, otherwise `'disabled'`) + + This option controls how ALE will display problems using |virtual-text|. + The following values can be used. + + `'all'`, `'2'`, or `2` - Show problems for all lines. + `'current'`, `'1'`, or `1` - Show problems for the current line. + `'disabled'`, `'0'`, or `0` - Do not show problems with virtual-text. + + When |g:ale_use_neovim_diagnostics_api| is `1`, `'current'` will behave the + same as `'all'`. + + Messages are only displayed after a short delay. See |g:ale_virtualtext_delay|. + + Messages can be prefixed with a string if not using Neovim's diagnostics + API. See |g:ale_virtualtext_prefix|. + + If and only if not displaying problems via Neovim's diagnostics API, + highlights for configuring ALE's virtualtext messages can be configured with + custom highlight groups: + + ALEVirtualTextError items with `'type': 'E'` |hl-ALEVirtualTextError| + ALEVirtualTextWarning items with `'type': 'W'` |hl-ALEVirtualTextWarning| + ALEVirtualTextInfo items with `'type': 'I'` |hl-ALEVirtualTextInfo| + ALEVirtualTextStyleError items with `'type': 'E'` and + `'sub_type': 'style'` |hl-ALEVirtualTextStyleError| + ALEVirtualTextStyleWarning items with `'type': 'W'` and + `'sub_type': 'style'` |hl-ALEVirtualTextStyleWarning| + + *ale-options.virtualtext_delay* + *g:ale_virtualtext_delay* + *b:ale_virtualtext_delay* +virtualtext_delay +g:ale_virtualtext_delay + Type: |Number| + Default: `10` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + Given any integer, this option controls the number of milliseconds before + ALE will show a message for a problem near the cursor. + + The value can be increased to decrease the amount of processing ALE will do + for files displaying a large number of problems. + + *ale-options.virtualtext_prefix* + *g:ale_virtualtext_prefix* + *b:ale_virtualtext_prefix* +virtualtext_prefix +g:ale_virtualtext_prefix + Type: |String| + Default: `'%comment% %type%: '` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + Prefix to be used with |g:ale_virtualtext_cursor|. + + This setting can be changed in each buffer with |b:ale_virtualtext_prefix||. + + All of the same format markers used for |g:ale_echo_msg_format| can be used + for defining the prefix, including some additional sequences of characters. + + `%comment%` - replaced with comment characters in the current language + + ALE will read the comment characters from 'commentstring', reading only the + part before `%s`, with whitespace trimmed. If comment syntax cannot be + pulled from 'commentstring', ALE will default to `'#'`. + + *ale-options.virtualtext_column* + *g:ale_virtualtext_column* + *b:ale_virtualtext_column* + *ale-options.virtualtext_maxcolumn* + *g:ale_virtualtext_maxcolumn* + *b:ale_virtualtext_maxcolumn* +virtualtext_column +virtualtext_maxcolumn +g:ale_virtualtext_column +g:ale_virtualtext_maxcolumn + Type: |String| or |Number| + Default: `0` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + Virtualtext column range, from `column` to `maxcolumn`. If a line is + `column` or less characters long, the virtualtext message is shifted right + to `column`. + + Where the line is greater than `column` characters long, but less than + `maxcolumn`, the virtualtext message is placed at the end of the line. + + Where the line is greater than `maxcolumn` the virtualtext message is + omitted. + + A |Number| greater than `0` is used as the fixed column position, however + a |String| ending in `%` represents a percentage of the window width. + When `column` is set to zero, column positioning is disabled, when `maxcolumn` + is set to zero, no maximum line length is enforced. + + *ale-options.virtualtext_single* + *g:ale_virtualtext_single* + *b:ale_virtualtext_single* +virtualtext_single +g:ale_virtualtext_single + Type: |Boolean| or |Number| + Default: `true` + + This has no effect when |g:ale_use_neovim_diagnostics_api| is `true` or `1`. + + Enable or disable concatenation of multiple virtual text messages on a single + line. By default, if a line has multiple errors or warnings, each will be + appended in turn. + + With `single` set to a non-zero value, only the first problem on a line will + be printed with virtual text. The most severe problem on a line will be + printed. If two problems exist on a line of equal severity, the problem at + the left-most position will be printed. + + *ale-options.virtualenv_dir_names* + *g:ale_virtualenv_dir_names* + *b:ale_virtualenv_dir_names* +virtualenv_dir_names +g:ale_virtualenv_dir_names + Type: |List| + Default: `['.venv', 'env', 've', 'venv', 'virtualenv', '.env']` + + A list of directory names to be used when searching upwards from Python + files to discover virtualenv directories with. + + For directory named `'foo'`, ALE will search for `'foo/bin/activate'` + (`foo\Scripts\activate\` on Windows) in all directories on and above the + directory containing the Python file to find virtualenv paths. + + *ale-options.warn_about_trailing_blank_lines* + *g:ale_warn_about_trailing_blank_lines* + *b:ale_warn_about_trailing_blank_lines* +warn_about_trailing_blank_lines +g:ale_warn_about_trailing_blank_lines + Type: |Number| + Default: `1` + + When this option is set to `1`, warnings about trailing blank lines will be + shown. + + This option behaves similarly to |g:ale_warn_about_trailing_whitespace|. + + *ale-options.warn_about_trailing_whitespace* + *g:ale_warn_about_trailing_whitespace* + *b:ale_warn_about_trailing_whitespace* +warn_about_trailing_whitespace +g:ale_warn_about_trailing_whitespace + Type: |Number| + Default: `1` + + When this option is set to `1`, warnings relating to trailing whitespace on + lines will be shown. If warnings are too irritating while editing buffers, + and you have configured Vim to automatically remove trailing whitespace, + you can disable these warnings by setting this option to `0`. + + Not all linters may respect this option. If a linter does not, please file a + bug report, and it may be possible to add such support. + + This option may be configured on a per buffer basis. + + *ale-options.windows_node_executable_path* + *g:ale_windows_node_executable_path* + *b:ale_windows_node_executable_path* +windows_node_executable_path +g:ale_windows_node_executable_path + Type: |String| + Default: `'node.exe'` + + This variable is used as the path to the executable to use for executing + scripts with Node.js on Windows. + + For Windows, any file with a `.js` file extension needs to be executed with + the node executable explicitly. Otherwise, Windows could try and open the + scripts with other applications, like a text editor. Therefore, these + scripts are executed with whatever executable is configured with this + setting. + + +------------------------------------------------------------------------------- +6.1. Highlights *ale-highlights* + +ALEError *hl-ALEError* + + Default: `highlight link ALEError SpellBad` + + The highlight for highlighted errors. See |g:ale_set_highlights|. + + +ALEErrorLine *hl-ALEErrorLine* + + Default: Undefined + + The highlight for an entire line where errors appear. Only the first + line for a problem will be highlighted. + + See |g:ale_set_signs| and |g:ale_set_highlights|. + + +ALEErrorSign *hl-ALEErrorSign* + + Default: `highlight link ALEErrorSign error` + + The highlight for error signs. See |g:ale_set_signs|. + + +ALEErrorSignLineNr *hl-ALEErrorSignLineNr* + + Default: `highlight link ALEErrorSignLineNr CursorLineNr` + + The highlight for error signs. See |g:ale_set_signs|. + + NOTE: This highlight is only available on Neovim 0.3.2 or higher. + + +ALEInfo *hl-ALEInfo* + *ALEInfo-highlight* + Default: `highlight link ALEInfo ALEWarning` + + The highlight for highlighted info messages. See |g:ale_set_highlights|. + + +ALEInfoSign *hl-ALEInfoSign* + + Default: `highlight link ALEInfoSign ALEWarningSign` + + The highlight for info message signs. See |g:ale_set_signs|. + + +ALEInfoLine *hl-ALEInfoLine* + + Default: Undefined + + The highlight for entire lines where info messages appear. Only the first + line for a problem will be highlighted. + + See |g:ale_set_signs| and |g:ale_set_highlights|. + + +ALEInfoSignLineNr *hl-ALEInfoSignLineNr* + + Default: `highlight link ALEInfoSignLineNr CursorLineNr` + + The highlight for error signs. See |g:ale_set_signs|. + + NOTE: This highlight is only available on Neovim 0.3.2 or higher. + + +ALEStyleError *hl-ALEStyleError* + + Default: `highlight link ALEStyleError ALEError` + + The highlight for highlighted style errors. See |g:ale_set_highlights|. + + +ALEStyleErrorSign *hl-ALEStyleErrorSign* + + Default: `highlight link ALEStyleErrorSign ALEErrorSign` + + The highlight for style error signs. See |g:ale_set_signs|. + + +ALEStyleErrorSignLineNr *hl-ALEStyleErrorSignLineNr* + + Default: `highlight link ALEStyleErrorSignLineNr CursorLineNr` + + The highlight for error signs. See |g:ale_set_signs|. + + NOTE: This highlight is only available on Neovim 0.3.2 or higher. + + +ALEStyleWarning *hl-ALEStyleWarning* + + Default: `highlight link ALEStyleWarning ALEError` + + The highlight for highlighted style warnings. See |g:ale_set_highlights|. + + +ALEStyleWarningSign *hl-ALEStyleWarningSign* + + Default: `highlight link ALEStyleWarningSign ALEWarningSign` + + The highlight for style warning signs. See |g:ale_set_signs|. + + +ALEStyleWarningSignLineNr *hl-ALEStyleWarningSignLineNr* + + Default: `highlight link ALEStyleWarningSignLineNr CursorLineNr` + + The highlight for error signs. See |g:ale_set_signs|. + + NOTE: This highlight is only available on Neovim 0.3.2 or higher. + + +ALEVirtualTextError *hl-ALEVirtualTextError* + + Default: `highlight link ALEVirtualTextError Comment` + + The highlight for virtualtext errors. See |g:ale_virtualtext_cursor|. + + +ALEVirtualTextInfo *hl-ALEVirtualTextInfo* + + Default: `highlight link ALEVirtualTextInfo ALEVirtualTextWarning` + + The highlight for virtualtext info. See |g:ale_virtualtext_cursor|. + + +ALEVirtualTextStyleError *hl-ALEVirtualTextStyleError* + + Default: `highlight link ALEVirtualTextStyleError ALEVirtualTextError` + + The highlight for virtualtext style errors. See |g:ale_virtualtext_cursor|. + + +ALEVirtualTextStyleWarning *hl-ALEVirtualTextStyleWarning* + + Default: `highlight link ALEVirtualTextStyleWarning ALEVirtualTextWarning` + + The highlight for virtualtext style warnings. See |g:ale_virtualtext_cursor|. + + +ALEVirtualTextWarning *hl-ALEVirtualTextWarning* + + Default: `highlight link ALEVirtualTextWarning Comment` + + The highlight for virtualtext errors. See |g:ale_virtualtext_cursor|. + + +ALEWarning *hl-ALEWarning* + + Default: `highlight link ALEWarning SpellCap` + + The highlight for highlighted warnings. See |g:ale_set_highlights|. + + +ALEWarningLine *hl-ALEWarningLine* + + Default: Undefined + + The highlight for entire lines where warnings appear. Only the first line + for a problem will be highlighted. + + See |g:ale_set_signs| and |g:ale_set_highlights|. + + +ALEWarningSign *hl-ALEWarningSign* + + Default: `highlight link ALEWarningSign todo` + + The highlight for warning signs. See |g:ale_set_signs|. + + +ALEWarningSignLineNr *hl-ALEWarningSignLineNr* + + Default: `highlight link ALEWarningSignLineNr CursorLineNr` + + The highlight for error signs. See |g:ale_set_signs|. + + NOTE: This highlight is only available on Neovim 0.3.2 or higher. + + +=============================================================================== +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 `true` or `1` in your init or +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. + + +------------------------------------------------------------------------------- +7.1. Options for alex *ale-alex-options* + +The options for `alex` are shared between all filetypes, so options can be +configured once. + + *ale-options.alex_executable* + *g:ale_alex_executable* + *b:ale_alex_executable* +alex_executable +g:ale_alex_executable + Type: |String| + Default: `'alex'` + + See |ale-integrations-local-executables| + + *ale-options.alex_use_global* + *g:ale_alex_use_global* + *b:ale_alex_use_global* +alex_use_global +g:ale_alex_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +------------------------------------------------------------------------------- +7.2. Options for cspell *ale-cspell-options* + +The options for `cspell` are shared between all filetypes, so options can be +configured only once. + + *ale-options.cspell_executable* + *g:ale_cspell_executable* + *b:ale_cspell_executable* +cspell_executable +g:ale_cspell_executable + Type: |String| + Default: `'cspell'` + + See |ale-integrations-local-executables| + + *ale-options.cspell_options* + *g:ale_cspell_options* + *b:ale_cspell_options* +cspell_options +g:ale_cspell_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to `cspell`. + + *ale-options.cspell_use_global* + *g:ale_cspell_use_global* + *b:ale_cspell_use_global* +cspell_use_global +g:ale_cspell_use_global + Type: |Number| + Default: `get(g: 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +------------------------------------------------------------------------------- +7.3. Options for dprint *ale-dprint-options* + +`dprint` is a fixer for many file types, including: (java|type)script, +json(c?), markdown, and more. See https://dprint.dev/plugins for an up-to-date +list of supported plugins and their configuration options. + + *ale-options.dprint_executable* + *g:ale_dprint_executable* + *b:ale_dprint_executable* +dprint_executable +g:ale_dprint_executable + Type: |String| + Default: `'dprint'` + + See |ale-integrations-local-executables| + + *ale-options.dprint_config* + *g:ale_dprint_config* + *b:ale_dprint_config* +dprint_config +g:ale_dprint_config + Type: |String| + Default: `'dprint.json'` + + This variable can be changed to provide a config file to `dprint`. The + default is the nearest `dprint.json` searching upward from the current + buffer. + + See https://dprint.dev/config and https://plugins.dprint.dev + + *ale-options.dprint_options* + *g:ale_dprint_options* + *b:ale_dprint_options* +dprint_options +g:ale_dprint_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to `dprint`. + + *ale-options.dprint_use_global* + *g:ale_dprint_use_global* + *b:ale_dprint_use_global* +dprint_use_global +g:ale_dprint_use_global + Type: |Number| + Default: `get(g: 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +------------------------------------------------------------------------------- +7.4. Options for languagetool *ale-languagetool-options* + + *ale-options.languagetool_executable* + *g:ale_languagetool_executable* + *b:ale_languagetool_executable* +languagetool_executable +g:ale_languagetool_executable + Type: |String| + Default: `'languagetool'` + + The executable to run for languagetool. + + *ale-options.languagetool_options* + *g:ale_languagetool_options* + *b:ale_languagetool_options* +languagetool_options +g:ale_languagetool_options + Type: |String| + Default: `'--autoDetect'` + + This variable can be set to pass additional options to languagetool. + + +------------------------------------------------------------------------------- +7.5. Options for write-good *ale-write-good-options* + +The options for `write-good` are shared between all filetypes, so options can +be configured once. + + *ale-options.writegood_executable* + *g:ale_writegood_executable* + *b:ale_writegood_executable* +writegood_executable +g:ale_writegood_executable + Type: |String| + Default: `'writegood'` + + See |ale-integrations-local-executables| + + *ale-options.writegood_options* + *g:ale_writegood_options* + *b:ale_writegood_options* +writegood_options +g:ale_writegood_options + Type: |String| + Default: `''` + + This variable can be set to pass additional options to writegood. + + *ale-options.writegood_use_global* + *g:ale_writegood_use_global* + *b:ale_writegood_use_global* +writegood_use_global +g:ale_writegood_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +------------------------------------------------------------------------------- +7.6. 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| + cspell................................|ale-ada-cspell| + gcc...................................|ale-ada-gcc| + gnatpp................................|ale-ada-gnatpp| + ada-language-server...................|ale-ada-language-server| + ansible.................................|ale-ansible-options| + ansible-language-server...............|ale-ansible-language-server| + ansible-lint..........................|ale-ansible-ansible-lint| + apkbuild................................|ale-apkbuild-options| + apkbuild-fixer........................|ale-apkbuild-apkbuild-fixer| + apkbuild-lint.........................|ale-apkbuild-apkbuild-lint| + secfixes-check........................|ale-apkbuild-secfixes-check| + asciidoc................................|ale-asciidoc-options| + cspell................................|ale-asciidoc-cspell| + write-good............................|ale-asciidoc-write-good| + textlint..............................|ale-asciidoc-textlint| + asm.....................................|ale-asm-options| + gcc...................................|ale-asm-gcc| + llvm_mc...............................|ale-asm-llvm_mc| + astro...................................|ale-astro-options| + eslint................................|ale-astro-eslint| + prettier..............................|ale-astro-prettier| + avra....................................|ale-avra-options| + avra..................................|ale-avra-avra| + awk.....................................|ale-awk-options| + gawk..................................|ale-awk-gawk| + bats....................................|ale-bats-options| + shellcheck............................|ale-bats-shellcheck| + bazel...................................|ale-bazel-options| + buildifier............................|ale-bazel-buildifier| + bib.....................................|ale-bib-options| + bibclean..............................|ale-bib-bibclean| + bicep...................................|ale-bicep-options| + bicep.................................|ale-bicep-bicep| + az_bicep..............................|ale-bicep-az_bicep| + bitbake.................................|ale-bitbake-options| + oelint-adv............................|ale-bitbake-oelint_adv| + c.......................................|ale-c-options| + astyle................................|ale-c-astyle| + cc....................................|ale-c-cc| + ccls..................................|ale-c-ccls| + clangcheck............................|ale-c-clangcheck| + clangd................................|ale-c-clangd| + clang-format..........................|ale-c-clangformat| + clangtidy.............................|ale-c-clangtidy| + cppcheck..............................|ale-c-cppcheck| + cquery................................|ale-c-cquery| + cspell................................|ale-c-cspell| + flawfinder............................|ale-c-flawfinder| + uncrustify............................|ale-c-uncrustify| + cairo...................................|ale-cairo-options| + scarb.................................|ale-cairo-scarb| + starknet..............................|ale-cairo-starknet| + chef....................................|ale-chef-options| + cookstyle.............................|ale-chef-cookstyle| + foodcritic............................|ale-chef-foodcritic| + clojure.................................|ale-clojure-options| + clj-kondo.............................|ale-clojure-clj-kondo| + cljfmt................................|ale-clojure-cljfmt| + 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-lint............................|ale-cmake-cmake-lint| + cmake-format..........................|ale-cmake-cmakeformat| + cpp.....................................|ale-cpp-options| + astyle................................|ale-cpp-astyle| + cc....................................|ale-cpp-cc| + ccls..................................|ale-cpp-ccls| + clangcheck............................|ale-cpp-clangcheck| + clangd................................|ale-cpp-clangd| + 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| + cspell................................|ale-cpp-cspell| + flawfinder............................|ale-cpp-flawfinder| + uncrustify............................|ale-cpp-uncrustify| + c#......................................|ale-cs-options| + clang-format..........................|ale-cs-clangformat| + csc...................................|ale-cs-csc| + cspell................................|ale-cs-cspell| + dotnet-format.........................|ale-cs-dotnet-format| + mcs...................................|ale-cs-mcs| + mcsc..................................|ale-cs-mcsc| + uncrustify............................|ale-cs-uncrustify| + css.....................................|ale-css-options| + cspell................................|ale-css-cspell| + css-beautify..........................|ale-css-css-beautify| + fecs..................................|ale-css-fecs| + prettier..............................|ale-css-prettier| + stylelint.............................|ale-css-stylelint| + vscodecss.............................|ale-css-vscode| + cuda....................................|ale-cuda-options| + clang-format..........................|ale-cuda-clangformat| + clangd................................|ale-cuda-clangd| + nvcc..................................|ale-cuda-nvcc| + c3......................................|ale-c3-options| + c3lsp.................................|ale-c3-c3lsp| + d.......................................|ale-d-options| + dfmt..................................|ale-d-dfmt| + dls...................................|ale-d-dls| + uncrustify............................|ale-d-uncrustify| + dafny...................................|ale-dafny-options| + dafny.................................|ale-dafny-dafny| + dart....................................|ale-dart-options| + analysis_server.......................|ale-dart-analysis_server| + dart-analyze..........................|ale-dart-analyze| + dart-format...........................|ale-dart-format| + dartfmt...............................|ale-dart-dartfmt| + desktop.................................|ale-desktop-options| + desktop-file-validate.................|ale-desktop-desktop-file-validate| + dhall...................................|ale-dhall-options| + dhall-format..........................|ale-dhall-format| + dhall-freeze..........................|ale-dhall-freeze| + dhall-lint............................|ale-dhall-lint| + dockerfile..............................|ale-dockerfile-options| + dockerfile_lint.......................|ale-dockerfile-dockerfile_lint| + dockerlinter..........................|ale-dockerfile-dockerlinter| + dprint................................|ale-dockerfile-dprint| + 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| + cspell................................|ale-elixir-cspell| + lexical...............................|ale-elixir-lexical| + elm.....................................|ale-elm-options| + elm-format............................|ale-elm-elm-format| + elm-ls................................|ale-elm-elm-ls| + elm-make..............................|ale-elm-elm-make| + erlang..................................|ale-erlang-options| + dialyzer..............................|ale-erlang-dialyzer| + elvis.................................|ale-erlang-elvis| + erlang-mode...........................|ale-erlang-erlang-mode| + erlang_ls.............................|ale-erlang-erlang_ls| + erlc..................................|ale-erlang-erlc| + erlfmt................................|ale-erlang-erlfmt| + syntaxerl.............................|ale-erlang-syntaxerl| + eruby...................................|ale-eruby-options| + erb-formatter.........................|ale-eruby-erbformatter| + erblint...............................|ale-eruby-erblint| + htmlbeautifier........................|ale-eruby-htmlbeautifier| + ruumba................................|ale-eruby-ruumba| + fish....................................|ale-fish-options| + fish_indent...........................|ale-fish-fish_indent| + 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| + gleam...................................|ale-gleam-options| + gleam_format..........................|ale-gleam-gleam_format| + gleamlsp..............................|ale-gleam-gleamlsp| + glsl....................................|ale-glsl-options| + glslang...............................|ale-glsl-glslang| + glslls................................|ale-glsl-glslls| + go......................................|ale-go-options| + bingo.................................|ale-go-bingo| + cspell................................|ale-go-cspell| + gobuild...............................|ale-go-gobuild| + gofmt.................................|ale-go-gofmt| + gofumpt...............................|ale-go-gofumpt| + golangci-lint.........................|ale-go-golangci-lint| + golangserver..........................|ale-go-golangserver| + golines...............................|ale-go-golines| + gopls.................................|ale-go-gopls| + govet.................................|ale-go-govet| + revive................................|ale-go-revive| + staticcheck...........................|ale-go-staticcheck| + go html template........................|ale-gohtmltmpl-options| + djlint................................|ale-gohtmltmpl-djlint| + graphql.................................|ale-graphql-options| + eslint................................|ale-graphql-eslint| + gqlint................................|ale-graphql-gqlint| + prettier..............................|ale-graphql-prettier| + groovy..................................|ale-groovy-options| + npm-groovy-lint.......................|ale-groovy-npm-groovy-lint| + hack....................................|ale-hack-options| + hack..................................|ale-hack-hack| + hackfmt...............................|ale-hack-hackfmt| + hhast.................................|ale-hack-hhast| + handlebars..............................|ale-handlebars-options| + djlint................................|ale-handlebars-djlint| + prettier..............................|ale-handlebars-prettier| + ember-template-lint...................|ale-handlebars-embertemplatelint| + haskell.................................|ale-haskell-options| + brittany..............................|ale-haskell-brittany| + cspell................................|ale-haskell-cspell| + 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| + hindent...............................|ale-haskell-hindent| + hlint.................................|ale-haskell-hlint| + hls...................................|ale-haskell-hls| + stack-build...........................|ale-haskell-stack-build| + stack-ghc.............................|ale-haskell-stack-ghc| + stylish-haskell.......................|ale-haskell-stylish-haskell| + hie...................................|ale-haskell-hie| + ormolu................................|ale-haskell-ormolu| + fourmolu..............................|ale-haskell-fourmolu| + hcl.....................................|ale-hcl-options| + packer-fmt............................|ale-hcl-packer-fmt| + terraform-fmt.........................|ale-hcl-terraform-fmt| + help....................................|ale-help-options| + cspell................................|ale-help-cspell| + html....................................|ale-html-options| + angular...............................|ale-html-angular| + cspell................................|ale-html-cspell| + djlint................................|ale-html-djlint| + fecs..................................|ale-html-fecs| + html-beautify.........................|ale-html-beautify| + htmlhint..............................|ale-html-htmlhint| + prettier..............................|ale-html-prettier| + rustywind.............................|ale-html-rustywind| + stylelint.............................|ale-html-stylelint| + tidy..................................|ale-html-tidy| + vscodehtml............................|ale-html-vscode| + write-good............................|ale-html-write-good| + html angular template...................|ale-htmlangular-options| + djlint................................|ale-htmlangular-djlint| + html django template....................|ale-htmldjango-options| + djlint................................|ale-htmldjango-djlint| + http....................................|ale-http-options| + kulala_fmt............................|ale-http-kulala_fmt| + hurl....................................|ale-hurl-options| + hurlfmt...............................|ale-hurl-hurlfmt| + idris...................................|ale-idris-options| + idris.................................|ale-idris-idris| + ink.....................................|ale-ink-options| + ink-language-server...................|ale-ink-language-server| + inko....................................|ale-inko-options| + inko..................................|ale-inko-inko| + ispc....................................|ale-ispc-options| + ispc..................................|ale-ispc-ispc| + java....................................|ale-java-options| + checkstyle............................|ale-java-checkstyle| + clang-format..........................|ale-java-clangformat| + cspell................................|ale-java-cspell| + 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| + biome.................................|ale-javascript-biome| + clang-format..........................|ale-javascript-clangformat| + cspell................................|ale-javascript-cspell| + deno..................................|ale-javascript-deno| + dprint................................|ale-javascript-dprint| + 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| + jinja...................................|ale-jinja-options| + djlint................................|ale-jinja-djlint| + json....................................|ale-json-options| + biome.................................|ale-json-biome| + clang-format..........................|ale-json-clangformat| + cspell................................|ale-json-cspell| + dprint................................|ale-json-dprint| + eslint................................|ale-json-eslint| + fixjson...............................|ale-json-fixjson| + pytool................................|ale-json-pytool| + jsonlint..............................|ale-json-jsonlint| + jq....................................|ale-json-jq| + prettier..............................|ale-json-prettier| + spectral..............................|ale-json-spectral| + vscodejson............................|ale-json-vscode| + jsonc...................................|ale-jsonc-options| + biome.................................|ale-jsonc-biome| + eslint................................|ale-jsonc-eslint| + jsonnet.................................|ale-jsonnet-options| + jsonnetfmt............................|ale-jsonnet-jsonnetfmt| + jsonnet-lint..........................|ale-jsonnet-jsonnet-lint| + json5...................................|ale-json5-options| + eslint................................|ale-json5-eslint| + 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| + cspell................................|ale-latex-cspell| + 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| + cspell................................|ale-lua-cspell| + lua-format............................|ale-lua-lua-format| + lua-language-server...................|ale-lua-lua-language-server| + luac..................................|ale-lua-luac| + luacheck..............................|ale-lua-luacheck| + luafmt................................|ale-lua-luafmt| + selene................................|ale-lua-selene| + stylua................................|ale-lua-stylua| + make....................................|ale-make-options| + checkmake.............................|ale-make-checkmake| + markdown................................|ale-markdown-options| + cspell................................|ale-markdown-cspell| + dprint................................|ale-markdown-dprint| + markdownlint..........................|ale-markdown-markdownlint| + marksman..............................|ale-markdown-marksman| + mdl...................................|ale-markdown-mdl| + pandoc................................|ale-markdown-pandoc| + prettier..............................|ale-markdown-prettier| + pymarkdown............................|ale-markdown-pymarkdown| + 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| + nickel..................................|ale-nickel-options| + nickel_format.........................|ale-nickel-nickel-format| + nim.....................................|ale-nim-options| + nimcheck..............................|ale-nim-nimcheck| + nimlsp................................|ale-nim-nimlsp| + nimpretty.............................|ale-nim-nimpretty| + nix.....................................|ale-nix-options| + alejandra.............................|ale-nix-alejandra| + nixfmt................................|ale-nix-nixfmt| + nixpkgs-fmt...........................|ale-nix-nixpkgs-fmt| + statix................................|ale-nix-statix| + deadnix...............................|ale-nix-deadnix| + nroff...................................|ale-nroff-options| + write-good............................|ale-nroff-write-good| + nunjucks................................|ale-nunjucks-options| + djlint................................|ale-nunjucks-djlint| + objc....................................|ale-objc-options| + ccls..................................|ale-objc-ccls| + clang.................................|ale-objc-clang| + clang-format..........................|ale-objc-clangformat| + clangd................................|ale-objc-clangd| + uncrustify............................|ale-objc-uncrustify| + objcpp..................................|ale-objcpp-options| + clang.................................|ale-objcpp-clang| + clangd................................|ale-objcpp-clangd| + uncrustify............................|ale-objcpp-uncrustify| + ocaml...................................|ale-ocaml-options| + dune..................................|ale-ocaml-dune| + merlin................................|ale-ocaml-merlin| + ocamllsp..............................|ale-ocaml-ocamllsp| + ols...................................|ale-ocaml-ols| + ocamlformat...........................|ale-ocaml-ocamlformat| + ocp-indent............................|ale-ocaml-ocp-indent| + odin....................................|ale-odin-options| + ols...................................|ale-odin-ols| + openapi.................................|ale-openapi-options| + ibm_validator.........................|ale-openapi-ibm-validator| + prettier..............................|ale-openapi-prettier| + yamllint..............................|ale-openapi-yamllint| + openscad................................|ale-openscad-options| + sca2d.................................|ale-openscad-sca2d| + scadformat............................|ale-openscad-scadformat| + packer..................................|ale-packer-options| + packer-fmt-fixer......................|ale-packer-fmt-fixer| + pascal..................................|ale-pascal-options| + ptop..................................|ale-pascal-ptop| + 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| + cspell................................|ale-php-cspell| + 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| + pint..................................|ale-php-pint| + tlint.................................|ale-php-tlint| + intelephense..........................|ale-php-intelephense| + 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| + cspell................................|ale-powershell-cspell| + powershell............................|ale-powershell-powershell| + psscriptanalyzer......................|ale-powershell-psscriptanalyzer| + prolog..................................|ale-prolog-options| + swipl.................................|ale-prolog-swipl| + proto...................................|ale-proto-options| + buf-format............................|ale-proto-buf-format| + buf-lint..............................|ale-proto-buf-lint| + clang-format..........................|ale-proto-clangformat| + protoc-gen-lint.......................|ale-proto-protoc-gen-lint| + protolint.............................|ale-proto-protolint| + 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| + purescript..............................|ale-purescript-options| + purescript-language-server............|ale-purescript-language-server| + purs-tidy.............................|ale-purescript-tidy| + purty.................................|ale-purescript-purty| + pyrex (cython)..........................|ale-pyrex-options| + cython................................|ale-pyrex-cython| + python..................................|ale-python-options| + autoflake.............................|ale-python-autoflake| + autoimport............................|ale-python-autoimport| + autopep8..............................|ale-python-autopep8| + bandit................................|ale-python-bandit| + black.................................|ale-python-black| + cspell................................|ale-python-cspell| + flake8................................|ale-python-flake8| + flakehell.............................|ale-python-flakehell| + isort.................................|ale-python-isort| + mypy..................................|ale-python-mypy| + prospector............................|ale-python-prospector| + pycln.................................|ale-python-pycln| + pycodestyle...........................|ale-python-pycodestyle| + pydocstyle............................|ale-python-pydocstyle| + pyflakes..............................|ale-python-pyflakes| + pyflyby...............................|ale-python-pyflyby| + pylama................................|ale-python-pylama| + pylint................................|ale-python-pylint| + pylsp.................................|ale-python-pylsp| + pyre..................................|ale-python-pyre| + pyright...............................|ale-python-pyright| + refurb................................|ale-python-refurb| + reorder-python-imports................|ale-python-reorder_python_imports| + ruff..................................|ale-python-ruff| + ruff-format...........................|ale-python-ruff-format| + unimport..............................|ale-python-unimport| + vulture...............................|ale-python-vulture| + yapf..................................|ale-python-yapf| + qml.....................................|ale-qml-options| + qmlfmt................................|ale-qml-qmlfmt| + r.......................................|ale-r-options| + languageserver........................|ale-r-languageserver| + lintr.................................|ale-r-lintr| + styler................................|ale-r-styler| + racket..................................|ale-racket-options| + racket_langserver.....................|ale-racket-langserver| + raco_fmt..............................|ale-racket-raco-fmt| + reasonml................................|ale-reasonml-options| + merlin................................|ale-reasonml-merlin| + ols...................................|ale-reasonml-ols| + reason-language-server................|ale-reasonml-language-server| + refmt.................................|ale-reasonml-refmt| + rego....................................|ale-rego-options| + cspell................................|ale-rego-cspell| + opacheck..............................|ale-rego-opa-check| + opafmt................................|ale-rego-opa-fmt-fixer| + rest....................................|ale-rest-options| + kulala_fmt............................|ale-rest-kulala_fmt| + restructuredtext........................|ale-restructuredtext-options| + cspell................................|ale-restructuredtext-cspell| + textlint..............................|ale-restructuredtext-textlint| + write-good............................|ale-restructuredtext-write-good| + robot...................................|ale-robot-options| + rflint................................|ale-robot-rflint| + ruby....................................|ale-ruby-options| + brakeman..............................|ale-ruby-brakeman| + cspell................................|ale-ruby-cspell| + debride...............................|ale-ruby-debride| + packwerk..............................|ale-ruby-packwerk| + prettier..............................|ale-ruby-prettier| + 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| + sorbet................................|ale-ruby-sorbet| + standardrb............................|ale-ruby-standardrb| + syntax_tree...........................|ale-ruby-syntax_tree| + rubyfmt...............................|ale-ruby-rubyfmt| + rust....................................|ale-rust-options| + analyzer..............................|ale-rust-analyzer| + cargo.................................|ale-rust-cargo| + cspell................................|ale-rust-cspell| + rls...................................|ale-rust-rls| + rustc.................................|ale-rust-rustc| + rustfmt...............................|ale-rust-rustfmt| + salt....................................|ale-salt-options| + salt-lint.............................|ale-salt-salt-lint| + sass....................................|ale-sass-options| + sasslint..............................|ale-sass-sasslint| + stylelint.............................|ale-sass-stylelint| + scala...................................|ale-scala-options| + cspell................................|ale-scala-cspell| + metals................................|ale-scala-metals| + 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| + bashate...............................|ale-sh-bashate| + cspell................................|ale-sh-cspell| + 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| + solc..................................|ale-solidity-solc| + solhint...............................|ale-solidity-solhint| + solium................................|ale-solidity-solium| + forge.................................|ale-solidity-forge| + spec....................................|ale-spec-options| + rpmlint...............................|ale-spec-rpmlint| + sql.....................................|ale-sql-options| + dprint................................|ale-sql-dprint| + pgformatter...........................|ale-sql-pgformatter| + sqlfluff..............................|ale-sql-sqlfluff| + sqlfmt................................|ale-sql-sqlfmt| + sqlformat.............................|ale-sql-sqlformat| + stylus..................................|ale-stylus-options| + stylelint.............................|ale-stylus-stylelint| + sugarss.................................|ale-sugarss-options| + stylelint.............................|ale-sugarss-stylelint| + svelte..................................|ale-svelte-options| + prettier..............................|ale-svelte-prettier| + svelteserver..........................|ale-svelte-svelteserver| + swift...................................|ale-swift-options| + apple-swift-format....................|ale-swift-apple-swift-format| + cspell................................|ale-swift-cspell| + sourcekitlsp..........................|ale-swift-sourcekitlsp| + systemd.................................|ale-systemd-options| + systemd-analyze.......................|ale-systemd-analyze| + tcl.....................................|ale-tcl-options| + nagelfar..............................|ale-tcl-nagelfar| + terraform...............................|ale-terraform-options| + checkov...............................|ale-terraform-checkov| + terraform-fmt-fixer...................|ale-terraform-fmt-fixer| + terraform.............................|ale-terraform-terraform| + terraform-ls..........................|ale-terraform-terraform-ls| + terraform-lsp.........................|ale-terraform-terraform-lsp| + tflint................................|ale-terraform-tflint| + tfsec.................................|ale-terraform-tfsec| + tex.....................................|ale-tex-options| + chktex................................|ale-tex-chktex| + cspell................................|ale-tex-cspell| + lacheck...............................|ale-tex-lacheck| + latexindent...........................|ale-tex-latexindent| + texlab................................|ale-tex-texlab| + texinfo.................................|ale-texinfo-options| + cspell................................|ale-texinfo-cspell| + write-good............................|ale-texinfo-write-good| + text....................................|ale-text-options| + cspell................................|ale-text-cspell| + textlint..............................|ale-text-textlint| + write-good............................|ale-text-write-good| + thrift..................................|ale-thrift-options| + thrift................................|ale-thrift-thrift| + thriftcheck...........................|ale-thrift-thriftcheck| + toml....................................|ale-toml-options| + dprint................................|ale-toml-dprint| + typescript..............................|ale-typescript-options| + biome.................................|ale-typescript-biome| + cspell................................|ale-typescript-cspell| + deno..................................|ale-typescript-deno| + dprint................................|ale-typescript-dprint| + eslint................................|ale-typescript-eslint| + prettier..............................|ale-typescript-prettier| + standard..............................|ale-typescript-standard| + tslint................................|ale-typescript-tslint| + tsserver..............................|ale-typescript-tsserver| + xo....................................|ale-typescript-xo| + typst...................................|ale-typst-options| + typstyle..............................|ale-typst-typstyle| + v.......................................|ale-v-options| + v.....................................|ale-v-v| + vfmt..................................|ale-v-vfmt| + vala....................................|ale-vala-options| + uncrustify............................|ale-vala-uncrustify| + verilog/systemverilog...................|ale-verilog-options| + hdl-checker...........................|ale-verilog-hdl-checker| + iverilog..............................|ale-verilog-iverilog| + slang.................................|ale-verilog-slang| + verilator.............................|ale-verilog-verilator| + vlog..................................|ale-verilog-vlog| + xvlog.................................|ale-verilog-xvlog| + yosys.................................|ale-verilog-yosys| + vhdl....................................|ale-vhdl-options| + ghdl..................................|ale-vhdl-ghdl| + hdl-checker...........................|ale-vhdl-hdl-checker| + vcom..................................|ale-vhdl-vcom| + xvhdl.................................|ale-vhdl-xvhdl| + vim help................................|ale-vim-help-options| + write-good............................|ale-vim-help-write-good| + vim.....................................|ale-vim-options| + vimls.................................|ale-vim-vimls| + vint..................................|ale-vim-vint| + vue.....................................|ale-vue-options| + cspell................................|ale-vue-cspell| + prettier..............................|ale-vue-prettier| + vls...................................|ale-vue-vls| + volar.................................|ale-vue-volar| + wgsl....................................|ale-wgsl-options| + naga..................................|ale-wgsl-naga| + xhtml...................................|ale-xhtml-options| + cspell................................|ale-xhtml-cspell| + write-good............................|ale-xhtml-write-good| + xml.....................................|ale-xml-options| + xmllint...............................|ale-xml-xmllint| + yaml....................................|ale-yaml-options| + actionlint............................|ale-yaml-actionlint| + circleci..............................|ale-yaml-circleci| + prettier..............................|ale-yaml-prettier| + spectral..............................|ale-yaml-spectral| + swaglint..............................|ale-yaml-swaglint| + yaml-language-server..................|ale-yaml-language-server| + yamlfix...............................|ale-yaml-yamlfix| + yamlfmt...............................|ale-yaml-yamlfmt| + yamllint..............................|ale-yaml-yamllint| + gitlablint............................|ale-yaml-gitlablint| + yq....................................|ale-yaml-yq| + yang....................................|ale-yang-options| + yang-lsp..............................|ale-yang-lsp| + yara....................................|ale-yara-options| + yls...................................|ale-yara-yls| + zeek....................................|ale-zeek-options| + zeek..................................|ale-zeek-zeek| + zig.....................................|ale-zig-options| + zigfmt................................|ale-zig-zigfmt| + zlint.................................|ale-zig-zlint| + zls...................................|ale-zig-zls| + + +=============================================================================== +8. Commands/Keybinds *ale-commands* + +:ALEComplete *:ALEComplete* + + Manually trigger LSP autocomplete and show the menu. Works only when called + from insert mode. > + + inoremap :ALEComplete +< + A plug mapping `(ale_complete)` is defined for this command. > + + imap (ale_complete) +< + +:ALEDocumentation *:ALEDocumentation* + + 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. + + NOTE: This command is only available for `tsserver`. + + A plug mapping `(ale_documentation)` is defined for this command. + + +:ALEFindReferences *:ALEFindReferences* + + Find references in the codebase for the symbol under the cursor using the + enabled LSP linters for the buffer. ALE will display a preview window + containing the results if some references are found. + + The window can be navigated using the usual Vim navigation commands. The + Enter key () can be used to jump to a referencing location, or the `t` + key can be used to jump to the location in a new tab. + + The locations opened in different ways using the following variations. + + `:ALEFindReferences -tab` - Open the location in a new tab. + `:ALEFindReferences -split` - Open the location in a horizontal split. + `:ALEFindReferences -vsplit` - Open the location in a vertical split. + `:ALEFindReferences -quickfix` - Put the locations into quickfix list. + + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. + + You can add `-relative` to the command to view results with relatives paths, + instead of absolute paths. This option has no effect if `-quickfix` is used. + + The selection can be opened again with the `:ALERepeatSelection` command. + + You can jump back to the position you were at before going to a reference of + something with jump motions like CTRL-O. See |jump-motions|. + + A plug mapping `(ale_find_references)` is defined for this command. + You can define additional plug mapping with any additional options you want + like so: > + + nnoremap (my_mapping) :ALEFindReferences -relative +< + +:ALEFix [linter] *:ALEFix* + + Fix problems with the current buffer. See |ale-fix| for more information. + + If the command is run with a bang (`:ALEFix!`), all warnings will be + suppressed, including warnings about no fixers being defined, and warnings + about not being able to apply fixes to a file because it has been changed. + + A plug mapping `(ale_fix)` is defined for this command. + + +:ALEFixSuggest *:ALEFixSuggest* + + Suggest tools that can be used to fix problems in the current buffer. + + See |ale-fix| for more information. + + +:ALEGoToDefinition [options] *:ALEGoToDefinition* + + Jump to the definition of a symbol under the cursor using the enabled LSP + linters for the buffer. ALE will jump to a definition if an LSP server + provides a location to jump to. Otherwise, ALE will do nothing. + + The locations opened in different ways using the following variations. + + `:ALEGoToDefinition -tab` - Open the location in a new tab. + `:ALEGoToDefinition -split` - Open the location in a horizontal split. + `:ALEGoToDefinition -vsplit` - Open the location in a vertical split. + + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. + + 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|. + + You should consider using the 'hidden' option in combination with this + command. Otherwise, Vim will refuse to leave the buffer you're jumping from + unless you have saved your edits. + + The following Plug mappings are defined for this command, which correspond + to the following commands. + + `(ale_go_to_definition)` - `:ALEGoToDefinition` + `(ale_go_to_definition_in_tab)` - `:ALEGoToDefinition -tab` + `(ale_go_to_definition_in_split)` - `:ALEGoToDefinition -split` + `(ale_go_to_definition_in_vsplit)` - `:ALEGoToDefinition -vsplit` + + +:ALEGoToTypeDefinition [options] *: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. + + The locations opened in different ways using the following variations. + + `:ALEGoToTypeDefinition -tab` - Open the location in a new tab. + `:ALEGoToTypeDefinition -split` - Open the location in a horizontal split. + `:ALEGoToTypeDefinition -vsplit` - Open the location in a vertical split. + + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. + + 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|. + + The following Plug mappings are defined for this command, which correspond + to the following commands. + + `(ale_go_to_type_definition)` - `:ALEGoToTypeDefinition` + `(ale_go_to_type_definition_in_tab)` - `:ALEGoToTypeDefinition -tab` + `(ale_go_to_type_definition_in_split)` - `:ALEGoToTypeDefinition -split` + `(ale_go_to_type_definition_in_vsplit)` - `:ALEGoToTypeDefinition -vsplit` + + +:ALEGoToImplementation [options] *:ALEGoToImplementation* + + This works similar to `:ALEGoToDefinition` but instead jumps to the + implementation of 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. + + The locations opened in different ways using the following variations. + + `:ALEGoToImplementation -tab` - Open the location in a new tab. + `:ALEGoToImplementation -split` - Open the location in a horizontal split. + `:ALEGoToImplementation -vsplit` - Open the location in a vertical split. + + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. + + 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|. + + The following Plug mappings are defined for this command, which correspond + to the following commands. + + `(ale_go_to_implementation)` - `:ALEGoToImplementation` + `(ale_go_to_implementation_in_tab)` - `:ALEGoToImplementation -tab` + `(ale_go_to_implementation_in_split)` - `:ALEGoToImplementation -split` + `(ale_go_to_implementation_in_vsplit)` - `:ALEGoToImplementation -vsplit` + + +:ALEHover *:ALEHover* + + Print brief information about the symbol under the cursor, taken from any + available LSP linters. There may be a small non-blocking delay before + information is printed. + + NOTE: In Vim 8, long messages will be shown in a preview window, as Vim 8 + does not support showing a prompt to press enter to continue for long + messages from asynchronous callbacks. + + A plug mapping `(ale_hover)` is defined for this command. + + +:ALEImport *:ALEImport* + + Try to import a symbol using `tsserver` or a Language Server. + + ALE will look for completions for the word at the cursor which contain + additional text edits that possible insert lines to import the symbol. The + first match with additional text edits will be used, and may add other code + to the current buffer other than import lines. + + If linting is enabled, and |g:ale_lint_on_text_changed| is set to ever check + buffers when text is changed, the buffer will be checked again after changes + are made. + + A Plug mapping `(ale_import)` is defined for this command. This + mapping should only be bound for normal mode. + + +:ALEOrganizeImports *:ALEOrganizeImports* + + Organize imports using tsserver. Currently not implemented for LSPs. + + +:ALERename *:ALERename* + + Rename a symbol using `tsserver` or a Language Server. + + The symbol where the cursor is resting will be the symbol renamed, and a + prompt will open to request a new name. + + The rename operation will not save modified buffers when 'hidden' is on + unless |g:ale_save_hidden| is `true` or `1`. + + +:ALEFileRename *:ALEFileRename* + + Rename a file and fix imports using `tsserver`. + + +:ALECodeAction *:ALECodeAction* + + Apply a code action via LSP servers or `tsserver`. + + If there is an error present on a line that can be fixed, ALE will + automatically fix a line, unless there are multiple possible code fixes to + apply. + + This command can be run in visual mode apply actions, such as applicable + refactors. A menu will be shown to select code action to apply. + + +:ALERepeatSelection *:ALERepeatSelection* + + Repeat the last selection displayed in the preview window. + + +:ALESymbolSearch [query] *:ALESymbolSearch* + + Search for symbols in the workspace, taken from any available LSP linters. + + The arguments provided to this command will be used as a search query for + finding symbols in the workspace, such as functions, types, etc. + + You can add `-relative` to the command to view results with relatives paths, + instead of absolute paths. + + +:ALELint *:ALELint* + + Run ALE once for the current buffer. This command can be used to run ALE + manually, instead of automatically, if desired. + + This command will also run linters where `lint_file` is evaluates to `1`, + meaning linters which check the file instead of the Vim buffer. + + A plug mapping `(ale_lint)` is defined for this command. + + +:ALELintStop *:ALELintStop* + + Stop any currently running jobs for checking the current buffer. + + Any problems from previous linter results will continue to be shown. + + +:ALEPopulateQuickfix *:ALEPopulateQuickfix* +:ALEPopulateLocList *:ALEPopulateLocList* + + Manually populate the |quickfix| or |location-list| and show the + corresponding list. Useful when you have other uses for both the |quickfix| + and |location-list| and don't want them automatically populated. Be sure to + disable auto populating: > + + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 +< + With these settings, ALE will still run checking and display it with signs, + highlighting, and other output described in |ale-lint-file-linters|. + +:ALEPrevious *:ALEPrevious* +:ALEPreviousWrap *:ALEPreviousWrap* +:ALENext *:ALENext* +:ALENextWrap *:ALENextWrap* +:ALEFirst *:ALEFirst* +:ALELast *:ALELast* + *ale-navigation-commands* + + Move between warnings or errors in a buffer. ALE will only navigate between + the errors or warnings it generated, even if both |g:ale_set_quickfix| + and |g:ale_set_loclist| are set to `0`. + + `:ALEPrevious` and `:ALENext` will stop at the top and bottom of a file, while + `: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 + behavior : + `-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 beginning 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 || mappings are defined for the commands: > + (ale_previous) - ALEPrevious + (ale_previous_wrap) - ALEPreviousWrap + (ale_previous_error) - ALEPrevious -error + (ale_previous_wrap_error) - ALEPrevious -wrap -error + (ale_previous_warning) - ALEPrevious -warning + (ale_previous_wrap_warning) - ALEPrevious -wrap -warning + (ale_next) - ALENext + (ale_next_wrap) - ALENextWrap + (ale_next_error) - ALENext -error + (ale_next_wrap_error) - ALENext -wrap -error + (ale_next_warning) - ALENext -warning + (ale_next_wrap_warning) - ALENext -wrap -warning + (ale_first) - ALEFirst + (ale_last) - ALELast +< + For example, these commands could be bound to the keys CTRL-j + and CTRL-k: > + + " Map movement through errors without wrapping. + nmap (ale_previous) + nmap (ale_next) + " OR map keys to use wrapping. + nmap (ale_previous_wrap) + nmap (ale_next_wrap) +< + +:ALEToggle *:ALEToggle* +:ALEEnable *:ALEEnable* +:ALEDisable *:ALEDisable* +:ALEToggleBuffer *:ALEToggleBuffer* +:ALEEnableBuffer *:ALEEnableBuffer* +:ALEDisableBuffer *:ALEDisableBuffer* + + `:ALEToggle`, `:ALEEnable`, and `:ALEDisable` enable or disable ALE linting, + including all of its autocmd events, loclist items, quickfix items, signs, + current jobs, etc., globally. Executing any of these commands will change + the |g:ale_enabled| variable. + + ALE can be disabled or enabled for only a single buffer with + `:ALEToggleBuffer`, `:ALEEnableBuffer`, and `:ALEDisableBuffer`. Disabling ALE + for a buffer will not remove autocmd events, but will prevent ALE from + checking for problems and reporting problems for whatever buffer the + `:ALEDisableBuffer` or `:ALEToggleBuffer` command is executed from. These + commands can be used for temporarily disabling ALE for a buffer. These + commands will modify the |b:ale_enabled| variable. + + ALE linting cannot be enabled for a single buffer when it is disabled + globally, as disabling ALE globally removes the autocmd events needed to + perform linting with. + + The following plug mappings are defined, for conveniently defining keybinds: + + `:ALEToggle` - `(ale_toggle)` + `:ALEEnable` - `(ale_enable)` + `:ALEDisable` - `(ale_disable)` + `:ALEToggleBuffer` - `(ale_toggle_buffer)` + `:ALEEnableBuffer` - `(ale_enable_buffer)` + `:ALEDisableBuffer` - `(ale_disable_buffer)` + + For removing problems reported by ALE, but leaving ALE enabled, see + `:ALEReset` and `:ALEResetBuffer`. + + +:ALEDetail *:ALEDetail* + + Show the full linter message for the problem nearest to the cursor on the + given line in the preview window. The preview window can be easily closed + with the `q` key. If there is no message to show, the window will not be + opened. + + If a loclist item has a `detail` key set, the message for that key will be + preferred over `text`. See |ale-loclist-format|. + + A plug mapping `(ale_detail)` is defined for this command. + + +:ALEInfo *:ALEInfo* + *:ALEInfoToFile* + + Print runtime information about ALE, including the values of global and + buffer-local settings for ALE, the linters that are enabled, the commands + that have been run, and the output of commands. + + ALE will log the commands that are run by default. If you wish to disable + this, set |g:ale_history_enabled| to `0`. Because it could be expensive, ALE + does not remember the output of recent commands by default. Set + |g:ale_history_log_output| to `1` to enable logging of output for commands. + ALE will only log the output captured for parsing problems, etc. + + You can pass options to the command to control how ALE displays the + information, such as `:ALEInfo -echo`, etc. > + + -preview Show the info in a preview window. + -clip OR -clipboard Copy the information to your clipboard. + -echo echo all of the information with :echo +< + The default mode can be configured with |g:ale_info_default_mode|. + + When shown in a preview window, syntax highlights can be defined for the + `ale-info` filetype. + + `:ALEInfoToFile` will write the ALE runtime information to a given filename. + The filename works just like `:write`. + + +:ALEReset *:ALEReset* +:ALEResetBuffer *:ALEResetBuffer* + + `:ALEReset` will remove all problems reported by ALE for all buffers. + `:ALEResetBuffer` will remove all problems reported for a single buffer. + + Either command will leave ALE linting enabled, so ALE will report problems + when linting is performed again. See |ale-lint| for more information. + + The following plug mappings are defined, for conveniently defining keybinds: + + `:ALEReset` - `(ale_reset)` + `:ALEResetBuffer` - `(ale_reset_buffer)` + + ALE can be disabled globally or for a buffer with `:ALEDisable` or + `:ALEDisableBuffer`. + + +:ALEStopAllLSPs *:ALEStopAllLSPs* + + `:ALEStopAllLSPs` will close and stop all channels and jobs for all LSP-like + clients, including tsserver, remove all of the data stored for them, and + delete all of the problems found for them, updating every linted buffer. + + This command can be used when LSP clients mess up and need to be restarted. + + +:ALEStopLSP [linter] *:ALEStopLSP* + + `:ALEStopLSP` will stop a specific language server with a given linter name. + Completion is supported for currently running language servers. All language + servers with the given name will be stopped across all buffers for all + projects. + + If the command is run with a bang (`:ALEStopLSP!`), all warnings will be + suppressed. + + +=============================================================================== +9. API *ale-api* + +ALE offers a number of functions for running linters or fixers, or defining +them. The following functions are part of the publicly documented part of that +API, and should be expected to continue to work. Functions documented with +Vim autocmd names `ale#Foo` are available in the Vim context, and functions +documented with dot names `ale.foo` are available in Lua scripts. + + +ale.env(variable_name, value) *ale.env()* +ale#Env(variable_name, value) *ale#Env()* + + Given a variable name and a string value, produce a string for including in + a command for setting environment variables. This function can be used for + building a command like so. > + + :echo string(ale#Env('VAR', 'some value') . 'command') + 'VAR=''some value'' command' # On Linux or Mac OSX + 'set VAR="some value" && command' # On Windows + + +ale.escape(str) *ale.escape()* +ale#Escape(str) *ale#Escape()* + + Given a string, escape that string so it is ready for shell execution. + + If the shell is detected to be `cmd.exe`, ALE will apply its own escaping + that tries to avoid escaping strings unless absolutely necessary to avoid + issues with Windows programs that do not properly handle quoted arguments. + + In all other cases, ALE will call |shellescape|. + + +ale.get_filename_mappings(buffer, name) *ale.get_filename_mappings()* +ale#GetFilenameMappings(buffer, name) *ale#GetFilenameMappings()* + + Given a `buffer` and the `name` of either a linter for fixer, return a + |List| of two-item |List|s that describe mapping to and from the local and + foreign file systems for running a particular linter or fixer. + + See |g:ale_filename_mappings| for details on filename mapping. + + +ale.has(feature) *ale.has()* +ale#Has(feature) *ale#Has()* + + In Vim, `ale#Has` returns `1` if ALE supports a given feature, like |has()| + for Vim features. In Lua `ale.has` returns `true` instead, and `false` if a + feature is not supported. + + 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(str) *ale.pad()* +ale#Pad(str) *ale#Pad()* + + Given a string or any |empty()| value, return either the string prefixed + with a single space, or an empty string. This function can be used to build + parts of a command from variables. + + +ale.queue(delay, [linting_flag, buffer]) *ale.queue()* +ale#Queue(delay, [linting_flag, buffer]) *ale#Queue()* + + Run linters for the current buffer, based on the filetype of the buffer, + with a given `delay`. A `delay` of `0` will run the linters immediately. + The linters will always be run in the background. Calling this function + several times will reset an internal timer so ALE doesn't check buffers too + often. + + An optional `linting_flag` argument can be given. If `linting_flag` is + `'lint_file'`, then linters where the `lint_file` option evaluates to `1` + will be run. Otherwise, those linters will not be run. + + An optional `buffer` argument can be given for specifying the buffer to + check. The active buffer (`bufnr('')`) will be checked by default. + + *ale-cool-down* + If an exception is thrown when queuing/running ALE linters, ALE will enter + a cool down period where it will stop checking anything for a short period + of time. This is to prevent ALE from seriously annoying users if a linter + is broken, or when developing ALE itself. + + +ale.setup(config) *ale.setup()* + + Configure ALE global settings, which are documented in |ale-options|. For + example: > + + require("ale").setup({ + completion_enabled = true, + maximum_file_size = 1024 * 1024, + warn_about_trailing_whitespace = false, + }) +< + You can also call this function with `ale.setup.global` to make what context + ALE is being configured in less ambiguous if you like. + + +ale.setup.buffer(config) *ale.setup.buffer()* + + Configure ALE buffer-local settings, which are documented in |ale-options|. + For example: > + require("ale").setup.buffer({ + linters = {"ruff", "pyright"}, + fixers = {"ruff"} + }) +< + +ale.var(buffer, variable_name) *ale.var()* +ale#Var(buffer, variable_name) *ale#Var()* + + Given a buffer number and an ALE variable name return the value of that + if defined in the buffer, and if not defined in the buffer return the + global value. The `ale_` prefix will be added to the Vim variable name. + + The `ale#Var` Vim function will return errors if the variable is not defined + in either the buffer or globally. The `ale.var` Lua function will return + `nil` if the variable is not defined in either the buffer or globally. + + +ale#command#CreateDirectory(buffer) *ale#command#CreateDirectory()* + + Create a new temporary directory with a unique name, and manage that + 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#command#CreateFile(buffer) *ale#command#CreateFile()* + + Create a new temporary file with a unique name, and manage that file with + |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#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. + + `cwd` - An optional |String| for setting the working directory + for the command, just as per |ale#linter#Define|. + + If not set, or `v:null`, the `cwd` of the last command + that spawned this one will be used. + + `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. + + `filename_mappings` - A |List| of two-item |List|s describing filename + mappings to apply for formatted filenames in the + command string, as per |g:ale_filename_mappings|. + + If the call to this function is being used for a + linter or fixer, the mappings should be provided with + this option, and can be retrieved easily with + |ale#GetFilenameMappings()|. + + The default is `[]`. + + +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 + dynamically generated for commands before handing them over to ALE, + so that ALE doesn't treat any strings with `%` formatting sequences + specially. + + +ale#command#ManageDirectory(buffer, directory) *ale#command#ManageDirectory()* + + 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. + + It is advised to use |ale#command#ManageFile()| instead for deleting single + files. + + +ale#command#ManageFile(buffer, filename) *ale#command#ManageFile()* + + 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. + It is advised to create temporary files in the operating system's managed + temporary file directory, such as with |tempname()|. + + Directory names should not be given to this function. ALE will only delete + 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 separately with the |ale#command#ManageDirectory| function. + + +ale#completion#OmniFunc(findstart, base) *ale#completion#OmniFunc()* + + A completion function to use with 'omnifunc'. + + See |ale-completion|. + + +ale#engine#GetLoclist(buffer) *ale#engine#GetLoclist()* + + 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. + + This function can be used for status lines, tab names, etc. + + *ale#fix#registry#Add()* +ale#fix#registry#Add(name, func, filetypes, desc, [aliases]) + + Given a |String| `name` for a name to add to the registry, a |String| `func` + for a function name, a |List| `filetypes` for a list of filetypes to + set for suggestions, and a |String| `desc` for a short description of + the fixer, register a fixer in the registry. + + The `name` can then be used for |g:ale_fixers| in place of the function + name, and suggested for fixing files. + + An optional |List| of |String|s for aliases can be passed as the `aliases` + argument. These aliases can also be used for looking up a fixer function. + ALE will search for fixers in the registry first by `name`, then by their + `aliases`. + + For example to register a custom fixer for `luafmt`: > + + function! FormatLua(buffer) abort + return { + \ 'command': 'luafmt --stdin' + \} + endfunction + + execute ale#fix#registry#Add('luafmt', 'FormatLua', ['lua'], 'luafmt for lua') + + " You can now use it in g:ale_fixers + let g:ale_fixers = { + \ 'lua': ['luafmt'] + } +< + +ale#linter#Define(filetype, linter) *ale#linter#Define()* + + Given a |String| for a filetype and a |Dictionary| Describing a linter + configuration, add a linter for the given filetype. The dictionaries each + offer the following options: + + `name` The name of the linter. These names will be used by + |g:ale_linters| option for enabling/disabling + particular linters. + + This argument is required. + + `callback` A |String| or |Funcref| for a callback function + accepting two arguments (buffer, lines), for a + buffer number the output is for, and the lines of + output from a linter. + + This callback function should return a |List| of + |Dictionary| objects in the format accepted by + |setqflist()|. The |List| will be sorted by line and + then column order so it can be searched with a binary + search by in future before being passed on to the + |location-list|, etc. + + This argument is required, unless the linter is an + LSP linter. In which case, this argument must not be + defined, as LSP linters handle diagnostics + automatically. See |ale-lsp-linters|. + + If the function named does not exist, including if + the function is later deleted, ALE will behave as if + the callback returned an empty list. + + The keys for each item in the List will be handled in + the following manner: + *ale-loclist-format* + `text` - This error message is required. + `detail` - An optional, more descriptive message. + This message can be displayed with the `:ALEDetail` + command instead of the message for `text`, if set. + `lnum` - The line number is required. Any strings + will be automatically converted to numbers by + using |str2nr()|. + + Line 0 will be moved to line 1, and lines beyond + the end of the file will be moved to the end. + `col` - The column number is optional and will + default to `0`. Any strings will be automatically + converted to number using |str2nr()|. + `end_col` - An optional end column number. + This key can be set to specify the column problems + end on, for improved highlighting. + `end_lnum` - An optional end line number. + This key can set along with `end_col` for + highlighting multi-line problems. + `bufnr` - This key represents the buffer number the + problems are for. This value will default to + the buffer number being checked. + + The `filename` key can be set instead of this key, + and then the eventual `bufnr` value in the final + list will either represent the number for an open + buffer or `-1` for a file not open in any buffer. + `filename` - An optional filename for the file the + problems are for. This should be an absolute path to + a file. + + Problems for files which have not yet been opened + will be set in those files after they are opened + and have been checked at least once. + + Temporary files in directories used for Vim + temporary files with |tempname()| will be assumed + to be the buffer being checked, unless the `bufnr` + key is also set with a valid number for some other + buffer. + `vcol` - Defaults to `0`. + + If set to `1`, ALE will convert virtual column + positions for `col` and `end_col` to byte column + positions. If the buffer is changed in-between + checking it and displaying the results, the + calculated byte column positions will probably be + wrong. + `type` - Defaults to `'E'`. + `nr` - Defaults to `-1`. + + Numeric error code. If `nr` is not `-1`, `code` + likely should contain the string representation of + the same value. + `code` - No default; may be unset. + + Human-readable |String| error code. + + `executable` A |String| naming the executable itself which + 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()|. + + This value will be used to check if the program + requested is installed or not. + + 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()|. + + The command string can be formatted with format + markers. See |ale-command-format-strings|. + + This command will be fed the lines from the buffer to + check, and will produce the lines of output given to + the `callback`. + + `cwd` An optional |String| for setting the working + directory for the command, or a |Funcref| for a + function to call for computing the command, accepting + a buffer number. The working directory can be + specified as a format string for determining the path + dynamically. See |ale-command-format-strings|. + + To set the working directory to the directory + containing the file you're checking, you should + probably use `'%s:h'` as the option value. + + If this option is absent or the string is empty, the + `command` will be run with no determined working + directory in particular. + + The directory specified with this option will be used + as the default working directory for all commands run + in a chain with |ale#command#Run()|, unless otherwise + specified. + + `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 + `'both'`. This argument defaults to `'stdout'`. This + argument can be set for linter programs which output + their errors and warnings to the stderr stream + instead of stdout. The option `'both'` will read + from both stder and stdout at the same time. + + `read_buffer` A |Number| (`0` or `1`) indicating whether a command + should read the Vim buffer as input via stdin. This + option is set to `1` by default, and can be disabled + if a command manually reads from a temporary file + instead, etc. + + This option behaves as if it was set to `0` when the + `lint_file` option evaluates to `1`. + + *ale-lint-file* + `lint_file` A |Number| (`0` or `1`), or a |Funcref| for a function + accepting a buffer number for computing either `0` or + `1`, indicating whether a command should read the file + instead of the Vim buffer. This option can be used + for linters which must check the file on disk, and + which cannot check a Vim buffer instead. + + The result can be computed with |ale#command#Run()|. + + Linters where the eventual value of this option + evaluates to `1` will not be run as a user types, per + |g:ale_lint_on_text_changed|. Linters will instead be + run only when events occur against the file on disk, + including |g:ale_lint_on_enter| and + |g:ale_lint_on_save|. Linters where this option + evaluates to `1` will also be run when the `:ALELint` + command is run. + + When this option is evaluates to `1`, ALE will behave + as if `read_buffer` was set to `0`. + + *ale-lsp-linters* + `lsp` A |String| for defining LSP (Language Server Protocol) + linters. + + This argument may be omitted or `''` when a linter + does not represent an LSP linter. + + When this argument is set to `'stdio'`, then the + linter will be defined as an LSP linter which keeps a + process for a language server running, and + communicates with it directly via a |channel|. + `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 + or named pipe socket connection. `address` must be set. + + ALE will not start a server automatically. + + When this argument is not empty `project_root` must + be defined. + + `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. + + An optional `completion_filter` callback may be + defined for filtering completion results. + + `initialization_options` may be defined to pass + initialization options to the LSP. + + `lsp_config` may be defined to pass configuration + settings to the LSP. + + `address` A |String| representing an address to connect to, + or a |Funcref| accepting a buffer number and + returning the |String|. If the value contains a + colon, it is interpreted as referring to a TCP + socket; otherwise it is interpreted as the path of a + named pipe. + + 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` 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, 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. + + 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. + + The completion item will be a |Dictionary| following + the Language Server Protocol `CompletionItem` + interface as described in the specification, + available online here: + https://microsoft.github.io/language-server-protocol + + `aliases` A |List| of aliases for the linter name. + + This argument can be set with alternative names for + selecting the linter with |g:ale_linters|. This + setting can make it easier to guess the linter name + by offering a few alternatives. + + `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. + + `lsp_config` A |Dictionary| for configuring a language server, + 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 + workspace/didChangeConfiguration command. + + If temporary files or directories are created for commands run with + `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* + + All command strings will be formatted for special character sequences. + Any substring `%s` will be replaced with the full path to the current file + being edited. This format option can be used to pass the exact filename + being edited to a program. + + For example: > + 'command': 'eslint -f unix --stdin --stdin-filename %s' +< + Any substring `%t` will be replaced with a path to a temporary file. Merely + adding `%t` will cause ALE to create a temporary file containing the + contents of the buffer being checked. All occurrences of `%t` in command + 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 behavior of + |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`. 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', +< + The character sequence `%%` can be used to emit a literal `%` into a + command, so literal character sequences `%s` and `%t` can be escaped by + using `%%s` and `%%t` instead, etc. + + Some |filename-modifiers| can be applied to `%s` and `%t`. Only `:h`, `:t`, + `:r`, and `:e` may be applied, other modifiers will be ignored. Filename + modifiers can be applied to the format markers by placing them after them. + + For example: > + 'command': '%s:h %s:e %s:h:t', +< + Given a path `/foo/baz/bar.txt`, the above command string will generate + something akin to `'/foo/baz' 'txt' 'baz'` + + 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#command#EscapeCommandPart()| function can + be used to replace those characters to avoid formatting issues. + + *ale-linter-loading-behavior* + + Linters for ALE will be loaded by searching |runtimepath| in the following + format: > + + ale_linters//.vim +< + Any linters which exist anywhere in 'runtimepath' with that directory + structure will be automatically loaded for the matching |filetype|. Filetypes + containing `.` characters will be split into individual parts, and files + will be loaded for each filetype between the `.` characters. + + Linters can be defined from vimrc and other files as long as this function + is loaded first. For example, the following code will define a Hello World + linter in vimrc in Vim 8: > + + " Plugins have to be loaded first. + " If you are using a plugin manager, run that first. + packloadall + + call ale#linter#Define('vim', { + \ 'name': 'echo-test', + \ 'executable': 'echo', + \ 'command': 'echo hello world', + \ 'callback': {buffer, lines -> map(lines, '{"text": v:val, "lnum": 1}')}, + \}) +< + +ale#linter#Get(filetype) *ale#linter#Get()* + + Return all of linters configured for a given filetype as a |List| of + |Dictionary| values in the format specified by |ale#linter#Define()|. + + Filetypes may be dot-separated to invoke linters for multiple filetypes: + for instance, the filetype `javascript.jsx` will return linters for both the + `javascript` and `jsx` filetype. + + Aliases may be defined in as described in |g:ale_linter_aliases|. Aliases + are applied after dot-separated filetypes are broken up into their + components. + + +ale#linter#PreventLoading(filetype) *ale#linter#PreventLoading()* + + Given a `filetype`, prevent any more linters from being loaded from + |runtimepath| for that filetype. This function can be called from vimrc or + similar to prevent ALE from loading linters. + + *ale#lsp_linter#SendRequest()* +ale#lsp_linter#SendRequest(buffer, linter_name, message, [Handler]) + + Send a custom request to an LSP linter. The arguments are defined as + follows: + + `buffer` A valid buffer number. + + `linter_name` A |String| identifying an LSP linter that is available and + enabled for the |filetype| of `buffer`. + + `message` A |List| in the form `[is_notification, method, parameters]`, + containing three elements: + `is_notification` - an |Integer| that has value 1 if the + request is a notification, 0 otherwise; + `method` - a |String|, identifying an LSP method supported + by `linter`; + `parameters` - a |dictionary| of LSP parameters that are + applicable to `method`. + + `Handler` Optional argument, meaningful only when `message[0]` is 0. + A |Funcref| that is called when a response to the request is + received, and takes as unique argument a dictionary + representing the response obtained from the server. + + *ale#other_source#ShowResults()* +ale#other_source#ShowResults(buffer, linter_name, loclist) + + Show results from another source of information. + + `buffer` must be a valid buffer number, and `linter_name` must be a unique + name for identifying another source of information. The `loclist` given + where the problems in a buffer are, and should be provided in the format ALE + uses for regular linter results. See |ale-loclist-format|. + + *ale#other_source#StartChecking()* +ale#other_source#StartChecking(buffer, linter_name) + + Tell ALE that another source of information has started checking a buffer. + + `buffer` must be a valid buffer number, and `linter_name` must be a unique + name for identifying another source of information. + + +ale#statusline#Count(buffer) *ale#statusline#Count()* + + Given the number of a buffer which may have problems, return a |Dictionary| + containing information about the number of problems detected by ALE. The + following keys are supported: + + `error` -> The number of problems with type `E` and `sub_type != 'style'` + `warning` -> The number of problems with type `W` and `sub_type != 'style'` + `info` -> The number of problems with type `I` + `style_error` -> The number of problems with type `E` and `sub_type == 'style'` + `style_warning` -> The number of problems with type `W` and `sub_type == 'style'` + `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 entry, 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 + ALE after all linters for one lint cycle have finished checking a buffer. + This variable may not be defined until ALE first checks a buffer, so it + should be accessed with |get()| or |getbufvar()|. For example: > + + " Print a message indicating how many times ALE has checked this buffer. + echo 'ALE has checked this buffer ' . get(b:, 'ale_linted') . ' time(s).' + " Print 'checked' using getbufvar() if a buffer has been checked. + echo getbufvar(bufnr(''), 'ale_linted', 0) > 0 ? 'checked' : 'not checked' +< + +g:ale_want_results_buffer *g:ale_want_results_buffer* + + `g:ale_want_results_buffer` is set to the number of the buffer being checked + when the |ALEWantResults| event is signaled. This variable should be read to + figure out which buffer other sources should lint. This variable can be read + in Lua scripts in the usual way via `vim.g.ale_want_results_buffer`. + + *ALECompletePost-autocmd* +ALECompletePost *ALECompletePost* + + This |User| autocmd is triggered after ALE inserts an item on + |CompleteDone|. This event can be used to run commands after a buffer + is changed by ALE as the result of completion. For example, `:ALEFix` can + be configured to run automatically when completion is done: > + + augroup FixAfterComplete + autocmd! + " Run ALEFix when completion items are added. + autocmd User ALECompletePost ALEFix! + " If ALE starts fixing a file, stop linters running for now. + autocmd User ALEFixPre ALELintStop + augroup END +< + *ALELintPre-autocmd* +ALELintPre *ALELintPre* + *ALELintPost-autocmd* +ALELintPost *ALELintPost* + *ALEFixPre-autocmd* +ALEFixPre *ALEFixPre* + *ALEFixPost-autocmd* +ALEFixPost *ALEFixPost* + + These |User| autocommands are triggered before and after every lint or fix + cycle. They can be used to update statuslines, send notifications, etc. + The autocmd commands are run with |:silent|, so |:unsilent| is required for + echoing messages. + + For example to change the color of the statusline while the linter is + running: > + + augroup ALEProgress + autocmd! + autocmd User ALELintPre hi Statusline ctermfg=darkgrey + autocmd User ALELintPost hi Statusline ctermfg=NONE + augroup END +< + Or to display the progress in the statusline: > + + let s:ale_running = 0 + let l:stl .= '%{s:ale_running ? "[linting]" : ""}' + augroup ALEProgress + autocmd! + autocmd User ALELintPre let s:ale_running = 1 | redrawstatus + autocmd User ALELintPost let s:ale_running = 0 | redrawstatus + augroup END +< + *ALEJobStarted-autocmd* +ALEJobStarted *ALEJobStarted* + + This |User| autocommand is triggered immediately after a job is successfully + run. This provides better accuracy for checking linter status with + |ale#engine#IsCheckingBuffer()| over |ALELintPre-autocmd|, which is actually + triggered before any linters are executed. + + *ALELSPStarted-autocmd* +ALELSPStarted *ALELSPStarted* + + This |User| autocommand is triggered immediately after an LSP connection is + successfully initialized. This provides a way to perform any additional + initialization work, such as setting up buffer-level mappings. + + *ALEWantResults-autocmd* +ALEWantResults *ALEWantResults* + + This |User| autocommand is triggered before ALE begins a lint cycle. Another + source can respond by calling |ale#other_source#StartChecking()|, and + |ALELintPre| will be signaled thereafter, to allow other plugins to know + that another source is checking the buffer. + + |g:ale_want_results_buffer| will be set to the number for a buffer being + checked when the event is signaled, and deleted after the event is done. + This variable should be read to know which buffer to check. + + Other plugins can use this event to start checking buffers when ALE events + for checking buffers are triggered. + + +=============================================================================== +10. Special Thanks *ale-special-thanks* + +Special thanks to Mark Grealish (https://www.bhalash.com/) for providing ALE's +snazzy looking ale glass logo. Cheers, Mark! + + +=============================================================================== +11. Contact *ale-contact* + +If you like this plugin, and wish to get in touch, check out the GitHub +page for issues and more at https://github.com/dense-analysis/ale + +If you wish to contact the author of this plugin directly, please feel +free to send an email to devw0rp@gmail.com. + +Please drink responsibly, or not at all, which is ironically the preference +of w0rp, who is teetotal. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/ftplugin/ale-fix-suggest.vim b/ftplugin/ale-fix-suggest.vim new file mode 100644 index 00000000..42ade0fd --- /dev/null +++ b/ftplugin/ale-fix-suggest.vim @@ -0,0 +1,5 @@ +" Close the ALEFixSuggest window with the q key. +noremap q :q! + +let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'execute') +let b:undo_ftplugin .= ' | execute "silent! unmap q"' diff --git a/ftplugin/ale-info.vim b/ftplugin/ale-info.vim new file mode 100644 index 00000000..a982a1b6 --- /dev/null +++ b/ftplugin/ale-info.vim @@ -0,0 +1,22 @@ +" Close the ALEInfo preview window with the q key. +noremap q :q! + +" Explicitly use the default synmaxcol for ale-info. +setlocal synmaxcol=3000 + +function! ALEInfoOpenHelp() abort + let l:variable = matchstr(getline('.'), '\v[gb]:ale_[a-z0-9_]+') + + if !empty(l:variable) + execute('help ' . l:variable) + endif +endfunction + +" Press space to open :help for an ALE Variable +nnoremap :call ALEInfoOpenHelp() + +let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'execute') +let b:undo_ftplugin .= ' | setlocal synmaxcol<' +let b:undo_ftplugin .= ' | execute "silent! unmap q"' +let b:undo_ftplugin .= ' | execute "silent! nunmap "' +let b:undo_ftplugin .= ' | if exists(''*ALEInfoOpenHelp'') | delfunction ALEInfoOpenHelp | endif' diff --git a/ftplugin/ale-preview-selection.vim b/ftplugin/ale-preview-selection.vim new file mode 100644 index 00000000..1ddd0abf --- /dev/null +++ b/ftplugin/ale-preview-selection.vim @@ -0,0 +1,31 @@ +" Close the ALEPreviewWindow window with the q key. +noremap q :q! +" Disable some keybinds for the selection window. +noremap v +noremap i +noremap I +noremap +noremap +noremap +noremap a +noremap A +noremap o +noremap O +" Keybinds for opening selection items. +noremap :call ale#preview#OpenSelection() +noremap t :call ale#preview#OpenSelectionInTab() + +let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'execute') +let b:undo_ftplugin .= ' | execute "silent! unmap q"' +let b:undo_ftplugin .= ' | execute "silent! unmap v"' +let b:undo_ftplugin .= ' | execute "silent! unmap i"' +let b:undo_ftplugin .= ' | execute "silent! unmap I"' +let b:undo_ftplugin .= ' | execute "silent! unmap "' +let b:undo_ftplugin .= ' | execute "silent! unmap "' +let b:undo_ftplugin .= ' | execute "silent! unmap "' +let b:undo_ftplugin .= ' | execute "silent! unmap a"' +let b:undo_ftplugin .= ' | execute "silent! unmap A"' +let b:undo_ftplugin .= ' | execute "silent! unmap o"' +let b:undo_ftplugin .= ' | execute "silent! unmap O"' +let b:undo_ftplugin .= ' | execute "silent! unmap "' +let b:undo_ftplugin .= ' | execute "silent! unmap t"' diff --git a/ftplugin/ale-preview.vim b/ftplugin/ale-preview.vim new file mode 100644 index 00000000..75f3bb50 --- /dev/null +++ b/ftplugin/ale-preview.vim @@ -0,0 +1,5 @@ +" Close the ALEPreviewWindow window with the q key. +noremap q :q! + +let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'execute') +let b:undo_ftplugin .= ' | execute "silent! unmap q"' diff --git a/lspconfig.vim b/lspconfig.vim new file mode 100644 index 00000000..0e25fdc2 --- /dev/null +++ b/lspconfig.vim @@ -0,0 +1,3 @@ +if get(g:, 'lspconfig', 0) + " lspconfig is installed. +endif diff --git a/lua/ale/diagnostics.lua b/lua/ale/diagnostics.lua new file mode 100644 index 00000000..ccd4c030 --- /dev/null +++ b/lua/ale/diagnostics.lua @@ -0,0 +1,95 @@ +local ale = require("ale") + +local module = {} + +local diagnostic_severity_map = { + E = vim.diagnostic.severity.ERROR, + W = vim.diagnostic.severity.WARN, + I = vim.diagnostic.severity.INFO +} + +-- A map of all possible values that we can consider virtualtext enabled for +-- from ALE's setting. +local virtualtext_enabled_set = { + ["all"] = true, + ["2"] = true, + [2] = true, + ["current"] = true, + ["1"] = true, + [1] = true, + [true] = true, +} + +---Send diagnostics to the Neovim diagnostics API +---@param buffer number The buffer number to retreive the variable for. +---@param loclist table The loclist array to report as diagnostics. +---@return nil +module.send = function(buffer, loclist) + local diagnostics = {} + + -- Convert all the ALE loclist items to the shape that Neovim's diagnostic + -- API is expecting. + for _, location in ipairs(loclist) do + if location.bufnr == buffer then + table.insert( + diagnostics, + -- All line numbers from ALE are 1-indexed, but all line + -- numbers in the diagnostics API are 0-indexed, so we have to + -- subtract 1 to make this work. + { + lnum = location.lnum - 1, + -- Ending line number, or if we don't have one, just make + -- it the same as the starting line number + end_lnum = (location.end_lnum or location.lnum) - 1, + -- Which column does the error start on? + col = math.max((location.col or 1) - 1, 0), + -- end_col does not appear to need 1 subtracted. + end_col = location.end_col, + -- Which severity: error, warning, or info? + severity = diagnostic_severity_map[location.type] or "E", + -- An error code + code = location.code, + -- The error message + message = location.text, + -- e.g. "rubocop" + source = location.linter_name, + } + ) + end + end + + local set_signs = ale.var(buffer, "set_signs") + local sign_priority = ale.var(buffer, "sign_priority") + local signs + + if (set_signs == 1 or set_signs == true) and sign_priority then + -- If signs are enabled, set the priority for them. + local local_cfg = { priority = sign_priority } + local global_cfg = vim.diagnostic.config().signs + + if global_cfg == false or global_cfg == true or global_cfg == nil then + signs = local_cfg + elseif type(global_cfg) == "table" then + signs = vim.tbl_extend("force", global_cfg, local_cfg) + else + -- If a global function is defined, then define a function + -- that calls that function when Neovim calls our function. + signs = function(...) + return vim.tbl_extend("force", global_cfg(...), local_cfg) + end + end + end + + vim.diagnostic.set( + vim.api.nvim_create_namespace("ale"), + buffer, + diagnostics, + { + virtual_text = + virtualtext_enabled_set[vim.g.ale_virtualtext_cursor] ~= nil, + signs = signs, + } + ) +end + +return module diff --git a/lua/ale/init.lua b/lua/ale/init.lua new file mode 100644 index 00000000..a436e78d --- /dev/null +++ b/lua/ale/init.lua @@ -0,0 +1,168 @@ +local ale = {} + +local global_settings = setmetatable({}, { + __index = function(_, key) + return vim.g['ale_' .. key] + end, + __newindex = function(_, key, value) + vim.g['ale_' .. key] = value + end +}) + +local buffer_settings = setmetatable({}, { + __index = function(_, key) + return vim.b['ale_' .. key] + end, + __newindex = function(_, key, value) + vim.b['ale_' .. key] = value + end +}) + +ale.set_global = function(c) + for key, value in pairs(c) do + global_settings[key] = value + end +end + +ale.set_buffer = function(c) + for key, value in pairs(c) do + buffer_settings[key] = value + end +end + +---(when called) Set global ALE settings, just like ale.setup.global. +---@class ALESetup +---@field global fun(c: table): nil -- Set global ALE settings. +---@field buffer fun(c: table): nil -- Set buffer-local ALE settings. +---@overload fun(c: table): nil +---@type ALESetup +ale.setup = setmetatable({ + ---Set global ALE settings. + ---@param c table The table of ALE settings to set. + ---@return nil + global = function(c) + ale.set_global(c) + end, + ---Set buffer-local ALE settings. + ---@param c table The table of ALE settings to set. + ---@return nil + buffer = function(c) + ale.set_buffer(c) + end, +}, { + __call = function(self, c) + self.global(c) + end, +}) + +---Run ALE linters on a buffer after a delay. +--- +---If a delay in milliseconds multiple times, the internal timer used by ALE +---will be reset, so ALE doesn't lint too often. +--- +---If the `linting_flag` is not 'lint_file' then linters that require files to +---be saved will no be run. +---@param delay number Milliseconds to wait for. A delay of 0 lints immediately. +---@param linting_flag string|nil If set to 'lint_file', run all linters. +---@param buffer number|nil The buffer to check. Defaults to the current buffer. +---@return nil +ale.queue = function(delay, linting_flag, buffer) + vim.fn["ale#Queue"](delay, linting_flag, buffer) +end + +---Check if ALE supports a given feature. +--- +---The ALE version can be checked with ale.has("ale-1.0.0"), etc. +---@param feature string The feature to test for. +---@return boolean supported If the feature is supported. +ale.has = function(feature) + return vim.fn["ale#Has"](feature) == 1 +end + +---Prefix a string with a single space if it is not empty. +---nil will be treated the same as an empty string. +--- +---This function is a convenience for chaining options for commands together +---without adding redundant whitespace. +---@param str string|nil A value to pad with whitespace. +---@return string padded A value padded with whitespace. +ale.pad = function(str) + if str == nil or str == "" then + return "" + end + + return " " .. str +end + +---Get an ALE variable for a buffer (first) or globally (second) +---@param buffer number The buffer number to retreive the variable for. +---@param variable_name string The variable to retrieve. +---@return any value The value for the ALE variable +ale.var = function(buffer, variable_name) + variable_name = "ale_" .. variable_name + local exists, value = pcall(vim.api.nvim_buf_get_var, buffer, variable_name) + + if exists then + return value + end + + return vim.g[variable_name] +end + +---Escape a string for use in a shell command +---@param str string The string to escape. +---@return string escaped The escaped string. +ale.escape = function(str) + local shell = vim.fn.fnamemodify(vim.o.shell, ":t") + + if shell:lower() == "cmd.exe" then + local step1 + + if str:find(" ") then + step1 = '"' .. str:gsub('"', '""') .. '"' + else + step1 = str:gsub("([&|<>^])", "^%1") + end + + local percent_subbed = step1:gsub("%%", "%%%%") + + return percent_subbed + end + + return vim.fn.shellescape(str) +end + +---Create a prefix for a shell command for adding environment variables. +---@param variable_name string The environment variable name. +---@param value string The value to set for the environment variable. +---@return string prefix The shell code for prefixing a command. +ale.env = function(variable_name, value) + if vim.fn.has("win32") then + return "set " .. ale.escape(variable_name .. "=" .. value) .. " && " + end + + return variable_name .. "=" .. ale.escape(value) .. " " +end + +---Get an array of arrays for mapping paths to and from filesystems for an ALE +---linter, as configured in the `filename_mappings` setting. +--- +---The result can be used to instruct ALE how to map between filesystems. +---@param buffer number The buffer number. +---@param name string The linter name. +---@return table mappings An array of arrays for mapping filenames. +ale.get_filename_mappings = function(buffer, name) + local linter_mappings = ale.var(buffer, "filename_mappings") + + if linter_mappings[1] ~= nil then + return linter_mappings + end + + if linter_mappings[name] == nil then + name = "*" + end + + return linter_mappings[name] or {} +end + +return ale diff --git a/lua/ale/lsp.lua b/lua/ale/lsp.lua new file mode 100644 index 00000000..6c54db12 --- /dev/null +++ b/lua/ale/lsp.lua @@ -0,0 +1,171 @@ +local module = {} + +module.start = function(config) + -- Neovim's luaeval sometimes adds a Boolean key to table we need to remove. + if type(config.init_options) == "table" + and config.init_options[true] ~= nil + then + config.init_options[true] = nil + end + + -- If configuring LSP via a socket connection, then generate the cmd + -- using vim.lsp.rpc.connect(), as defined in Neovim documentation. + if config.host then + local cmd_func = vim.lsp.rpc.connect(config.host, config.port) + config.host = nil + config.port = nil + + -- Wrap the cmd function so we don't throw errors back to the user + -- if the connection to an address fails to start. + -- + -- We will separately log in ALE that we failed to start a connection. + -- + -- In older Neovim versions TCP connections do not function if supplied + -- a hostname instead of an address. + config.cmd = function(dispatch) + local success, result = pcall(cmd_func, dispatch) + + if success then + return result + end + + return nil + end + end + + config.handlers = { + -- Override Neovim's handling of diagnostics to run through ALE's + -- functions so all of the functionality in ALE works. + ["textDocument/publishDiagnostics"] = function(err, result, _, _) + if err == nil then + vim.fn["ale#lsp_linter#HandleLSPDiagnostics"]( + config.name, + result.uri, + result.diagnostics + ) + end + end, + -- Handle pull model diagnostic data. + ["textDocument/diagnostic"] = function(err, result, request, _) + if err == nil then + local diagnostics + + if result.kind == "unchanged" then + diagnostics = "unchanged" + else + diagnostics = result.items + end + + vim.fn["ale#lsp_linter#HandleLSPDiagnostics"]( + config.name, + request.params.textDocument.uri, + diagnostics + ) + end + end, + -- When the pull model is enabled we have to handle and return + -- some kind of data for a server diagnostic refresh request. + ["workspace/diagnostic/refresh"] = function() + return {} + end, + } + + config.on_init = function(client, _) + -- Tell ALE about server capabilities as soon as we can. + -- This will inform ALE commands what can be done with each server, + -- such as "go to definition" support, etc. + vim.fn["ale#lsp#UpdateCapabilities"]( + config.name, + client.server_capabilities + ) + + -- Neovim calls `on_init` before marking a client as active, meaning + -- we can't get a client via get_client_by_id until after `on_init` is + -- called. By deferring execution of calling the init callbacks we + -- can only call them after the client becomes available, which + -- will make notifications for configuration changes work, etc. + vim.defer_fn(function() + vim.fn["ale#lsp#CallInitCallbacks"](config.name) + end, 0) + end + + config.get_language_id = function(bufnr, _) + return vim.fn["ale#lsp#GetLanguage"](config.name, bufnr) + end + + local capabilities = vim.lsp.protocol.make_client_capabilities() + + -- Language servers like Pyright do not enable the diagnostics pull model + -- unless dynamicRegistration is enabled for diagnostics. + if capabilities.textDocument.diagnostic ~= nil then + capabilities.textDocument.diagnostic.dynamicRegistration = true + config.capabilities = capabilities + end + + ---@diagnostic disable-next-line: missing-fields + return vim.lsp.start(config, { + attach = false, + silent = true, + }) +end + +module.buf_attach = function(args) + return vim.lsp.buf_attach_client(args.bufnr, args.client_id) +end + +module.buf_detach = function(args) + return vim.lsp.buf_detach_client(args.bufnr, args.client_id) +end + +-- Send a message to an LSP server. +-- Notifications do not need to be handled. +-- +-- Returns -1 when a message is sent, but no response is expected +-- 0 when the message is not sent and +-- >= 1 with the message ID when a response is expected. +module.send_message = function(args) + local client = vim.lsp.get_client_by_id(args.client_id) + + if client == nil then + return 0 + end + + if args.is_notification then + -- For notifications we send a request and expect no direct response. + local success = client.notify(args.method, args.params) + + if success then + return -1 + end + + return 0 + end + + local success, request_id + + -- For request we send a request and handle the response. + -- + -- We set the bufnr to -1 to prevent Neovim from flushing anything, as ALE + -- already flushes changes to files before sending requests. + success, request_id = client.request( + args.method, + args.params, + ---@diagnostic disable-next-line: param-type-mismatch + function(_, result, _, _) + vim.fn["ale#lsp#HandleResponse"](client.name, { + id = request_id, + result = result, + }) + end, + ---@diagnostic disable-next-line: param-type-mismatch + -1 + ) + + if success then + return request_id + end + + return 0 +end + +return module diff --git a/lua/ale/util.lua b/lua/ale/util.lua new file mode 100644 index 00000000..98b3bbd9 --- /dev/null +++ b/lua/ale/util.lua @@ -0,0 +1,14 @@ +local M = {} + +function M.configured_lspconfig_servers() + local configs = require 'lspconfig.configs' + local keys = {} + + for key, _ in pairs(configs) do + table.insert(keys, key) + end + + return keys +end + +return M diff --git a/plugin/ale.vim b/plugin/ale.vim new file mode 100644 index 00000000..ba702956 --- /dev/null +++ b/plugin/ale.vim @@ -0,0 +1,399 @@ +" Author: w0rp +" Description: Main entry point for the plugin: sets up prefs and autocommands +" Preferences can be set in vimrc files and so on to configure ale + +" Sanity Checks + +if exists('g:loaded_ale_dont_use_this_in_other_plugins_please') + finish +endif + +" Set a special flag used only by this plugin for preventing doubly +" loading the script. +let g:loaded_ale_dont_use_this_in_other_plugins_please = 1 + +" A flag for detecting if the required features are set. +if has('nvim') + " We check for Neovim 0.2.0+, but we only officially support NeoVim 0.7.0 + let s:has_features = has('timers') && has('nvim-0.2.0') +else + " Check if Job and Channel functions are available, instead of the + " features. This works better on old MacVim versions. + let s:has_features = has('timers') && exists('*job_start') && exists('*ch_close_in') +endif + +if !s:has_features + " Only output a warning if editing some special files. + if index(['', 'gitcommit'], &filetype) == -1 + " no-custom-checks + echoerr 'ALE requires NeoVim >= 0.7.0 or Vim 8 with +timers +job +channel' + " no-custom-checks + echoerr 'Please update your editor appropriately.' + endif + + " Stop here, as it won't work. + finish +endif + +" Set this flag so that other plugins can use it, like airline. +let g:loaded_ale = 1 + +" This global variable is used internally by ALE for tracking information for +" each buffer which linters are being run against. +let g:ale_buffer_info = {} +" This global Dictionary tracks data for fixing code. Don't mess with it. +let g:ale_fix_buffer_data = {} + +" User Configuration + +" This option prevents ALE autocmd commands from being run for particular +" filetypes which can cause issues. +let g:ale_filetype_blacklist = [ +\ 'dirvish', +\ 'nerdtree', +\ 'qf', +\ 'tags', +\ 'unite', +\] + +" This Dictionary configures which linters are enabled for which languages. +let g:ale_linters = get(g:, 'ale_linters', {}) +" This option can be changed to only enable explicitly selected linters. +let g:ale_linters_explicit = get(g:, 'ale_linters_explicit', v:false) +" Ignoring linters, for disabling some, or ignoring LSP diagnostics. +let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {}) +" Disabling all language server functionality. +let g:ale_disable_lsp = get(g:, 'ale_disable_lsp', 'auto') + +" This Dictionary configures which functions will be used for fixing problems. +let g:ale_fixers = get(g:, 'ale_fixers', {}) + +" This Dictionary allows users to set up filetype aliases for new filetypes. +let g:ale_linter_aliases = get(g:, 'ale_linter_aliases', {}) + +" This flag can be set with a number of milliseconds for delaying the +" execution of a linter when text is changed. The timeout will be set and +" cleared each time text is changed, so repeated edits won't trigger the +" jobs for linting until enough time has passed after editing is done. +let g:ale_lint_delay = get(g:, 'ale_lint_delay', 200) + +" This flag can be set to 'never' to disable linting when text is changed. +" This flag can also be set to 'always' or 'insert' to lint when text is +" changed in both normal and insert mode, or only in insert mode respectively. +let g:ale_lint_on_text_changed = get(g:, 'ale_lint_on_text_changed', 'normal') + +" This flag can be set to true or 1 to enable linting when leaving insert mode. +let g:ale_lint_on_insert_leave = get(g:, 'ale_lint_on_insert_leave', v:true) + +" When true or 1 linting is done when a buffer is entered. +let g:ale_lint_on_enter = get(g:, 'ale_lint_on_enter', v:true) + +" When true or 1 linting is done when a buffer is written. +let g:ale_lint_on_save = get(g:, 'ale_lint_on_save', v:true) + +" When true or 1 linting is done when the filetype changes. +let g:ale_lint_on_filetype_changed = get(g:, 'ale_lint_on_filetype_changed', v:true) + +" If set to true or 1, suggestions from LSP servers and tsserver will be shown. +let g:ale_lsp_suggestions = get(g:, 'ale_lsp_suggestions', v:false) + +" When true or 1 files are automatically fixed on save. +let g:ale_fix_on_save = get(g:, 'ale_fix_on_save', v:false) + +" When true or 1 ALE linting is enabled. +" Disabling ALE linting does not disable fixing of files. +let g:ale_enabled = get(g:, 'ale_enabled', 1) + +" A Dictionary mapping linter or fixer names to Arrays of two-item Arrays +" mapping filename paths from one system to another. +let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {}) + +" This Dictionary configures the default project roots for various linters. +let g:ale_root = get(g:, 'ale_root', {}) + +" These flags dictates if ale uses the quickfix or the loclist (loclist is the +" default, quickfix overrides loclist). +let g:ale_set_loclist = get(g:, 'ale_set_loclist', v:true) +let g:ale_set_quickfix = get(g:, 'ale_set_quickfix', v:false) + +" This flag can be set to 0 to disable setting signs. +" This is enabled by default only if the 'signs' feature exists. +let g:ale_set_signs = get(g:, 'ale_set_signs', has('signs') ? v:true : v:false) + +" This flag can be set to 0 to disable setting error highlights. +let g:ale_set_highlights = get(g:, 'ale_set_highlights', has('syntax') ? v:true : v:false) + +" This List can be configured to exclude particular highlights. +let g:ale_exclude_highlights = get(g:, 'ale_exclude_highlights', []) + +" When set to true or 1 problems on lines are echoed when the cursor moves. +let g:ale_echo_cursor = get(g:, 'ale_echo_cursor', v:true) + +" If set to true or 1 automatically show errors in the preview window. +let g:ale_cursor_detail = get(g:, 'ale_cursor_detail', v:false) + +" This flag can be changed to disable/enable virtual text. +let g:ale_virtualtext_cursor = get(g:, 'ale_virtualtext_cursor', (has('nvim-0.3.2') || has('patch-9.0.0297') && has('textprop') && has('popupwin')) ? 'all' : 'disabled') + +" When set to true or 1 LSP hover messages are shown at the cursor. +let g:ale_hover_cursor = get(g:, 'ale_hover_cursor', v:true) + +" When true or 1 to close the preview window on entering Insert Mode. +let g:ale_close_preview_on_insert = get(g:, 'ale_close_preview_on_insert', v:false) + +" When set to true or 1 balloon support is enabled. +let g:ale_set_balloons = get(g:, 'ale_set_balloons', (has('balloon_eval') && has('gui_running')) ? v:true : v:false) + +" When set to true or 1 use the preview window for showing hover messages. +let g:ale_hover_to_preview = get(g:, 'ale_hover_to_preview', v:false) + +" When set to true or 1 use floating preview windows in Neovim. +let g:ale_floating_preview = get(g:, 'ale_floating_preview', v:false) + +" When set to true or 1 show hove messages in floating windows in Neovim. +let g:ale_hover_to_floating_preview = get(g:, 'ale_hover_to_floating_preview', v:false) + +" When set to true or 1 details are shown in floating windows in Neovim. +let g:ale_detail_to_floating_preview = get(g:, 'ale_detail_to_floating_preview', v:false) + +" Border setting for floating preview windows +" +" The elements in the list set the characters for the left, top, top-left, +" top-right, bottom-right, bottom-left, right, and bottom of the border +" respectively +let g:ale_floating_window_border = get(g:, 'ale_floating_window_border', ['|', '-', '+', '+', '+', '+', '|', '-']) + +" When set to true or 1 warnings for trailing whitespace are shown. +let g:ale_warn_about_trailing_whitespace = get(g:, 'ale_warn_about_trailing_whitespace', v:true) +" When set to true or 1 warnings for trailing blank lines are shown. +let g:ale_warn_about_trailing_blank_lines = get(g:, 'ale_warn_about_trailing_blank_lines', v:true) + +" When set to true or 1 the command history is logged. +let g:ale_history_enabled = get(g:, 'ale_history_enabled', v:true) + +" When set to true or 1 the full output of commands is logged. +let g:ale_history_log_output = get(g:, 'ale_history_log_output', v:true) + +" When set to true or 1 enable ALE's built-in autocompletion functionality. +let g:ale_completion_enabled = get(g:, 'ale_completion_enabled', v:false) + +" When set to true or 1 enable automatic detection of pipenv for Python. +let g:ale_python_auto_pipenv = get(g:, 'ale_python_auto_pipenv', v:false) + +" When set to true or 1 enable automatic detection of poetry for Python. +let g:ale_python_auto_poetry = get(g:, 'ale_python_auto_poetry', v:false) + +" When set to true or 1 enable automatic detection of uv for Python. +let g:ale_python_auto_uv = get(g:, 'ale_python_auto_uv', v:false) + +" When set to true or 1 enable automatically updating environment variables +" for running Python linters from virtualenv directories. +" +" The variables are set based on ALE's virtualenv detection. +let g:ale_python_auto_virtualenv = get(g:, 'ale_python_auto_virtualenv', v:false) + +" This variable can be overridden to set the GO111MODULE environment variable. +let g:ale_go_go111module = get(g:, 'ale_go_go111module', '') + +" The default executable for deno. Must be set before ALE lints any buffers. +let g:ale_deno_executable = get(g:, 'ale_deno_executable', 'deno') + +" If true or 1, enable a popup menu for commands. +let g:ale_popup_menu_enabled = get(g:, 'ale_popup_menu_enabled', has('gui_running') ? v:true : v:false) + +" If true or 1, save hidden files when code actions are applied. +let g:ale_save_hidden = get(g:, 'ale_save_hidden', v:false) + +" If true or 1, disables ALE's built in error display. +" +" Instead, all errors are piped to the Neovim diagnostics API. +let g:ale_use_neovim_diagnostics_api = get(g:, 'ale_use_neovim_diagnostics_api', has('nvim-0.7') ? v:true : v:false) + +if g:ale_use_neovim_diagnostics_api && !has('nvim-0.7') + " no-custom-checks + echoerr('Setting g:ale_use_neovim_diagnostics_api to true or 1 requires Neovim 0.7+.') +endif + +" If true or 1, uses Neovim's built-in LSP client to integrate with LSP, which +" improves ALE's integration with built-in Neovim tools and other plugins. +let g:ale_use_neovim_lsp_api = get(g:, 'ale_use_neovim_lsp_api', has('nvim-0.8') ? v:true : v:false) + +" If 1, replaces ALE's use of jobs and channels to connect to language +" servers, plus the custom code, and instead hooks ALE into Neovim's built-in +" language server tools. +if g:ale_use_neovim_lsp_api && !has('nvim-0.8') + " no-custom-checks + echoerr('Setting g:ale_use_neovim_lsp_api to true or 1 requires Neovim 0.8+.') +endif + +if g:ale_set_balloons || g:ale_set_balloons is# 'hover' + call ale#balloon#Enable() +endif + +if g:ale_completion_enabled + call ale#completion#Enable() +endif + +if g:ale_popup_menu_enabled + call ale#code_action#EnablePopUpMenu() +endif + +" Define commands for moving through warnings and errors. +command! -bar -nargs=* ALEPrevious +\ :call ale#loclist_jumping#WrapJump('before', ) +command! -bar -nargs=* ALENext +\ :call ale#loclist_jumping#WrapJump('after', ) + +command! -bar ALEPreviousWrap :call ale#loclist_jumping#Jump('before', 1) +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) + +" A command for showing error details. +command! -bar ALEDetail :call ale#cursor#ShowCursorDetail() + +" Define commands for turning ALE on or off. +command! -bar ALEToggle :call ale#toggle#Toggle() +command! -bar ALEEnable :call ale#toggle#Enable() +command! -bar ALEDisable :call ale#toggle#Disable() +command! -bar ALEReset :call ale#toggle#Reset() +" Commands for turning ALE on or off for a buffer. +command! -bar ALEToggleBuffer :call ale#toggle#ToggleBuffer(bufnr('')) +command! -bar ALEEnableBuffer :call ale#toggle#EnableBuffer(bufnr('')) +command! -bar ALEDisableBuffer :call ale#toggle#DisableBuffer(bufnr('')) +command! -bar ALEResetBuffer :call ale#toggle#ResetBuffer(bufnr('')) +" A command to stop all LSP-like clients, including tsserver. +command! -bar ALEStopAllLSPs :call ale#lsp#reset#StopAllLSPs() +" A command to stop a specific language server, or tsseserver. +command! -bar -bang -nargs=1 -complete=customlist,ale#lsp#reset#Complete ALEStopLSP :call ale#lsp#reset#StopLSP(, '') + +" A command for linting manually. +command! -bar ALELint :call ale#Queue(0, 'lint_file') +" Stop current jobs when linting. +command! -bar ALELintStop :call ale#engine#Stop(bufnr('')) + +" Commands to manually populate the quickfixes. +command! -bar ALEPopulateQuickfix :call ale#list#ForcePopulateErrorList(1) +command! -bar ALEPopulateLocList :call ale#list#ForcePopulateErrorList(0) + +" Define a command to get information about current filetype. +command! -bar -nargs=* ALEInfo :call ale#debugging#InfoCommand() +" Deprecated and scheduled for removal in 4.0.0. +command! -bar ALEInfoToClipboard :call ale#debugging#InfoToClipboardDeprecatedCommand() +" Copy ALE information to a file. +command! -bar -nargs=1 ALEInfoToFile :call ale#debugging#InfoToFile() + +" Fix problems in files. +command! -bar -bang -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '', ) +" Suggest registered functions to use for fixing problems. +command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype) + +" Go to definition for tsserver and LSP +command! -bar -nargs=* ALEGoToDefinition :call ale#definition#GoToCommandHandler('', ) + +" Go to type definition for tsserver and LSP +command! -bar -nargs=* ALEGoToTypeDefinition :call ale#definition#GoToCommandHandler('type', ) + +" Go to implementation for tsserver and LSP +command! -bar -nargs=* ALEGoToImplementation :call ale#definition#GoToCommandHandler('implementation', ) + +" Repeat a previous selection in the preview window +command! -bar ALERepeatSelection :call ale#preview#RepeatSelection() + +" Find references for tsserver and LSP +command! -bar -nargs=* ALEFindReferences :call ale#references#Find() + +" 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() + +" Complete text with tsserver and LSP +command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual') + +" Try to find completions for the current symbol that add additional text. +command! -bar ALEImport :call ale#completion#Import() + +" Rename symbols using tsserver and LSP +command! -bar -bang ALERename :call ale#rename#Execute() + +" Rename file using tsserver +command! -bar -bang ALEFileRename :call ale#filerename#Execute() + +" Apply code actions to a range. +command! -bar -range ALECodeAction :call ale#codefix#Execute() + +" Organize import statements using tsserver +command! -bar ALEOrganizeImports :call ale#organize_imports#Execute() + +" mappings for commands +nnoremap (ale_previous) :ALEPrevious +nnoremap (ale_previous_wrap) :ALEPreviousWrap +nnoremap (ale_previous_error) :ALEPrevious -error +nnoremap (ale_previous_wrap_error) :ALEPrevious -wrap -error +nnoremap (ale_previous_warning) :ALEPrevious -warning +nnoremap (ale_previous_wrap_warning) :ALEPrevious -wrap -warning +nnoremap (ale_next) :ALENext +nnoremap (ale_next_wrap) :ALENextWrap +nnoremap (ale_next_error) :ALENext -error +nnoremap (ale_next_wrap_error) :ALENext -wrap -error +nnoremap (ale_next_warning) :ALENext -warning +nnoremap (ale_next_wrap_warning) :ALENext -wrap -warning +nnoremap (ale_first) :ALEFirst +nnoremap (ale_last) :ALELast +nnoremap (ale_toggle) :ALEToggle +nnoremap (ale_enable) :ALEEnable +nnoremap (ale_disable) :ALEDisable +nnoremap (ale_reset) :ALEReset +nnoremap (ale_toggle_buffer) :ALEToggleBuffer +nnoremap (ale_enable_buffer) :ALEEnableBuffer +nnoremap (ale_disable_buffer) :ALEDisableBuffer +nnoremap (ale_reset_buffer) :ALEResetBuffer +nnoremap (ale_lint) :ALELint +nnoremap (ale_detail) :ALEDetail +nnoremap (ale_fix) :ALEFix +nnoremap (ale_go_to_definition) :ALEGoToDefinition +nnoremap (ale_go_to_definition_in_tab) :ALEGoToDefinition -tab +nnoremap (ale_go_to_definition_in_split) :ALEGoToDefinition -split +nnoremap (ale_go_to_definition_in_vsplit) :ALEGoToDefinition -vsplit +nnoremap (ale_go_to_type_definition) :ALEGoToTypeDefinition +nnoremap (ale_go_to_type_definition_in_tab) :ALEGoToTypeDefinition -tab +nnoremap (ale_go_to_type_definition_in_split) :ALEGoToTypeDefinition -split +nnoremap (ale_go_to_type_definition_in_vsplit) :ALEGoToTypeDefinition -vsplit +nnoremap (ale_go_to_implementation) :ALEGoToImplementation +nnoremap (ale_go_to_implementation_in_tab) :ALEGoToImplementation -tab +nnoremap (ale_go_to_implementation_in_split) :ALEGoToImplementation -split +nnoremap (ale_go_to_implementation_in_vsplit) :ALEGoToImplementation -vsplit +nnoremap (ale_find_references) :ALEFindReferences +nnoremap (ale_hover) :ALEHover +nnoremap (ale_documentation) :ALEDocumentation +inoremap (ale_complete) :ALEComplete +nnoremap (ale_import) :ALEImport +nnoremap (ale_rename) :ALERename +nnoremap (ale_filerename) :ALEFileRename +nnoremap (ale_code_action) :ALECodeAction +nnoremap (ale_repeat_selection) :ALERepeatSelection +nnoremap (ale_info) :ALEInfo +nnoremap (ale_info_echo) :ALEInfo -echo +nnoremap (ale_info_clipboard) :ALEInfo -clipboard +nnoremap (ale_info_preview) :ALEInfo -preview + +" Set up autocmd groups now. +call ale#events#Init() + +" Housekeeping + +augroup ALECleanupGroup + autocmd! + " Clean up buffers automatically when they are unloaded. + autocmd BufDelete * if exists('*ale#engine#Cleanup') | call ale#engine#Cleanup(str2nr(expand(''))) | endif + autocmd QuitPre * call ale#events#QuitEvent(str2nr(expand(''))) + + if exists('##VimSuspend') + 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..a692dc31 --- /dev/null +++ b/rplugin/python3/deoplete/sources/ale.py @@ -0,0 +1,62 @@ +""" +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 = 1000 + self.is_bytepos = True + self.min_pattern_length = 1 + self.is_volatile = True + # Do not forget to update s:trigger_character_map in completion.vim in + # updating entries in this map. + self.input_patterns = { + '_': r'\.\w*$', + 'rust': r'(\.|::)\w*$', + 'typescript': r'(\.|\'|")\w*$', + 'cpp': r'(\.|::|->)\w*$', + 'c': r'(\.|->)\w*$', + } + + # Returns an integer for the start position, as with omnifunc. + def get_complete_position(self, context): + return self.vim.call( + 'ale#completion#GetCompletionPositionForDeoplete', context['input'] + ) + + def gather_candidates(self, context): + # Stop early if ALE can't provide completion data for this buffer. + if not self.vim.call('ale#completion#CanProvideCompletions'): + return None + + event = context.get('event') + + if event == 'Async': + result = self.vim.call('ale#completion#GetCompletionResult') + + return result or [] + + if context.get('is_refresh'): + self.vim.command( + "call ale#completion#GetCompletions('ale-callback', " + + "{'callback': {completions -> deoplete#auto_complete() }})" + ) + + return [] diff --git a/run-tests b/run-tests new file mode 100755 index 00000000..a4ddb051 --- /dev/null +++ b/run-tests @@ -0,0 +1,317 @@ +#!/usr/bin/env bash +DOCKER=${DOCKER:-docker} +export DOCKER +# Author: w0rp +# +# This script runs tests for the ALE project. Run `./run-tests --help` for +# options, or read the output below. +# + +image=docker.io/denseanalysis/ale + +# Create docker image tag based on Dockerfile contents +if [ -n "$(command -v md5)" ]; then + image_tag=$(md5 -q Dockerfile) +else + image_tag=$(md5sum Dockerfile | cut -d' ' -f1) +fi +git_version=$(git describe --always --tags) + +# Used in all test scripts for running the selected Docker image. +DOCKER_RUN_IMAGE="$image:$image_tag" +export DOCKER_RUN_IMAGE + +tests='test/*.vader test/*/*.vader test/*/*/*.vader' +# These flags are forwarded to the script for running Vader tests. +verbose_flag='' +quiet_flag='' +run_neovim_07_tests=1 +run_neovim_08_tests=1 +run_vim_80_tests=1 +run_vim_90_tests=1 +run_lua_tests=1 +run_linters=1 + +while [ $# -ne 0 ]; do + case $1 in + -v) + verbose_flag='-v' + shift + ;; + -q) + quiet_flag='-q' + shift + ;; + --build-image) + run_vim_80_tests=0 + run_vim_90_tests=0 + run_neovim_07_tests=0 + run_neovim_08_tests=0 + run_lua_tests=0 + run_linters=0 + shift + ;; + --neovim-only) + run_vim_80_tests=0 + run_vim_90_tests=0 + run_lua_tests=0 + run_linters=0 + shift + ;; + --neovim-07-only) + run_neovim_08_tests=0 + run_vim_80_tests=0 + run_vim_90_tests=0 + run_lua_tests=0 + run_linters=0 + shift + ;; + --neovim-08-only) + run_neovim_07_tests=0 + run_vim_80_tests=0 + run_vim_90_tests=0 + run_lua_tests=0 + run_linters=0 + shift + ;; + --vim-only) + run_neovim_07_tests=0 + run_neovim_08_tests=0 + run_lua_tests=0 + run_linters=0 + shift + ;; + --vim-80-only) + run_neovim_07_tests=0 + run_neovim_08_tests=0 + run_vim_90_tests=0 + run_lua_tests=0 + run_linters=0 + shift + ;; + --vim-90-only) + run_neovim_07_tests=0 + run_neovim_08_tests=0 + run_vim_80_tests=0 + run_lua_tests=0 + run_linters=0 + shift + ;; + --linters-only) + run_vim_80_tests=0 + run_vim_90_tests=0 + run_neovim_07_tests=0 + run_neovim_08_tests=0 + run_lua_tests=0 + shift + ;; + --lua-only) + run_vim_80_tests=0 + run_vim_90_tests=0 + run_neovim_07_tests=0 + run_neovim_08_tests=0 + run_linters=0 + shift + ;; + --fast) + run_vim_80_tests=0 + run_vim_90_tests=0 + run_neovim_07_tests=0 + run_neovim_08_tests=1 + shift + ;; + --help) + echo 'Usage: ./run-tests [OPTION]... [FILE]...' + echo + echo 'Filenames can be given as arguments to run a subset of tests.' + echo 'For example: ./run-tests test/test_ale_var.vader' + echo + echo 'Options:' + echo ' -v Enable verbose output' + echo ' -q Hide output for successful tests' + echo ' --build-image Run docker image build only.' + echo ' --neovim-only Run tests only for Neovim' + echo ' --neovim-07-only Run tests only for Neovim 0.7' + echo ' --neovim-08-only Run tests only for Neovim 0.8' + echo ' --vim-only Run tests only for Vim' + echo ' --vim-80-only Run tests only for Vim 8.2' + echo ' --vim-90-only Run tests only for Vim 9.0' + echo ' --lua-only Run only Lua tests' + echo ' --linters-only Run only Vint and custom checks' + echo ' --fast Run only the fastest Vim and custom checks' + echo ' --help Show this help text' + echo ' -- Stop parsing options after this' + exit 0 + ;; + --) + shift + break + ;; + -?*) + echo "Invalid argument: $1" 1>&2 + exit 1 + ;; + *) + break + ;; + esac +done + +# Allow tests to be passed as arguments. +if [ $# -ne 0 ]; then + # This doesn't perfectly handle work splitting, but none of our files + # have spaces in the names. + tests="$*" + + # Don't run other tools when targeting tests. + run_linters=0 + run_lua_tests=0 +fi + +# Delete .swp files in the test directory, which cause Vim 8 to hang. +find test -name '*.swp' -delete + +set -eu + +# Check if docker un image is available locally +has_image=$(docker images --quiet "${image}:${image_tag}" | wc -l) + +if [[ "$DOCKER" == docker ]]; then + arch=$(docker info -f '{{ .Architecture }}') +elif [[ "$DOCKER" == podman ]]; then + arch=$(podman info -f '{{ .Host.Arch }}') + if [[ "$arch" == "amd64" ]]; then + arch="x86_64" + fi +else + echo "The DOCKER environment variable must be docker or podman, not ${DOCKER}" + exit 1 +fi + +download_image() { + if [[ $arch != x86_64 ]]; then + echo "Pre-built docker image is not available for architecture ${arch}" + return 1 + fi + echo "Downloading run image ${image}:${image_tag}" + docker pull "${image}:${image_tag}" &> /dev/null +} + +if [ "$has_image" -eq 0 ] && ! download_image; then + echo "Building run image ${image}:${image_tag}" + + build_args=( --build-arg GIT_VERSION="$git_version" ) + + if [[ $arch != x86_64 ]]; then + echo "Building testbed/vim:24 for $arch" + testbed_vim_ref=902917c4caa50db2f2e80009b839605602f9f014 + "$DOCKER" build -t "testbed/vim:$testbed_vim_ref" "https://github.com/Vimjas/vim-testbed.git#$testbed_vim_ref" + build_args+=( --build-arg TESTBED_VIM_VERSION="$testbed_vim_ref" ) + fi + + "$DOCKER" build "${build_args[@]}" -t "${image}:${image_tag}" . + "$DOCKER" tag "${image}:${image_tag}" "${image}:latest" + + if [[ -z "${DOCKER_HUB_USER:-}" || -z "${DOCKER_HUB_PASS:-}" ]]; then + echo "Docker Hub credentials not set, skip push" + else + echo "Push ${image}:${image_tag} to Docker Hub" + echo "$DOCKER_HUB_PASS" | docker login -u "$DOCKER_HUB_USER" --password-stdin + docker push "${image}:${image_tag}" + fi +else + echo "Docker run image ${image}:${image_tag} ready" +fi + +"$DOCKER" tag "${image}:${image_tag}" "${image}:latest" + +output_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir') + +trap '{ rm -rf "$output_dir"; }' EXIT + +file_number=0 +pid_list='' + +# Used for killing tests when you kill the script. +cancel_tests() { + set +e + + if [ -n "$pid_list" ]; then + for pid in $pid_list; do + kill "$pid" + wait "$pid" + done + fi + + # shellcheck disable=SC2046 + docker kill $(docker ps -a -q --filter ancestor="$image" --format='{{.ID}}') &> /dev/null + + if [ -d "$output_dir" ]; then + rm -rf "$output_dir" + fi + + echo + exit 1 +} + +trap cancel_tests INT TERM + +for vim in $("$DOCKER" run --rm "$DOCKER_RUN_IMAGE" ls /vim-build/bin | grep '^neovim\|^vim' ); do + if ( [[ $vim =~ ^vim-v8.0 ]] && ((run_vim_80_tests)) ) \ + || ( [[ $vim =~ ^vim-v9.0 ]] && ((run_vim_90_tests)) ) \ + || ( [[ $vim =~ ^neovim-v0.7 ]] && ((run_neovim_07_tests)) ) \ + || ( [[ $vim =~ ^neovim-v0.8 ]] && ((run_neovim_08_tests)) ); then + echo "Starting Vim: $vim..." + file_number=$((file_number+1)) + test/script/run-vader-tests $quiet_flag $verbose_flag "$vim" "$tests" \ + > "$output_dir/$file_number" 2>&1 & + pid_list="$pid_list $!" + fi +done + +if ((run_lua_tests)); then + echo "Starting Lua tests..." + file_number=$((file_number+1)) + test/script/run-lua-tests $quiet_flag > "$output_dir/$file_number" 2>&1 & + pid_list="$pid_list $!" +fi + +if ((run_linters)); then + echo "Starting Vint..." + file_number=$((file_number+1)) + test/script/run-vint > "$output_dir/$file_number" 2>&1 & + pid_list="$pid_list $!" + + echo "Starting Custom checks..." + file_number=$((file_number+1)) + test/script/custom-checks &> "$output_dir/$file_number" 2>&1 & + pid_list="$pid_list $!" +fi + +echo + +failed=0 +index=0 + +for pid in $pid_list; do + this_failed=0 + index=$((index+1)) + + if ! wait "$pid"; then + failed=1 + this_failed=1 + fi + + # Hide output for things that passed if -q is set. + if [ "$quiet_flag" != '-q' ] || ((this_failed)); then + cat "$output_dir/$index" + fi +done + +if ((failed)); then + echo 'Something went wrong!' +else + echo 'All tests passed!' +fi + +exit $failed diff --git a/run-tests.bat b/run-tests.bat new file mode 100644 index 00000000..9ba6b554 --- /dev/null +++ b/run-tests.bat @@ -0,0 +1,30 @@ +@echo off +REM Run tests on Windows. +REM +REM To run these tests, you should set up your Windows machine with the same +REM paths that are used in AppVeyor. + +set tests=test/*.vader test/*/*.vader test/*/*/*.vader test/*/*/*/*.vader + +REM Use the first argument for selecting tests to run. +if not "%1"=="" set tests=%1 + +set VADER_OUTPUT_FILE=%~dp0\vader_output +REM Automatically re-run Windows tests, which can fail some times. +set tries=0 + +:RUN_TESTS +set /a tries=%tries%+1 +type nul > "%VADER_OUTPUT_FILE%" +C:\vim\vim\vim80\vim.exe -u test/vimrc "+Vader! %tests%" +set code=%ERRORLEVEL% + +IF %code% EQU 0 GOTO :SHOW_RESULTS +IF %tries% GEQ 2 GOTO :SHOW_RESULTS +GOTO :RUN_TESTS + +:SHOW_RESULTS +type "%VADER_OUTPUT_FILE%" +del "%VADER_OUTPUT_FILE%" + +exit /B %code% diff --git a/supported-tools.md b/supported-tools.md new file mode 100644 index 00000000..fa3200ea --- /dev/null +++ b/supported-tools.md @@ -0,0 +1,775 @@ +# 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. + + + +**Legend** + +| Key | Definition | +| ------------- | ----------------------------------------------------------------- | +| :floppy_disk: | May only run on files on disk (see: `help ale-lint-file-linters` | +| :warning: | Disabled by default | + +--- + +* Ada + * [ada_language_server](https://github.com/AdaCore/ada_language_server) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [gcc](https://gcc.gnu.org) + * [gnatpp](https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ugn/gnat_utility_programs.html#the-gnat-pretty-printer-gnatpp) :floppy_disk: +* Ansible + * [ansible-language-server](https://github.com/ansible/ansible-language-server/) + * [ansible-lint](https://github.com/willthames/ansible-lint) :floppy_disk: +* API Blueprint + * [drafter](https://github.com/apiaryio/drafter) +* APKBUILD + * [apkbuild-fixer](https://gitlab.alpinelinux.org/Leo/atools) + * [apkbuild-lint](https://gitlab.alpinelinux.org/Leo/atools) + * [secfixes-check](https://gitlab.alpinelinux.org/Leo/atools) +* AsciiDoc + * [alex](https://github.com/get-alex/alex) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [languagetool](https://languagetool.org/) :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) + * [llvm-mc](https://llvm.org) +* Astro + * [eslint](http://eslint.org/) + * [prettier](https://github.com/prettier/prettier) +* AVRA + * [avra](https://github.com/Ro5bert/avra) +* Awk + * [gawk](https://www.gnu.org/software/gawk/) +* Bash + * [bashate](https://github.com/openstack/bashate) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [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) +* Bats + * [shellcheck](https://www.shellcheck.net/) +* Bazel + * [buildifier](https://github.com/bazelbuild/buildtools) +* BibTeX + * [bibclean](http://ftp.math.utah.edu/pub/bibclean/) +* Bicep + * [bicep](https://github.com/Azure/bicep) :floppy_disk: +* BitBake + * [oelint-adv](https://github.com/priv-kweihmann/oelint-adv) +* Bourne Shell + * shell [-n flag](http://linux.die.net/man/1/sh) + * [shellcheck](https://www.shellcheck.net/) + * [shfmt](https://github.com/mvdan/sh) +* C + * [astyle](http://astyle.sourceforge.net/) + * [ccls](https://github.com/MaskRay/ccls) + * [clang](http://clang.llvm.org/) + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) :floppy_disk: + * [clangd](https://clang.llvm.org/extra/clangd.html) + * [clangtidy](http://clang.llvm.org/extra/clang-tidy/) :floppy_disk: + * [cppcheck](http://cppcheck.sourceforge.net) + * [cpplint](https://github.com/cpplint/cpplint) + * [cquery](https://github.com/cquery-project/cquery) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [flawfinder](https://www.dwheeler.com/flawfinder/) + * [gcc](https://gcc.gnu.org/) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* C# + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [csc](http://www.mono-project.com/docs/about-mono/languages/csharp/) :floppy_disk: see:`help ale-cs-csc` for details and configuration + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [dotnet-format](https://github.com/dotnet/format) + * [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) + * [astyle](http://astyle.sourceforge.net/) + * [ccls](https://github.com/MaskRay/ccls) + * [clang](http://clang.llvm.org/) + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) :floppy_disk: + * [clangd](https://clang.llvm.org/extra/clangd.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) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [flawfinder](https://www.dwheeler.com/flawfinder/) + * [gcc](https://gcc.gnu.org/) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* C3 + * [c3lsp](https://github.com/pherrymason/c3-lsp) +* Cairo + * [scarb](https://docs.swmansion.com/scarb/) :floppy_disk: + * [starknet](https://starknet.io/docs) +* Chef + * [cookstyle](https://docs.chef.io/cookstyle.html) + * [foodcritic](http://www.foodcritic.io/) :floppy_disk: +* Clojure + * [clj-kondo](https://github.com/borkdude/clj-kondo) + * [cljfmt](https://github.com/weavejester/cljfmt) + * [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) + * [cmake-lint](https://github.com/cheshirekow/cmake_format) + * [cmakelint](https://github.com/cmake-lint/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 + * [VSCode CSS language server](https://github.com/hrsh7th/vscode-langservers-extracted) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [css-beautify](https://github.com/beautify-web/js-beautify) + * [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 + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [clangd](https://clang.llvm.org/extra/clangd.html) + * [nvcc](http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html) :floppy_disk: +* Cypher + * [cypher-lint](https://github.com/cleishm/libcypher-parser) +* Cython (pyrex filetype) + * [cython](http://cython.org/) +* D + * [dfmt](https://github.com/dlang-community/dfmt) + * [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 + * [analysis_server](https://github.com/dart-lang/sdk/tree/master/pkg/analysis_server) + * [dart-analyze](https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli) :floppy_disk: + * [dart-format](https://github.com/dart-lang/sdk/tree/master/utils/dartfmt) + * [dartfmt](https://github.com/dart-lang/sdk/tree/master/utils/dartfmt) + * [language_server](https://github.com/natebosch/dart_language_server) +* desktop + * [desktop-file-validate](https://www.freedesktop.org/wiki/Software/desktop-file-utils/) +* Dhall + * [dhall-format](https://github.com/dhall-lang/dhall-lang) + * [dhall-freeze](https://github.com/dhall-lang/dhall-lang) + * [dhall-lint](https://github.com/dhall-lang/dhall-lang) +* Dockerfile + * [dockerfile_lint](https://github.com/projectatomic/dockerfile_lint) + * [dockerlinter](https://github.com/buddy-works/dockerfile-linter) + * [dprint](https://dprint.dev) + * [hadolint](https://github.com/hadolint/hadolint) +* Elixir + * [credo](https://github.com/rrrene/credo) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) :warning: + * [dialyxir](https://github.com/jeremyjh/dialyxir) + * [dogma](https://github.com/lpil/dogma) :floppy_disk: + * [elixir-ls](https://github.com/elixir-lsp/elixir-ls) :warning: + * [lexical](https://github.com/lexical-lsp/lexical) :warning: + * [mix](https://hexdocs.pm/mix/Mix.html) :warning: :floppy_disk: +* Elm + * [elm-format](https://github.com/avh4/elm-format) + * [elm-ls](https://github.com/elm-tooling/elm-language-server) + * [elm-make](https://github.com/elm/compiler) +* Erb + * [erb](https://apidock.com/ruby/ERB) + * [erb-formatter](https://github.com/nebulab/erb-formatter) + * [erblint](https://github.com/Shopify/erb-lint) + * [erubi](https://github.com/jeremyevans/erubi) + * [erubis](https://github.com/kwatch/erubis) + * [htmlbeautifier](https://github.com/threedaymonk/htmlbeautifier) + * [ruumba](https://github.com/ericqweinstein/ruumba) +* Erlang + * [SyntaxErl](https://github.com/ten0s/syntaxerl) + * [dialyzer](http://erlang.org/doc/man/dialyzer.html) :floppy_disk: + * [elvis](https://github.com/inaka/elvis) :floppy_disk: + * [erlang-mode](https://www.erlang.org/doc/apps/tools/erlang_mode_chapter.html) (The Erlang mode for Emacs) + * [erlang_ls](https://github.com/erlang-ls/erlang_ls) + * [erlc](http://erlang.org/doc/man/erlc.html) + * [erlfmt](https://github.com/WhatsApp/erlfmt) +* Fish + * fish [-n flag](https://linux.die.net/man/1/fish) + * [fish_indent](https://fishshell.com/docs/current/cmds/fish_indent.html) +* 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) +* Gleam + * [gleam_format](https://github.com/gleam-lang/gleam) + * [gleamlsp](https://github.com/gleam-lang/gleam) +* GLSL + * [glslang](https://github.com/KhronosGroup/glslang) + * [glslls](https://github.com/svenstaro/glsl-language-server) +* Go + * [bingo](https://github.com/saibing/bingo) :warning: + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) :warning: + * [go build](https://golang.org/cmd/go/) :warning: :floppy_disk: + * [go mod](https://golang.org/cmd/go/) :warning: :floppy_disk: + * [go vet](https://golang.org/cmd/vet/) :floppy_disk: + * [gofmt](https://golang.org/cmd/gofmt/) + * [gofumpt](https://github.com/mvdan/gofumpt) + * [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: + * [golines](https://github.com/segmentio/golines) + * [gopls](https://github.com/golang/go/wiki/gopls) + * [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: + * [revive](https://github.com/mgechev/revive) :warning: :floppy_disk: + * [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) :warning: :floppy_disk: +* Go HTML Templates + * [djlint](https://djlint.com/) +* GraphQL + * [eslint](http://eslint.org/) + * [gqlint](https://github.com/happylinks/gqlint) + * [prettier](https://github.com/prettier/prettier) +* Groovy + * [npm-groovy-lint](https://github.com/nvuillam/npm-groovy-lint) +* 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 + * [djlint](https://djlint.com/) + * [ember-template-lint](https://github.com/rwjblue/ember-template-lint) +* Haskell + * [brittany](https://github.com/lspitzner/brittany) + * [cabal-ghc](https://www.haskell.org/cabal/) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [floskell](https://github.com/ennocramer/floskell) + * [fourmolu](https://github.com/fourmolu/fourmolu) + * [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) + * [hindent](https://hackage.haskell.org/package/hindent) + * [hlint](https://hackage.haskell.org/package/hlint) + * [hls](https://github.com/haskell/haskell-language-server) + * [ormolu](https://github.com/tweag/ormolu) + * [stack-build](https://haskellstack.org/) :floppy_disk: + * [stack-ghc](https://haskellstack.org/) + * [stylish-haskell](https://github.com/jaspervdj/stylish-haskell) +* HCL + * [packer-fmt](https://github.com/hashicorp/packer) + * [terraform-fmt](https://github.com/hashicorp/terraform) +* HTML + * [VSCode HTML language server](https://github.com/hrsh7th/vscode-langservers-extracted) + * [alex](https://github.com/get-alex/alex) + * [angular](https://www.npmjs.com/package/@angular/language-server) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [djlint](https://www.djlint.com/) + * [eslint](https://github.com/BenoitZugmeyer/eslint-plugin-html) + * [fecs](http://fecs.baidu.com/) + * [html-beautify](https://beautifier.io/) + * [htmlhint](http://htmlhint.com/) + * [prettier](https://github.com/prettier/prettier) + * [proselint](http://proselint.com/) + * [rustywind](https://github.com/avencera/rustywind) + * [tidy](http://www.html-tidy.org/) + * [write-good](https://github.com/btford/write-good) +* HTML Angular + * [djlint](https://djlint.com/) +* HTML Django + * [djlint](https://djlint.com/) +* HTTP + * [kulala_fmt](https://github.com/mistweaverco/kulala-fmt) +* Hurl + * [hurlfmt](https://hurl.dev) +* Idris + * [idris](http://www.idris-lang.org/) +* Ink + * [ink-language-server](https://github.com/ephread/ink-language-server) +* Inko + * [inko](https://inko-lang.org/) :floppy_disk: +* ISPC + * [ispc](https://ispc.github.io/) :floppy_disk: +* Java + * [PMD](https://pmd.github.io/) + * [checkstyle](http://checkstyle.sourceforge.net) :floppy_disk: + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [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) + * [uncrustify](https://github.com/uncrustify/uncrustify) +* JavaScript + * [biome](https://biomejs.dev/) + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [deno](https://deno.land/) + * [dprint](https://dprint.dev/) + * [eslint](http://eslint.org/) + * [fecs](http://fecs.baidu.com/) + * [flow](https://flowtype.org/) + * [jscs](https://jscs-dev.github.io/) + * [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) +* Jinja + * [djlint](https://djlint.com/) +* JSON + * [VSCode JSON language server](https://github.com/hrsh7th/vscode-langservers-extracted) + * [biome](https://biomejs.dev/) + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) :warning: + * [dprint](https://dprint.dev) + * [eslint](http://eslint.org/) :warning: + * [fixjson](https://github.com/rhysd/fixjson) + * [jq](https://stedolan.github.io/jq/) :warning: + * [json.tool](https://docs.python.org/3/library/json.html#module-json.tool) :warning: + * [jsonlint](https://github.com/zaach/jsonlint) + * [prettier](https://github.com/prettier/prettier) + * [spectral](https://github.com/stoplightio/spectral) +* JSON5 + * [eslint](http://eslint.org/) :warning: +* JSONC + * [biome](https://biomejs.dev/) + * [eslint](http://eslint.org/) :warning: +* Jsonnet + * [jsonnet-lint](https://jsonnet.org/learning/tools.html) + * [jsonnetfmt](https://jsonnet.org/learning/tools.html) +* Julia + * [languageserver](https://github.com/JuliaEditorSupport/LanguageServer.jl) +* Kotlin + * [kotlinc](https://kotlinlang.org) :floppy_disk: + * [ktlint](https://ktlint.github.io) + * [languageserver](https://github.com/fwcd/KotlinLanguageServer) see `:help ale-integration-kotlin` for configuration instructions +* LaTeX + * [alex](https://github.com/get-alex/alex) + * [chktex](http://www.nongnu.org/chktex/) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [lacheck](https://www.ctan.org/pkg/lacheck) + * [proselint](http://proselint.com/) + * [redpen](http://redpen.cc/) + * [texlab](https://texlab.netlify.com) + * [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 + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [lua-format](https://github.com/Koihik/LuaFormatter) + * [lua-language-server](https://github.com/LuaLS/lua-language-server) + * [luac](https://www.lua.org/manual/5.1/luac.html) + * [luacheck](https://github.com/mpeterv/luacheck) + * [luafmt](https://github.com/trixnz/lua-fmt) + * [selene](https://github.com/Kampfkarren/selene) + * [stylua](https://github.com/johnnymorganz/stylua) +* Mail + * [alex](https://github.com/get-alex/alex) + * [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/get-alex/alex) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [languagetool](https://languagetool.org/) :floppy_disk: + * [markdownlint](https://github.com/DavidAnson/markdownlint) :floppy_disk: + * [marksman](https://github.com/artempyanykh/marksman) + * [mdl](https://github.com/mivok/markdownlint) + * [pandoc](https://pandoc.org) + * [prettier](https://github.com/prettier/prettier) + * [proselint](http://proselint.com/) + * [pymarkdown](https://github.com/jackdewinter/pymarkdown) + * [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: +* Nickel + * [nickel_format](https://github.com/tweag/nickel#formatting) +* Nim + * [nim check](https://nim-lang.org/docs/nimc.html) :floppy_disk: + * [nimlsp](https://github.com/PMunch/nimlsp) + * nimpretty +* nix + * [alejandra](https://github.com/kamadorueda/alejandra) + * [deadnix](https://github.com/astro/deadnix) + * [nix-instantiate](http://nixos.org/nix/manual/#sec-nix-instantiate) + * [nixfmt](https://github.com/serokell/nixfmt) + * [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt) + * [rnix-lsp](https://github.com/nix-community/rnix-lsp) + * [statix](https://github.com/nerdypepper/statix) +* nroff + * [alex](https://github.com/get-alex/alex) + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* Nunjucks + * [djlint](https://djlint.com/) +* Objective-C + * [ccls](https://github.com/MaskRay/ccls) + * [clang](http://clang.llvm.org/) + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [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 + * [dune](https://dune.build/) + * [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-ocaml-merlin` for configuration instructions + * [ocamlformat](https://github.com/ocaml-ppx/ocamlformat) + * [ocamllsp](https://github.com/ocaml/ocaml-lsp) + * [ocp-indent](https://github.com/OCamlPro/ocp-indent) + * [ols](https://github.com/freebroccolo/ocaml-language-server) +* Odin + * [ols](https://github.com/DanielGavin/ols) +* OpenApi + * [ibm_validator](https://github.com/IBM/openapi-validator) + * [prettier](https://github.com/prettier/prettier) + * [yamllint](https://yamllint.readthedocs.io/) +* OpenSCAD + * [SCA2D](https://gitlab.com/bath_open_instrumentation_group/sca2d) :floppy_disk: + * [scadformat](https://github.com/hugheaves/scadformat) +* Packer (HCL) + * [packer-fmt-fixer](https://github.com/hashicorp/packer) +* Pascal + * [ptop](https://www.freepascal.org/tools/ptop.var) +* 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 + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [intelephense](https://github.com/bmewburn/intelephense-docs) + * [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/) + * [php-cs-fixer](https://cs.symfony.com) + * [phpactor](https://github.com/phpactor/phpactor) + * [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer) + * [phpcs](https://github.com/squizlabs/PHP_CodeSniffer) + * [phpmd](https://phpmd.org) + * [phpstan](https://github.com/phpstan/phpstan) + * [pint](https://github.com/laravel/pint) :beer: + * [psalm](https://getpsalm.org) :floppy_disk: + * [tlint](https://github.com/tightenco/tlint) +* PO + * [alex](https://github.com/get-alex/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/get-alex/alex) + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* Pony + * [ponyc](https://github.com/ponylang/ponyc) +* PowerShell + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [powershell](https://github.com/PowerShell/PowerShell) + * [psscriptanalyzer](https://github.com/PowerShell/PSScriptAnalyzer) +* Prolog + * [swipl](https://github.com/SWI-Prolog/swipl-devel) +* proto + * [buf-format](https://github.com/bufbuild/buf) :floppy_disk: + * [buf-lint](https://github.com/bufbuild/buf) :floppy_disk: + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) + * [protoc-gen-lint](https://github.com/ckaznocha/protoc-gen-lint) :floppy_disk: + * [protolint](https://github.com/yoheimuta/protolint) :floppy_disk: +* 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) +* PureScript + * [purescript-language-server](https://github.com/nwolverson/purescript-language-server) + * [purs-tidy](https://github.com/natefaubion/purescript-tidy) + * [purty](https://gitlab.com/joneshf/purty) +* Python + * [autoflake](https://github.com/myint/autoflake) :floppy_disk: + * [autoimport](https://lyz-code.github.io/autoimport/) + * [autopep8](https://github.com/hhatto/autopep8) + * [bandit](https://github.com/PyCQA/bandit) :warning: + * [black](https://github.com/psf/black) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [flake8](http://flake8.pycqa.org/en/latest/) + * [flakehell](https://github.com/flakehell/flakehell) + * [isort](https://github.com/timothycrosley/isort) + * [mypy](http://mypy-lang.org/) + * [prospector](https://github.com/PyCQA/prospector) :warning: :floppy_disk: + * [pycln](https://github.com/hadialqattan/pycln) + * [pycodestyle](https://github.com/PyCQA/pycodestyle) :warning: + * [pydocstyle](https://www.pydocstyle.org/) :warning: + * [pyflakes](https://github.com/PyCQA/pyflakes) + * [pyflyby](https://github.com/deshaw/pyflyby) :warning: + * [pylama](https://github.com/klen/pylama) :floppy_disk: + * [pylint](https://www.pylint.org/) :floppy_disk: + * [pylsp](https://github.com/python-lsp/python-lsp-server) :warning: + * [pyre](https://github.com/facebook/pyre-check) :warning: + * [pyright](https://github.com/microsoft/pyright) + * [refurb](https://github.com/dosisod/refurb) :floppy_disk: + * [reorder-python-imports](https://github.com/asottile/reorder_python_imports) + * [ruff](https://github.com/charliermarsh/ruff) + * [ruff-format](https://docs.astral.sh/ruff/formatter/) + * [unimport](https://github.com/hakancelik96/unimport) + * [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 + * [languageserver](https://github.com/REditorSupport/languageserver) + * [lintr](https://github.com/jimhester/lintr) + * [styler](https://github.com/r-lib/styler) +* Racket + * [racket-langserver](https://github.com/jeapostrophe/racket-langserver/tree/master) + * [raco](https://docs.racket-lang.org/raco/) + * [raco_fmt](https://docs.racket-lang.org/fmt/) +* Re:VIEW + * [redpen](http://redpen.cc/) +* 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) + * [reason-language-server](https://github.com/jaredly/reason-language-server) + * [refmt](https://github.com/reasonml/reason-cli) +* Rego + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [opacheck](https://www.openpolicyagent.org/docs/latest/cli/#opa-check) + * [opafmt](https://www.openpolicyagent.org/docs/latest/cli/#opa-fmt) +* REST + * [kulala_fmt](https://github.com/mistweaverco/kulala-fmt) +* reStructuredText + * [alex](https://github.com/get-alex/alex) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [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) +* Robot + * [rflint](https://github.com/boakley/robotframework-lint) +* RPM spec + * [rpmlint](https://github.com/rpm-software-management/rpmlint) :warning: (see `:help ale-integration-spec`) +* Ruby + * [brakeman](http://brakemanscanner.org/) :floppy_disk: + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [debride](https://github.com/seattlerb/debride) + * [packwerk](https://github.com/Shopify/packwerk) :floppy_disk: + * [prettier](https://github.com/prettier/plugin-ruby) + * [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) + * [rubyfmt](https://github.com/fables-tales/rubyfmt) + * [rufo](https://github.com/ruby-formatter/rufo) + * [solargraph](https://solargraph.org) + * [sorbet](https://github.com/sorbet/sorbet) + * [standardrb](https://github.com/testdouble/standard) + * [steep](https://github.com/soutaro/steep) + * [syntax_tree](https://github.com/ruby-syntax-tree/syntax_tree) +* Rust + * [cargo](https://github.com/rust-lang/cargo) :floppy_disk: (see `:help ale-integration-rust` for configuration instructions) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [rls](https://github.com/rust-lang-nursery/rls) :warning: + * [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer) :warning: + * [rustc](https://www.rust-lang.org/) :warning: + * [rustfmt](https://github.com/rust-lang-nursery/rustfmt) +* Salt + * [salt-lint](https://github.com/warpnet/salt-lint) +* Sass + * [sass-lint](https://www.npmjs.com/package/sass-lint) + * [stylelint](https://github.com/stylelint/stylelint) +* Scala + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [fsc](https://www.scala-lang.org/old/sites/default/files/linuxsoft_archives/docu/files/tools/fsc.html) + * [metals](https://scalameta.org/metals/) + * [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 + * [forge](https://github.com/foundry-rs/forge) + * [solc](https://solidity.readthedocs.io/) + * [solhint](https://github.com/protofire/solhint) + * [solium](https://github.com/duaraghav8/Solium) +* SQL + * [dprint](https://dprint.dev) + * [pgformatter](https://github.com/darold/pgFormatter) + * [sql-lint](https://github.com/joereynolds/sql-lint) + * [sqlfluff](https://github.com/sqlfluff/sqlfluff) + * [sqlfmt](https://github.com/jackc/sqlfmt) + * [sqlformat](https://github.com/andialbrecht/sqlparse) + * [sqlint](https://github.com/purcell/sqlint) +* Stylus + * [stylelint](https://github.com/stylelint/stylelint) +* SugarSS + * [stylelint](https://github.com/stylelint/stylelint) +* Svelte + * [prettier](https://github.com/prettier/prettier) + * [svelteserver](https://github.com/sveltejs/language-tools/tree/master/packages/language-server) +* Swift + * [Apple swift-format](https://github.com/apple/swift-format) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [sourcekit-lsp](https://github.com/apple/sourcekit-lsp) + * [swiftformat](https://github.com/nicklockwood/SwiftFormat) + * [swiftlint](https://github.com/realm/SwiftLint) +* systemd + * [systemd-analyze](https://www.freedesktop.org/software/systemd/man/systemd-analyze.html) :floppy_disk: +* Tcl + * [nagelfar](http://nagelfar.sourceforge.net) :floppy_disk: +* Terraform + * [checkov](https://github.com/bridgecrewio/checkov) + * [terraform](https://github.com/hashicorp/terraform) + * [terraform-fmt-fixer](https://github.com/hashicorp/terraform) + * [terraform-ls](https://github.com/hashicorp/terraform-ls) + * [terraform-lsp](https://github.com/juliosueiras/terraform-lsp) + * [tflint](https://github.com/wata727/tflint) + * [tfsec](https://github.com/aquasecurity/tfsec) +* Texinfo + * [alex](https://github.com/get-alex/alex) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* Text + * [alex](https://github.com/get-alex/alex) :warning: + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [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/) + * [thriftcheck](https://github.com/pinterest/thriftcheck) +* TOML + * [dprint](https://dprint.dev) +* TypeScript + * [biome](https://biomejs.dev/) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [deno](https://deno.land/) + * [dprint](https://dprint.dev/) + * [eslint](http://eslint.org/) + * [fecs](http://fecs.baidu.com/) + * [prettier](https://github.com/prettier/prettier) + * [standard](http://standardjs.com/) + * [tslint](https://github.com/palantir/tslint) + * [tsserver](https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29) + * typecheck +* Typst + * [typstyle](https://github.com/Enter-tainer/typstyle) +* V + * [v](https://github.com/vlang/v/) :floppy_disk: + * [vfmt](https://github.com/vlang/v/) +* VALA + * [uncrustify](https://github.com/uncrustify/uncrustify) + * [vala_lint](https://github.com/vala-lang/vala-lint) :floppy_disk: +* Verilog + * [hdl-checker](https://pypi.org/project/hdl-checker) + * [iverilog](https://github.com/steveicarus/iverilog) + * [slang](https://github.com/MikePopoloski/slang) + * [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) + * [yosys](http://www.clifford.at/yosys/) :floppy_disk: +* 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 + * [vimls](https://github.com/iamcco/vim-language-server) + * [vint](https://github.com/Kuniwak/vint) +* Vim help + * [alex](https://github.com/get-alex/alex) :warning: + * [proselint](http://proselint.com/) :warning: + * [write-good](https://github.com/btford/write-good) :warning: +* Vue + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [prettier](https://github.com/prettier/prettier) + * [vls](https://github.com/vuejs/vetur/tree/master/server) + * [volar](https://github.com/johnsoncodehk/volar) +* WGSL + * [naga](https://github.com/gfx-rs/naga) +* XHTML + * [alex](https://github.com/get-alex/alex) + * [cspell](https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell) + * [proselint](http://proselint.com/) + * [write-good](https://github.com/btford/write-good) +* XML + * [xmllint](http://xmlsoft.org/xmllint.html) +* YAML + * [actionlint](https://github.com/rhysd/actionlint) + * [circleci](https://circleci.com/docs/2.0/local-cli) :floppy_disk: :warning: + * [gitlablint](https://github.com/elijah-roberts/gitlab-lint) + * [prettier](https://github.com/prettier/prettier) + * [spectral](https://github.com/stoplightio/spectral) + * [swaglint](https://github.com/byCedric/swaglint) :warning: + * [yaml-language-server](https://github.com/redhat-developer/yaml-language-server) + * [yamlfix](https://lyz-code.github.io/yamlfix) + * [yamlfmt](https://github.com/google/yamlfmt) + * [yamllint](https://yamllint.readthedocs.io/) + * [yq](https://github.com/mikefarah/yq) +* YANG + * [yang-lsp](https://github.com/theia-ide/yang-lsp) +* Yara + * [yls](https://github.com/avast/yls) +* Zeek + * [zeek](http://zeek.org) :floppy_disk: +* Zig + * [zigfmt](https://github.com/ziglang/zig) + * [zlint](https://github.com/DonIsaac/zlint) + * [zls](https://github.com/zigtools/zls) diff --git a/syntax/ale-fix-suggest.vim b/syntax/ale-fix-suggest.vim new file mode 100644 index 00000000..19734f4c --- /dev/null +++ b/syntax/ale-fix-suggest.vim @@ -0,0 +1,13 @@ +if exists('b:current_syntax') + finish +endif + +syn match aleFixerComment /^.*$/ +syn match aleFixerName /\(^ *\|, \)'[^']*'/ +syn match aleFixerHelp /^See :help ale-fix-configuration/ + +hi def link aleFixerComment Comment +hi def link aleFixerName String +hi def link aleFixerHelp Statement + +let b:current_syntax = 'ale-fix-suggest' diff --git a/syntax/ale-info.vim b/syntax/ale-info.vim new file mode 100644 index 00000000..d47b58b8 --- /dev/null +++ b/syntax/ale-info.vim @@ -0,0 +1,30 @@ +if exists('b:current_syntax') + finish +endif + +" Exhaustively list different ALE Info directives to match here. +" This should hopefully avoid matching too eagerly. +syn match aleInfoDirective /^ *Current Filetype:/ +syn match aleInfoDirective /^ *Available Linters:/ +syn match aleInfoDirective /^ *Enabled Linters:/ +syn match aleInfoDirective /^ *Ignored Linters:/ +syn match aleInfoDirective /^ *Suggested Fixers:/ +syn match aleInfoDirective /^ *Command History:/ + +syn match aleCommandNoOutput /^<<>>$/ + +hi def link aleInfoDirective Title +hi def link aleInfoDirective Title +hi def link aleCommandNoOutput Comment + +" Use Vim syntax highlighting for Vim options. +unlet! b:current_syntax +syntax include @srcVim syntax/vim.vim +syntax region aleInfoVimRegionLinter matchgroup=aleInfoDirective start="^ *Linter Variables:$" end="^ $" contains=@srcVim +syntax region aleInfoVimRegionGlobal matchgroup=aleInfoDirective start="^ *Global Variables:$" end="^ $" contains=@srcVim + +unlet! b:current_syntax +syntax include @srcAleFixSuggest syntax/ale-fix-suggest.vim +syntax region aleInfoFixSuggestRegion matchgroup=aleInfoDirective start="^ *Suggested Fixers:$" end="^ $" contains=@srcAleFixSuggest + +let b:current_syntax = 'ale-info' diff --git a/syntax/ale-preview-selection.vim b/syntax/ale-preview-selection.vim new file mode 100644 index 00000000..879ba096 --- /dev/null +++ b/syntax/ale-preview-selection.vim @@ -0,0 +1,11 @@ +if exists('b:current_syntax') + finish +endif + +syn match alePreviewSelectionFilename /\v^([a-zA-Z]?:?[^:]+)/ +syn match alPreviewNumber /\v:\d+:\d+$/ + +hi def link alePreviewSelectionFilename String +hi def link alePreviewNumber Number + +let b:current_syntax = 'ale-preview-selection' diff --git a/test-files/python/no_uv/whatever.py b/test-files/python/no_uv/whatever.py new file mode 100644 index 00000000..e69de29b diff --git a/test/.config/nvim/init.vim b/test/.config/nvim/init.vim new file mode 120000 index 00000000..90f52f07 --- /dev/null +++ b/test/.config/nvim/init.vim @@ -0,0 +1 @@ +../../vimrc \ No newline at end of file diff --git a/test/completion/test_ale_import_command.vader b/test/completion/test_ale_import_command.vader new file mode 100644 index 00000000..6f186aae --- /dev/null +++ b/test/completion/test_ale_import_command.vader @@ -0,0 +1,568 @@ +Before: + Save g:ale_enabled + Save b:ale_enabled + Save g:ale_lint_on_text_changed + Save g:ale_completion_enabled + Save g:ale_completion_autoimport + Save g:ale_completion_max_suggestions + Save g:ale_linters + Save b:ale_linters + + let g:ale_enabled = 0 + let b:ale_enabled = 0 + let g:ale_lint_on_text_changed = 'always' + let g:ale_completion_enabled = 0 + let g:ale_completion_autoimport = 0 + let g:ale_completion_max_suggestions = 50 + let g:ale_linters = {'typescript': ['tsserver'], 'python': ['pyre']} + unlet! b:ale_linters + + let g:server_started_value = 1 + let g:request_id = 0 + let g:LastCallback = v:null + let g:LastHandleCallback = v:null + let g:sent_message_list = [] + let g:code_action_list = [] + let g:execute_list = [] + let g:ale_queue_call_list = [] + + runtime autoload/ale.vim + runtime autoload/ale/util.vim + runtime autoload/ale/code_action.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + + function! ale#util#Execute(expr) abort + call add(g:execute_list, a:expr) + endfunction + + function! ale#Queue(...) abort + call add(g:ale_queue_call_list, a:000) + endfunction + + function! ale#lsp#RegisterCallback(id, Callback) abort + let g:LastHandleCallback = a:Callback + endfunction + + function! ale#lsp#NotifyForChanges(id, buffer) abort + endfunction + + function! ale#lsp#HasCapability(id, capability) abort + return 1 + endfunction + + function! ale#lsp#Send(id, message) abort + let g:request_id += 1 + + call add(g:sent_message_list, a:message) + + return g:request_id + endfunction + + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let g:LastCallback = a:Callback + + return g:server_started_value + endfunction + + function! ale#code_action#HandleCodeAction(code_action, options) abort + Assert !get(a:options, 'should_save') + + call add(g:code_action_list, a:code_action) + endfunction + + function GetLastMessage() + return get(g:execute_list, -1, '') + endfunction + + function CheckLintStates(conn_id, message) + " Check that we request more linter results after adding completions. + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_enabled = 0 + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_enabled = 1 + let g:ale_lint_on_text_changed = 1 + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = v:true + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'normal' + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'insert' + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_queue_call_list = [] + let g:ale_lint_on_text_changed = 'never' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = '0' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 0 + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'xxx' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + endfunction + +After: + call ale#linter#Reset() + + Restore + + delfunction GetLastMessage + delfunction CheckLintStates + + unlet! g:LastCallback + unlet! g:LastHandleCallback + unlet! g:request_id + unlet! g:server_started_value + unlet! g:sent_message_list + unlet! g:code_action_list + unlet! g:ale_queue_call_list + unlet! g:execute_list + unlet! g:received_message + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completeopt + unlet! b:ale_completion_info + unlet! b:ale_completion_result + unlet! b:ale_complete_done_time + + runtime autoload/ale.vim + runtime autoload/ale/util.vim + runtime autoload/ale/code_action.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + +Given typescript(Some example TypeScript code): + let xyz = 123 + let foo = missingword + + let abc = 456 + +Execute(ALEImport should complain when there's no word at the cursor): + call setpos('.', [bufnr(''), 3, 1, 0]) + ALEImport + + AssertEqual 'echom ''Nothing to complete at cursor!''', GetLastMessage() + +Execute(ALEImport should tell the user if no LSP is available): + let g:server_started_value = 0 + + call setpos('.', [bufnr(''), 2, 16, 0]) + ALEImport + + AssertEqual + \ 'echom ''No completion providers are available.''', + \ GetLastMessage() + +Execute(ALEImport should request imports correctly for tsserver): + call setpos('.', [bufnr(''), 2, 16, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 21, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 21, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + call g:LastHandleCallback(347, { + \ 'request_seq': 1, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'missingwordIgnoreMe'}, + \ {'name': 'missingword'}, + \ ], + \}) + + AssertEqual + \ [ + \ [0, 'ts@completions', { + \ 'file': expand('%:p'), + \ 'includeExternalModuleExports': v:true, + \ 'offset': 21, + \ 'line': 2, + \ 'prefix': 'missingword', + \ }], + \ [0, 'ts@completionEntryDetails', { + \ 'file': expand('%:p'), + \ 'entryNames': [{'name': 'missingword'}], + \ 'offset': 21, + \ 'line': 2, + \ }] + \ ], + \ g:sent_message_list + AssertEqual 2, b:ale_completion_info.request_id + + let g:ale_enabled = 1 + let g:received_message = { + \ 'request_seq': 2, + \ 'command': 'completionEntryDetails', + \ 'body': [ + \ { + \ 'name': 'missingword', + \ 'kind': 'className', + \ 'displayParts': [], + \ 'codeActions': [{ + \ 'description': 'import { missingword } from "./Something";', + \ 'changes': [], + \ }], + \ }, + \ ], + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual + \ [ + \ { + \ 'description': 'import { missingword } from "./Something";', + \ 'changes': [], + \ }, + \ ], + \ g:code_action_list + + call CheckLintStates(347, g:received_message) + +Execute(ALEImport should tell the user when no completions were found from tsserver): + call setpos('.', [bufnr(''), 2, 16, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 21, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 21, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + call g:LastHandleCallback(347, { + \ 'request_seq': 1, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'missingwordIgnoreMe'}, + \ ], + \}) + + AssertEqual 'echom ''No possible imports found.''', GetLastMessage() + +Given python(Some example Python code): + xyz = 123 + foo = missingword + + abc = 456 + +Execute(ALEImport should request imports correctly for language servers): + call setpos('.', [bufnr(''), 2, 12, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 17, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 17, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + AssertEqual + \ [ + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'character': 16, 'line': 1} + \ }], + \ ], + \ g:sent_message_list + AssertEqual 1, b:ale_completion_info.request_id + + let g:ale_enabled = 1 + let g:received_message = { + \ 'id': 1, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'Some other word we should ignore', + \ 'filterText': 'missingwordIgnoreMe', + \ 'insertText': 'missingwordIgnoreMe', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingwordIgnoreMe', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingwordIgnoreMe', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'Some word without text edits', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ }, + \ { + \ 'detail': 'The word we should use', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingword', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'The other word we should not use', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something_else import missingword', + \ }, + \ ], + \ }, + \ ], + \ }, + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual + \ [ + \ { + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('%:p'), + \ 'textChanges': [ + \ { + \ 'start': {'line': 2, 'offset': 2}, + \ 'end': {'line': 3, 'offset': 2}, + \ 'newText': 'from something import missingword', + \ }, + \ ], + \ }, + \ ], + \ }, + \ ], + \ g:code_action_list + + call CheckLintStates(347, g:received_message) + +Execute(ALEImport should tell the user when no completions were found from a language server): + call setpos('.', [bufnr(''), 2, 12, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 17, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 17, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + AssertEqual + \ [ + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'character': 16, 'line': 1} + \ }], + \ ], + \ g:sent_message_list + AssertEqual 1, b:ale_completion_info.request_id + + let g:received_message = { + \ 'id': 1, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'Some other word we should ignore', + \ 'filterText': 'missingwordIgnoreMe', + \ 'insertText': 'missingwordIgnoreMe', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingwordIgnoreMe', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingwordIgnoreMe', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'Some word without text edits', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ }, + \ ], + \ }, + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual 'echom ''No possible imports found.''', GetLastMessage() diff --git a/test/completion/test_complete_events.vader b/test/completion/test_complete_events.vader new file mode 100644 index 00000000..cee15985 --- /dev/null +++ b/test/completion/test_complete_events.vader @@ -0,0 +1,35 @@ +Before: + let g:complete_post_triggered = 0 + + augroup VaderTest + autocmd! + autocmd User ALECompletePost let g:complete_post_triggered = 1 + augroup END + +After: + unlet! b:ale_completion_info + unlet! g:complete_post_triggered + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + +Execute(ALECompletePost should not be triggered when completion is cancelled): + call ale#completion#HandleUserData({}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should not be triggered when tools other than ALE insert completions): + call ale#completion#HandleUserData({'user_data': ''}) + call ale#completion#HandleUserData({'user_data': '{}'}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should be triggered when ALE inserts completions): + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \}) + + Assert g:complete_post_triggered diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader new file mode 100644 index 00000000..ed6dbea3 --- /dev/null +++ b/test/completion/test_completion_events.vader @@ -0,0 +1,486 @@ +Before: + Save g:ale_completion_enabled + Save g:ale_completion_delay + Save g:ale_completion_max_suggestions + Save &l:omnifunc + Save &l:completeopt + + unlet! b:ale_completion_enabled + let g:ale_completion_enabled = 1 + let g:get_completions_called = 0 + let g:feedkeys_calls = [] + let g:fake_mode = 'i' + + let b:ale_linters = { + \ 'typescript': ['tsserver'], + \} + + let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' + + runtime autoload/ale/util.vim + + function! ale#util#FeedKeys(string) abort + call add(g:feedkeys_calls, [a:string]) + endfunction + + " Pretend we're in insert mode for most tests. + function! ale#util#Mode(...) abort + return g:fake_mode + endfunction + + function! CheckCompletionCalled(expect_success) abort + let g:get_completions_called = 0 + + " We just want to check if the function is called. + function! ale#completion#GetCompletions(source) + let g:get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + + " Run this check a few times, as it can fail randomly. + for l:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) + call ale#completion#Queue() + sleep 1m + + if g:get_completions_called is a:expect_success + break + endif + endfor + + AssertEqual a:expect_success, g:get_completions_called + endfunction + + let g:handle_code_action_called = 0 + function! MockHandleCodeAction() abort + " delfunction! ale#code_action#HandleCodeAction + function! ale#code_action#HandleCodeAction(action, options) abort + Assert !get(a:options, 'should_save') + let g:handle_code_action_called += 1 + endfunction + endfunction + +After: + Restore + + unlet! b:ale_completion_enabled + unlet! g:output + unlet! g:fake_mode + unlet! g:get_completions_called + unlet! g:handle_code_action_called + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completeopt + unlet! b:ale_completion_info + unlet! b:ale_completion_result + unlet! b:ale_complete_done_time + unlet! b:ale_linters + + delfunction CheckCompletionCalled + delfunction ale#code_action#HandleCodeAction + delfunction MockHandleCodeAction + + if exists('*CompleteCallback') + delfunction CompleteCallback + endif + + " Stop any timers we left behind. + " This stops the tests from failing randomly. + call ale#completion#StopTimer() + + " Reset the function. The runtime command below should fix this, but doesn't + " seem to fix it. + function! ale#util#Mode(...) abort + return call('mode', a:000) + endfunction + + runtime autoload/ale/completion.vim + runtime autoload/ale/code_action.vim + runtime autoload/ale/util.vim + +Execute(ale#completion#GetCompletions should be called when the cursor position stays the same): + call CheckCompletionCalled(1) + +Execute(ale#completion#GetCompletions should not be called if the global setting is disabled): + let g:ale_completion_enabled = 0 + call CheckCompletionCalled(0) + +Execute(ale#completion#GetCompletions should not be called if the buffer setting is disabled): + let b:ale_completion_enabled = 0 + call CheckCompletionCalled(0) + +Given typescript(): + let abc = y. + let foo = ab + let foo = (ab) + +Execute(ale#completion#GetCompletions should not be called when the cursor position changes): + call setpos('.', [bufnr(''), 1, 2, 0]) + + " We just want to check if the function is called. + function! ale#completion#GetCompletions(source) + let g:get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + + " Change the cursor position before the callback is triggered. + call setpos('.', [bufnr(''), 2, 2, 0]) + + sleep 1m + + Assert !g:get_completions_called + +Execute(ale#completion#GetCompletions should not be called if you switch to normal mode): + let &l:completeopt = 'menu,preview' + let g:fake_mode = 'n' + + " We just want to check if the function is called. + function! ale#completion#GetCompletions(source) + let g:get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + + sleep 1m + + Assert !g:get_completions_called + +Execute(Completion should not be done shortly after the CompleteDone function): + call CheckCompletionCalled(1) + call ale#completion#Done() + call CheckCompletionCalled(0) + +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([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + AssertEqual 'FooBar', b:ale_old_omnifunc + AssertEqual 'ale#completion#AutomaticOmniFunc', &l:omnifunc + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\(ale_show_completion_menu)"]], g:feedkeys_calls + +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([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + AssertEqual 'menu', b:ale_old_completeopt + AssertEqual 'menu,menuone,noinsert', &l:completeopt + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\(ale_show_completion_menu)"]], g:feedkeys_calls + +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([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + AssertEqual 'menu,preview', b:ale_old_completeopt + AssertEqual 'menu,menuone,noinsert,preview', &l:completeopt + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\(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([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + Assert !exists('b:ale_old_completeopt') + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\(ale_show_completion_menu)"]], g:feedkeys_calls + +Execute(ale#completion#AutomaticOmniFunc() should also remember the completeopt setting and replace it): + let &l:completeopt = 'menu,noselect' + + let b:ale_completion_info = {'source': 'ale-automatic'} + call ale#completion#AutomaticOmniFunc(0, '') + + AssertEqual 'menu,noselect', b:ale_old_completeopt + AssertEqual 'menu,menuone,noinsert,noselect', &l:completeopt + +Execute(ale#completion#AutomaticOmniFunc() 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#AutomaticOmniFunc(0, '') + + AssertEqual 'menu,preview', b:ale_old_completeopt + AssertEqual 'menu,menuone,noinsert,preview', &l:completeopt + +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([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\(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([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + AssertEqual [], g:feedkeys_calls + sleep 1ms + AssertEqual [["\(ale_show_completion_menu)"]], g:feedkeys_calls + +Execute(ale#completion#Show() should not call feedkeys() for other sources): + let b:ale_completion_info = {'source': 'other-source'} + call ale#completion#Show([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + 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' + + call ale#completion#Show([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + AssertEqual 'menu,preview', &l:completeopt + Assert !exists('b:ale_old_omnifunc') + Assert !exists('b:ale_old_completeopt') + Assert !exists('b:ale_completion_result') + AssertEqual [], g:feedkeys_calls + +Execute(ale#completion#Show() should save the result it is given): + call ale#completion#Show([]) + + AssertEqual [], b:ale_completion_result + + call ale#completion#Show([{'word': 'x', 'kind': 'v', 'icase': 1}]) + + AssertEqual [{'word': 'x', 'kind': 'v', 'icase': 1}], b:ale_completion_result + +Execute(ale#completion#Done() should restore old omnifunc values): + let b:ale_old_omnifunc = 'FooBar' + + call ale#completion#Done() + + " We reset the old omnifunc setting and remove the buffer variable. + AssertEqual 'FooBar', &l:omnifunc + Assert !has_key(b:, 'ale_old_omnifunc') + +Execute(ale#completion#Done() should restore the old completeopt setting): + let b:ale_old_completeopt = 'menu' + + call ale#completion#Done() + + AssertEqual 'menu', &l:completeopt + Assert !has_key(b:, 'ale_old_completeopt') + +Execute(ale#completion#Done() should leave settings alone when none were remembered): + let &l:omnifunc = 'BazBoz' + let &l:completeopt = 'menu' + + call ale#completion#Done() + + AssertEqual 'BazBoz', &l:omnifunc + AssertEqual 'menu', &l:completeopt + +Execute(The completion request_id should be reset when queuing again): + let b:ale_completion_info = {'request_id': 123} + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + sleep 1m + + AssertEqual 0, b:ale_completion_info.request_id + +Execute(b:ale_completion_info should be set up correctly when requesting completions 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('ale-callback') + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line_length': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ 'source': 'ale-callback', + \ }, + \ b:ale_completion_info + Assert !exists('b:ale_completion_result') + +Execute(b:ale_completion_info should be set up correctly when requesting completions via callback): + let b:ale_completion_result = [] + call setpos('.', [bufnr(''), 3, 14, 0]) + + function! CompleteCallback() abort + echo 'Called' + endfunction + + + call ale#completion#GetCompletions('ale-callback', {'callback': funcref('CompleteCallback')}) + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line_length': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ 'source': 'ale-callback', + \ }, + \ b:ale_completion_info + Assert !exists('b:ale_completion_result') + +Execute(The correct keybinds should be configured): + redir => g:output + silent map (ale_show_completion_menu) + redir END + + AssertEqual + \ [ + \ 'n (ale_show_completion_menu) * :call ale#completion#RestoreCompletionOptions()', + \ 'o (ale_show_completion_menu) * ', + \ 'v (ale_show_completion_menu) * ', + \ ], + \ sort(split(g:output, "\n")) + +Execute(Running the normal mode keybind should reset the settings): + let b:ale_old_omnifunc = 'FooBar' + let b:ale_old_completeopt = 'menu' + + " We can't run the keybind, but we can call the function. + call ale#completion#RestoreCompletionOptions() + + AssertEqual 'FooBar', &l:omnifunc + AssertEqual 'menu', &l:completeopt + Assert !has_key(b:, 'ale_old_omnifunc') + Assert !has_key(b:, 'ale_old_completeopt') + +Execute(HandleUserData should call ale#code_action#HandleCodeAction): + let b:ale_completion_info = {'source': 'ale-manual'} + call MockHandleCodeAction() + + call ale#completion#HandleUserData({}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': '' + \}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({}), + \}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [], + \ }), + \}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), + \}) + AssertEqual g:handle_code_action_called, 1 + + let b:ale_completion_info = {'source': 'ale-automatic'} + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), + \}) + AssertEqual g:handle_code_action_called, 2 + + let b:ale_completion_info = {'source': 'ale-callback'} + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), + \}) + AssertEqual g:handle_code_action_called, 3 + + let b:ale_completion_info = {'source': 'ale-omnifunc'} + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), + \}) + AssertEqual g:handle_code_action_called, 4 + +Execute(ale#code_action#HandleCodeAction should not be called when when source is not ALE): + call MockHandleCodeAction() + let b:ale_completion_info = {'source': 'syntastic'} + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), + \}) + AssertEqual g:handle_code_action_called, 0 diff --git a/test/completion/test_completion_filtering.vader b/test/completion/test_completion_filtering.vader new file mode 100644 index 00000000..172203a4 --- /dev/null +++ b/test/completion/test_completion_filtering.vader @@ -0,0 +1,142 @@ +Before: + Save g:ale_completion_excluded_words + + let g:ale_completion_excluded_words = [] + +After: + Restore + + unlet! b:ale_completion_excluded_words + unlet! b:suggestions + +Execute(Prefix filtering should work for Lists of strings): + AssertEqual + \ ['FooBar', 'foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo', 0) + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.', 0) + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '', 0) + +Execute(Exact filtering should work): + AssertEqual + \ ['foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo', 1) + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.', 1) + AssertEqual + \ ['Foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'Foo', 'foo'], 'Foo', 1) + +Execute(Prefix filtering should work for completion items): + AssertEqual + \ [{'word': 'FooBar'}, {'word': 'foo'}], + \ ale#completion#Filter( + \ bufnr(''), + \ '', + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ 'foo', + \ 0, + \ ) + + AssertEqual + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ ale#completion#Filter( + \ bufnr(''), + \ '', + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ '.', + \ 0, + \ ) + +Execute(Excluding words from completion results should work): + let b:ale_completion_excluded_words = ['it', 'describe'] + + AssertEqual + \ [{'word': 'Italian'}], + \ ale#completion#Filter( + \ bufnr(''), + \ '', + \ [ + \ {'word': 'Italian'}, + \ {'word': 'it'}, + \ ], + \ 'it', + \ 0, + \ ) + + AssertEqual + \ [{'word': 'Deutsch'}], + \ ale#completion#Filter( + \ bufnr(''), + \ '', + \ [ + \ {'word': 'describe'}, + \ {'word': 'Deutsch'}, + \ ], + \ 'de', + \ 0, + \ ) + + AssertEqual + \ [{'word': 'Deutsch'}], + \ ale#completion#Filter( + \ bufnr(''), + \ '', + \ [ + \ {'word': 'describe'}, + \ {'word': 'Deutsch'}, + \ ], + \ '.', + \ 0, + \ ) + +Execute(Excluding words from completion results should work with lists of Strings): + let b:ale_completion_excluded_words = ['it', 'describe'] + + AssertEqual + \ ['Italian'], + \ ale#completion#Filter(bufnr(''), '', ['Italian', 'it'], 'it', 0) + AssertEqual + \ ['Deutsch'], + \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], 'de', 0) + AssertEqual + \ ['Deutsch'], + \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], '.', 0) + AssertEqual + \ ['Deutsch'], + \ ale#completion#Filter(bufnr(''), '', ['Deutsch'], '', 0) + +Execute(Filtering shouldn't modify the original list): + let b:ale_completion_excluded_words = ['it', 'describe'] + let b:suggestions = [{'word': 'describe'}] + + AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, '.', 0) + AssertEqual b:suggestions, [{'word': 'describe'}] + AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, 'de', 0) + AssertEqual b:suggestions, [{'word': 'describe'}] + +Execute(Filtering should respect filetype triggers): + let b:suggestions = [{'word': 'describe'}] + + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), '', b:suggestions, '.', 0) + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '.', 0) + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '::', 0) diff --git a/test/completion/test_completion_prefixes.vader b/test/completion/test_completion_prefixes.vader new file mode 100644 index 00000000..3f2cab15 --- /dev/null +++ b/test/completion/test_completion_prefixes.vader @@ -0,0 +1,65 @@ +Given typescript(): + let abc = y. + let foo = ab + let foo = (ab) + let string1 = ' + let string2 = " + +Execute(Completion should be done after dots in TypeScript): + AssertEqual '.', ale#completion#GetPrefix(&filetype, 1, 13) + +Execute(Completion should be done after words in TypeScript): + AssertEqual 'ab', ale#completion#GetPrefix(&filetype, 2, 13) + +Execute(Completion should be done after words in parens in TypeScript): + AssertEqual 'ab', ale#completion#GetPrefix(&filetype, 3, 14) + +Execute(Completion should not be done after parens in TypeScript): + AssertEqual '', ale#completion#GetPrefix(&filetype, 3, 15) + +Execute(Completion should be done after strings in TypeScript): + AssertEqual '''', ale#completion#GetPrefix(&filetype, 4, 16) + AssertEqual '"', ale#completion#GetPrefix(&filetype, 5, 16) + +Execute(Completion prefixes should work for other filetypes): + AssertEqual 'ab', ale#completion#GetPrefix('xxxyyyzzz', 3, 14) + +Execute(Completion prefixes should work for other filetypes): + AssertEqual 'ab', ale#completion#GetPrefix('xxxyyyzzz', 3, 14) + +Given rust(): + let abc = y. + let abc = String:: + let foo = (ab) + +Execute(Completion should be done after dots in Rust): + AssertEqual '.', ale#completion#GetPrefix(&filetype, 1, 13) + +Execute(Completion should be done after colons in Rust): + AssertEqual '::', ale#completion#GetPrefix(&filetype, 2, 19) + +Execute(Completion should be done after words in parens in Rust): + AssertEqual 'ab', ale#completion#GetPrefix(&filetype, 3, 14) + +Execute(Completion should not be done after parens in Rust): + AssertEqual '', ale#completion#GetPrefix(&filetype, 3, 15) + +Given lisp(): + (minus-name + (full-name) + +Execute(Completion should be done for function names with minuses in Lisp): + AssertEqual 'minus-name', ale#completion#GetPrefix(&filetype, 1, 12) + +Execute(Completion should not be done after parens in Lisp): + AssertEqual '', ale#completion#GetPrefix(&filetype, 2, 12) + +Given clojure(): + (minus-name + (full-name) + +Execute(Completion should be done for function names with minuses in Clojure): + AssertEqual 'minus-name', ale#completion#GetPrefix(&filetype, 1, 12) + +Execute(Completion should not be done after parens in Clojure): + AssertEqual '', ale#completion#GetPrefix(&filetype, 2, 12) diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader new file mode 100644 index 00000000..008ba180 --- /dev/null +++ b/test/completion/test_lsp_completion_messages.vader @@ -0,0 +1,310 @@ +Before: + Save g:ale_completion_delay + Save g:ale_completion_enabled + Save g:ale_completion_max_suggestions + Save g:ale_completion_info + Save g:ale_completion_autoimport + Save &l:omnifunc + Save &l:completeopt + + let g:ale_completion_enabled = v:true + let g:ale_completion_autoimport = v:true + + call ale#test#SetDirectory('/testplugin/test/completion') + call ale#test#SetFilename('dummy.txt') + + runtime autoload/ale/lsp.vim + + let g:message_list = [] + let g:capability_checked = '' + let g:conn_id = v:null + let g:Callback = '' + let g:init_callback_list = [] + + 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) + + let l:details = { + \ 'command': 'foobar', + \ 'buffer': a:buffer, + \ 'connection_id': g:conn_id, + \ 'project_root': '/foo/bar', + \} + + call add(g:init_callback_list, {-> a:Callback(a:linter, l:details)}) + endfunction + + " Pretend we're in insert mode for most tests. + function! ale#util#Mode(...) abort + return 'i' + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + " Replace the Send function for LSP, so we can monitor calls to it. + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 1 + endfunction + +After: + Restore + + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + unlet! g:message_list + unlet! g:capability_checked + unlet! g:init_callback_list + unlet! g:conn_id + unlet! g:Callback + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completeopt + unlet! b:ale_completion_info + unlet! b:ale_complete_done_time + unlet! b:ale_linters + unlet! b:ale_tsserver_completion_names + + " Reset the function. + function! ale#util#Mode(...) abort + return call('mode', a:000) + endfunction + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + " Stop any timers we left behind. + " This stops the tests from failing randomly. + call ale#completion#StopTimer() + + runtime autoload/ale/completion.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(The right message should be sent for the initial tsserver request): + runtime ale_linters/typescript/tsserver.vim + let b:ale_linters = ['tsserver'] + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 3, 0]) + + call ale#completion#GetCompletions('ale-automatic') + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual 1, len(g:init_callback_list) + call map(g:init_callback_list, 'v:val()') + + AssertEqual 'completion', g:capability_checked + + " We should send the right callback. + AssertEqual + \ 'function(''ale#completion#HandleTSServerResponse'')', + \ string(g:Callback) + " We should send the right message. + AssertEqual + \ [[0, 'ts@completions', { + \ 'file': expand('%:p'), + \ 'line': 1, + \ 'offset': 3, + \ 'prefix': 'fo', + \ 'includeExternalModuleExports': g:ale_completion_autoimport, + \ }]], + \ g:message_list + " We should set up the completion info correctly. + AssertEqual + \ { + \ 'line_length': 3, + \ 'conn_id': g:conn_id, + \ 'column': 3, + \ 'request_id': 1, + \ 'line': 1, + \ 'prefix': 'fo', + \ 'source': 'ale-automatic', + \ }, + \ get(b:, 'ale_completion_info', {}) + +Execute(The right message sent to the tsserver LSP when the first completion message is received): + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 1, 0]) + let b:ale_completion_info = { + \ 'conn_id': 123, + \ 'prefix': 'f', + \ 'request_id': 4, + \ 'line': 1, + \ 'column': 1, + \} + " We should only show up to this many suggestions. + let g:ale_completion_max_suggestions = 3 + + " Handle the response for completions. + call ale#completion#HandleTSServerResponse(123, { + \ 'request_seq': 4, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'Baz'}, + \ {'name': 'dingDong'}, + \ {'name': 'Foo', 'source': '/path/to/foo.ts'}, + \ {'name': 'FooBar'}, + \ {'name': 'frazzle'}, + \ {'name': 'FFS'}, + \ ], + \}) + + " We should save the names we got in the buffer, as TSServer doesn't return + " details for every name. + AssertEqual [{ + \ 'word': 'Foo', + \ 'source': '/path/to/foo.ts', + \ }, { + \ 'word': 'FooBar', + \ 'source': '', + \ }, { + \ 'word': 'frazzle', + \ 'source': '', + \}], + \ get(b:, 'ale_tsserver_completion_names', []) + + " The entry details messages should have been sent. + AssertEqual + \ [[ + \ 0, + \ 'ts@completionEntryDetails', + \ { + \ 'file': expand('%:p'), + \ 'entryNames': [{ + \ 'name': 'Foo', + \ 'source': '/path/to/foo.ts', + \ }, { + \ 'name': 'FooBar', + \ }, { + \ 'name': 'frazzle', + \ }], + \ 'offset': 1, + \ 'line': 1, + \ }, + \ ]], + \ g:message_list + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(The right message should be sent for the initial LSP request): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 5, 0]) + + call ale#completion#GetCompletions('ale-automatic') + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual 1, len(g:init_callback_list) + call map(g:init_callback_list, 'v:val()') + + AssertEqual 'completion', g:capability_checked + + " We should send the right callback. + AssertEqual + \ 'function(''ale#completion#HandleLSPResponse'')', + \ string(g:Callback) + " We should send the right message. + " The character index needs to be at most the index of the last character on + " the line, or integration with pylsp will be broken. + " + " We need to send the message for changing the document first. + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + " We should set up the completion info correctly. + AssertEqual + \ { + \ 'line_length': 3, + \ 'conn_id': g:conn_id, + \ 'column': 3, + \ 'request_id': 1, + \ 'line': 1, + \ 'prefix': 'fo', + \ 'source': 'ale-automatic', + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ }, + \ get(b:, 'ale_completion_info', {}) + +Execute(Two completion requests shouldn't be sent in a row): + call ale#linter#PreventLoading('python') + call ale#linter#Define('python', { + \ 'name': 'foo', + \ 'lsp': 'stdio', + \ 'executable': 'foo', + \ 'command': 'foo', + \ 'project_root': {-> '/foo/bar'}, + \}) + call ale#linter#Define('python', { + \ 'name': 'bar', + \ 'lsp': 'stdio', + \ 'executable': 'foo', + \ 'command': 'foo', + \ 'project_root': {-> '/foo/bar'}, + \}) + let b:ale_linters = ['foo', 'bar'] + + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 5, 0]) + + call ale#completion#GetCompletions('ale-automatic') + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual 2, len(g:init_callback_list) + call map(g:init_callback_list, 'v:val()') + + AssertEqual 'completion', g:capability_checked + + " We should only send one completion message for two LSP servers. + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader new file mode 100644 index 00000000..7fe22e0c --- /dev/null +++ b/test/completion/test_lsp_completion_parsing.vader @@ -0,0 +1,736 @@ +Before: + Save g:ale_completion_autoimport + Save g:ale_completion_max_suggestions + + let g:ale_completion_max_suggestions = 50 + +After: + Restore + + unlet! b:ale_completion_info + +Execute(Should handle Rust completion results correctly): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ {'word': 'new', 'dup': 0, 'menu': 'pub fn new() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'with_capacity', 'dup': 0, 'menu': 'pub fn with_capacity(capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8', 'dup': 0, 'menu': 'pub fn from_utf8(vec: Vec) -> Result', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_lossy', 'dup': 0, 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16', 'dup': 0, 'menu': 'pub fn from_utf16(v: &[u16]) -> Result', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16_lossy', 'dup': 0, 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_raw_parts', 'dup': 0, 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_unchecked', 'dup': 0, 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'dup': 0, 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'dup': 0, 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'dup': 0, 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'dup': 0, 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'dup': 0, 'menu': 'fn from_iter>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Searcher', 'dup': 0, 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'default', 'dup': 0, 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'dup': 0, 'menu': 'type Output = String;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'dup': 0, 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'dup': 0, 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'dup': 0, 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'dup': 0, 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'dup': 0, 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'dup': 0, 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Target', 'dup': 0, 'menu': 'type Target = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Err', 'dup': 0, 'menu': 'type Err = ParseError;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_str', 'dup': 0, 'menu': 'fn from_str(s: &str) -> Result', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'dup': 0, 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'dup': 0, 'menu': 'fn from(s: Box) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'dup': 0, 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'to_vec', 'dup': 0, 'menu': 'pub fn to_vec(&self) -> Vec where T: Clone,', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \], + \ ale#completion#ParseLSPCompletions({ + \ "jsonrpc":"2.0", + \ "id":65, + \ "result":[ + \ { + \ "label":"new", + \ "kind":3, + \ "detail":"pub fn new() -> String" + \ }, + \ { + \ "label":"with_capacity", + \ "kind":3, + \ "detail":"pub fn with_capacity(capacity: usize) -> String" + \ }, + \ { + \ "label":"from_utf8", + \ "kind":3, + \ "detail":"pub fn from_utf8(vec: Vec) -> Result" + \ }, + \ { + \ "label":"from_utf8_lossy", + \ "kind":3, + \ "detail":"pub fn from_utf8_lossy<'a>(v: &'a [u8]) -> Cow<'a, str>" + \ }, + \ { + \ "label":"from_utf16", + \ "kind":3, + \ "detail":"pub fn from_utf16(v: &[u16]) -> Result" + \ }, + \ { + \ "label":"from_utf16_lossy", + \ "kind":3, + \ "detail":"pub fn from_utf16_lossy(v: &[u16]) -> String" + \ }, + \ { + \ "label":"from_raw_parts", + \ "kind":3, + \ "detail":"pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String" + \ }, + \ { + \ "label":"from_utf8_unchecked", + \ "kind":3, + \ "detail":"pub unsafe fn from_utf8_unchecked(bytes: Vec) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter>(iter: I) -> String" + \ }, + \ { + \ "label":"from_iter", + \ "kind":3, + \ "detail":"fn from_iter>>(iter: I) -> String" + \ }, + \ { + \ "label":"Searcher", + \ "kind":8, + \ "detail":"type Searcher = <&'b str as Pattern<'a>>::Searcher;" + \ }, + \ { + \ "label":"default", + \ "kind":3, + \ "detail":"fn default() -> String" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = String;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Output", + \ "kind":8, + \ "detail":"type Output = str;" + \ }, + \ { + \ "label":"Target", + \ "kind":8, + \ "detail":"type Target = str;" + \ }, + \ { + \ "label":"Err", + \ "kind":8, + \ "detail":"type Err = ParseError;" + \ }, + \ { + \ "label":"from_str", + \ "kind":3, + \ "detail":"fn from_str(s: &str) -> Result" + \ }, + \ { + \ "label":"from", + \ "kind":3, + \ "detail":"fn from(s: &'a str) -> String" + \ }, + \ { + \ "label":"from", + \ "kind":3, + \ "detail":"fn from(s: Box) -> String" + \ }, + \ { + \ "label":"from", + \ "kind":3, + \ "detail":"fn from(s: Cow<'a, str>) -> String" + \ }, + \ { + \ "label":"to_vec", + \ "kind":3, + \ "detail":"pub fn to_vec(&self) -> Vec\nwhere\n T: Clone," + \ } + \ ] + \ }) + +Execute(Should handle Python completion results correctly): + let g:ale_completion_autoimport = 0 + let b:ale_completion_info = { + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \} + + AssertEqual + \ [ + \ {'word': 'what', 'dup': 0, 'menu': 'example-python-project.bar.Bar', 'info': "what()\n\n", 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ "jsonrpc":"2.0", + \ "id":6, + \ "result":{ + \ "isIncomplete":v:false, + \ "items":[ + \ { + \ "label":"what()", + \ "kind":3, + \ "detail":"example-python-project.bar.Bar", + \ "documentation":"what()\n\n", + \ "sortText":"awhat", + \ "insertText":"what" + \ }, + \ { + \ "label":"__class__", + \ "kind":7, + \ "detail":"object", + \ "documentation":"type(object_or_name, bases, dict)\ntype(object) -> the object's type\ntype(name, bases, dict) -> a new type", + \ "sortText":"z__class__", + \ "insertText":"__class__" + \ }, + \ { + \ "label":"__delattr__(name)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Implement delattr(self, name).", + \ "sortText":"z__delattr__", + \ "insertText":"__delattr__" + \ }, + \ { + \ "label":"__dir__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"__dir__() -> list\ndefault dir() implementation", + \ "sortText":"z__dir__", + \ "insertText":"__dir__" + \ }, + \ { + \ "label":"__doc__", + \ "kind":18, + \ "detail":"object", + \ "documentation":"str(object='') -> str\nstr(bytes_or_buffer[, encoding[, errors]]) -> str\n\nCreate a new string object from the given object. If encoding or\nerrors is specified, then the object must expose a data buffer\nthat will be decoded using the given encoding and error handler.\nOtherwise, returns the result of object.__str__() (if defined)\nor repr(object).\nencoding defaults to sys.getdefaultencoding().\nerrors defaults to 'strict'.", + \ "sortText":"z__doc__", + \ "insertText":"__doc__" + \ }, + \ { + \ "label":"__eq__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self==value.", + \ "sortText":"z__eq__", + \ "insertText":"__eq__" + \ }, + \ { + \ "label":"__format__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"default object formatter", + \ "sortText":"z__format__", + \ "insertText":"__format__" + \ }, + \ { + \ "label":"__ge__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self>=value.", + \ "sortText":"z__ge__", + \ "insertText":"__ge__" + \ }, + \ { + \ "label":"__getattribute__(name)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return getattr(self, name).", + \ "sortText":"z__getattribute__", + \ "insertText":"__getattribute__" + \ }, + \ { + \ "label":"__gt__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self>value.", + \ "sortText":"z__gt__", + \ "insertText":"__gt__" + \ }, + \ { + \ "label":"__hash__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return hash(self).", + \ "sortText":"z__hash__", + \ "insertText":"__hash__" + \ }, + \ { + \ "label":"__init__(args, kwargs)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Initialize self.\u00a0\u00a0See help(type(self)) for accurate signature.", + \ "sortText":"z__init__", + \ "insertText":"__init__" + \ }, + \ { + \ "label":"__init_subclass__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"This method is called when a class is subclassed.\n\nThe default implementation does nothing. It may be\noverridden to extend subclasses.", + \ "sortText":"z__init_subclass__", + \ "insertText":"__init_subclass__" + \ }, + \ { + \ "label":"__le__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self<=value.", + \ "sortText":"z__le__", + \ "insertText":"__le__" + \ }, + \ { + \ "label":"__lt__(value)", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return self int\nsize of object in memory, in bytes", + \ "sortText":"z__sizeof__", + \ "insertText":"__sizeof__" + \ }, + \ { + \ "label":"__str__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Return str(self).", + \ "sortText":"z__str__", + \ "insertText":"__str__" + \ }, + \ { + \ "label":"__subclasshook__()", + \ "kind":3, + \ "detail":"object", + \ "documentation":"Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented.\u00a0\u00a0If it returns\nNotImplemented, the normal algorithm is used.\u00a0\u00a0Otherwise, it\noverrides the normal algorithm (and the outcome is cached).", + \ "sortText":"z__subclasshook__", + \ "insertText":"__subclasshook__" + \ } + \ ] + \ } + \ }) + +Execute(Should handle extra Python completion results correctly): + let g:ale_completion_autoimport = 0 + + let b:ale_completion_info = { + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ 'prefix': 'mig', + \} + + AssertEqual + \ [ + \ {'word': 'migrations', 'dup': 0, 'menu': 'xxx', 'info': 'migrations', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'MigEngine', 'dup': 0, 'menu': 'xxx', 'info': 'mig engine', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'jsonrpc': '2.0', + \ 'id': 6, + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'label': 'migrations', + \ 'kind': 3, + \ 'detail': 'xxx', + \ 'documentation': 'migrations', + \ }, + \ { + \ 'label': 'MigEngine', + \ 'kind': 3, + \ 'detail': 'xxx', + \ 'documentation': 'mig engine', + \ }, + \ { + \ 'label': 'ignore me', + \ 'kind': 3, + \ 'detail': 'nope', + \ 'documentation': 'nope', + \ }, + \ ] + \ } + \ }) + +Execute(Should handle missing keys): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ {'word': 'x', 'dup': 0, 'menu': '', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'jsonrpc': '2.0', + \ 'id': 6, + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'label': 'x', + \ }, + \ ] + \ } + \ }) + +Execute(Should handle documentation in the markdown format): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ {'word': 'migrations', 'dup': 0, 'menu': 'xxx', 'info': 'Markdown documentation', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'jsonrpc': '2.0', + \ 'id': 6, + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'label': 'migrations', + \ 'kind': 3, + \ 'detail': 'xxx', + \ 'documentation': { + \ 'kind': 'markdown', + \ 'value': 'Markdown documentation', + \ }, + \ }, + \ ], + \ }, + \ }) + +Execute(Should handle completion messages with textEdit objects): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ {'word': 'next_callback', 'dup': 0, 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'id': 226, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'PlayTimeCallback', + \ 'filterText': 'next_callback', + \ 'insertText': 'ignoreme', + \ '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}, + \ }, + \ }, + \ }, + \ ], + \ }, + \ }) + +Execute(Should handle completion messages with textEdit objects and no insertTextFormat key): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ {'word': 'next_callback', 'dup': 0, 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'id': 226, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'PlayTimeCallback', + \ 'filterText': 'next_callback', + \ 'insertText': 'ignoreme', + \ 'kind': 6, + \ 'label': ' next_callback', + \ 'sortText': '3ee19999next_callback', + \ 'textEdit': { + \ 'newText': 'next_callback', + \ 'range': { + \ 'end': {'character': 13, 'line': 12}, + \ 'start': {'character': 4, 'line': 12}, + \ }, + \ }, + \ }, + \ ], + \ }, + \ }) + +Execute(Should handle completion messages with the deprecated insertText attribute): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ {'word': 'next_callback', 'dup': 0, 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 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', + \ }, + \ ], + \ }, + \ }) + +Execute(Should handle completion messages with additionalTextEdits when ale_completion_autoimport is turned on): + let g:ale_completion_autoimport = 1 + + AssertEqual + \ [ + \ { + \ 'word': 'next_callback', + \ 'dup': 1, + \ 'menu': 'PlayTimeCallback', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ { + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('#' . bufnr('') . ':p'), + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 11, + \ 'offset': 2, + \ }, + \ 'end': { + \ 'line': 13, + \ 'offset': 4, + \ }, + \ 'newText': 'from "module" import next_callback', + \ }, + \ ], + \ }, + \ ], + \ }, + \ ], + \ }), + \ }, + \ ], + \ 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', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 10, + \ 'character': 1, + \ }, + \ 'end': { + \ 'line': 12, + \ 'character': 3, + \ }, + \ }, + \ 'newText': 'from "module" import next_callback', + \ }, + \ ], + \ }, + \ ], + \ }, + \ }) + +Execute(Should not handle completion messages with additionalTextEdits when ale_completion_autoimport is turned off): + let g:ale_completion_autoimport = 0 + let b:ale_completion_info = {'line': 30} + + AssertEqual + \ [], + \ 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', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 10, + \ 'character': 1, + \ }, + \ 'end': { + \ 'line': 12, + \ 'character': 3, + \ }, + \ }, + \ 'newText': 'from "module" import next_callback', + \ }, + \ ], + \ }, + \ ], + \ }, + \ }) + +Execute(Should still handle completion messages with empty additionalTextEdits with ale_completion_autoimport turned off): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ { + \ 'word': 'next_callback', + \ 'dup': 0, + \ 'menu': 'PlayTimeCallback', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 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', + \ 'additionalTextEdits': [], + \ }, + \ ], + \ }, + \ }) diff --git a/test/completion/test_omnifunc_completion.vader b/test/completion/test_omnifunc_completion.vader new file mode 100644 index 00000000..c9ecd205 --- /dev/null +++ b/test/completion/test_omnifunc_completion.vader @@ -0,0 +1,60 @@ +Before: + unlet! b:ale_completion_info + unlet! b:ale_completion_result + + let b:lsp_started = 0 + + runtime autoload/ale/lsp_linter.vim + + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + return b:lsp_started + endfunction + + function! SetCompletionResult(...) abort + let b:ale_completion_result = ['foo'] + endfunction + + function! SetCompletionResponse(...) abort + let b:ale_completion_result = ['foo'] + endfunction + +After: + unlet! b:ale_completion_info + unlet! b:ale_completion_result + unlet! b:lsp_started + + delfunction SetCompletionResult + delfunction SetCompletionResponse + + runtime autoload/ale/lsp_linter.vim + + call ale#linter#Reset() + +Given typescript(): + let abc = y. + let foo = ab + let foo = (ab) + +Execute(-3 should be returned when completion results cannot be requested): + AssertEqual -3, ale#completion#OmniFunc(1, '') + +Execute(The start position should be returned when results can be requested): + let b:lsp_started = 1 + call setpos('.', [bufnr(''), 3, 14, 0]) + + AssertEqual 11, ale#completion#OmniFunc(1, '') + +Execute(The omnifunc function should return async results): + " Neovim struggles at running these tests. + if !has('nvim') + call timer_start(0, function('SetCompletionResult')) + + AssertEqual ['foo'], ale#completion#OmniFunc(0, '') + endif + +Execute(The omnifunc function should parse and return async responses): + if !has('nvim') + call timer_start(0, function('SetCompletionResponse')) + + AssertEqual ['foo'], ale#completion#OmniFunc(0, '') + endif diff --git a/test/completion/test_public_completion_api.vader b/test/completion/test_public_completion_api.vader new file mode 100644 index 00000000..03394820 --- /dev/null +++ b/test/completion/test_public_completion_api.vader @@ -0,0 +1,47 @@ +Before: + call ale#linter#Reset() + + unlet! b:ale_linters + unlet! b:ale_completion_info + unlet! b:ale_completion_result + +After: + call ale#linter#Reset() + + unlet! b:ale_linters + unlet! b:ale_completion_info + unlet! b:ale_completion_result + +Execute(ale#completion#GetCompletionResult() should return v:null when there are no results): + AssertEqual v:null, ale#completion#GetCompletionResult() + +Execute(ale#completion#GetCompletionResult() should return a result computed previously): + let b:ale_completion_result = [1] + + AssertEqual [1], ale#completion#GetCompletionResult() + +Execute(ale#completion#GetCompletionPosition() should return 0 when there is no completion information): + AssertEqual 0, ale#completion#GetCompletionPosition() + +Given python(Some Python file): + foo bar + +Execute(ale#completion#GetCompletionPosition() should return the position in the file when information is available): + let b:ale_completion_info = {'line': 1, 'column': 6} + + " This is the first character of 'bar' + AssertEqual 4, ale#completion#GetCompletionPosition() + +Execute(ale#completion#GetCompletionPositionForDeoplete() should return the position on the given input string): + " This is the first character of 'bar' + AssertEqual 4, ale#completion#GetCompletionPositionForDeoplete('foo bar') + +Execute(ale#completion#CanProvideCompletions should return 0 when no completion sources are available): + let b:ale_linters = ['flake8'] + AssertEqual 0, ale#completion#CanProvideCompletions() + +Execute(ale#completion#CanProvideCompletions should return 1 when at least one completion source is available): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + + AssertEqual 1, ale#completion#CanProvideCompletions() diff --git a/test/completion/test_tsserver_completion_parsing.vader b/test/completion/test_tsserver_completion_parsing.vader new file mode 100644 index 00000000..fedd09e0 --- /dev/null +++ b/test/completion/test_tsserver_completion_parsing.vader @@ -0,0 +1,311 @@ +Before: + Save g:ale_completion_autoimport + Save g:ale_completion_tsserver_remove_warnings + + let g:ale_completion_autoimport = v:true + let g:ale_completion_tsserver_remove_warnings = v:false + +After: + Restore + + unlet! b:ale_tsserver_completion_names + +Execute(TypeScript completions responses should be parsed correctly): + AssertEqual [], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [], + \}) + AssertEqual + \ [ + \ { + \ 'word': 'foo', + \ 'source': '/path/to/foo.ts', + \ }, + \ { + \ 'word': 'bar', + \ 'source': '', + \ }, + \ { + \ 'word': 'baz', + \ 'source': '', + \ } + \ ], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [ + \ {'name': 'foo', 'source': '/path/to/foo.ts'}, + \ {'name': 'bar'}, + \ {'name': 'baz'}, + \ ], + \}) + +Execute(TypeScript completions responses should include warnings): + AssertEqual + \ [ + \ { + \ 'word': 'foo', + \ 'source': '/path/to/foo.ts', + \ }, + \ { + \ 'word': 'bar', + \ 'source': '', + \ }, + \ { + \ 'word': 'baz', + \ 'source': '', + \ } + \ ], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [ + \ {'name': 'foo', 'source': '/path/to/foo.ts'}, + \ {'name': 'bar', 'kind': 'warning'}, + \ {'name': 'baz'}, + \ ], + \}) + +Execute(TypeScript completions responses should not include warnings if excluded): + let g:ale_completion_tsserver_remove_warnings = 1 + AssertEqual + \ [ + \ { + \ 'word': 'foo', + \ 'source': '/path/to/foo.ts', + \ }, + \ { + \ 'word': 'baz', + \ 'source': '', + \ } + \ ], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [ + \ {'name': 'foo', 'source': '/path/to/foo.ts'}, + \ {'name': 'bar', 'kind': 'warning'}, + \ {'name': 'baz'}, + \ ], + \}) + +Execute(TypeScript completion details responses should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'word': 'abc', + \ 'menu': '(property) Foo.abc: number', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport + 0, + \ }, + \ { + \ 'word': 'def', + \ 'menu': '(property) Foo.def: number', + \ 'info': 'foo bar baz', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport + 0, + \ }, + \ { + \ 'word': 'ghi', + \ 'menu': '(class) Foo', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport + 0, + \ }, + \ ], + \ ale#completion#ParseTSServerCompletionEntryDetails({ + \ 'body': [ + \ { + \ 'name': 'abc', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'abc'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ }, + \ { + \ 'name': 'def', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'def'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ 'documentation': [ + \ {'text': 'foo'}, + \ {'text': ' '}, + \ {'text': 'bar'}, + \ {'text': ' '}, + \ {'text': 'baz'}, + \ ], + \ }, + \ { + \ 'name': 'ghi', + \ 'kind': 'className', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'class'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ ], + \ }, + \ ], + \}) + +Execute(Entries without details should be included in the responses): + let b:ale_tsserver_completion_names = [{ + \ 'word': 'xyz', + \ 'source': '/path/to/xyz.ts', + \ }] + + AssertEqual + \ [ + \ { + \ 'word': 'abc', + \ 'menu': 'import { def } from "./Foo"; (property) Foo.abc: number', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [{ + \ 'description': 'import { def } from "./Foo";', + \ 'changes': [], + \ }], + \ }), + \ 'dup': g:ale_completion_autoimport + 0, + \ }, + \ { + \ 'word': 'def', + \ 'menu': '(property) Foo.def: number', + \ 'info': 'foo bar baz', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport + 0, + \ }, + \ { + \ 'word': 'xyz', + \ 'menu': '', + \ 'info': '', + \ 'kind': 'v', + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'icase': 1, + \ }, + \ ], + \ ale#completion#ParseTSServerCompletionEntryDetails({ + \ 'body': [ + \ { + \ 'name': 'abc', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'abc'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ 'codeActions': [{ + \ 'description': 'import { def } from "./Foo";', + \ 'changes': [], + \ }], + \ }, + \ { + \ 'name': 'def', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'def'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ 'documentation': [ + \ {'text': 'foo'}, + \ {'text': ' '}, + \ {'text': 'bar'}, + \ {'text': ' '}, + \ {'text': 'baz'}, + \ ], + \ }, + \ ], + \}) + +Execute(Default imports should be handled correctly): + AssertEqual + \ [ + \ { + \ 'word': 'abcd', + \ 'menu': 'Import default ''abcd'' from module "./foo" (alias) const abcd: 3', + \ 'info': '', + \ 'kind': 't', + \ 'icase': 1, + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [{ + \ 'description': 'Import default ''abcd'' from module "./foo"', + \ 'changes': [], + \ }], + \ }), + \ 'dup': g:ale_completion_autoimport + 0, + \ }, + \ ], + \ ale#completion#ParseTSServerCompletionEntryDetails({ + \ 'body': [ + \ { + \ 'name': 'default', + \ 'kind': 'alias', + \ 'displayParts': [ + \ {'kind': 'punctuation', 'text': '('}, + \ {'kind': 'text', 'text': 'alias'}, + \ {'kind': 'punctuation', 'text': ')'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'keyword', 'text': 'const'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'localName', 'text': 'abcd'}, + \ {'kind': 'punctuation', 'text': ':'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'stringLiteral', 'text': '3'}, + \ {'kind': 'lineBreak', 'text': '^@'}, + \ {'kind': 'keyword', 'text': 'export'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'keyword', 'text': 'default'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'aliasName', 'text': 'abcd'} + \ ], + \ 'codeActions': [ + \ { + \ 'description': 'Import default ''abcd'' from module "./foo"', + \ 'changes': [], + \ }, + \ ], + \ }, + \ ], + \ }) diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader new file mode 100644 index 00000000..429d1b2e --- /dev/null +++ b/test/fix/test_ale_fix.vader @@ -0,0 +1,884 @@ +Before: + Save g:ale_fixers + Save &shell + Save g:ale_enabled + Save g:ale_fix_on_save + Save g:ale_lint_on_save + Save g:ale_echo_cursor + Save g:ale_command_wrapper + Save g:ale_filename_mappings + + silent! cd /testplugin/test/fix + + unlet! b:ale_lint_on_save + let g:ale_enabled = 0 + let g:ale_echo_cursor = 0 + let g:ale_command_wrapper = '' + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + let g:ale_fix_buffer_data = {} + let g:ale_fixers = { + \ 'testft': [], + \} + let g:ale_filename_mappings = {} + + let g:pre_success = 0 + let g:post_success = 0 + augroup VaderTest + autocmd! + autocmd User ALEFixPre let g:pre_success = 1 + autocmd User ALEFixPost let g:post_success = 1 + augroup END + + if !has('win32') + let &shell = '/bin/bash' + endif + + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('test.txt') + call ale#linter#PreventLoading('testft') + + function AddCarets(buffer, lines) abort + " map() is applied to the original lines here. + " This way, we can ensure that defensive copies are made. + return map(a:lines, '''^'' . v:val') + endfunction + + function Capitalize(buffer, lines) abort + return map(a:lines, 'toupper(v:val)') + endfunction + + function DoNothing(buffer, lines) abort + return 0 + endfunction + + function CatLine(buffer, lines) abort + return {'command': 'cat - <(echo d)'} + endfunction + + function CatLineOneArg(buffer) abort + 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 EchoFilename(buffer, lines) abort + return {'command': 'echo %s'} + endfunction + + function RemoveLastLine(buffer, lines) abort + return ['a', 'b'] + endfunction + + function RemoveLastLineOneArg(buffer) abort + return ['a', 'b'] + endfunction + + function! TestCallback(buffer, output) + return [{'lnum': 1, 'col': 1, 'text': 'xxx'}] + endfunction + + " echo will output a single blank line, and we should ignore it. + function! IgnoredEmptyOutput(buffer, output) + return {'command': has('win32') ? 'echo(' : 'echo'} + endfunction + + function! EchoLineNoPipe(buffer, output) + return {'command': 'echo new line', 'read_buffer': 0} + endfunction + + function! SetUpLinters() + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': 'true', + \ 'command': 'true', + \}) + endfunction + + function GetLastMessage() + redir => l:output + silent mess + redir END + + let l:lines = split(l:output, "\n") + + return empty(l:lines) ? '' : l:lines[-1] + endfunction + + function! FixWithJSONPostProcessing(buffer) abort + let l:ProcessWith = 'JSONPostProcessor' + + " Test with lambdas where support is available. + if has('lambda') + let l:ProcessWith = {buffer, output -> JSONPostProcessor(buffer, output)} + endif + + " Escaping needs to be handled specially for CMD on Windows. + let l:json_string = has('win32') + \ ? '{"output":["x","y","z"]}' + \ : ale#Escape('{"output": ["x", "y", "z"]}') + + return { + \ 'command': 'echo ' . l:json_string, + \ 'read_buffer': 0, + \ 'process_with': l:ProcessWith, + \} + endfunction + + function! JSONPostProcessor(buffer, output) abort + return json_decode(a:output[0]).output + endfunction + +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_lint_on_save + unlet! b:ale_fix_on_save + unlet! b:ale_quitting + delfunction AddCarets + delfunction Capitalize + delfunction DoNothing + delfunction CatLine + delfunction CatLineOneArg + delfunction CatLineDeferred + delfunction ReplaceWithTempFile + delfunction CatWithTempFile + delfunction EchoFilename + delfunction RemoveLastLine + delfunction RemoveLastLineOneArg + delfunction TestCallback + delfunction SetUpLinters + delfunction GetLastMessage + delfunction IgnoredEmptyOutput + delfunction EchoLineNoPipe + delfunction FixWithJSONPostProcessing + delfunction JSONPostProcessor + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + + call ale#test#RestoreDirectory() + + call ale#fix#registry#ResetToDefaults() + call ale#linter#Reset() + + setlocal buftype=nofile + + if exists('g:test_filename') && filereadable(g:test_filename) + call delete(g:test_filename) + endif + + call setloclist(0, []) + + let g:ale_fix_buffer_data = {} + + " Clear the messages between tests. + echomsg '' + + if !exists('g:ale_command_wrapper') + let g:ale_command_wrapper = '' + endif + +Given testft (A file with three lines): + a + b + c + +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 not complain when the command is run with a bang): + echom 'none' + + ALEFix! + call ale#test#FlushJobs() + AssertEqual 'none', 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 + ^b + ^c + +Execute(Should apply filename mpapings): + " The command echos %s, and we'll map the current path so we can check + " that ALEFix applies filename mappings, end-to-end. + let g:ale_filename_mappings = { + \ 'echo_filename': [ + \ [expand('%:p:h') . '/', '/some/fake/path/'], + \ ], + \} + + call ale#fix#registry#Add('echo_filename', 'EchoFilename', [], 'echo filename') + let g:ale_fixers.testft = ['echo_filename'] + ALEFix + call ale#test#FlushJobs() + " Remote trailing whitespace from the line. + call setline(1, substitute(getline(1), '[ \r]\+$', '', '')) + +Expect(The mapped filename should be printed): + /some/fake/path/test.txt + +Execute(ALEFix should apply simple functions in a chain): + let g:ale_fixers.testft = ['AddCarets', 'Capitalize'] + ALEFix + call ale#test#FlushJobs() + +Expect(Both functions should be used): + ^A + ^B + ^C + +Execute(ALEFix should allow 0 to be returned to skip functions): + let g:ale_fixers.testft = ['DoNothing', 'Capitalize'] + ALEFix + call ale#test#FlushJobs() + +Expect(Only the second function should be applied): + A + B + C + +Execute(The * fixers shouldn't be used if an empty list is set for fixers): + let g:ale_fixers.testft = [] + let g:ale_fixers['*'] = ['Capitalize'] + ALEFix + call ale#test#FlushJobs() + +Expect(Nothing should be changed): + a + b + c + +Execute(* fixers should be used if no filetype is matched): + let g:ale_fixers = {'*': ['Capitalize']} + ALEFix + call ale#test#FlushJobs() + +Expect(The file should be changed): + A + B + C + +Execute(ALEFix should allow commands to be run): + if has('win32') + " Just skip this test on Windows, we can't run it. + call setline(1, ['a', 'b', 'c', 'd']) + else + let g:ale_fixers.testft = ['CatLine'] + ALEFix + call ale#test#FlushJobs() + endif + +Expect(An extra line should be added): + a + b + c + d + +Execute(ALEFix should use fixers passed in commandline when provided): + let g:ale_fixers.testft = ['RemoveLastLine'] + ALEFix AddCarets Capitalize + call ale#test#FlushJobs() + +Expect(Only fixers passed via command line should be run): + ^A + ^B + ^C + +Execute(ALEFix should allow temporary files to be read): + if has('win32') + " Just skip this test on Windows, we can't run it. + call setline(1, ['x']) + 2,3d + 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. + call setline(1, ['X']) + 2,3d + else + let g:ale_fixers.testft = ['ReplaceWithTempFile', 'Capitalize'] + ALEFix + call ale#test#FlushJobs() + endif + +Expect(The lines from the temporary file should be modified): + X + +Execute(ALEFix should send lines modified by functions to jobs): + 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 = ['Capitalize', 'CatLine'] + ALEFix + call ale#test#FlushJobs() + endif + +Expect(The lines should first be modified by the function, then the job): + A + B + C + d + +Execute(ALEFix should skip commands when jobs fail to run): + let g:ale_emulate_job_failure = 1 + let g:ale_fixers.testft = ['CatLine', 'Capitalize'] + ALEFix + call ale#test#FlushJobs() + +Expect(Only the second function should be applied): + A + B + C + +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 + ^b + ^c + +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 + ^b + ^c + +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 + b + +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 + b + +Execute(ALEFix should accept lambdas): + if has('nvim') + " NeoVim 0.1.7 can't interpret lambdas correctly, so just set the lines + " to make the test pass. + call setline(1, ['a', 'b', 'c', 'd']) + else + let g:ale_fixers.testft = [{buffer, lines -> lines + ['d']}] + ALEFix + call ale#test#FlushJobs() + endif + +Expect(There should be an extra line): + a + b + c + d + +Execute(ALEFix should user buffer-local fixer settings): + let g:ale_fixers.testft = ['AddCarets', 'Capitalize'] + let b:ale_fixers = {'testft': ['RemoveLastLine']} + ALEFix + call ale#test#FlushJobs() + +Expect(There should be only two lines): + a + b + +Execute(ALEFix should allow Lists to be used for buffer-local fixer settings): + let g:ale_fixers.testft = ['AddCarets', 'Capitalize'] + let b:ale_fixers = ['RemoveLastLine'] + ALEFix + call ale#test#FlushJobs() + +Expect(There should be only two lines): + a + b + +Given testft (A file with three lines): + a + b + c + +Execute(ALEFix should fix files on the save event): + let g:ale_fix_on_save = 1 + let g:ale_lint_on_save = 1 + let g:ale_enabled = 1 + + 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 = ['Capitalize'] + + " We have to set the buftype to empty so the file will be written. + setlocal buftype= + + call SetUpLinters() + call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() + + " We should save the file. + 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. + AssertEqual [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 1, + \ 'text': 'xxx', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}], ale#test#GetLoclistWithoutNewerKeys() + endif + +Expect(The buffer should be modified): + A + B + C + +Given testft (A file with three lines): + a + b + c + +Execute(ALEFix should run the linters with b:ale_lint_on_save = 1): + let g:ale_fix_on_save = 0 + let b:ale_fix_on_save = 1 + let g:ale_lint_on_save = 1 + let g:ale_enabled = 1 + + 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 = ['Capitalize'] + + " We have to set the buftype to empty so the file will be written. + setlocal buftype= + + call SetUpLinters() + call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() + + " We should save the file. + 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. + AssertEqual [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 1, + \ 'text': 'xxx', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}], ale#test#GetLoclistWithoutNewerKeys() + endif + +Expect(The buffer should be modified): + A + B + C + +Execute(ALEFix should not fix files on :wq): + let g:ale_fix_on_save = 1 + let g:ale_lint_on_save = 1 + let g:ale_enabled = 1 + + 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 = ['Capitalize'] + + " We have to set the buftype to empty so the file will be written. + setlocal buftype= + + call ale#events#QuitEvent(bufnr('')) + + call SetUpLinters() + call ale#events#SaveEvent(bufnr('')) + + " We should save the file. + AssertEqual ['a', 'b', 'c'], readfile(g:test_filename) + Assert &modified, 'The was not marked as ''modified''' + + " We should not run the linter. + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + +Expect(The buffer should not be modified): + a + b + c + +Given testft (A file with three lines): + a + b + c + +Execute(ALEFix should still lint with no linters to be applied): + let g:ale_fix_on_save = 1 + let g:ale_lint_on_save = 1 + let g:ale_enabled = 1 + + 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(g:test_filename), 'The file should not have been saved' + + if !has('win32') + " We have run the linter. + AssertEqual [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 1, + \ 'text': 'xxx', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}], ale#test#GetLoclistWithoutNewerKeys() + endif + +Expect(The buffer should be the same): + a + b + c + +Execute(ALEFix should still lint when nothing was fixed on save): + let g:ale_fix_on_save = 1 + let g:ale_lint_on_save = 1 + let g:ale_enabled = 1 + + 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(g:test_filename), 'The file should not have been saved' + + if !has('win32') + " We should have run the linter. + AssertEqual [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 1, + \ 'text': 'xxx', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}], ale#test#GetLoclistWithoutNewerKeys() + endif + +Expect(The buffer should be the same): + a + b + c + +Execute(ALEFix should not lint the buffer on save if linting on save is disabled globally): + let g:ale_fix_on_save = 1 + let g:ale_lint_on_save = 0 + let g:ale_enabled = 1 + + 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(g:test_filename), 'The file should not have been saved' + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + +Expect(The buffer should be the same): + a + b + c + +Execute(ALEFix should not lint the buffer on save if linting on save is disabled locally): + let g:ale_fix_on_save = 1 + let b:ale_lint_on_save = 0 + let g:ale_enabled = 1 + + 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(g:test_filename), 'The file should not have been saved' + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + +Expect(The buffer should be the same): + a + b + c + +Given testft (A file with three lines): + a + b + c + +Execute(ale#fix#InitBufferData() should set up the correct data): + let g:test_filename = tempname() + execute 'noautocmd silent file ' . fnameescape(g:test_filename) + + call ale#fix#InitBufferData(bufnr(''), 'save_file') + + AssertEqual { + \ bufnr(''): { + \ 'temporary_directory_list': [], + \ 'done': 0, + \ 'lines_before': ['a', 'b', 'c'], + \ 'should_save': 1, + \ 'ignore_file_changed_errors': 0, + \ }, + \}, g:ale_fix_buffer_data + + call ale#fix#InitBufferData(bufnr(''), '!') + + AssertEqual { + \ bufnr(''): { + \ 'temporary_directory_list': [], + \ 'done': 0, + \ 'lines_before': ['a', 'b', 'c'], + \ 'should_save': 0, + \ 'ignore_file_changed_errors': 1, + \ }, + \}, g:ale_fix_buffer_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 + b + +Execute(ALEFix should modify a buffer that is not modifiable, if it becomes modifiable later): + let g:ale_fixers.testft = ['RemoveLastLineOneArg'] + + set nomodifiable + ALEFix + call ale#test#FlushJobs() + set modifiable + call ale#fix#ApplyQueuedFixes(bufnr('')) + +Expect(There should be only two lines): + a + b + +Execute(b:ale_fix_on_save = 1 should override g:ale_fix_on_save = 0): + let g:ale_fix_on_save = 0 + let b:ale_fix_on_save = 1 + + let g:ale_fixers.testft = ['RemoveLastLineOneArg'] + call ale#events#SaveEvent(bufnr('')) + +Expect(There should be only two lines): + a + b + +Execute(b:ale_fix_on_save = 0 should override g:ale_fix_on_save = 1): + let g:ale_fix_on_save = 1 + let b:ale_fix_on_save = 0 + + let g:ale_fixers.testft = ['RemoveLastLineOneArg'] + call ale#events#SaveEvent(bufnr('')) + +Expect(The lines should be the same): + a + b + c + +Execute(ALEFix functions returning jobs should be able to accept one argument): + 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 = ['CatLine'] + ALEFix + call ale#test#FlushJobs() + endif + +Expect(An extra line should be added): + a + b + c + d + +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(Empty output should be ignored): + let g:ale_fixers.testft = ['IgnoredEmptyOutput'] + ALEFix + call ale#test#FlushJobs() + +Expect(The lines should be the same): + a + b + c + +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')), + \ string(ale#history#Get(bufnr(''))[-1].command) + + " Remove trailing whitespace for Windows. + if has('win32') + %s/[[:space:]]*$//g + endif + +Expect(The new line should be used): + new line + +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 + y + z + +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_aliases.vader b/test/fix/test_ale_fix_aliases.vader new file mode 100644 index 00000000..d3c47b34 --- /dev/null +++ b/test/fix/test_ale_fix_aliases.vader @@ -0,0 +1,5 @@ +Execute(prettier-eslint should be aliased): + AssertEqual 'ale#fixers#prettier_eslint#Fix', ale#fix#registry#GetFunc('prettier-eslint') + +Execute(prettier-standard should be aliased): + AssertEqual 'ale#fixers#prettier_standard#Fix', ale#fix#registry#GetFunc('prettier-standard') diff --git a/test/fix/test_ale_fix_completion.vader b/test/fix/test_ale_fix_completion.vader new file mode 100644 index 00000000..6c38bb8d --- /dev/null +++ b/test/fix/test_ale_fix_completion.vader @@ -0,0 +1,23 @@ +Execute (List of available fixers is empty): + call ale#fix#registry#Clear() + +Then (List of applicable fixers for python file is empty): + AssertEqual [], ale#fix#registry#GetApplicableFixers('python') + +Execute (Add ruby fixer): + call ale#fix#registry#Add('ruby_fixer', 'fixer_fun', ['ruby'], 'ruby fixer') + +Then (List of applicable fixers for python file is still empty): + AssertEqual [], ale#fix#registry#GetApplicableFixers('python') + +Execute (Add generic fixer): + call ale#fix#registry#Add('generic_fixer', 'fixer_fun', [], 'generic fixer') + +Then (Generic fixer should be returned as applicable for python file): + AssertEqual ['generic_fixer'], ale#fix#registry#GetApplicableFixers('python') + +Execute (Add python fixer): + call ale#fix#registry#Add('python_fixer', 'fixer_func', ['python'], 'python fixer') + +Then (List of fixers should contain both generic and python fixers): + AssertEqual ['generic_fixer', 'python_fixer'], ale#fix#registry#GetApplicableFixers('python') diff --git a/test/fix/test_ale_fix_completion_filter.vader b/test/fix/test_ale_fix_completion_filter.vader new file mode 100644 index 00000000..536b7138 --- /dev/null +++ b/test/fix/test_ale_fix_completion_filter.vader @@ -0,0 +1,14 @@ +Before: + call ale#fix#registry#Clear() + call ale#test#SetFilename('test.js') + call ale#fix#registry#Add('prettier', '', ['javascript'], 'prettier') + call ale#fix#registry#Add('eslint', '', ['javascript'], 'eslint') + setfiletype javascript + +Execute(completeFixers returns all of the applicable fixers without an arglead): + AssertEqual ['eslint', 'prettier'], + \ ale#fix#registry#CompleteFixers('', 'ALEFix ', 7) + +Execute(completeFixers returns all of the applicable fixers without an arglead): + AssertEqual ['prettier'], + \ ale#fix#registry#CompleteFixers('pre', 'ALEFix ', 10) diff --git a/test/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/fix/test_ale_fix_suggest.vader b/test/fix/test_ale_fix_suggest.vader new file mode 100644 index 00000000..1100aeed --- /dev/null +++ b/test/fix/test_ale_fix_suggest.vader @@ -0,0 +1,102 @@ +Before: + call ale#fix#registry#Clear() + + let g:buffer = bufnr('') + + function GetSuggestions() + silent ALEFixSuggest + + if bufnr('') != g:buffer + let l:lines = getline(1, '$') + else + let l:lines = [] + endif + + return l:lines + endfunction + +After: + if bufnr('') != g:buffer + :q! + endif + + unlet! g:buffer + + call ale#fix#registry#ResetToDefaults() + delfunction GetSuggestions + +Execute(ALEFixSuggest should return something sensible with no suggestions): + AssertEqual + \ [ + \ 'There is nothing in the registry to suggest.', + \ '', + \ 'Press q to close this window', + \ ], + \ GetSuggestions() + +Execute(ALEFixSuggest should set the appropriate settings): + silent ALEFixSuggest + + AssertEqual 'ale-fix-suggest', &filetype + Assert !&modified, 'The buffer was marked as modified' + Assert !&modifiable, 'The buffer was modifiable' + +Execute(ALEFixSuggest output should be correct for only generic handlers): + call ale#fix#registry#Add('zed', 'XYZ', [], 'Zedify things.') + call ale#fix#registry#Add('alpha', 'XYZ', [], 'Alpha things.') + + AssertEqual + \ [ + \ 'Try the following generic fixers:', + \ '', + \ '''alpha'' - Alpha things.', + \ '''zed'' - Zedify things.', + \ '', + \ 'See :help ale-fix-configuration', + \ '', + \ 'Press q to close this window', + \ ], + \ GetSuggestions() + +Execute(ALEFixSuggest output should be correct for only filetype handlers): + let &filetype = 'testft2.testft' + + call ale#fix#registry#Add('zed', 'XYZ', ['testft2'], 'Zedify things.') + call ale#fix#registry#Add('alpha', 'XYZ', ['testft'], 'Alpha things.') + + AssertEqual + \ [ + \ 'Try the following fixers appropriate for the filetype:', + \ '', + \ '''alpha'' - Alpha things.', + \ '''zed'' - Zedify things.', + \ '', + \ 'See :help ale-fix-configuration', + \ '', + \ 'Press q to close this window', + \ ], + \ GetSuggestions() + +Execute(ALEFixSuggest should suggest filetype and generic handlers): + let &filetype = 'testft2.testft' + + call ale#fix#registry#Add('zed', 'XYZ', ['testft2'], 'Zedify things.', ['foobar']) + call ale#fix#registry#Add('alpha', 'XYZ', ['testft'], 'Alpha things.') + call ale#fix#registry#Add('generic', 'XYZ', [], 'Generic things.') + + AssertEqual + \ [ + \ 'Try the following fixers appropriate for the filetype:', + \ '', + \ '''alpha'' - Alpha things.', + \ '''zed'', ''foobar'' - Zedify things.', + \ '', + \ 'Try the following generic fixers:', + \ '', + \ '''generic'' - Generic things.', + \ '', + \ 'See :help ale-fix-configuration', + \ '', + \ 'Press q to close this window', + \ ], + \ GetSuggestions() diff --git a/test/fixers/test_alejandra_fixer_callback.vader b/test/fixers/test_alejandra_fixer_callback.vader new file mode 100644 index 00000000..ecbab6ff --- /dev/null +++ b/test/fixers/test_alejandra_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_nix_alejandra_executable + Save g:ale_nix_alejandra_options + +After: + Restore + +Execute(The alejandra callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('alejandra') . ' -- -' + \ }, + \ ale#fixers#alejandra#Fix(bufnr('')) + +Execute(The alejandra executable and options should be configurable): + let g:ale_nix_alejandra_executable = '/path/to/alejandra' + let g:ale_nix_alejandra_options = '-q' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/alejandra') + \ . ' -q -- -', + \ }, + \ ale#fixers#alejandra#Fix(bufnr('')) diff --git a/test/fixers/test_apkbuild_fixer_callback.vader b/test/fixers/test_apkbuild_fixer_callback.vader new file mode 100644 index 00000000..f7bce5cd --- /dev/null +++ b/test/fixers/test_apkbuild_fixer_callback.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpFixerTest('apkbuild', 'apkbuild-fixer') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The apkbuild-fixer callback should return the correct default values): + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('apkbuild-fixer') . ' -p apkbuild-lint %t', + \} + +Execute(The apkbuild-fixer callback should include custom apkbuild-fixer options): + let g:ale_apkbuild_apkbuild_fixer_executable = "another-apkbuild-fixer" + let g:ale_apkbuild_apkbuild_fixer_options = "-s" + + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('another-apkbuild-fixer') . ' -p apkbuild-lint -s %t' + \} diff --git a/test/fixers/test_appleswiftformat_fixer_callback.vader b/test/fixers/test_appleswiftformat_fixer_callback.vader new file mode 100644 index 00000000..72465f4f --- /dev/null +++ b/test/fixers/test_appleswiftformat_fixer_callback.vader @@ -0,0 +1,47 @@ +Before: + call ale#assert#SetUpFixerTest('swift', 'apple-swift-format') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The swiftformat callback should return the correct default values): + call ale#test#SetFilename('../test-files/swift/dummy.swift') + let g:ale_swift_appleswiftformat_executable = 'xxxinvalid' + let g:ale_swift_appleswiftformat_use_swiftpm = 0 + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_appleswiftformat_executable) + \ . ' format --in-place %t', + \ }, + \ ale#fixers#appleswiftformat#Fix(bufnr('')) + +Execute(The swiftformat callback should return the correct default values and located configuration): + call ale#test#SetDirectory('/testplugin/test/test-files/swift/swift-package-project-with-config') + call ale#test#SetFilename('src/folder/dummy.swift') + + let g:ale_swift_appleswiftformat_executable = 'xxxinvalid' + let g:ale_swift_appleswiftformat_use_swiftpm = 0 + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_appleswiftformat_executable) + \ . ' format --in-place %t --configuration ' . glob(g:dir . '/.swift-format'), + \ }, + \ ale#fixers#appleswiftformat#Fix(bufnr('')) + + call ale#test#RestoreDirectory() + +Execute(The swiftformat callback should use swiftpm is use_swiftpm is set to 1): + call ale#test#SetFilename('../test-files/swift/swift-package-project/src/folder/dummy.swift') + let g:ale_swift_appleswiftformat_use_swiftpm = 1 + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('swift') + \ . ' run swift-format format --in-place %t', + \ }, + \ ale#fixers#appleswiftformat#Fix(bufnr('')) diff --git a/test/fixers/test_astyle_fixer_callback.vader b/test/fixers/test_astyle_fixer_callback.vader new file mode 100644 index 00000000..9d2e4c80 --- /dev/null +++ b/test/fixers/test_astyle_fixer_callback.vader @@ -0,0 +1,96 @@ +Before: + Save g:ale_c_astyle_executable + Save g:ale_c_astyle_project_options + Save g:ale_cpp_astyle_project_options + + " Use an invalid global executable, so we don't match it. + let g:ale_c_astyle_executable = 'xxxinvalid' + let g:ale_cpp_astyle_executable = 'invalidpp' + let g:ale_c_astyle_project_options = '' + let g:ale_cpp_astyle_project_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The astyle callback should return the correct default values): + " Because this file doesn't exist, no astylrc config + " exists near it. Therefore, project_options is empty. + call ale#test#SetFilename('../c_files/testfile.c') + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_astyle_executable) + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should support cpp files): + " Because this file doesn't exist, no astylrc config + " exists near it. Therefore, project_options is empty. + call ale#test#SetFilename('../cpp_files/dummy.cpp') + set filetype=cpp " The test fails without this + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_cpp_astyle_executable) + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should support cpp files with option file set): + call ale#test#SetFilename('../cpp_files/dummy.cpp') + let g:ale_cpp_astyle_project_options = '.astylerc_cpp' + let targetfile = bufname(bufnr('%')) + set filetype=cpp " The test fails without this + + AssertEqual + \ { + \ 'command': ale#Escape('invalidpp') + \ . ' --project=' . g:ale_cpp_astyle_project_options + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should return the correct default values with a specified option file): + call ale#test#SetFilename('../c_files/testfile.c') + let g:ale_c_astyle_project_options = '.astylerc_c' + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --project=' . g:ale_c_astyle_project_options + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should find nearest default option file _astylrc): + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/file.c') + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --project=_astylerc' + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should find .astylrc in the same directory as src): + call ale#test#SetFilename('../test-files/cpp/dummy.cpp') + set filetype=cpp " The test fails without this + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape('invalidpp') + \ . ' --project=.astylerc' + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) diff --git a/test/fixers/test_autoflake_fixer_callback.vader b/test/fixers/test_autoflake_fixer_callback.vader new file mode 100644 index 00000000..2abe5fc7 --- /dev/null +++ b/test/fixers/test_autoflake_fixer_callback.vader @@ -0,0 +1,69 @@ +Before: + Save g:ale_python_autoflake_executable + Save g:ale_python_autoflake_options + + " Use an invalid global executable, so we don't match it. + let g:ale_python_autoflake_executable = 'xxxinvalid' + let g:ale_python_autoflake_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + let g:dir = getcwd() + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The autoflake callback should include options): + let g:ale_python_autoflake_options = '--some-option' + + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + AssertEqual + \ { + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/autoflake')) + \ . ' --some-option' + \ . ' --in-place ' + \ . ' %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#autoflake#Fix(bufnr('')) + +Execute(pipenv is detected when python_autoflake_auto_pipenv is set): + let g:ale_python_autoflake_auto_pipenv = 1 + + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('pipenv') . ' run autoflake --in-place %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#autoflake#Fix(bufnr('')) + +Execute(Poetry is detected when python_autoflake_auto_poetry is set): + let g:ale_python_autoflake_auto_poetry = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('poetry') . ' run autoflake --in-place %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#autoflake#Fix(bufnr('')) + +Execute(uv is detected when python_autoflake_auto_uv is set): + let g:ale_python_autoflake_auto_uv = 1 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('uv') . ' run autoflake --in-place %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#autoflake#Fix(bufnr('')) diff --git a/test/fixers/test_autoimport_fixer_callback.vader b/test/fixers/test_autoimport_fixer_callback.vader new file mode 100644 index 00000000..785f4f65 --- /dev/null +++ b/test/fixers/test_autoimport_fixer_callback.vader @@ -0,0 +1,77 @@ +Before: + Save g:ale_python_autoimport_executable + Save g:ale_python_autoimport_options + + " Use an invalid global executable, so we don't match it. + let g:ale_python_autoimport_executable = 'xxxinvalid' + let g:ale_python_autoimport_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The autoimport callback should return the correct default values): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/autoimport')) . ' -', + \ }, + \ ale#fixers#autoimport#Fix(bufnr('')) + +Execute(The autoimport callback should respect custom options): + let g:ale_python_autoimport_options = '--multi-line=3 --trailing-comma' + + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/autoimport')) + \ . ' --multi-line=3 --trailing-comma -', + \ }, + \ ale#fixers#autoimport#Fix(bufnr('')) + +Execute(pipenv is detected when python_autoimport_auto_pipenv is set): + let g:ale_python_autoimport_auto_pipenv = 1 + + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pipenv') . ' run autoimport -', + \ }, + \ ale#fixers#autoimport#Fix(bufnr('')) + +Execute(Poetry is detected when python_autoimport_auto_poetry is set): + let g:ale_python_autoimport_auto_poetry = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('poetry') . ' run autoimport -', + \ }, + \ ale#fixers#autoimport#Fix(bufnr('')) + +Execute(uv is detected when python_autoimport_auto_uv is set): + let g:ale_python_autoimport_auto_uv = 1 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('uv') . ' run autoimport -', + \ }, + \ ale#fixers#autoimport#Fix(bufnr('')) diff --git a/test/fixers/test_autopep8_fixer_callback.vader b/test/fixers/test_autopep8_fixer_callback.vader new file mode 100644 index 00000000..094677f3 --- /dev/null +++ b/test/fixers/test_autopep8_fixer_callback.vader @@ -0,0 +1,68 @@ +Before: + Save g:ale_python_autopep8_executable + Save g:ale_python_autopep8_options + + " Use an invalid global executable, so we don't match it. + let g:ale_python_autopep8_executable = 'xxxinvalid' + let g:ale_python_autopep8_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + let g:dir = getcwd() + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The autopep8 callback should return the correct default values): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/autopep8')) . ' -'}, + \ ale#fixers#autopep8#Fix(bufnr('')) + +Execute(The autopep8 callback should include options): + let g:ale_python_autopep8_options = '--some-option' + + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/autopep8')) . ' --some-option -' }, + \ ale#fixers#autopep8#Fix(bufnr('')) + +Execute(pipenv is detected when python_autopep8_auto_pipenv is set): + let g:ale_python_autopep8_auto_pipenv = 1 + + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('pipenv') . ' run autopep8 -', + \ }, + \ ale#fixers#autopep8#Fix(bufnr('')) + +Execute(Poetry is detected when python_autopep8_auto_poetry is set): + let g:ale_python_autopep8_auto_poetry = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('poetry') . ' run autopep8 -', + \ }, + \ ale#fixers#autopep8#Fix(bufnr('')) + +Execute(uv is detected when python_autopep8_auto_uv is set): + let g:ale_python_autopep8_auto_uv = 1 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('uv') . ' run autopep8 -', + \ }, + \ ale#fixers#autopep8#Fix(bufnr('')) diff --git a/test/fixers/test_bibclean_fixer_callback.vader b/test/fixers/test_bibclean_fixer_callback.vader new file mode 100644 index 00000000..88412eca --- /dev/null +++ b/test/fixers/test_bibclean_fixer_callback.vader @@ -0,0 +1,30 @@ +Before: + Save g:ale_bib_bibclean_executable + Save g:ale_bib_bibclean_options + + let g:ale_bib_bibclean_executable = 'xxxinvalid' + let g:ale_bib_bibclean_options = '-align-equals' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + call ale#test#RestoreDirectory() + +Execute(The bibclean callback should return the correct default values): + call ale#test#SetFilename('../test-files/bib/dummy.bib') + + AssertEqual + \ {'command': ale#Escape(g:ale_bib_bibclean_executable) . ' -align-equals'}, + \ ale#fixers#bibclean#Fix(bufnr('')) + +Execute(The bibclean callback should include custom bibclean options): + let g:ale_bib_bibclean_options = '-author -check-values' + call ale#test#SetFilename('../test-files/bib/dummy.bib') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_bib_bibclean_executable) . ' -author -check-values' + \ }, + \ ale#fixers#bibclean#Fix(bufnr('')) + diff --git a/test/fixers/test_biome_fixer_callback.vader b/test/fixers/test_biome_fixer_callback.vader new file mode 100644 index 00000000..b11b657e --- /dev/null +++ b/test/fixers/test_biome_fixer_callback.vader @@ -0,0 +1,43 @@ +Before: + Save g:ale_biome_options + Save g:ale_biome_fixer_apply_unsafe + + let g:ale_biome_options = '' + let g:ale_biome_fixer_apply_unsafe = 0 + + call ale#assert#SetUpFixerTest('typescript', 'biome') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The default biome command should be correct): + call ale#test#SetFilename('../test-files/biome/jsonc/src/test.ts') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('biome') + \ . ' check --write %t' + \ } + +Execute(Unsafe fixes can be applied via an option): + call ale#test#SetFilename('../test-files/biome/jsonc/src/test.ts') + let g:ale_biome_fixer_apply_unsafe = 1 + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('biome') + \ . ' check --write --unsafe %t' + \ } + +Execute(The fixer should accept options): + call ale#test#SetFilename('../test-files/biome/jsonc/src/test.ts') + let g:ale_biome_options = '--foobar' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('biome') + \ . ' check --write --foobar %t', + \ } diff --git a/test/fixers/test_black_fixer_callback.vader b/test/fixers/test_black_fixer_callback.vader new file mode 100644 index 00000000..1a1f82fc --- /dev/null +++ b/test/fixers/test_black_fixer_callback.vader @@ -0,0 +1,94 @@ +Before: + call ale#assert#SetUpFixerTest('python', 'black') + + let g:dir = getcwd() + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + call ale#assert#TearDownFixerTest() + + unlet! g:dir + unlet! b:bin_dir + +Execute(The black callback should return the correct default values): + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + silent execute 'file ' . fnameescape(file_path) + let fname = ale#Escape(ale#path#Simplify(file_path)) + + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/black')) . ' --stdin-filename ' . fname . ' -'}, + \ 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 + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + silent execute 'file ' . fnameescape(file_path) + let fname = ale#Escape(ale#path#Simplify(file_path)) + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/black')) . ' --some-option --stdin-filename ' . fname . ' -'}, + \ ale#fixers#black#Fix(bufnr('')) + +Execute(The black callback should include --pyi for .pyi files): + let g:ale_python_black_change_directory = 0 + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.pyi' + silent execute 'file ' . fnameescape(file_path) + let fname = ale#Escape(ale#path#Simplify(file_path)) + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/black')) . ' --stdin-filename ' . fname . ' --pyi -'}, + \ ale#fixers#black#Fix(bufnr('')) + +Execute(The black callback should not concatenate options): + let g:ale_python_black_options = '--some-option' + let g:ale_python_black_change_directory = 0 + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.pyi' + silent execute 'file ' . fnameescape(file_path) + let fname = ale#Escape(ale#path#Simplify(file_path)) + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/black')) . ' --some-option --stdin-filename ' . fname. ' --pyi -'}, + \ 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('../test-files/python/pipenv/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/pipenv/whatever.py')) + + AssertEqual + \ {'command': ale#Escape('pipenv') . ' run black --stdin-filename '.fname.' -'}, + \ ale#fixers#black#Fix(bufnr('')) + +Execute(Poetry is detected when python_black_auto_poetry is set): + let g:ale_python_black_auto_poetry = 1 + let g:ale_python_black_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/poetry/whatever.py')) + + AssertEqual + \ {'command': ale#Escape('poetry') . ' run black --stdin-filename '.fname.' -'}, + \ ale#fixers#black#Fix(bufnr('')) + +Execute(uv is detected when python_black_auto_uv is set): + let g:ale_python_black_auto_uv = 1 + let g:ale_python_black_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/uv/whatever.py')) + + AssertEqual + \ {'command': ale#Escape('uv') . ' run black --stdin-filename '.fname.' -'}, + \ ale#fixers#black#Fix(bufnr('')) + \ No newline at end of file diff --git a/test/fixers/test_break_up_long_lines_python_fixer.vader b/test/fixers/test_break_up_long_lines_python_fixer.vader new file mode 100644 index 00000000..c7809acd --- /dev/null +++ b/test/fixers/test_break_up_long_lines_python_fixer.vader @@ -0,0 +1,39 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + call ale#test#RestoreDirectory() + +Execute(Long lines with basic function calls should be broken up correctly): + AssertEqual + \ [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(', + \ 'first_argument,', + \ ' second_argument,', + \ ' third_with_function_call(', + \ 'foo,', + \ ' bar,', + \ '))', + \ ], + \ ale#fixers#generic_python#BreakUpLongLines(bufnr(''), [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(first_argument, second_argument, third_with_function_call(foo, bar))', + \ ]) + +Execute(Longer lines should be permitted if a configuration file allows it): + call ale#test#SetFilename('../test-files/long-line/foo/bar.py') + + AssertEqual + \ [ + \ 'x = this_line_is_between_79_and_90_characters(first, second, third, fourth, fifth)', + \ 'y = this_line_is_longer_than_90_characters(', + \ 'much_longer_word,', + \ ' another_longer_word,', + \ ' a_third_long_word,', + \ ')' + \ ], + \ ale#fixers#generic_python#BreakUpLongLines(bufnr(''), [ + \ 'x = this_line_is_between_79_and_90_characters(first, second, third, fourth, fifth)', + \ 'y = this_line_is_longer_than_90_characters(much_longer_word, another_longer_word, a_third_long_word)', + \ ]) diff --git a/test/fixers/test_brittany_fixer_callback.vader b/test/fixers/test_brittany_fixer_callback.vader new file mode 100644 index 00000000..073e368c --- /dev/null +++ b/test/fixers/test_brittany_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_haskell_brittany_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_haskell_brittany_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The brittany callback should return the correct default values): + call ale#test#SetFilename('../haskell_files/testfile.hs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' --write-mode inplace' + \ . ' %t', + \ }, + \ ale#fixers#brittany#Fix(bufnr('')) diff --git a/test/fixers/test_buf_format_fixer_callback.vader b/test/fixers/test_buf_format_fixer_callback.vader new file mode 100644 index 00000000..ee484820 --- /dev/null +++ b/test/fixers/test_buf_format_fixer_callback.vader @@ -0,0 +1,21 @@ +Before: + Save g:ale_proto_buf_format_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_proto_buf_format_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The buf-format callback should return the correct default values): + call ale#test#SetFilename('../test-files/proto/testfile.proto') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' format %t', + \ }, + \ ale#fixers#buf_format#Fix(bufnr('')) diff --git a/test/fixers/test_buildifier_fixer_callback.vader b/test/fixers/test_buildifier_fixer_callback.vader new file mode 100644 index 00000000..f82e8e6e --- /dev/null +++ b/test/fixers/test_buildifier_fixer_callback.vader @@ -0,0 +1,29 @@ +Before: + let g:ale_bazel_buildifier_options = '' + call ale#assert#SetUpFixerTest('bzl', 'buildifier') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The buildifier callback should return the correct default values): + call ale#test#SetFilename('../test-files/bazel/WORKSPACE') + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_bazel_buildifier_executable) + \ . ' -mode fix -lint fix -path ' + \ . ale#Escape(ale#test#GetFilename('../test-files/bazel/WORKSPACE')) + \ . ' -' + \ } + +Execute(The buildifier callback should include any additional options): + call ale#test#SetFilename('../test-files/bazel/WORKSPACE') + let g:ale_bazel_buildifier_options = '--some-option' + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_bazel_buildifier_executable) + \ . ' -mode fix -lint fix -path ' + \ . ale#Escape(ale#test#GetFilename('../test-files/bazel/WORKSPACE')) + \ . ' --some-option -' + \ } diff --git a/test/fixers/test_clangformat_fixer_callback.vader b/test/fixers/test_clangformat_fixer_callback.vader new file mode 100644 index 00000000..130ca7f7 --- /dev/null +++ b/test/fixers/test_clangformat_fixer_callback.vader @@ -0,0 +1,64 @@ +Before: + Save g:ale_c_clangformat_executable + Save g:c_clangformat_style_option + Save g:c_clangformat_use_local_file + + " Use an invalid global executable, so we don't match it. + let g:ale_c_clangformat_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The clang-format callback should return the correct default values): + call ale#test#SetFilename('../test-files/c/dummy.c') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' --assume-filename=' . ale#Escape(bufname(bufnr(''))) + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) + +Execute(The clangformat callback should include any additional options): + call ale#test#SetFilename('../test-files/c/dummy.c') + let g:ale_c_clangformat_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' --assume-filename=' . ale#Escape(bufname(bufnr(''))) + \ . ' --some-option', + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) + +Execute(The clangformat callback should include style options as well): + call ale#test#SetFilename('../test-files/c/dummy.c') + let g:ale_c_clangformat_options = '--some-option' + let g:ale_c_clangformat_style_option = '{BasedOnStyle: Microsoft, ColumnLimit:80,}' + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' --assume-filename=' . ale#Escape(bufname(bufnr(''))) + \ . ' --some-option' . " -style='{BasedOnStyle: Microsoft, ColumnLimit:80,}'", + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) + +Execute(The clangformat callback should use local file instead of style options): + call ale#test#SetFilename('../test-files/clangformat/with_clangformat/dummy.c') + let g:ale_c_clangformat_options = '--some-option' + let g:ale_c_clangformat_style_option = '{BasedOnStyle: Microsoft, ColumnLimit:80,}' + let g:ale_c_clangformat_use_local_file = 1 + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' --assume-filename=' . ale#Escape(bufname(bufnr(''))) + \ . ' --some-option' . ' -style=file', + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) diff --git a/test/fixers/test_clangtidy_fixer_callback.vader b/test/fixers/test_clangtidy_fixer_callback.vader new file mode 100644 index 00000000..d6678bd9 --- /dev/null +++ b/test/fixers/test_clangtidy_fixer_callback.vader @@ -0,0 +1,47 @@ +Before: + Save g:ale_c_build_dir + Save g:ale_c_clangtidy_executable + Save g:ale_c_clangtidy_checks + Save g:ale_c_clangtidy_extra_options + Save g:ale_cpp_clangtidy_executable + Save g:ale_cpp_clangtidy_checks + Save g:ale_cpp_clangtidy_extra_options + + " Use an invalid global executable, so we don't match it. + let g:ale_c_clangtidy_executable = 'xxxinvalid' + let g:ale_c_clangtidy_checks = [] + let g:ale_c_clangtidy_extra_options = '' + let g:ale_cpp_clangtidy_executable = 'xxxinvalidpp' + let g:ale_cpp_clangtidy_checks = [] + let g:ale_cpp_clangtidy_extra_options = '' + let g:ale_c_build_dir = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The clangtidy callback should return the correct default values): + call ale#test#SetFilename('../test-files/c/dummy.c') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_c_clangtidy_executable) + \ . ' -fix -fix-errors %t' + \ }, + \ ale#fixers#clangtidy#Fix(bufnr('')) + +Execute(The clangtidy callback should include any additional options): + call ale#test#SetFilename('../test-files/c/dummy.c') + let g:ale_c_clangtidy_extra_options = '--some-option' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_c_clangtidy_executable) + \ . ' -fix -fix-errors --some-option %t', + \ }, + \ ale#fixers#clangtidy#Fix(bufnr('')) diff --git a/test/fixers/test_cljfmt_fixer_callback.vader b/test/fixers/test_cljfmt_fixer_callback.vader new file mode 100644 index 00000000..984689fb --- /dev/null +++ b/test/fixers/test_cljfmt_fixer_callback.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpFixerTest('clojure', 'cljfmt') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The cljfmt callback should return the correct default values): + AssertFixer { + \ 'command': ale#Escape('cljfmt') . ' fix %t', + \ 'read_temporary_file': 1, + \} + diff --git a/test/fixers/test_cmakeformat_fixer_callback.vader b/test/fixers/test_cmakeformat_fixer_callback.vader new file mode 100644 index 00000000..545fe067 --- /dev/null +++ b/test/fixers/test_cmakeformat_fixer_callback.vader @@ -0,0 +1,36 @@ +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 + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' -' + \ }, + \ 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 + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_cmake_cmakeformat_options + \ . ' -', + \ }, + \ ale#fixers#cmakeformat#Fix(bufnr('')) diff --git a/test/fixers/test_crystal_format_fixer_callback.vader b/test/fixers/test_crystal_format_fixer_callback.vader new file mode 100644 index 00000000..d7d9588b --- /dev/null +++ b/test/fixers/test_crystal_format_fixer_callback.vader @@ -0,0 +1,33 @@ +Before: + Save g:ale_crystal_format_executable + Save g:ale_crystal_format_options + + " Use an invalid global executable, so we don't match it. + let g:ale_crystal_format_executable = 'xxxinvalid' + let g:ale_crystal_format_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The crystal format callback should return the correct default values): + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' tool format -', + \ }, + \ ale#fixers#crystal#Fix(bufnr('')) + +Execute(The crystal format callback should include custom options): + let g:ale_crystal_format_options = "-list=true" + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' tool format ' . g:ale_crystal_format_options + \ . ' -', + \ }, + \ ale#fixers#crystal#Fix(bufnr('')) diff --git a/test/fixers/test_css_beautify_fixer_callback.vader b/test/fixers/test_css_beautify_fixer_callback.vader new file mode 100644 index 00000000..a6b61713 --- /dev/null +++ b/test/fixers/test_css_beautify_fixer_callback.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpFixerTest('css', 'css-beautify', 'beautify') + +After: + Restore + + call ale#assert#TearDownFixerTest() + +Execute(The css-beautify callback should return the correct default command): + AssertEqual + \ {'command': ale#Escape('css-beautify') . ' -'}, + \ ale#fixers#css_beautify#Fix(bufnr('')) diff --git a/test/fixers/test_dart_format_fixer_callback.vader b/test/fixers/test_dart_format_fixer_callback.vader new file mode 100644 index 00000000..8dfd20b6 --- /dev/null +++ b/test/fixers/test_dart_format_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_dart_format_executable + Save g:ale_dart_format_options + + " Use an invalid global executable, so we don't match it. + let g:ale_dart_format_executable = 'xxxinvalid' + let g:ale_dart_format_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The dart format callback should return the correct default values): + call ale#test#SetFilename('../test-files/dart/testfile.dart') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format' + \ . ' %t', + \ }, + \ ale#fixers#dart_format#Fix(bufnr('')) + +Execute(The dart format callback should include custom dart format options): + let g:ale_dart_format_options = "-l 80" + call ale#test#SetFilename('../test-files/dart/testfile.dart') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format' + \ . ' ' . g:ale_dart_format_options + \ . ' %t', + \ }, + \ ale#fixers#dart_format#Fix(bufnr('')) diff --git a/test/fixers/test_dartfmt_fixer_callback.vader b/test/fixers/test_dartfmt_fixer_callback.vader new file mode 100644 index 00000000..c783c9a4 --- /dev/null +++ b/test/fixers/test_dartfmt_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_dart_dartfmt_executable + Save g:ale_dart_dartfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_dart_dartfmt_executable = 'xxxinvalid' + let g:ale_dart_dartfmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The dartfmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/dart/testfile.dart') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -w' + \ . ' %t', + \ }, + \ ale#fixers#dartfmt#Fix(bufnr('')) + +Execute(The dartfmt callback should include custom dartfmt options): + let g:ale_dart_dartfmt_options = "-l 80" + call ale#test#SetFilename('../test-files/dart/testfile.dart') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -w' + \ . ' ' . g:ale_dart_dartfmt_options + \ . ' %t', + \ }, + \ ale#fixers#dartfmt#Fix(bufnr('')) diff --git a/test/fixers/test_dfmt_fixer_callback.vader b/test/fixers/test_dfmt_fixer_callback.vader new file mode 100644 index 00000000..5749224e --- /dev/null +++ b/test/fixers/test_dfmt_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_d_dfmt_executable + Save g:ale_d_dfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_d_dfmt_executable = 'xxxinvalid' + let g:ale_d_dfmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The dfmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/d/test.d') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i' + \ . ' %t', + \ }, + \ ale#fixers#dfmt#Fix(bufnr('')) + +Execute(The dfmt callback should include custom dfmt options): + let g:ale_d_dfmt_options = "--space-after-cast" + call ale#test#SetFilename('../test-files/d/test.d') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i' + \ . ' ' . g:ale_d_dfmt_options + \ . ' %t', + \ }, + \ ale#fixers#dfmt#Fix(bufnr('')) diff --git a/test/fixers/test_dhall_format_fixer_callback.vader b/test/fixers/test_dhall_format_fixer_callback.vader new file mode 100644 index 00000000..8fa2fe7c --- /dev/null +++ b/test/fixers/test_dhall_format_fixer_callback.vader @@ -0,0 +1,22 @@ +Before: + Save g:ale_dhall_executable + Save g:ale_dhall_options + + " Use an invalid global executable, so we don’t match it. + let g:ale_dhall_executable = 'odd-dhall' + let g:ale_dhall_options = '--ascii' + + call ale#assert#SetUpFixerTest('dhall', 'dhall-format') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The dhall-format callback should return the correct options): + call ale#test#SetFilename('../dhall_files/testfile.dhall') + + AssertFixer + \ { + \ 'command': ale#Escape('odd-dhall') + \ . ' --ascii' + \ . ' format' + \ } diff --git a/test/fixers/test_dhall_freeze_fixer_callback.vader b/test/fixers/test_dhall_freeze_fixer_callback.vader new file mode 100644 index 00000000..02473697 --- /dev/null +++ b/test/fixers/test_dhall_freeze_fixer_callback.vader @@ -0,0 +1,22 @@ +Before: + Save g:ale_dhall_executable + Save g:ale_dhall_options + + " Use an invalid global executable, so we don’t match it. + let g:ale_dhall_executable = 'odd-dhall' + let g:ale_dhall_options = '--ascii' + let g:ale_dhall_freeze_options = '--all' + + call ale#assert#SetUpFixerTest('dhall', 'dhall-freeze') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The dhall-freeze callback should return the correct options): + AssertFixer + \ { + \ 'command': ale#Escape('odd-dhall') + \ . ' --ascii' + \ . ' freeze' + \ . ' --all' + \ } diff --git a/test/fixers/test_dhall_lint_fixer_callback.vader b/test/fixers/test_dhall_lint_fixer_callback.vader new file mode 100644 index 00000000..e2054eb0 --- /dev/null +++ b/test/fixers/test_dhall_lint_fixer_callback.vader @@ -0,0 +1,20 @@ +Before: + Save g:ale_dhall_executable + Save g:ale_dhall_options + + " Use an invalid global executable, so we don’t match it. + let g:ale_dhall_executable = 'odd-dhall' + let g:ale_dhall_options = '--ascii' + + call ale#assert#SetUpFixerTest('dhall', 'dhall-lint') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The dhall-lint callback should return the correct options): + AssertFixer + \ { + \ 'command': ale#Escape('odd-dhall') + \ . ' --ascii' + \ . ' lint' + \ } diff --git a/test/fixers/test_djlint_fixer_callback.vader b/test/fixers/test_djlint_fixer_callback.vader new file mode 100644 index 00000000..17224690 --- /dev/null +++ b/test/fixers/test_djlint_fixer_callback.vader @@ -0,0 +1,98 @@ +Before: + call ale#assert#SetUpFixerTest('html', 'djlint', 'djlint') + +After: + Restore + + call ale#assert#TearDownFixerTest() + +Execute(The djlint callback should return the correct default command): + AssertEqual + \ {'command': ale#Escape('djlint') . ' --reformat -'}, + \ ale#fixers#djlint#Fix(bufnr('')) + +Execute(The --profile option should not be overridden): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=htmldjango + let g:ale_html_djlint_options = '--profile jinja' + + AssertFixer + \ { 'command': ale#Escape(g:ale_html_djlint_executable) + \ . ' --reformat' + \ . ' --profile jinja' + \ . ' -', + \ } + + +Execute(Should set --profile for experimental language, Handlebars): + call ale#test#SetFilename('../test-files/djlint/testfile.hbs') + + set filetype=handlebars + + AssertFixer + \ { 'command': ale#Escape(g:ale_html_djlint_executable) + \ . ' --reformat' + \ . ' --profile handlebars' + \ . ' -', + \ } + +Execute(Should set --profile for htmldjango, Django templates): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=htmldjango + + AssertFixer + \ { 'command': ale#Escape(g:ale_html_djlint_executable) + \ . ' --reformat' + \ . ' --profile django' + \ . ' -', + \ } + +Execute(Should set --profile for htmlangular): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=htmlangular + + AssertFixer + \ { 'command': ale#Escape(g:ale_html_djlint_executable) + \ . ' --reformat' + \ . ' --profile angular' + \ . ' -', + \ } + +Execute(Should set --profile for jinja): + call ale#test#SetFilename('../test-files/djlint/testfile.jinja2') + + set filetype=jinja + + AssertFixer + \ { 'command': ale#Escape(g:ale_html_djlint_executable) + \ . ' --reformat' + \ . ' --profile jinja' + \ . ' -', + \ } + +Execute(Should set --profile for nunjucks): + call ale#test#SetFilename('../test-files/djlint/testfile.njk') + + set filetype=nunjucks + + AssertFixer + \ { 'command': ale#Escape(g:ale_html_djlint_executable) + \ . ' --reformat' + \ . ' --profile nunjucks' + \ . ' -', + \ } + +Execute(Should set --profile for Go HTML templates): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=gohtmltmpl + + AssertFixer + \ { 'command': ale#Escape(g:ale_html_djlint_executable) + \ . ' --reformat' + \ . ' --profile golang' + \ . ' -', + \ } diff --git a/test/fixers/test_dotnet_format_fixer_callback.vader b/test/fixers/test_dotnet_format_fixer_callback.vader new file mode 100644 index 00000000..a3993573 --- /dev/null +++ b/test/fixers/test_dotnet_format_fixer_callback.vader @@ -0,0 +1,41 @@ +Before: + Save g:ale_cs_dotnet_format_executable + Save g:ale_cs_dotnet_format_options + + " Use an invalid global executable, so we don't match it. + let g:ale_cs_dotnet_format_executable = 'xxxinvalid' + let g:ale_cs_dotnet_format_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The dotnet format callback should return the correct default values): + call ale#test#SetFilename('../test-files/cs/testfile.cs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format' + \ . ' --folder --include %t "$(dirname %t)"', + \ }, + \ ale#fixers#dotnet_format#Fix(bufnr('')) + +Execute(The dotnet format callback should include custom dotnet format options): + let g:ale_cs_dotnet_format_options = "-l 80" + call ale#test#SetFilename('../test-files/cs/testfile.cs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format' + \ . ' ' . g:ale_cs_dotnet_format_options + \ . ' --folder --include %t "$(dirname %t)"', + \ }, + \ ale#fixers#dotnet_format#Fix(bufnr('')) + diff --git a/test/fixers/test_dprint_fixer_callback.vader b/test/fixers/test_dprint_fixer_callback.vader new file mode 100644 index 00000000..6a9d0118 --- /dev/null +++ b/test/fixers/test_dprint_fixer_callback.vader @@ -0,0 +1,44 @@ +Before: + call ale#assert#SetUpFixerTest('typescript', 'dprint') + call ale#test#SetFilename('../test-files/dprint/blank.ts') + let g:ale_dprint_executable_override = 0 + let g:ale_dprint_executable = 'dprint' + let g:ale_dprint_config = '' + +After: + Restore + call ale#assert#TearDownFixerTest() + +Execute(The dprint callback should return 0 for a non-existent executable): + let g:ale_dprint_executable = 'foo' + AssertFixer 0 + +Execute(The dprint callback should return the correct default values): + let g:ale_dprint_executable_override = 1 + AssertFixer { + \ 'command': ale#Escape('dprint') + \ . ' fmt ' + \ . ' --stdin %s' + \ } + +Execute(The dprint callback should include config): + let g:ale_dprint_executable_override = 1 + let g:ale_dprint_config = 'dprint.json' + + AssertFixer { + \ 'command': ale#Escape('dprint') + \ . ' fmt ' + \ . ' -c ' + \ . ale#Escape((has('win32') ? 'C:\testplugin\test\test-files\dprint\dprint.json' : '/testplugin/test/test-files/dprint/dprint.json')) + \ . ' --stdin %s' + \ } + +Execute(The dprint callback should include custom options): + let g:ale_dprint_executable_override = 1 + let g:ale_dprint_options = '--verbose' + + AssertFixer { + \ 'command': ale#Escape('dprint') + \ . ' fmt ' + \ . '--verbose' . ' --stdin %s' + \ } diff --git a/test/fixers/test_dune_fixer_callback.vader b/test/fixers/test_dune_fixer_callback.vader new file mode 100644 index 00000000..7fc0733c --- /dev/null +++ b/test/fixers/test_dune_fixer_callback.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_ocaml_dune_executable + Save g:ale_ocaml_dune_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ocaml_dune_executable = 'xxxinvalid' + let g:ale_ocaml_dune_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The dune callback should return the correct default values): + call ale#test#SetFilename('../test-files/ocaml/testfile.re') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' format', + \ }, + \ ale#fixers#dune#Fix(bufnr('')) + +Execute(The dune callback should include custom dune options): + let g:ale_ocaml_dune_options = "--random-option" + call ale#test#SetFilename('../test-files/ocaml/testfile.re') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' format' + \ . ' ' . g:ale_ocaml_dune_options, + \ }, + \ ale#fixers#dune#Fix(bufnr('')) diff --git a/test/fixers/test_elm_format_fixer_callback.vader b/test/fixers/test_elm_format_fixer_callback.vader new file mode 100644 index 00000000..35244737 --- /dev/null +++ b/test/fixers/test_elm_format_fixer_callback.vader @@ -0,0 +1,74 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + unlet! b:ale_elm_format_executable + unlet! b:ale_elm_format_use_global + unlet! b:ale_elm_format_options + + call ale#test#RestoreDirectory() + +Execute(The elm-format command should have default params): + call ale#test#SetFilename('../test-files/elm/src/subdir/testfile.elm') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/elm/node_modules/.bin/elm-format')) + \ . ' %t --yes', + \ }, + \ ale#fixers#elm_format#Fix(bufnr('')) + +Execute(The elm-format command should manage use_global = 1 param): + call ale#test#SetFilename('../test-files/elm/src/subdir/testfile.elm') + let b:ale_elm_format_use_global = 1 + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('elm-format') + \ . ' %t --yes', + \ }, + \ ale#fixers#elm_format#Fix(bufnr('')) + +Execute(The elm-format command should manage executable param): + call ale#test#SetFilename('../test-files/elm/src/subdir/testfile.elm') + let b:ale_elm_format_use_global = 1 + let b:ale_elm_format_executable = 'elmformat' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('elmformat') + \ . ' %t --yes', + \ }, + \ ale#fixers#elm_format#Fix(bufnr('')) + +Execute(The elm-format command should manage empty options): + call ale#test#SetFilename('../test-files/elm/src/subdir/testfile.elm') + let b:ale_elm_format_options = '' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/elm/node_modules/.bin/elm-format')) + \ . ' %t', + \ }, + \ ale#fixers#elm_format#Fix(bufnr('')) + +Execute(The elm-format command should manage custom options): + call ale#test#SetFilename('../test-files/elm/src/subdir/testfile.elm') + let b:ale_elm_format_options = '--param1 --param2' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/elm/node_modules/.bin/elm-format')) + \ . ' %t --param1 --param2', + \ }, + \ ale#fixers#elm_format#Fix(bufnr('')) diff --git a/test/fixers/test_erbformatter_fixer_callback.vader b/test/fixers/test_erbformatter_fixer_callback.vader new file mode 100644 index 00000000..417a64e8 --- /dev/null +++ b/test/fixers/test_erbformatter_fixer_callback.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpFixerTest('eruby', 'erb-formatter') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The erb-formatter callback should return the correct default values): + AssertFixer { + \ 'command': ale#Escape('erb-formatter') . ' -w %t', + \ 'read_temporary_file': 1, + \} + +Execute(The erb-formatter callback should allow custom erb-formatter executables): + let g:ale_eruby_erbformatter_executable = 'foo/bar' + + AssertFixer { + \ 'command': ale#Escape('foo/bar') . ' -w %t', + \ 'read_temporary_file': 1, + \} diff --git a/test/fixers/test_erblint_fixer_callback.vader b/test/fixers/test_erblint_fixer_callback.vader new file mode 100644 index 00000000..7b56e3a9 --- /dev/null +++ b/test/fixers/test_erblint_fixer_callback.vader @@ -0,0 +1,55 @@ +Before: + Save g:ale_eruby_erblint_executable + Save g:ale_eruby_erblint_options + + " Use an invalid global executable, so we don't match it. + let g:ale_eruby_erblint_executable = 'xxxinvalid' + let g:ale_eruby_erblint_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The erblint callback should return the correct default values): + call ale#test#SetFilename('../test-files/eruby/dummy.html.erb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#erblint#PostProcess', + \ 'command': ale#Escape(g:ale_eruby_erblint_executable) + \ . ' --autocorrect --stdin %s', + \ }, + \ ale#fixers#erblint#Fix(bufnr('')) + +Execute(The erblint callback should include custom erblint options): + let g:ale_eruby_erblint_options = '--lint-all' + call ale#test#SetFilename('../test-files/ruby/with_config/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#erblint#PostProcess', + \ 'command': ale#Escape(g:ale_eruby_erblint_executable) + \ . ' --lint-all' + \ . ' --autocorrect --stdin %s', + \ }, + \ ale#fixers#erblint#Fix(bufnr('')) + +Execute(The erblint post-processor should remove diagnostics content): + AssertEqual + \ [ + \ '
', + \ '', + \ '
', + \ ], + \ ale#fixers#erblint#PostProcess(bufnr(''), [ + \ 'Linting 1 files with 11 autocorrectable linters...', + \ '', + \ '1 error(s) corrected in ERB files', + \ '================ /home/user/demo.html.erb ==================', + \ '
', + \ '', + \ '
', + \ ]) diff --git a/test/fixers/test_erlang_mode_fixer_callback.vader b/test/fixers/test_erlang_mode_fixer_callback.vader new file mode 100644 index 00000000..a9581d93 --- /dev/null +++ b/test/fixers/test_erlang_mode_fixer_callback.vader @@ -0,0 +1,72 @@ +Before: + call ale#assert#SetUpFixerTest('erlang', 'erlang_mode') + + function! Fixer(key, ...) abort + let l:name = get(a:, 1, 'erlang_mode') + + let l:func = ale#fix#registry#GetFunc(l:name) + let l:dict = call(l:func, [bufnr('')]) + + return l:dict[a:key] + endfunction + +After: + delfunction Fixer + call ale#assert#TearDownFixerTest() + +Execute(Emacs should edit temporary file in batch mode): + AssertEqual 0, stridx( + \ Fixer('command'), + \ ale#Escape('emacs') . ' --batch --find-file=%t --eval=', + \) + +Execute(The temporary file should be read): + AssertEqual 1, Fixer('read_temporary_file') + +Execute(Fixer alias erlang-mode should be provided): + AssertEqual 0, stridx( + \ Fixer('command', 'erlang-mode'), + \ ale#Escape('emacs') . ' --batch --find-file=%t --eval=', + \) + +Execute(Emacs executable should be configurable): + let b:ale_erlang_erlang_mode_emacs_executable = '/path/to/emacs' + AssertEqual 0, stridx(Fixer('command'), ale#Escape('/path/to/emacs')) + +Execute(enable-local-variables should be :safe): + Assert Fixer('command') =~# '\m\' + +Execute(erlang-indent-level should be 4 by default): + Assert Fixer('command') =~# '\m\' + +Execute(erlang-indent-level should be configurable): + let b:ale_erlang_erlang_mode_indent_level = 2 + Assert Fixer('command') =~# '\m\' + +Execute(erlang-icr-indent should be nil by default): + Assert Fixer('command') =~# '\m\' + +Execute(erlang-icr-indent should be configurable): + let b:ale_erlang_erlang_mode_icr_indent = 0 + Assert Fixer('command') =~# '\m\' + +Execute(erlang-indent-guard should be 2 by default): + Assert Fixer('command') =~# '\m\' + +Execute(erlang-indent-guard should be configurable): + let b:ale_erlang_erlang_mode_indent_guard = 0 + Assert Fixer('command') =~# '\m\' + +Execute(erlang-argument-indent should be 2 by default): + Assert Fixer('command') =~# '\m\' + +Execute(erlang-argument-indent should be configurable): + let b:ale_erlang_erlang_mode_argument_indent = 4 + Assert Fixer('command') =~# '\m\' + +Execute(indent-tabs-mode should be nil by default): + Assert Fixer('command') =~# '\m\' + +Execute(indent-tabs-mode should be configurable): + let b:ale_erlang_erlang_mode_indent_tabs_mode = 't' + Assert Fixer('command') =~# '\m\' diff --git a/test/fixers/test_erlfmt_fixer_callback.vader b/test/fixers/test_erlfmt_fixer_callback.vader new file mode 100644 index 00000000..21e252e6 --- /dev/null +++ b/test/fixers/test_erlfmt_fixer_callback.vader @@ -0,0 +1,34 @@ +Before: + call ale#assert#SetUpFixerTest('erlang', 'erlfmt') + +After: + unlet! b:root + + call ale#assert#TearDownFixerTest() + +Execute(The local erlfmt executable should be used by default): + " Not sure if this is a good default though. It seems to imply + " that the executable is committed to the repository. + + let b:root = '../test-files/erlang/app_with_erlfmt' + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertFixer { + \ 'command': ale#Escape(ale#test#GetFilename(b:root . '/erlfmt')) . ' -', + \} + +Execute(The global erlfmt executable should be configurable): + let b:root = '../test-files/erlang/app_with_erlfmt' + + let b:ale_erlang_erlfmt_executable = '/path/to/erlfmt' + let b:ale_erlang_erlfmt_use_global = 1 + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertFixer {'command': ale#Escape('/path/to/erlfmt') . ' -'} + +Execute(The erlfmt command should handle empty options): + AssertFixer {'command': ale#Escape('erlfmt') . ' -'} + +Execute(The erlfmt command should handle custom options): + let b:ale_erlang_erlfmt_options = '--insert-pragma' + AssertFixer {'command': ale#Escape('erlfmt') . ' --insert-pragma -'} diff --git a/test/fixers/test_eslint_fixer_callback.vader b/test/fixers/test_eslint_fixer_callback.vader new file mode 100644 index 00000000..5cf58014 --- /dev/null +++ b/test/fixers/test_eslint_fixer_callback.vader @@ -0,0 +1,321 @@ +Before: + call ale#assert#SetUpFixerTest('javascript', 'eslint') + Save g:ale_command_wrapper + + runtime autoload/ale/handlers/eslint.vim + + let g:ale_command_wrapper = '' + +After: + call ale#assert#TearDownFixerTest() + +Execute(The executable path should be correct): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir/testfile.js') + + " eslint_d output with an older eslint version is used here. + GivenCommandOutput ['v4.4.1 (eslint_d v5.1.0)'] + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/.eslintrc.js')) + \ . ' --fix %t', + \ } + +Execute(The ESLint fixer shouldn't run if no configuration file can be found): + call ale#test#SetFilename('../no-configuration') + AssertFixerNotExecuted + +Execute(The ESLint fixer should use a config file option if set for old versions): + call ale#test#SetFilename('../no-configuration') + let b:ale_javascript_eslint_options = '-c /foo.cfg' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': '', + \ 'command': ale#Escape('eslint') . ' -c /foo.cfg --fix %t', + \ } + + let b:ale_javascript_eslint_options = '--bar -c /foo.cfg' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': '', + \ 'command': ale#Escape('eslint') . ' --bar -c /foo.cfg --fix %t', + \ } + + let b:ale_javascript_eslint_options = '--config /foo.cfg' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': '', + \ 'command': ale#Escape('eslint') . ' --config /foo.cfg --fix %t', + \ } + + let b:ale_javascript_eslint_options = '--bar --config /foo.cfg' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': '', + \ 'command': ale#Escape('eslint') . ' --bar --config /foo.cfg --fix %t', + \ } + +Execute(The ESLint fixer should use a -c file option if set for eslint_d): + let b:ale_javascript_eslint_executable = '/bin/eslint_d' + GivenCommandOutput ['v3.19.0 (eslint_d v4.2.0)'] + call ale#test#SetFilename('../no-configuration') + let b:ale_javascript_eslint_options = '-c /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', + \ 'cwd': '', + \ 'command': ale#Escape('/bin/eslint_d') + \ . ' -c /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-to-stdout' + \ } + + let b:ale_javascript_eslint_options = '--bar -c /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', + \ 'cwd': '', + \ 'command': ale#Escape('/bin/eslint_d') + \ . ' --bar -c /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-to-stdout' + \ } + + let b:ale_javascript_eslint_options = '--config /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', + \ 'cwd': '', + \ 'command': ale#Escape('/bin/eslint_d') + \ . ' --config /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-to-stdout' + \ } + + let b:ale_javascript_eslint_options = '--bar --config /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', + \ 'cwd': '', + \ 'command': ale#Escape('/bin/eslint_d') + \ . ' --bar --config /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-to-stdout' + \ } + +Execute(The ESLint fixer should use a config file option if set for new versions): + GivenCommandOutput ['4.9.0'] + call ale#test#SetFilename('../no-configuration') + let b:ale_javascript_eslint_options = '-c /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ 'cwd': '', + \ 'command': ale#Escape('eslint') + \ . ' -c /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json' + \ } + + let b:ale_javascript_eslint_options = '--bar -c /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ 'cwd': '', + \ 'command': ale#Escape('eslint') + \ . ' --bar -c /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json' + \ } + + let b:ale_javascript_eslint_options = '--config /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ 'cwd': '', + \ 'command': ale#Escape('eslint') + \ . ' --config /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json' + \ } + + let b:ale_javascript_eslint_options = '--bar --config /foo.cfg' + + AssertFixer + \ { + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ 'cwd': '', + \ 'command': ale#Escape('eslint') + \ . ' --bar --config /foo.cfg' + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json' + \ } + +Execute(The lower priority configuration file in a nested directory should be preferred): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir-with-config/testfile.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/subdir-with-config'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/subdir-with-config/.eslintrc')) + \ . ' --fix %t', + \ } + +Execute(--config in options should override configuration file detection for old versions): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir-with-config/testfile.js') + + let b:ale_javascript_eslint_options = '--config /foo.cfg' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/subdir-with-config'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --config /foo.cfg' + \ . ' --fix %t', + \ } + + let b:ale_javascript_eslint_options = '-c /foo.cfg' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/subdir-with-config'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c /foo.cfg' + \ . ' --fix %t', + \ } + +Execute(package.json should be used as a last resort): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir-with-package-json/testfile.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/.eslintrc.js')) + \ . ' --fix %t', + \ } + + call ale#test#SetFilename('../test-files/eslint/package.json') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/node_modules/.bin/eslint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/package.json')) + \ . ' --fix %t', + \ } + +Execute(The version check should be correct): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir-with-config/testfile.js') + + " 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 . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --version', + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/subdir-with-config'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ }, + \] + + AssertFixer [ + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/subdir-with-config'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/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('../test-files/eslint/react-app/subdir/testfile.js') + + GivenCommandOutput ['4.9.0'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/react-app'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ } + +Execute(The --fix-dry-run post-processor should handle JSON output correctly): + AssertEqual + \ [], + \ ale#fixers#eslint#ProcessFixDryRunOutput(bufnr(''), []) + AssertEqual + \ [], + \ ale#fixers#eslint#ProcessFixDryRunOutput(bufnr(''), ['']) + AssertEqual + \ [], + \ ale#fixers#eslint#ProcessFixDryRunOutput(bufnr(''), ['[{}]']) + AssertEqual + \ ['foo', 'bar'], + \ ale#fixers#eslint#ProcessFixDryRunOutput(bufnr(''), ['[{"output": "foo\nbar"}]']) + +Execute(The eslint_d post-processor should permit regular JavaScript content): + AssertEqual + \ [ + \ 'const x = ''Error: foo''', + \ 'const y = 3', + \ ], + \ ale#fixers#eslint#ProcessEslintDOutput(bufnr(''), [ + \ 'const x = ''Error: foo''', + \ 'const y = 3', + \ ]) + +Execute(The eslint_d post-processor should handle error messages correctly): + AssertEqual + \ [], + \ ale#fixers#eslint#ProcessEslintDOutput(bufnr(''), [ + \ 'Error: No ESLint configuration found.', + \ ]) + +Execute(The eslint_d post-processor should handle failing to connect properly): + AssertEqual + \ [], + \ ale#fixers#eslint#ProcessEslintDOutput(bufnr(''), [ + \ 'Could not connect', + \ ]) + +Execute(The executable path should be correct for astro app): + call ale#test#SetFilename('../test-files/eslint/astro-app/src/pages/index.astro') + + " eslint_d output with an older eslint version is used here. + GivenCommandOutput ['v4.4.1 (eslint_d v5.1.0)'] + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/eslint/astro-app'), + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/astro-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/astro-app/.eslintrc.js')) + \ . ' --fix %t', + \ } diff --git a/test/fixers/test_fecs_fixer_callback.vader b/test/fixers/test_fecs_fixer_callback.vader new file mode 100644 index 00000000..146c0a87 --- /dev/null +++ b/test/fixers/test_fecs_fixer_callback.vader @@ -0,0 +1,24 @@ +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 = '../test-files/fecs/fecs' + let g:ale_javascript_fecs_use_global = 1 + + 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_fish_indent_fixer_callback.vader b/test/fixers/test_fish_indent_fixer_callback.vader new file mode 100644 index 00000000..3555a974 --- /dev/null +++ b/test/fixers/test_fish_indent_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_fish_fish_indent_executable + Save g:ale_fish_fish_indent_options + + " Use an invalid global executable, so we don't match it. + let g:ale_fish_fish_indent_executable = 'xxxinvalid' + let g:ale_fish_fish_indent_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The fish_indent callback should return the correct default values): + call ale#test#SetFilename('../test-files/fish/testfile.fish') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -w ' + \ . ' %t', + \ }, + \ ale#fixers#fish_indent#Fix(bufnr('')) + +Execute(The fish_indent callback should include custom fish_indent options): + let g:ale_fish_fish_indent_options = "-d" + call ale#test#SetFilename('../test-files/fish/testfile.fish') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -w ' + \ . ' -d' + \ . ' %t', + \ }, + \ ale#fixers#fish_indent#Fix(bufnr('')) diff --git a/test/fixers/test_fixjson_fixer_callback.vader b/test/fixers/test_fixjson_fixer_callback.vader new file mode 100644 index 00000000..2b023fad --- /dev/null +++ b/test/fixers/test_fixjson_fixer_callback.vader @@ -0,0 +1,50 @@ +Before: + Save g:ale_json_fixjson_executable + Save g:ale_json_fixjson_options + + let g:ale_json_fixjson_executable = '/path/to/fixjson' + let g:ale_json_fixjson_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + +Execute(The fixjson callback should return the correct default command): + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/fixjson') + \ . ' --stdin-filename ' + \ . ale#Escape(bufname(bufnr(''))) + \ }, + \ ale#fixers#fixjson#Fix(bufnr('')) + +Execute(The fixjson callback should set the buffer name as file name): + call ale#test#SetFilename('../test-files/json/testfile.json') + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/fixjson') + \ . ' --stdin-filename ' + \ . ale#Escape(bufname(bufnr(''))) + \ }, + \ ale#fixers#fixjson#Fix(bufnr('')) + + AssertNotEqual + \ stridx( + \ ale#fixers#fixjson#Fix(bufnr('')).command, + \ 'testfile.json', + \ ), + \ -1 + +Execute(The fixjson callback should include additional options): + let g:ale_json_fixjson_options = '-i 2' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/fixjson') + \ . ' --stdin-filename ' + \ . ale#Escape(bufname(bufnr(''))) + \ . ' -i 2' + \ }, + \ ale#fixers#fixjson#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_forge_fixer_callback.vader b/test/fixers/test_forge_fixer_callback.vader new file mode 100644 index 00000000..fbb087a1 --- /dev/null +++ b/test/fixers/test_forge_fixer_callback.vader @@ -0,0 +1,15 @@ +Before: + Save g:ale_solidity_forge_executable + +After: + Restore + +Execute(The forge fmt callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('forge') + \ . ' fmt %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#forge#Fix(bufnr('')) + diff --git a/test/fixers/test_fourmolu_fixer_callback.vader b/test/fixers/test_fourmolu_fixer_callback.vader new file mode 100644 index 00000000..728c02ed --- /dev/null +++ b/test/fixers/test_fourmolu_fixer_callback.vader @@ -0,0 +1,29 @@ +Before: + Save g:ale_haskell_fourmolu_executable + Save g:ale_haskell_fourmolu_options + +After: + Restore + +Execute(The fourmolu callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('fourmolu') + \ . ' --stdin-input-file ' + \ . ale#Escape(@%) + \ }, + \ ale#fixers#fourmolu#Fix(bufnr('')) + +Execute(The fourmolu executable and options should be configurable): + let g:ale_haskell_fourmolu_executable = '/path/to/fourmolu' + let g:ale_haskell_fourmolu_options = '-h' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/fourmolu') + \ . ' -h' + \ . ' --stdin-input-file ' + \ . ale#Escape(@%) + \ }, + \ ale#fixers#fourmolu#Fix(bufnr('')) + diff --git a/test/fixers/test_gleam_format_fixer_callback.vader b/test/fixers/test_gleam_format_fixer_callback.vader new file mode 100644 index 00000000..2adf9a24 --- /dev/null +++ b/test/fixers/test_gleam_format_fixer_callback.vader @@ -0,0 +1,28 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The gleam_format command should have default values): + call ale#test#SetFilename('../test-files/elixir/testfile.gleam') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('gleam') . ' format %t', + \ }, + \ ale#fixers#gleam_format#Fix(bufnr('')) + +Execute(The gleam_format executable should be configurable): + let g:ale_gleam_format_executable = 'xxxinvalid' + call ale#test#SetFilename('../test-files/elixir/testfile.gleam') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') . ' format %t', + \ }, + \ ale#fixers#gleam_format#Fix(bufnr('')) diff --git a/test/fixers/test_gnatpp_fixer_callback.vader b/test/fixers/test_gnatpp_fixer_callback.vader new file mode 100644 index 00000000..7a3ed517 --- /dev/null +++ b/test/fixers/test_gnatpp_fixer_callback.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpFixerTest('ada', 'gnatpp') + +After: + " Reset fixers, variables, etc. + " + " Vader's 'Restore' command will be called here. + call ale#assert#TearDownFixerTest() + +Execute(The default command should be correct): + call ale#test#SetFilename('../test-files/ada/testfile.adb') + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_ada_gnatpp_executable) .' %t', + \ 'read_temporary_file': 1, + \ } + +Execute(The version check should be correct): + call ale#test#SetFilename('../test-files/ada/testfile.adb') + let g:ale_ada_gnatpp_options = '--no-alignment' + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_ada_gnatpp_executable) + \ . ' --no-alignment %t', + \ 'read_temporary_file': 1, + \ } diff --git a/test/fixers/test_gofmt_fixer_callback.vader b/test/fixers/test_gofmt_fixer_callback.vader new file mode 100644 index 00000000..579dd3db --- /dev/null +++ b/test/fixers/test_gofmt_fixer_callback.vader @@ -0,0 +1,50 @@ +Before: + Save g:ale_go_gofmt_executable + Save g:ale_go_gofmt_options + Save g:ale_go_go111module + + " Use an invalid global executable, so we don't match it. + let g:ale_go_gofmt_executable = 'xxxinvalid' + let g:ale_go_gofmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#test#RestoreDirectory() + +Execute(The gofmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/go/testfile.go') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid'), + \ }, + \ ale#fixers#gofmt#Fix(bufnr('')) + +Execute(The gofmt callback should include custom gofmt options): + let g:ale_go_gofmt_options = "-r '(a) -> a'" + + call ale#test#SetFilename('../test-files/go/testfile.go') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_go_gofmt_options, + \ }, + \ ale#fixers#gofmt#Fix(bufnr('')) + +Execute(The gofmt callback should support Go environment variables): + let g:ale_go_go111module = 'off' + + call ale#test#SetFilename('../test-files/go/testfile.go') + + AssertEqual + \ { + \ 'command': ale#Env('GO111MODULE', 'off') + \ . ale#Escape('xxxinvalid') + \ }, + \ ale#fixers#gofmt#Fix(bufnr('')) diff --git a/test/fixers/test_gofumpt_fixer.vader b/test/fixers/test_gofumpt_fixer.vader new file mode 100644 index 00000000..63e04de8 --- /dev/null +++ b/test/fixers/test_gofumpt_fixer.vader @@ -0,0 +1,27 @@ +Before: + call ale#assert#SetUpFixerTest('go', 'gofumpt') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The gofumpt callback should return the correct default values): + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('gofumpt') . ' -w -- %t' + \} + +Execute(The gofumpt callback should allow custom gofumpt executables): + let g:ale_go_gofumpt_executable = 'foo/bar' + + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('foo/bar') . ' -w -- %t' + \} + +Execute(The gofumpt callback should allow custom gofumpt options): + let g:ale_go_gofumpt_options = '--foobar' + + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('gofumpt') . ' --foobar -w -- %t' + \} diff --git a/test/fixers/test_goimports_fixer_callback.vader b/test/fixers/test_goimports_fixer_callback.vader new file mode 100644 index 00000000..64c75b2d --- /dev/null +++ b/test/fixers/test_goimports_fixer_callback.vader @@ -0,0 +1,57 @@ +Before: + Save g:ale_go_goimports_executable + Save g:ale_go_goimports_options + Save g:ale_go_go111module + + " Use an invalid global executable, so we don't match it. + let g:ale_go_goimports_executable = 'xxxinvalid' + let g:ale_go_goimports_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#test#SetFilename('../test-files/go/testfile.go') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#test#RestoreDirectory() + +Execute(The goimports callback should return 0 when the executable isn't executable): + AssertEqual + \ 0, + \ ale#fixers#goimports#Fix(bufnr('')) + +Execute(The goimports callback should the command when the executable test passes): + let g:ale_go_goimports_executable = has('win32') ? 'cmd' : 'echo' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_go_goimports_executable) . ' -l -w -srcdir %s %t' + \ }, + \ ale#fixers#goimports#Fix(bufnr('')) + +Execute(The goimports callback should include extra options): + let g:ale_go_goimports_executable = has('win32') ? 'cmd' : 'echo' + let g:ale_go_goimports_options = '--xxx' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_go_goimports_executable) . ' -l -w -srcdir %s --xxx %t' + \ }, + \ ale#fixers#goimports#Fix(bufnr('')) + +Execute(The goimports callback should support Go environment variables): + let g:ale_go_goimports_executable = has('win32') ? 'cmd' : 'echo' + let g:ale_go_go111module = 'on' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Env('GO111MODULE', 'on') + \ . ale#Escape(g:ale_go_goimports_executable) + \ . ' -l -w -srcdir %s %t' + \ }, + \ ale#fixers#goimports#Fix(bufnr('')) diff --git a/test/fixers/test_golangci_lint_fixer_callback.vader b/test/fixers/test_golangci_lint_fixer_callback.vader new file mode 100644 index 00000000..3487d6f5 --- /dev/null +++ b/test/fixers/test_golangci_lint_fixer_callback.vader @@ -0,0 +1,48 @@ +Before: + Save g:ale_go_go111module + Save g:ale_go_golangci_lint_executable + Save g:ale_go_golangci_lint_options + Save g:ale_go_golangci_lint_package + + " Use an invalid global executable, so we don't match it. + let g:ale_go_golangci_lint_executable = 'xxxinvalid' + let g:ale_go_golangci_lint_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#test#SetFilename('../test-files/go/testfile.go') +After: + Restore + + unlet! b:ale_go_go111module + + call ale#test#RestoreDirectory() + +Execute(The golangci-lint callback should return the correct default values): + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' run --fix', + \ }, + \ ale#fixers#golangci_lint#Fix(bufnr('')) + +Execute(The golangci-lint callback should include custom golangci-lint options): + let g:ale_go_golangci_lint_options = "--new --config /dev/null" + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' run ' . g:ale_go_golangci_lint_options . ' --fix', + \ }, + \ ale#fixers#golangci_lint#Fix(bufnr('')) + +Execute(The golangci-lint callback should support per-file mode): + let g:ale_go_golangci_lint_package = 0 + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' run ' + \ . g:ale_go_golangci_lint_options + \ . ' --fix ' . ale#Escape('testfile.go'), + \ }, + \ ale#fixers#golangci_lint#Fix(bufnr('')) diff --git a/test/fixers/test_golines_fixer_callback.vader b/test/fixers/test_golines_fixer_callback.vader new file mode 100644 index 00000000..87e53c88 --- /dev/null +++ b/test/fixers/test_golines_fixer_callback.vader @@ -0,0 +1,54 @@ +Before: + Save g:ale_go_golines_executable + Save g:ale_go_golines_options + Save g:ale_go_go111module + + " Use an invalid global executable, so we don't match it. + let g:ale_go_golines_executable = 'xxxinvalid' + let g:ale_go_golines_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#test#RestoreDirectory() + +Execute(The golines callback should return 0 when the executable isn't executable): + AssertEqual + \ 0, + \ ale#fixers#golines#Fix(bufnr('')) + + +Execute(The golines callback should return the correct default values): + let g:ale_go_golines_executable = has('win32') ? 'cmd' : 'echo' + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_go_golines_executable), + \ }, + \ ale#fixers#golines#Fix(bufnr('')) + +Execute(The golines callback should include custom golines options): + let g:ale_go_golines_executable = has('win32') ? 'cmd' : 'echo' + let g:ale_go_golines_options = "--max-len --shorten-comments" + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_go_golines_executable) + \ . ' ' . g:ale_go_golines_options, + \ }, + \ ale#fixers#golines#Fix(bufnr('')) + +Execute(The golines callback should support Go environment variables): + let g:ale_go_golines_executable = has('win32') ? 'cmd' : 'echo' + let g:ale_go_go111module = 'off' + + AssertEqual + \ { + \ 'command': ale#Env('GO111MODULE', 'off') + \ . ale#Escape(g:ale_go_golines_executable) + \ }, + \ ale#fixers#golines#Fix(bufnr('')) diff --git a/test/fixers/test_gomod_fixer_callback.vader b/test/fixers/test_gomod_fixer_callback.vader new file mode 100644 index 00000000..56fb9854 --- /dev/null +++ b/test/fixers/test_gomod_fixer_callback.vader @@ -0,0 +1,41 @@ +Before: + Save g:ale_go_go_executable + Save g:ale_go_go111module + + " Use an invalid global executable, so we don't match it. + let g:ale_go_go_executable = 'xxxinvalid' + let g:ale_go_go111module = '' + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#test#RestoreDirectory() + +Execute(The gomod callback should return the correct default values): + call ale#test#SetFilename('../test-files/go/go.mod') + setl filetype=gomod + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' mod edit -fmt' + \ . ' %t', + \ }, + \ ale#fixers#gomod#Fix(bufnr('')) + +Execute(The gomod callback should support Go environment variables): + call ale#test#SetFilename('../test-files/go/go.mod') + setl filetype=gomod + let g:ale_go_go111module = 'on' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Env('GO111MODULE', 'on') + \ . ale#Escape('xxxinvalid') . ' mod edit -fmt %t' + \ }, + \ ale#fixers#gomod#Fix(bufnr('')) diff --git a/test/fixers/test_goofle_java_format_fixer_callback.vader b/test/fixers/test_goofle_java_format_fixer_callback.vader new file mode 100644 index 00000000..4c28b330 --- /dev/null +++ b/test/fixers/test_goofle_java_format_fixer_callback.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_java_google_java_format_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_java_google_java_format_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The google-java-format callback should return 0 when the executable isn't executable): + AssertEqual + \ 0, + \ ale#fixers#google_java_format#Fix(bufnr('')) + +Execute(The google-java-format callback should run the command when the executable test passes): + let g:ale_java_google_java_format_executable = has('win32') ? 'cmd' : 'echo' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(ale_java_google_java_format_executable) . ' --replace %t' + \ }, + \ ale#fixers#google_java_format#Fix(bufnr('')) diff --git a/test/fixers/test_gopls_fixer_callback.vader b/test/fixers/test_gopls_fixer_callback.vader new file mode 100644 index 00000000..218a356a --- /dev/null +++ b/test/fixers/test_gopls_fixer_callback.vader @@ -0,0 +1,57 @@ +Before: + Save g:ale_go_gopls_fix_executable + Save g:ale_go_gopls_fix_options + Save g:ale_go_go111module + + " Use an invalid global executable, so we don't match it. + let g:ale_go_gopls_fix_executable = 'xxxinvalid' + let g:ale_go_gopls_fix_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#test#SetFilename('../test-files/go/testfile.go') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#test#RestoreDirectory() + +Execute(The gopls callback should return 0 when the executable isn't executable): + AssertEqual + \ 0, + \ ale#fixers#gopls#Fix(bufnr('')) + +Execute(The gopls callback should the command when the executable test passes): + let g:ale_go_gopls_fix_executable = has('win32') ? 'cmd' : 'echo' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_go_gopls_fix_executable) . ' format -l -w %t' + \ }, + \ ale#fixers#gopls#Fix(bufnr('')) + +Execute(The gopls callback should include extra options): + let g:ale_go_gopls_fix_executable = has('win32') ? 'cmd' : 'echo' + let g:ale_go_gopls_fix_options = '--xxx' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_go_gopls_fix_executable) . ' format --xxx -l -w %t' + \ }, + \ ale#fixers#gopls#Fix(bufnr('')) + +Execute(The gopls callback should support Go environment variables): + let g:ale_go_gopls_fix_executable = has('win32') ? 'cmd' : 'echo' + let g:ale_go_go111module = 'on' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Env('GO111MODULE', 'on') + \ . ale#Escape(g:ale_go_gopls_fix_executable) + \ . ' format -l -w %t' + \ }, + \ ale#fixers#gopls#Fix(bufnr('')) diff --git a/test/fixers/test_hackfmt_fixer_callback.vader b/test/fixers/test_hackfmt_fixer_callback.vader new file mode 100644 index 00000000..d294c15e --- /dev/null +++ b/test/fixers/test_hackfmt_fixer_callback.vader @@ -0,0 +1,37 @@ +Before: + Save g:ale_hack_hackfmt_executable + Save g:ale_hack_hackfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_hack_hackfmt_executable = 'xxxinvalid' + let g:ale_hack_hackfmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The hackfmt callback should return the correct default values): + call ale#test#SetFilename('../hack_files/testfile.hack') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i %t', + \ }, + \ ale#fixers#hackfmt#Fix(bufnr('')) + +Execute(The hackfmt callback should include custom hackfmt options): + let g:ale_hack_hackfmt_options = "--some-option" + call ale#test#SetFilename('../hack_files/testfile.hack') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i --some-option %t', + \ }, + \ ale#fixers#hackfmt#Fix(bufnr('')) diff --git a/test/fixers/test_hfmt_fixer_callback.vader b/test/fixers/test_hfmt_fixer_callback.vader new file mode 100644 index 00000000..69cd03f8 --- /dev/null +++ b/test/fixers/test_hfmt_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_haskell_hfmt_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_haskell_hfmt_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The hfmt callback should return the correct default values): + call ale#test#SetFilename('../haskell_files/testfile.hs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -w' + \ . ' %t', + \ }, + \ ale#fixers#hfmt#Fix(bufnr('')) diff --git a/test/fixers/test_hindent_fixer_callback.vader b/test/fixers/test_hindent_fixer_callback.vader new file mode 100644 index 00000000..2e5a8b9f --- /dev/null +++ b/test/fixers/test_hindent_fixer_callback.vader @@ -0,0 +1,18 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The hindent callback should return the correct default values): + call ale#test#SetFilename('../haskell_files/testfile.hs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('hindent') + \ . ' %t', + \ }, + \ ale#fixers#hindent#Fix(bufnr('')) diff --git a/test/fixers/test_hlint_fixer_callback.vader b/test/fixers/test_hlint_fixer_callback.vader new file mode 100644 index 00000000..08f08fae --- /dev/null +++ b/test/fixers/test_hlint_fixer_callback.vader @@ -0,0 +1,20 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The hlint callback should return the correct default values): + call ale#test#SetFilename('../haskell_files/testfile.hs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('hlint') + \ . ' --refactor' + \ . ' --refactor-options="--inplace"' + \ . ' %t', + \ }, + \ ale#fixers#hlint#Fix(bufnr('')) diff --git a/test/fixers/test_html_beautify_fixer_callback.vader b/test/fixers/test_html_beautify_fixer_callback.vader new file mode 100644 index 00000000..3012c7f1 --- /dev/null +++ b/test/fixers/test_html_beautify_fixer_callback.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpFixerTest('html', 'html-beautify', 'beautify') + +After: + Restore + + call ale#assert#TearDownFixerTest() + +Execute(The html-beautify callback should return the correct default command): + AssertEqual + \ {'command': ale#Escape('html-beautify') . ' -'}, + \ ale#fixers#html_beautify#Fix(bufnr('')) diff --git a/test/fixers/test_htmlbeautifier_fixer_callback.vader b/test/fixers/test_htmlbeautifier_fixer_callback.vader new file mode 100644 index 00000000..e54bab97 --- /dev/null +++ b/test/fixers/test_htmlbeautifier_fixer_callback.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpFixerTest('eruby', 'htmlbeautifier') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The htmlbeautifier callback should return the correct default values): + AssertFixer { + \ 'command': ale#Escape('htmlbeautifier') . ' %t', + \ 'read_temporary_file': 1, + \} + +Execute(The htmlbeautifier callback should allow custom htmlbeautifier executables): + let g:ale_eruby_htmlbeautifier_executable = 'foo/bar' + + AssertFixer { + \ 'command': ale#Escape('foo/bar') . ' %t', + \ 'read_temporary_file': 1, + \} diff --git a/test/fixers/test_hurlfmt_fixer_callback.vader b/test/fixers/test_hurlfmt_fixer_callback.vader new file mode 100644 index 00000000..28f4b071 --- /dev/null +++ b/test/fixers/test_hurlfmt_fixer_callback.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_hurl_hurlfmt_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_hurl_hurlfmt_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The hurlfmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/hurl/dummy.hurl') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_hurl_hurlfmt_executable) + \ . ' --out hurl', + \ }, + \ ale#fixers#hurlfmt#Fix(bufnr('')) + diff --git a/test/fixers/test_importjs_fixer_callback.vader b/test/fixers/test_importjs_fixer_callback.vader new file mode 100644 index 00000000..727e6a16 --- /dev/null +++ b/test/fixers/test_importjs_fixer_callback.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_javascript_importjs_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_javascript_importjs_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#test#SetFilename('../test-files/javascript/test.js') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The importjs callback should return 0 when the executable isn't executable): + AssertEqual + \ 0, + \ ale#fixers#importjs#Fix(bufnr('')) + +Execute(The importjs callback should run the command when the executable test passes): + let g:ale_javascript_importjs_executable = has('win32') ? 'cmd' : 'echo' + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#importjs#ProcessOutput', + \ 'command': ale#Escape(g:ale_javascript_importjs_executable) . ' fix %s' + \ }, + \ ale#fixers#importjs#Fix(bufnr('')) + +Execute(The ProcessOutput callback should return the expected output): + let g:testOutput = '{"messages":[],"fileContent":"one\ntwo","unresolvedImports":{}}' + + AssertEqual + \ ['one', 'two'], + \ ale#fixers#importjs#ProcessOutput(bufnr(''), g:testOutput) diff --git a/test/fixers/test_isort_fixer_callback.vader b/test/fixers/test_isort_fixer_callback.vader new file mode 100644 index 00000000..ce42dca6 --- /dev/null +++ b/test/fixers/test_isort_fixer_callback.vader @@ -0,0 +1,83 @@ +Before: + call ale#assert#SetUpFixerTest('python', 'isort') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + call ale#assert#TearDownFixerTest() + + unlet! g:dir + unlet! b:bin_dir + +Execute(The isort callback should return the correct default values): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + " --filename option exists only after 5.7.0 + GivenCommandOutput ['VERSION 5.7.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/isort')) . ' --filename %s' . ' -', + \ } + +Execute(The isort callback should respect custom options): + let g:ale_python_isort_options = '--multi-line=3 --trailing-comma' + + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + " --filename option exists only after 5.7.0 + GivenCommandOutput ['VERSION 5.7.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/isort')) + \ . ' --filename %s' . ' --multi-line=3 --trailing-comma -', + \ } + +Execute(Pipenv is detected when python_isort_auto_pipenv is set): + let g:ale_python_isort_auto_pipenv = 1 + + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + GivenCommandOutput ['VERSION 5.7.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pipenv') . ' run isort' . ' --filename %s' . ' -' + \ } + +Execute(Poetry is detected when python_isort_auto_poetry is set): + let g:ale_python_isort_auto_poetry = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + GivenCommandOutput ['VERSION 5.7.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('poetry') . ' run isort' . ' --filename %s' . ' -' + \ } + +Execute(uv is detected when python_isort_auto_uv is set): + let g:ale_python_isort_auto_uv = 1 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + GivenCommandOutput ['VERSION 5.7.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('uv') . ' run isort' . ' --filename %s' . ' -' + \ } + + +Execute(The isort callback should not use --filename for older versions): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + " --filename option exists only after 5.7.0 + GivenCommandOutput ['VERSION 5.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/isort')) . ' -', + \ } diff --git a/test/fixers/test_jq_fixer_callback.vader b/test/fixers/test_jq_fixer_callback.vader new file mode 100644 index 00000000..74580d3b --- /dev/null +++ b/test/fixers/test_jq_fixer_callback.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_json_jq_executable + Save g:ale_json_jq_options + Save g:ale_json_jq_filters + +After: + Restore + +Execute(The jq fixer should use the options you set): + let g:ale_json_jq_executable = 'foo' + let g:ale_json_jq_options = '--bar' + let g:ale_json_jq_filters = '.baz' + + AssertEqual + \ {'command': ale#Escape('foo') . ' .baz --bar'}, + \ ale#fixers#jq#Fix(bufnr('')) + +Execute(The jq fixer should return 0 when there are no filters): + let g:ale_json_jq_executable = 'jq' + let g:ale_json_jq_options = '' + + let g:ale_json_jq_filters = '' + + AssertEqual + \ 0, + \ ale#fixers#jq#Fix(bufnr('')) diff --git a/test/fixers/test_jsonnetfmt_fixer_callback.vader b/test/fixers/test_jsonnetfmt_fixer_callback.vader new file mode 100644 index 00000000..204d6583 --- /dev/null +++ b/test/fixers/test_jsonnetfmt_fixer_callback.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_jsonnet_jsonnetfmt_executable + Save g:ale_jsonnet_jsonnetfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_jsonnet_jsonnetfmt_executable = 'xxxinvalid' + let g:ale_jsonnet_jsonnetfmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#assert#SetUpFixerTest('jsonnet', 'jsonnetfmt') + +After: + call ale#test#RestoreDirectory() + call ale#assert#TearDownFixerTest() + +Execute(The jsonnetfmt callback should return the correct default values): + call ale#test#SetFilename('../jsonnet_files/testfile.jsonnet') + + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_jsonnet_jsonnetfmt_executable) + \ . ' -i' + \ . ' %t', + \} + +Execute(The jsonnetfmt callback should include custom options): + let g:ale_jsonnet_jsonnetfmt_options = '--pad-arrays' + + call ale#test#SetFilename('../jsonnet_files/testfile.jsonnet') + + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_jsonnet_jsonnetfmt_executable) + \ . ' -i' + \ . ' ' . g:ale_jsonnet_jsonnetfmt_options + \ . ' %t', + \} + diff --git a/test/fixers/test_ktlint_fixer_callback.vader b/test/fixers/test_ktlint_fixer_callback.vader new file mode 100644 index 00000000..cfe39205 --- /dev/null +++ b/test/fixers/test_ktlint_fixer_callback.vader @@ -0,0 +1,42 @@ +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('../test-files/kotlin/testfile.kt') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --stdin' + \ . ' --format', + \ }, + \ 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('../test-files/kotlin/testfile.kt') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_kotlin_ktlint_options + \ . ' --ruleset /path/to/custom/ruleset.jar' + \ . ' --stdin' + \ . ' --format', + \ }, + \ ale#fixers#ktlint#Fix(bufnr('')) diff --git a/test/fixers/test_kulala_fmt_fixer_callback.vader b/test/fixers/test_kulala_fmt_fixer_callback.vader new file mode 100644 index 00000000..8431a8fd --- /dev/null +++ b/test/fixers/test_kulala_fmt_fixer_callback.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_http_kulala_fmt_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_http_kulala_fmt_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The kualala_fmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/http/dummy.http') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_http_kulala_fmt_executable) + \ . ' format %t > /dev/null', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#kulala_fmt#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..bd4ac69a --- /dev/null +++ b/test/fixers/test_latexindent_fixer_callback.vader @@ -0,0 +1,36 @@ +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('../test-files/tex/testfile.tex') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' -l' + \ }, + \ 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('../test-files/tex/testfile.tex') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' -l' + \ . ' ' . g:ale_tex_latexindent_options + \ }, + \ ale#fixers#latexindent#Fix(bufnr('')) diff --git a/test/fixers/test_lua_format_fixer_callback.vader b/test/fixers/test_lua_format_fixer_callback.vader new file mode 100644 index 00000000..29cafde6 --- /dev/null +++ b/test/fixers/test_lua_format_fixer_callback.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_lua_lua_format_executable + Save g:ale_lua_lua_format_options + + " Use an invalid global executable, so we don't match it. + let g:ale_lua_lua_format_executable = 'xxxinvalid' + let g:ale_lua_lua_format_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The lua_format callback should return the correct default values): + call ale#test#SetFilename('../test-files/lua/testfile.lua') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' -i', + \ }, + \ ale#fixers#lua_format#Fix(bufnr('')) + +Execute(The lua_format callback should include custom lua_format options): + let g:ale_lua_lua_format_options = "--no-chop-down-table" + call ale#test#SetFilename('../test-files/lua/testfile.lua') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_lua_lua_format_options + \ . ' -i', + \ }, + \ ale#fixers#lua_format#Fix(bufnr('')) diff --git a/test/fixers/test_luafmt_fixer_callback.vader b/test/fixers/test_luafmt_fixer_callback.vader new file mode 100644 index 00000000..ef69f297 --- /dev/null +++ b/test/fixers/test_luafmt_fixer_callback.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_lua_luafmt_executable + Save g:ale_lua_luafmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_lua_luafmt_executable = 'xxxinvalid' + let g:ale_lua_luafmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The luafmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/lua/testfile.lua') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' --stdin', + \ }, + \ ale#fixers#luafmt#Fix(bufnr('')) + +Execute(The luafmt callback should include custom luafmt options): + let g:ale_lua_luafmt_options = "--skip-children" + call ale#test#SetFilename('../test-files/lua/testfile.lua') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_lua_luafmt_options + \ . ' --stdin', + \ }, + \ ale#fixers#luafmt#Fix(bufnr('')) diff --git a/test/fixers/test_mix_format_fixer_callback.vader b/test/fixers/test_mix_format_fixer_callback.vader new file mode 100644 index 00000000..cd492e81 --- /dev/null +++ b/test/fixers/test_mix_format_fixer_callback.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_elixir_mix_executable + Save g:ale_elixir_mix_format_options + + let g:ale_elixir_mix_executable = 'xxxinvalid' + let g:ale_elixir_mix_format_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The mix_format callback should return the correct default values): + call ale#test#SetFilename('../test-files/elixir/testfile.ex') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format %t', + \ }, + \ ale#fixers#mix_format#Fix(bufnr('')) + +Execute(The mix_format callback should include the correct format options): + let g:ale_elixir_mix_format_options = 'invalid_options' + call ale#test#SetFilename('../test-files/elixir/testfile.ex') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format invalid_options %t', + \ }, + \ ale#fixers#mix_format#Fix(bufnr('')) diff --git a/test/fixers/test_nickel_format_fixer_callback.vader b/test/fixers/test_nickel_format_fixer_callback.vader new file mode 100644 index 00000000..af3e0c26 --- /dev/null +++ b/test/fixers/test_nickel_format_fixer_callback.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_nickel_nickel_format_executable + Save g:ale_nickel_nickel_format_options + Save &l:expandtab + Save &l:shiftwidth + Save &l:tabstop + +After: + Restore + +Execute(The nickel_format callback should return 'nickel format' as default command): + setlocal noexpandtab + Assert + \ ale#fixers#nickel_format#Fix(bufnr('')).command =~# '^' . ale#Escape('nickel') . ' format', + \ "Default command name is expected to be 'nickel format'" + +Execute(The nickel executable and options should be configurable): + let g:ale_nickel_nickel_format_executable = 'foobar' + let g:ale_nickel_nickel_format_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape('foobar') + \ . ' format' + \ . ' --some-option', + \ }, + \ ale#fixers#nickel_format#Fix(bufnr('')) diff --git a/test/fixers/test_nimpretty_fixer_callback.vader b/test/fixers/test_nimpretty_fixer_callback.vader new file mode 100644 index 00000000..a26f649a --- /dev/null +++ b/test/fixers/test_nimpretty_fixer_callback.vader @@ -0,0 +1,23 @@ +Before: + call ale#assert#SetUpFixerTest('nim', 'nimpretty') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The nimpretty callback should return the correct default values): + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('nimpretty') . ' %t --maxLineLen:80' + \ }, + \ ale#fixers#nimpretty#Fix(bufnr('')) + +Execute(The nimpretty callback should include any additional options): + let g:ale_nim_nimpretty_options = '--some-option' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('nimpretty') . ' %t --some-option' + \ }, + \ ale#fixers#nimpretty#Fix(bufnr('')) diff --git a/test/fixers/test_nixfmt_fixer_callback.vader b/test/fixers/test_nixfmt_fixer_callback.vader new file mode 100644 index 00000000..880ac83e --- /dev/null +++ b/test/fixers/test_nixfmt_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_nix_nixfmt_executable + Save g:ale_nix_nixfmt_options + +After: + Restore + +Execute(The nixfmt callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('nixfmt') + \ }, + \ ale#fixers#nixfmt#Fix(bufnr('')) + +Execute(The nixfmt executable and options should be configurable): + let g:ale_nix_nixfmt_executable = '/path/to/nixfmt' + let g:ale_nix_nixfmt_options = '--help' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/nixfmt') + \ . ' --help', + \ }, + \ ale#fixers#nixfmt#Fix(bufnr('')) diff --git a/test/fixers/test_nixpkgsfmt_fixer_callback.vader b/test/fixers/test_nixpkgsfmt_fixer_callback.vader new file mode 100644 index 00000000..0065f77b --- /dev/null +++ b/test/fixers/test_nixpkgsfmt_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_nix_nixpkgsfmt_executable + Save g:ale_nix_nixpkgsfmt_options + +After: + Restore + +Execute(The nixpkgs-fmt callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('nixpkgs-fmt') + \ }, + \ ale#fixers#nixpkgsfmt#Fix(bufnr('')) + +Execute(The nixpkgs-fmt executable and options should be configurable): + let g:ale_nix_nixpkgsfmt_executable = '/path/to/nixpkgs-fmt' + let g:ale_nix_nixpkgsfmt_options = '-h' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/nixpkgs-fmt') + \ . ' -h', + \ }, + \ ale#fixers#nixpkgsfmt#Fix(bufnr('')) diff --git a/test/fixers/test_npmgroovylint_fixer_callback.vader b/test/fixers/test_npmgroovylint_fixer_callback.vader new file mode 100644 index 00000000..a4b1ee9a --- /dev/null +++ b/test/fixers/test_npmgroovylint_fixer_callback.vader @@ -0,0 +1,23 @@ +Before: + Save b:ale_groovy_npmgroovylint_fix_options + + call ale#assert#SetUpFixerTest('groovy', 'npm-groovy-lint') + +After: + Restore + + call ale#assert#TearDownFixerTest() + +Execute(The callback should return the correct default values): + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('npm-groovy-lint') . ' --fix %t', + \ } + +Execute(The callback should include custom options): + let b:ale_groovy_npmgroovylint_fix_options = '--format' + + AssertFixer { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('npm-groovy-lint') . ' --format %t', + \ } diff --git a/test/fixers/test_ocamlformat_fixer_callback.vader b/test/fixers/test_ocamlformat_fixer_callback.vader new file mode 100644 index 00000000..587fcf56 --- /dev/null +++ b/test/fixers/test_ocamlformat_fixer_callback.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_ocaml_ocamlformat_executable + Save g:ale_ocaml_ocamlformat_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ocaml_ocamlformat_executable = 'xxxinvalid' + let g:ale_ocaml_ocamlformat_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The ocamlformat callback should return the correct default values): + call ale#test#SetFilename('../test-files/ocaml/testfile.re') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --name=%s -', + \ }, + \ ale#fixers#ocamlformat#Fix(bufnr('')) + +Execute(The ocamlformat callback should include custom ocamlformat options): + let g:ale_ocaml_ocamlformat_options = "-m 78" + call ale#test#SetFilename('../test-files/ocaml/testfile.re') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_ocaml_ocamlformat_options + \ . ' --name=%s -', + \ }, + \ ale#fixers#ocamlformat#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..fc336b2d --- /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('../test-files/ocaml/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('../test-files/ocaml/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_opa_fmt_fixer_callback.vader b/test/fixers/test_opa_fmt_fixer_callback.vader new file mode 100644 index 00000000..3b112b2e --- /dev/null +++ b/test/fixers/test_opa_fmt_fixer_callback.vader @@ -0,0 +1,33 @@ +Before: + Save g:ale_opa_fmt_executable + Save g:ale_opa_fmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_opa_fmt_executable = 'xxxinvalid' + let g:ale_opa_fmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The opa fmt callback should return the correct default values): + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' fmt', + \ }, + \ ale#fixers#opafmt#Fix(bufnr('')) + +Execute(The opa fmt callback should include custom options): + let g:ale_opa_fmt_options = "--list" + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' fmt' + \ . ' ' . g:ale_opa_fmt_options + \ }, + \ ale#fixers#opafmt#Fix(bufnr('')) diff --git a/test/fixers/test_ormolu_fixer_callback.vader b/test/fixers/test_ormolu_fixer_callback.vader new file mode 100644 index 00000000..8df3fca9 --- /dev/null +++ b/test/fixers/test_ormolu_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_haskell_ormolu_executable + Save g:ale_haskell_ormolu_options + +After: + Restore + +Execute(The ormolu callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('ormolu') + \ }, + \ ale#fixers#ormolu#Fix(bufnr('')) + +Execute(The ormolu executable and options should be configurable): + let g:ale_nix_nixpkgsfmt_executable = '/path/to/ormolu' + let g:ale_nix_nixpkgsfmt_options = '-h' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/ormolu') + \ . ' -h', + \ }, + \ ale#fixers#nixpkgsfmt#Fix(bufnr('')) diff --git a/test/fixers/test_packer_fmt_fixer_callback.vader b/test/fixers/test_packer_fmt_fixer_callback.vader new file mode 100644 index 00000000..2eb07ed4 --- /dev/null +++ b/test/fixers/test_packer_fmt_fixer_callback.vader @@ -0,0 +1,34 @@ +Before: + Save g:ale_packer_fmt_executable + Save g:ale_packer_fmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_packer_fmt_executable = 'xxxinvalid' + let g:ale_packer_fmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The packer fmt callback should return the correct default values): + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' fmt -', + \ }, + \ ale#fixers#packer#Fix(bufnr('')) + +Execute(The packer fmt callback should include custom options): + let g:ale_packer_fmt_options = "-list=true" + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' fmt' + \ . ' ' . g:ale_packer_fmt_options + \ . ' -', + \ }, + \ ale#fixers#packer#Fix(bufnr('')) diff --git a/test/fixers/test_pandoc_fixer_callback.vader b/test/fixers/test_pandoc_fixer_callback.vader new file mode 100644 index 00000000..a364ee56 --- /dev/null +++ b/test/fixers/test_pandoc_fixer_callback.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_markdown_pandoc_executable + Save g:ale_markdown_pandoc_options + +After: + Restore + +Execute(The pandoc callback should return 'pandoc' as default command): + setlocal noexpandtab + Assert + \ ale#fixers#pandoc#Fix(bufnr('')).command =~# '^' . ale#Escape('pandoc'), + \ "Default command name is expected to be 'pandoc'" + +Execute(The pandoc executable and options should be configurable): + let g:ale_markdown_pandoc_executable = 'foobar' + let g:ale_markdown_pandoc_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape('foobar') + \ . ' --some-option', + \ }, + \ ale#fixers#pandoc#Fix(bufnr('')) diff --git a/test/fixers/test_perltidy_fixer_callback.vader b/test/fixers/test_perltidy_fixer_callback.vader new file mode 100644 index 00000000..c7430bfa --- /dev/null +++ b/test/fixers/test_perltidy_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_perl_perltidy_executable + Save g:ale_perl_perltidy_options + + " Use an invalid global executable, so we don't match it. + let g:ale_perl_perltidy_executable = 'xxxinvalid' + let g:ale_perl_perltidy_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The perltidy callback should return the correct default values): + call ale#test#SetFilename('../pl_files/testfile.pl') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -b' + \ . ' %t', + \ }, + \ ale#fixers#perltidy#Fix(bufnr('')) + +Execute(The perltidy callback should include custom perltidy options): + let g:ale_perl_perltidy_options = "-r '(a) -> a'" + call ale#test#SetFilename('../pl_files/testfile.pl') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -b' + \ . ' ' . g:ale_perl_perltidy_options + \ . ' %t', + \ }, + \ ale#fixers#perltidy#Fix(bufnr('')) diff --git a/test/fixers/test_pgformatter_fixer_callback.vader b/test/fixers/test_pgformatter_fixer_callback.vader new file mode 100644 index 00000000..5baa6f6f --- /dev/null +++ b/test/fixers/test_pgformatter_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_sql_pgformatter_executable + Save g:ale_sql_pgformatter_options + +After: + Restore + +Execute(The pgFormatter callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('pg_format') + \ }, + \ ale#fixers#pgformatter#Fix(bufnr('')) + +Execute(The pgFormatter executable and options should be configurable): + let g:ale_sql_pgformatter_executable = '/path/to/pg_format' + let g:ale_sql_pgformatter_options = '-n' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/pg_format') + \ . ' -n', + \ }, + \ ale#fixers#pgformatter#Fix(bufnr('')) diff --git a/test/fixers/test_php_cs_fixer.vader b/test/fixers/test_php_cs_fixer.vader new file mode 100644 index 00000000..9ae98208 --- /dev/null +++ b/test/fixers/test_php_cs_fixer.vader @@ -0,0 +1,66 @@ +Before: + Save g:ale_php_cs_fixer_executable + Save g:ale_php_cs_fixer_options + Save g:ale_php_cs_fixer_fix_options + let g:ale_php_cs_fixer_executable = 'php-cs-fixer' + let g:ale_php_cs_fixer_options = '' + let g:ale_php_cs_fixer_fix_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + + +Execute(project with php-cs-fixer should use local by default): + call ale#test#SetFilename('../test-files/php/project-with-php-cs-fixer/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/php/project-with-php-cs-fixer/vendor/bin/php-cs-fixer'), + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_cs_fixer_use_global = 1 + call ale#test#SetFilename('../test-files/php/project-with-php-cs-fixer/test.php') + + AssertEqual + \ 'php-cs-fixer', + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + +Execute(project without php-cs-fixer should use global): + call ale#test#SetFilename('../test-files/php/project-without-php-cs-fixer/test.php') + + AssertEqual + \ 'php-cs-fixer', + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + + + + +Execute(The php-cs-fixer callback should return the correct default values): + call ale#test#SetFilename('../test-files/php/project-without-php-cs-fixer/foo/test.php') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('php-cs-fixer') + \ . ' ' . g:ale_php_cs_fixer_options + \ . ' fix ' . g:ale_php_cs_fixer_fix_options + \ . ' %t' + \ }, + \ ale#fixers#php_cs_fixer#Fix(bufnr('')) + +Execute(The php-cs-fixer callback should include custom php-cs-fixer options): + let g:ale_php_cs_fixer_options = '-nq' + let g:ale_php_cs_fixer_fix_options = '--config="$HOME/.php_cs"' + call ale#test#SetFilename('../test-files/php/project-without-php-cs-fixer/test.php') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_php_cs_fixer_executable) + \ . ' -nq fix --config="$HOME/.php_cs" %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#php_cs_fixer#Fix(bufnr('')) diff --git a/test/fixers/test_phpcbf_fixer_callback.vader b/test/fixers/test_phpcbf_fixer_callback.vader new file mode 100644 index 00000000..45229a1b --- /dev/null +++ b/test/fixers/test_phpcbf_fixer_callback.vader @@ -0,0 +1,117 @@ +Before: + Save g:ale_php_phpcbf_executable + Save g:ale_php_phpcbf_standard + Save g:ale_php_phpcbf_use_global + + let g:ale_php_phpcbf_executable = 'phpcbf_test' + let g:ale_php_phpcbf_standard = '' + let g:ale_php_phpcbf_options = '' + let g:ale_php_phpcbf_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(project with phpcbf should use local by default): + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/php/project-with-phpcbf/vendor/bin/phpcbf'), + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_phpcbf_use_global = 1 + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(project without phpcbf should use global): + call ale#test#SetFilename('../test-files/php/project-without-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(The phpcbf callback should return the correct default values): + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/php/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s -' }, + \ ale#fixers#phpcbf#Fix(bufnr('')) + +Execute(The phpcbf callback should include the phpcbf_standard option): + let g:ale_php_phpcbf_standard = 'phpcbf_ruleset.xml' + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/php/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s ' . '--standard=phpcbf_ruleset.xml' . ' -'}, + \ ale#fixers#phpcbf#Fix(bufnr('')) + +Execute(User provided options should be used): + let g:ale_php_phpcbf_options = '--my-user-provided-option my-value' + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/php/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s ' . ale#Pad('--my-user-provided-option my-value') . ' -'}, + \ ale#fixers#phpcbf#Fix(bufnr('')) + + +Before: + Save g:ale_php_phpcbf_executable + Save g:ale_php_phpcbf_standard + Save g:ale_php_phpcbf_use_global + + let g:ale_php_phpcbf_executable = 'phpcbf_test' + let g:ale_php_phpcbf_standard = '' + let g:ale_php_phpcbf_options = '' + let g:ale_php_phpcbf_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(project with phpcbf should use local by default): + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/php/project-with-phpcbf/vendor/bin/phpcbf'), + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_phpcbf_use_global = 1 + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(project without phpcbf should use global): + call ale#test#SetFilename('../test-files/php/project-without-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(The phpcbf callback should return the correct default values): + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/php/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s -' }, + \ ale#fixers#phpcbf#Fix(bufnr('')) + +Execute(The phpcbf callback should include the phpcbf_standard option): + let g:ale_php_phpcbf_standard = 'phpcbf_ruleset.xml' + call ale#test#SetFilename('../test-files/php/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/php/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s ' . '--standard=phpcbf_ruleset.xml' . ' -'}, + \ ale#fixers#phpcbf#Fix(bufnr('')) + diff --git a/test/fixers/test_pint_fixer.vader b/test/fixers/test_pint_fixer.vader new file mode 100644 index 00000000..5ea28b33 --- /dev/null +++ b/test/fixers/test_pint_fixer.vader @@ -0,0 +1,62 @@ +Before: + Save g:ale_php_pint_executable + Save g:ale_php_pint_options + let g:ale_php_pint_executable = 'pint' + let g:ale_php_pint_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + + +Execute(project with pint should use local by default): + call ale#test#SetFilename('../test-files/php/project-with-pint/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/php/project-with-pint/vendor/bin/pint'), + \ ale#fixers#pint#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_pint_use_global = 1 + call ale#test#SetFilename('../test-files/php/project-with-pint/test.php') + + AssertEqual + \ 'pint', + \ ale#fixers#pint#GetExecutable(bufnr('')) + +Execute(project without pint should use global): + call ale#test#SetFilename('../test-files/php/project-without-pint/test.php') + + AssertEqual + \ 'pint', + \ ale#fixers#pint#GetExecutable(bufnr('')) + + + + +Execute(The pint callback should return the correct default values): + call ale#test#SetFilename('../test-files/php/project-without-pint/foo/test.php') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('pint') + \ . ' ' . g:ale_php_pint_options + \ . ' %t' + \ }, + \ ale#fixers#pint#Fix(bufnr('')) + +Execute(The pint callback should include custom pint options): + let g:ale_php_pint_options = '--test' + call ale#test#SetFilename('../test-files/php/project-without-pint/test.php') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_php_pint_executable) + \ . ' --test %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#pint#Fix(bufnr('')) diff --git a/test/fixers/test_prettier_eslint_fixer.callback.vader b/test/fixers/test_prettier_eslint_fixer.callback.vader new file mode 100644 index 00000000..cfdd1c78 --- /dev/null +++ b/test/fixers/test_prettier_eslint_fixer.callback.vader @@ -0,0 +1,97 @@ +Before: + call ale#assert#SetUpFixerTest('javascript', 'prettier_eslint') + Save g:ale_command_wrapper + + let g:ale_command_wrapper = '' + +After: + call ale#assert#TearDownFixerTest() + +Execute(The default command should be correct): + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --write' + \ } + +Execute(Additional options should be used when set): + let b:ale_javascript_prettier_eslint_options = '--foobar' + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --foobar --write' + \ } + +Execute(--eslint-config-path should be set for 4.2.0 and up): + call ale#test#SetFilename('../test-files/eslint/react-app/foo/bar.js') + + GivenCommandOutput ['4.2.0'] + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --eslint-config-path ' . ale#Escape(ale#test#GetFilename('../test-files/eslint/react-app/.eslintrc.js')) + \ . ' --write' + \ } + +Execute(--eslint-config-path shouldn't be used for older versions): + call ale#test#SetFilename('../test-files/eslint/react-app/foo/bar.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --write' + \ } + +Execute(The version check should be correct): + AssertFixer [ + \ ale#Escape('prettier-eslint') . ' --version', + \ { + \ '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('../test-files/eslint/react-app/foo/bar.js') + + GivenCommandOutput ['4.4.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('prettier-eslint') + \ . ' --eslint-config-path ' . ale#Escape(ale#test#GetFilename('../test-files/eslint/react-app/.eslintrc.js')) + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(The version number should be cached): + GivenCommandOutput ['4.4.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('prettier-eslint') + \ . ' --stdin-filepath %s --stdin', + \ } + + GivenCommandOutput [] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('prettier-eslint') + \ . ' --stdin-filepath %s --stdin', + \ } diff --git a/test/fixers/test_prettier_fixer_callback.vader b/test/fixers/test_prettier_fixer_callback.vader new file mode 100644 index 00000000..5726fbc3 --- /dev/null +++ b/test/fixers/test_prettier_fixer_callback.vader @@ -0,0 +1,351 @@ +Before: + call ale#assert#SetUpFixerTest('javascript', 'prettier') + Save g:ale_command_wrapper + + let g:ale_command_wrapper = '' + +After: + call ale#assert#TearDownFixerTest() + +Execute(The prettier callback should return the correct default values): + call ale#test#SetFilename('../test-files/prettier/testfile.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --write', + \ } + +Execute(The --config option should not be set automatically): + let g:ale_javascript_prettier_use_local_config = 1 + call ale#test#SetFilename('../test-files/prettier/with_config/testfile.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --write', + \ } + +Execute(The prettier callback should include custom prettier options): + let g:ale_javascript_prettier_options = '--no-semi' + call ale#test#SetFilename('../test-files/prettier/with_config/testfile.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --no-semi' + \ . ' --write', + \ } + +Execute(The version check should be correct): + call ale#test#SetFilename('../test-files/prettier/testfile.js') + + 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('../test-files/prettier/with_config/testfile.js') + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --no-semi' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(The version number should be cached): + call ale#test#SetFilename('../test-files/prettier/with_config/testfile.js') + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --stdin-filepath %s --stdin', + \ } + + GivenCommandOutput [] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': 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('../test-files/prettier/testfile') + + set filetype=javascript + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser babylon' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser to `babel` by default, >= 1.16.0): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=javascript + + GivenCommandOutput ['1.16.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': 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('../test-files/prettier/testfile') + + set filetype=typescript + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser typescript' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, CSS): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=css + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser css' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, LESS): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=less + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser less' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, SCSS): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=scss + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser scss' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, JSON): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=json + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser json' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, JSON5): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=json5 + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser json5' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, GraphQL): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=graphql + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser graphql' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, Markdown): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=markdown + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser markdown' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, Vue): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=vue + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser vue' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, YAML): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=yaml + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser yaml' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, HTML): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=html + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser html' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on filetype, Ruby): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=ruby + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser ruby' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on first filetype of multiple filetypes): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=css.scss + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser css' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser based on first filetype of multiple filetypes): + call ale#test#SetFilename('../test-files/prettier/testfile') + + set filetype=astro + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser astro' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Should set --parser for experimental language, Handlebars): + call ale#test#SetFilename('../test-files/prettier/testfile.hbs') + + set filetype=html.handlebars + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --parser glimmer' + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(Changes to directory where .prettierignore is found): + call ale#test#SetFilename('../test-files/prettier/with_prettierignore/src/testfile.js') + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'cwd': expand('%:p:h:h'), + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --stdin-filepath %s --stdin', + \ } + +Execute(The prettier_d post-processor should permit regular JavaScript content): + AssertEqual + \ [ + \ 'const x = ''Error: foo''', + \ 'const y = 3', + \ ], + \ ale#fixers#prettier#ProcessPrettierDOutput(bufnr(''), [ + \ 'const x = ''Error: foo''', + \ 'const y = 3', + \ ]) + +Execute(The prettier_d post-processor should handle error messages correctly): + AssertEqual + \ [], + \ ale#fixers#prettier#ProcessPrettierDOutput(bufnr(''), [ + \ 'SyntaxError: Unexpected token, expected "," (36:28)', + \ ]) diff --git a/test/fixers/test_prettier_standard_callback.vader b/test/fixers/test_prettier_standard_callback.vader new file mode 100644 index 00000000..f5037ed6 --- /dev/null +++ b/test/fixers/test_prettier_standard_callback.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpFixerTest('javascript', 'prettier_standard') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The prettier callback should return the correct default values): + call ale#test#SetFilename('../test-files/prettier/testfile.js') + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_javascript_prettier_standard_executable) + \ . ' --stdin' + \ . ' --stdin-filepath=%s ', + \ } diff --git a/test/fixers/test_protolint_fixer_callback.vader b/test/fixers/test_protolint_fixer_callback.vader new file mode 100644 index 00000000..5a6931d7 --- /dev/null +++ b/test/fixers/test_protolint_fixer_callback.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpFixerTest('proto', 'protolint') + call ale#test#SetFilename('test.proto') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The default command should be correct): + AssertFixer + \ { + \ 'command': ale#Escape('protolint') + \ . ' -fix' + \ . ' %t', + \ 'read_temporary_file': 1, + \ } + +Execute(The callback should include any additional options): + let b:ale_proto_protolint_executable = '/tmp/protolint' + let b:ale_proto_protolint_config = '/tmp/protolint.yaml' + + AssertFixer + \ { + \ 'command': ale#Escape('/tmp/protolint') + \ . ' -config_path=' . ale#Escape('/tmp/protolint.yaml') + \ . ' -fix' + \ . ' %t', + \ 'read_temporary_file': 1, + \ } diff --git a/test/fixers/test_ptop_fixer_callback.vader b/test/fixers/test_ptop_fixer_callback.vader new file mode 100644 index 00000000..7cf632e3 --- /dev/null +++ b/test/fixers/test_ptop_fixer_callback.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_pascal_ptop_executable + Save g:ale_pascal_ptop_options + + " Use an invalid global executable, so we don't match it. + let g:ale_pascal_ptop_executable = 'xxxinvalid' + let g:ale_pascal_ptop_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The ptop callback should return the correct default values): + call ale#test#SetFilename('../test-files/pascal/test.pas') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' %s %t', + \ }, + \ ale#fixers#ptop#Fix(bufnr('')) + +Execute(The ptop callback should include custom ptop options): + let g:ale_pascal_ptop_options = "-i 2" + call ale#test#SetFilename('../test-files/pascal/test.pas') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_pascal_ptop_options + \ . ' %s %t', + \ }, + \ ale#fixers#ptop#Fix(bufnr('')) diff --git a/test/fixers/test_puppetlint_fixer_callback.vader b/test/fixers/test_puppetlint_fixer_callback.vader new file mode 100644 index 00000000..1a5a6cea --- /dev/null +++ b/test/fixers/test_puppetlint_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_puppet_puppetlint_executable + Save g:ale_puppet_puppetlint_options + + " Use an invalid global executable, so we don't match it. + let g:ale_puppet_puppetlint_executable = 'xxxinvalid' + let g:ale_puppet_puppetlint_options = '--invalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The puppetlint callback should return the correct default values): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/puppet/dummy.pp') + + AssertEqual + \ {'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_puppet_puppetlint_executable) + \ . ' ' . g:ale_puppet_puppetlint_options + \ . ' --fix %t' }, + \ ale#fixers#puppetlint#Fix(bufnr('')) diff --git a/test/fixers/test_purs_tidy_fixer_callback.vader b/test/fixers/test_purs_tidy_fixer_callback.vader new file mode 100644 index 00000000..fdeb3f21 --- /dev/null +++ b/test/fixers/test_purs_tidy_fixer_callback.vader @@ -0,0 +1,20 @@ +Before: + Save g:ale_purescript_tidy_executable + Save g:ale_purescript_tidy_options + + " Use an invalid global executable, so we don’t match it. + let g:ale_purescript_tidy_executable = 'odd-purs-tidy' + let g:ale_purescript_tidy_options = '--indent 3' + + call ale#assert#SetUpFixerTest('purescript', 'purs-tidy') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The purs-tidy callback should return the correct custom options): + AssertFixer + \ { + \ 'command': ale#Escape('odd-purs-tidy') + \ . ' format' + \ . ' --indent 3' + \ } diff --git a/test/fixers/test_purty_fixer_callback.vader b/test/fixers/test_purty_fixer_callback.vader new file mode 100644 index 00000000..e83b8c18 --- /dev/null +++ b/test/fixers/test_purty_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_purescript_purty_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_purescript_purty_executable = 'my-special-purty' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The purty callback should return the correct options): + call ale#test#SetFilename('../purescript_files/testfile.purs') + + AssertEqual + \ { + \ 'command': ale#Escape('my-special-purty') + \ . ' --write' + \ . ' %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#purty#Fix(bufnr('')) diff --git a/test/fixers/test_pycln_fixer_callback.vader b/test/fixers/test_pycln_fixer_callback.vader new file mode 100644 index 00000000..b0fb22b9 --- /dev/null +++ b/test/fixers/test_pycln_fixer_callback.vader @@ -0,0 +1,154 @@ +Before: + call ale#assert#SetUpFixerTest('python', 'pycln') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + + let b:cmd_tail = ' --silence' +After: + call ale#assert#TearDownFixerTest() + + unlet! g:dir + unlet! b:bin_dir + +Execute(The pycln callback should return the correct default values): + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pycln')) . b:cmd_tail . ' -', + \ } + +Execute(The pycln callback should not use stdin for older versions (< 1.3.0)): + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + GivenCommandOutput ['pycln, version 1.2.99'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pycln')) . b:cmd_tail . ' %s', + \ } + +Execute(The pycln callback should not change directory if the option is set to 0): + let g:ale_python_pycln_change_directory = 0 + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pycln')) . b:cmd_tail . ' -', + \ } + +Execute(The pycln callback should respect custom options): + let g:ale_python_pycln_options = '--expand-stars --no-gitignore' + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pycln')) + \ . ' --expand-stars --no-gitignore' . b:cmd_tail . ' -', + \ } + +Execute(Pipenv is detected when python_pycln_auto_pipenv is set): + let g:ale_python_pycln_auto_pipenv = 1 + let g:ale_python_pycln_change_directory = 0 + + let file_path = '../test-files/python/pipenv/whatever.py' + + call ale#test#SetFilename(file_path) + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pipenv') . ' run pycln' . b:cmd_tail . ' -' + \ } + +Execute(Poetry is detected when python_pycln_auto_poetry is set): + let g:ale_python_pycln_auto_poetry = 1 + let g:ale_python_pycln_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('poetry') . ' run pycln' . b:cmd_tail . ' -' + \ } + +Execute(Poetry is detected when python_pycln_auto_poetry is set, and cwd respects change_directory option): + let g:ale_python_pycln_auto_poetry = 1 + let g:ale_python_pycln_change_directory = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/poetry'), + \ 'command': ale#Escape('poetry') . ' run pycln' . b:cmd_tail . ' -' + \ } + +Execute(uv is detected when python_pycln_auto_uv is set): + let g:ale_python_pycln_auto_uv = 1 + let g:ale_python_pycln_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('uv') . ' run pycln' . b:cmd_tail . ' -' + \ } + +Execute(configuration files set in _config should be supported): + let g:ale_python_pycln_change_directory = 0 + let g:ale_python_pycln_config_file = ale#path#Simplify(g:dir . '/../test-files/pycln/other_config.xml') + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pycln') + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/pycln/other_config.xml')) + \ . b:cmd_tail . ' -' + \ } + +Execute(configuration file set in _options overrides _config): + let g:ale_python_pycln_change_directory = 0 + let g:ale_python_pycln_config_file = '/foo.xml' + let g:ale_python_pycln_options = '--config /bar.xml' + + GivenCommandOutput ['pycln, version 1.3.0'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pycln') . ' --config /bar.xml' . b:cmd_tail . ' -' + \ } + + let b:ale_python_pycln_options = '-x --config /bar.xml' + + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pycln') . ' -x --config /bar.xml' . b:cmd_tail . ' -' + \ } + diff --git a/test/fixers/test_pyflyby_fixer_callback.vader b/test/fixers/test_pyflyby_fixer_callback.vader new file mode 100644 index 00000000..4dcf16d5 --- /dev/null +++ b/test/fixers/test_pyflyby_fixer_callback.vader @@ -0,0 +1,49 @@ +Before: + call ale#assert#SetUpFixerTest('python', 'pyflyby') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + call ale#assert#TearDownFixerTest() + + unlet! b:bin_dir + +Execute(The pyflyby callback should return the correct default values): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertFixer + \ { + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/tidy-imports')), + \ } + +Execute(Pipenv is detected when python_pyflyby_auto_pipenv is set): + let g:ale_python_pyflyby_auto_pipenv = 1 + + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertFixer + \ { + \ 'command': ale#Escape('pipenv') . ' run tidy-imports' + \ } + +Execute(Poetry is detected when python_pyflyby_auto_poetry is set): + let g:ale_python_pyflyby_auto_poetry = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + GivenCommandOutput ['VERSION 5.7.0'] + AssertFixer + \ { + \ 'command': ale#Escape('poetry') . ' run tidy-imports' + \ } + +Execute(uv is detected when python_pyflyby_auto_uv is set): + let g:ale_python_pyflyby_auto_uv = 1 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + GivenCommandOutput ['VERSION 5.7.0'] + AssertFixer + \ { + \ 'command': ale#Escape('uv') . ' run tidy-imports' + \ } diff --git a/test/fixers/test_python_add_blank_lines_fixer.vader b/test/fixers/test_python_add_blank_lines_fixer.vader new file mode 100644 index 00000000..7d042c8a --- /dev/null +++ b/test/fixers/test_python_add_blank_lines_fixer.vader @@ -0,0 +1,167 @@ +Before: + Save g:ale_fixers + +After: + Restore + +Given python(Some Python without blank lines): + def foo(): + """ This is a simple test docstring """ + return 1 + + + def bar(): + '''This is another simple test docstring''' + return 1 + return 4 + + + def bar(): + """ + This is a multi-line + docstring + """ + + if x: + pass + for l in x: + pass + for l in x: + pass + break + continue + elif x: + pass + while x: + pass + while x: + pass + else: + pass + if x: + pass + elif x: + pass + else: + pass + +Execute(Blank lines should be added appropriately): + let g:ale_fixers = {'python': ['add_blank_lines_for_python_control_statements']} + ALEFix + +Expect python(Newlines should be added): + def foo(): + """ This is a simple test docstring """ + + return 1 + + + def bar(): + '''This is another simple test docstring''' + + return 1 + + return 4 + + + def bar(): + """ + This is a multi-line + docstring + """ + + if x: + pass + + for l in x: + pass + + for l in x: + pass + + break + + continue + elif x: + pass + + while x: + pass + + while x: + pass + else: + pass + + if x: + pass + elif x: + pass + else: + pass + +Given python(A file with a main block): + import os + + + def main(): + print('hello') + + + if __name__ == '__main__': + main() + +Execute(Fix the file): + let g:ale_fixers = {'python': ['add_blank_lines_for_python_control_statements']} + ALEFix + +Expect python(extra newlines shouldn't be added to the main block): + import os + + + def main(): + print('hello') + + + if __name__ == '__main__': + main() + + +Given python(A file with variables/docstring that start with a control statement): + def some(): + """ + This is a docstring that contains an + break control statement and also contains a + return something funny. + """ + + continue_some_var = True + forward_something = False + + if ( + continue_some_var and + forwarded_something + ): + return True + + +Execute(Fix the file): + let g:ale_fixers = {'python': ['add_blank_lines_for_python_control_statements']} + ALEFix + +Expect python(Extra new lines are not added to the file): + def some(): + """ + This is a docstring that contains an + break control statement and also contains a + return something funny. + """ + + continue_some_var = True + forward_something = False + + if ( + continue_some_var and + forwarded_something + ): + return True diff --git a/test/fixers/test_qmlfmt_fixer_callback.vader b/test/fixers/test_qmlfmt_fixer_callback.vader new file mode 100644 index 00000000..e216f2e1 --- /dev/null +++ b/test/fixers/test_qmlfmt_fixer_callback.vader @@ -0,0 +1,12 @@ +Before: + Save g:ale_qml_qmlfmt_executable + +After: + Restore + +Execute(The qmlfmt fixer should use the options you set): + let g:ale_qml_qmlfmt_executable = 'foo-exe' + + AssertEqual + \ {'command': ale#Escape('foo-exe')}, + \ ale#fixers#qmlfmt#Fix(bufnr('')) diff --git a/test/fixers/test_raco_fmt_fixer_callback.vader b/test/fixers/test_raco_fmt_fixer_callback.vader new file mode 100644 index 00000000..34c599a3 --- /dev/null +++ b/test/fixers/test_raco_fmt_fixer_callback.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpFixerTest('racket', 'raco_fmt') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The raco_fmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/racket/simple-script/foo.rkt') + + AssertFixer {'command': ale#Escape('raco') . ' fmt'} + +Execute(The raco_fmt callback should include custom raco_fmt options): + let g:ale_racket_raco_fmt_options = "--width 100" + call ale#test#SetFilename('../test-files/racket/simple-script/foo.rkt') + + AssertFixer {'command': ale#Escape('raco') . ' fmt ' . g:ale_racket_raco_fmt_options} + diff --git a/test/fixers/test_refmt_fixer_callback.vader b/test/fixers/test_refmt_fixer_callback.vader new file mode 100644 index 00000000..01b56bee --- /dev/null +++ b/test/fixers/test_refmt_fixer_callback.vader @@ -0,0 +1,41 @@ +Before: + Save g:ale_reasonml_refmt_executable + Save g:ale_reasonml_refmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_reasonml_refmt_executable = 'xxxinvalid' + let g:ale_reasonml_refmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The refmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/reasonml/testfile.re') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' --in-place' + \ . ' %t', + \ }, + \ ale#fixers#refmt#Fix(bufnr('')) + +Execute(The refmt callback should include custom refmt options): + let g:ale_reasonml_refmt_options = "-w 80" + call ale#test#SetFilename('../test-files/reasonml/testfile.re') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_reasonml_refmt_options + \ . ' --in-place' + \ . ' %t', + \ }, + \ ale#fixers#refmt#Fix(bufnr('')) + diff --git a/test/fixers/test_remark_lint_fixer_callback.vader b/test/fixers/test_remark_lint_fixer_callback.vader new file mode 100644 index 00000000..5e2e342d --- /dev/null +++ b/test/fixers/test_remark_lint_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_markdown_remark_lint_executable + Save g:ale_markdown_remark_lint_options + +After: + Restore + +Execute(The remark callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('remark') + \ }, + \ ale#fixers#remark_lint#Fix(bufnr('')) + +Execute(The remark executable and options should be configurable): + let g:ale_markdown_remark_lint_executable = '/path/to/remark' + let g:ale_markdown_remark_lint_options = '-h' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/remark') + \ . ' -h', + \ }, + \ ale#fixers#remark_lint#Fix(bufnr('')) diff --git a/test/fixers/test_reorder_python_imports_fixer_callback.vader b/test/fixers/test_reorder_python_imports_fixer_callback.vader new file mode 100644 index 00000000..82e4b84d --- /dev/null +++ b/test/fixers/test_reorder_python_imports_fixer_callback.vader @@ -0,0 +1,73 @@ +Before: + Save g:ale_python_reorder_python_imports_executable + Save g:ale_python_reorder_python_imports_options + + " Use an invalid global executable, so we don't match it. + let g:ale_python_reorder_python_imports_executable = 'xxxinvalid' + let g:ale_python_reorder_python_imports_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The reorder_python_imports callback should return the correct default values): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ { + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' + \ . b:bin_dir . '/reorder-python-imports')) . ' -', + \ }, + \ ale#fixers#reorder_python_imports#Fix(bufnr('')) + +Execute(The reorder_python_imports callback should respect custom options): + let g:ale_python_reorder_python_imports_options = '--py3-plus' + + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ { + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' + \ . b:bin_dir . '/reorder-python-imports')) . ' --py3-plus -', + \ }, + \ ale#fixers#reorder_python_imports#Fix(bufnr('')) + +Execute(pipenv is detected when python_reorder_python_imports_auto_pipenv is set): + let g:ale_python_reorder_python_imports_auto_pipenv = 1 + + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('pipenv') . ' run reorder-python-imports -', + \ }, + \ ale#fixers#reorder_python_imports#Fix(bufnr('')) + +Execute(Poetry is detected when python_reorder_python_imports_auto_poetry is set): + let g:ale_python_reorder_python_imports_auto_poetry = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('poetry') . ' run reorder-python-imports -', + \ }, + \ ale#fixers#reorder_python_imports#Fix(bufnr('')) + +Execute(uv is detected when python_reorder_python_imports_auto_uv is set): + let g:ale_python_reorder_python_imports_auto_uv = 1 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('uv') . ' run reorder-python-imports -', + \ }, + \ ale#fixers#reorder_python_imports#Fix(bufnr('')) diff --git a/test/fixers/test_rubocop_fixer_callback.vader b/test/fixers/test_rubocop_fixer_callback.vader new file mode 100644 index 00000000..f7b0eb60 --- /dev/null +++ b/test/fixers/test_rubocop_fixer_callback.vader @@ -0,0 +1,89 @@ +Before: + Save g:ale_ruby_rubocop_executable + Save g:ale_ruby_rubocop_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_rubocop_executable = 'xxxinvalid' + let g:ale_ruby_rubocop_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The rubocop callback should return the correct default values): + call ale#test#SetFilename('../test-files/ruby/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#rubocop#PostProcess', + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --auto-correct --force-exclusion --stdin %s', + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop callback should include custom rubocop options): + let g:ale_ruby_rubocop_options = '--except Lint/Debugger' + call ale#test#SetFilename('../test-files/ruby/with_config/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#rubocop#PostProcess', + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --except Lint/Debugger' + \ . ' --auto-correct --force-exclusion --stdin %s', + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop callback should use auto-correct-all option when set): + let g:ale_ruby_rubocop_auto_correct_all = 1 + call ale#test#SetFilename('../test-files/ruby/with_config/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#rubocop#PostProcess', + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --auto-correct-all --force-exclusion --stdin %s' + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop post-processor should remove diagnostics content): + AssertEqual + \ [ + \ 'class MyModel < ApplicationRecord', + \ ' # rubocop:disable Rails/InverseOf', + \ ' has_one :something', + \ ' # rubocop:enable Rails/InverseOf', + \ 'end', + \ '', + \ 'array = [1, 2, 3,', + \ ' 4, 5, 6]', + \ 'array = [''run'',', + \ ' ''forrest'',', + \ ' ''run'']', + \ ], + \ ale#fixers#rubocop#PostProcess(bufnr(''), [ + \ 'Inspecting 1 file', + \ 'C', + \ '', + \ 'Offenses:', + \ 'app/models/my_model.rb:8:3: C: [Corrected] Layout/ArrayAlignment: ', + \ '4, 5, 6]', + \ '^', + \ '', + \ '1 file inspected, 3 offenses detected, 3 offenses corrected', + \ '====================', + \ 'class MyModel < ApplicationRecord', + \ ' # rubocop:disable Rails/InverseOf', + \ ' has_one :something', + \ ' # rubocop:enable Rails/InverseOf', + \ 'end', + \ '', + \ 'array = [1, 2, 3,', + \ ' 4, 5, 6]', + \ 'array = [''run'',', + \ ' ''forrest'',', + \ ' ''run'']', + \ ]) diff --git a/test/fixers/test_rubyfmt_fixer_callback.vader b/test/fixers/test_rubyfmt_fixer_callback.vader new file mode 100644 index 00000000..e211640f --- /dev/null +++ b/test/fixers/test_rubyfmt_fixer_callback.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_ruby_rubyfmt_executable + Save g:ale_ruby_rubyfmt_options + Save &l:expandtab + Save &l:shiftwidth + Save &l:tabstop + +After: + Restore + +Execute(The rubyfmt callback should return 'rubyfmt' as default command): + setlocal noexpandtab + Assert + \ ale#fixers#rubyfmt#Fix(bufnr('')).command =~# '^' . ale#Escape('rubyfmt'), + \ "Default command name is expected to be 'rubyfmt'" + +Execute(The ruby executable and options should be configurable): + let g:ale_ruby_rubyfmt_executable = 'foobar' + let g:ale_ruby_rubyfmt_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape('foobar') + \ . ' --some-option', + \ }, + \ ale#fixers#rubyfmt#Fix(bufnr('')) diff --git a/test/fixers/test_ruff_fixer_callback.vader b/test/fixers/test_ruff_fixer_callback.vader new file mode 100644 index 00000000..82a0383a --- /dev/null +++ b/test/fixers/test_ruff_fixer_callback.vader @@ -0,0 +1,150 @@ +Before: + call ale#assert#SetUpFixerTest('python', 'ruff') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + call ale#assert#TearDownFixerTest() + + unlet! g:dir + unlet! b:bin_dir + +Execute(The ruff callback should return the correct default values): + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + " --fix does not support stdin until 0.0.72 + GivenCommandOutput ['ruff 0.0.72'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff')) . ' --stdin-filename ' . fname . ' --fix -', + \ } + +Execute(The ruff callback should not use stdin for older versions (< 0.0.72)): + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + " --fix does not support stdin until 0.0.72 + GivenCommandOutput ['ruff 0.0.71'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff')) . ' --stdin-filename ' . fname . ' --fix %s', + \ } + +Execute(The ruff callback should not change directory if the option is set to 0): + let g:ale_python_ruff_change_directory = 0 + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + " --fix does not support stdin until 0.0.72 + GivenCommandOutput ['ruff 0.0.72'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff')) . ' --stdin-filename ' . fname . ' --fix -', + \ } + +Execute(The ruff callback should respect custom options): + let g:ale_python_ruff_options = '--ignore F401 -q' + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + GivenCommandOutput ['ruff 0.0.72'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff')) + \ . ' --ignore F401 -q --stdin-filename '. fname . ' --fix -', + \ } + +Execute(The ruff callback should use ruff check for 0.5.0): + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + GivenCommandOutput ['ruff 0.5.0'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff')) . ' check --stdin-filename ' . fname . ' --fix -', + \ } + +Execute(Pipenv is detected when python_ruff_auto_pipenv is set): + let g:ale_python_ruff_auto_pipenv = 1 + let g:ale_python_ruff_change_directory = 0 + + let file_path = '../test-files/python/pipenv/whatever.py' + + call ale#test#SetFilename(file_path) + + let fname = ale#Escape(ale#path#Simplify(g:dir . '/'. file_path)) + + GivenCommandOutput ['ruff 0.0.72'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pipenv') . ' run ruff --stdin-filename ' . fname . ' --fix -' + \ } + +Execute(Poetry is detected when python_ruff_auto_poetry is set): + let g:ale_python_ruff_auto_poetry = 1 + let g:ale_python_ruff_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/poetry/whatever.py')) + + GivenCommandOutput ['ruff 0.0.72'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('poetry') . ' run ruff --stdin-filename ' . fname . ' --fix -' + \ } + +Execute(Poetry is detected when python_ruff_auto_poetry is set, and cwd respects change_directory option): + let g:ale_python_ruff_auto_poetry = 1 + let g:ale_python_ruff_change_directory = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/poetry/whatever.py')) + + GivenCommandOutput ['ruff 0.0.72'] + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/poetry'), + \ 'command': ale#Escape('poetry') . ' run ruff --stdin-filename ' . fname . ' --fix -' + \ } + +Execute(uv is detected when python_ruff_auto_uv is set): + let g:ale_python_ruff_auto_uv = 1 + let g:ale_python_ruff_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/uv/whatever.py')) + + GivenCommandOutput ['ruff 0.0.72'] + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('uv') . ' run ruff --stdin-filename ' . fname . ' --fix -' + \ } diff --git a/test/fixers/test_ruff_format_fixer_callback.vader b/test/fixers/test_ruff_format_fixer_callback.vader new file mode 100644 index 00000000..7672ee3e --- /dev/null +++ b/test/fixers/test_ruff_format_fixer_callback.vader @@ -0,0 +1,99 @@ +Before: + call ale#assert#SetUpFixerTest('python', 'ruff_format') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + call ale#assert#TearDownFixerTest() + + unlet! g:dir + unlet! b:bin_dir + +Execute(The ruff callback should not change directory if the option is set to 0): + let g:ale_python_ruff_format_change_directory = 0 + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff')) . ' format --stdin-filename ' . fname . ' -', + \ } + +Execute(The ruff callback should respect custom options): + let g:ale_python_ruff_format_options = '--ignore F401 -q' + + let file_path = g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.py' + + silent execute 'file ' . fnameescape(file_path) + + let fname = ale#Escape(ale#path#Simplify(file_path)) + + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir'), + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff')) + \ . ' format --ignore F401 -q --stdin-filename '. fname . ' -', + \ } + +Execute(Pipenv is detected when python_ruff_format_auto_pipenv is set): + let g:ale_python_ruff_format_auto_pipenv = 1 + let g:ale_python_ruff_format_change_directory = 0 + + let file_path = '../test-files/python/pipenv/whatever.py' + + call ale#test#SetFilename(file_path) + + let fname = ale#Escape(ale#path#Simplify(g:dir . '/'. file_path)) + + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('pipenv') . ' run ruff format --stdin-filename ' . fname . ' -' + \ } + +Execute(Poetry is detected when python_ruff_auto_poetry is set): + let g:ale_python_ruff_format_auto_poetry = 1 + let g:ale_python_ruff_format_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/poetry/whatever.py')) + + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('poetry') . ' run ruff format --stdin-filename ' . fname . ' -' + \ } + +Execute(Poetry is detected when python_ruff_format_auto_poetry is set, and cwd respects change_directory option): + let g:ale_python_ruff_format_auto_poetry = 1 + let g:ale_python_ruff_format_change_directory = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/poetry/whatever.py')) + + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/python/poetry'), + \ 'command': ale#Escape('poetry') . ' run ruff format --stdin-filename ' . fname . ' -' + \ } + +Execute(uv is detected when python_ruff_format_auto_uv is set): + let g:ale_python_ruff_format_auto_uv = 1 + let g:ale_python_ruff_format_change_directory = 0 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + let fname = ale#Escape(ale#path#Simplify(g:dir .'/../test-files/python/uv/whatever.py')) + + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('uv') . ' run ruff format --stdin-filename ' . fname . ' -' + \ } diff --git a/test/fixers/test_rufo_fixer_callback.vader b/test/fixers/test_rufo_fixer_callback.vader new file mode 100644 index 00000000..3d539f7a --- /dev/null +++ b/test/fixers/test_rufo_fixer_callback.vader @@ -0,0 +1,30 @@ +Before: + Save g:ale_ruby_rufo_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_rufo_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The rufo command should contain `bundle exec` when executable is `bundle`): + let g:ale_ruby_rufo_executable = 'bundle' + call ale#test#SetFilename('../test-files/ruby/dummy.rb') + + AssertEqual + \ ale#Escape('bundle') . ' exec rufo %t', + \ ale#fixers#rufo#GetCommand(bufnr('')) + +Execute(The rufo callback should return the correct default values): + call ale#test#SetFilename('../test-files/ruby/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') . ' %t' + \ }, + \ ale#fixers#rufo#Fix(bufnr('')) diff --git a/test/fixers/test_rustfmt_fixer_callback.vader b/test/fixers/test_rustfmt_fixer_callback.vader new file mode 100644 index 00000000..98761c94 --- /dev/null +++ b/test/fixers/test_rustfmt_fixer_callback.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpFixerTest('rust', 'rustfmt') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The rustfmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/rust/testfile.rs') + + AssertFixer {'command': ale#Escape('rustfmt')} + +Execute(The rustfmt callback should include custom rustfmt options): + let g:ale_rust_rustfmt_options = "--skip-children" + call ale#test#SetFilename('../test-files/rust/testfile.rs') + + AssertFixer {'command': ale#Escape('rustfmt') . ' ' . g:ale_rust_rustfmt_options} diff --git a/test/fixers/test_rustywind_fixer_callback.vader b/test/fixers/test_rustywind_fixer_callback.vader new file mode 100644 index 00000000..45364752 --- /dev/null +++ b/test/fixers/test_rustywind_fixer_callback.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_html_rustywind_executable + Save g:ale_html_rustywind_options + + " Use an invalid global executable, so we don't match it. + let g:ale_html_rustywind_executable = 'xxxinvalid' + let g:ale_html_rustywind_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The rustywind callback should return the correct default values): + call ale#test#SetFilename('../test-files/rustywind/test.html') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --stdin', + \ }, + \ ale#fixers#rustywind#Fix(bufnr('')) + +Execute(The rustywind callback should include custom rustywind options): + let g:ale_html_rustywind_options = "--custom-regex some-regex" + call ale#test#SetFilename('../test-files/rustywind/test.html') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_html_rustywind_options + \ . ' --stdin', + \ }, + \ ale#fixers#rustywind#Fix(bufnr('')) diff --git a/test/fixers/test_scadformat_fixer.vader b/test/fixers/test_scadformat_fixer.vader new file mode 100644 index 00000000..5461dc4b --- /dev/null +++ b/test/fixers/test_scadformat_fixer.vader @@ -0,0 +1,22 @@ +Before: + Save g:ale_openscad_scadformat_executable + Save g:ale_openscad_scadformat_options + + let g:ale_openscad_scadformat_executable = 'xxx' + let g:ale_openscad_scadformat_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(scadformat - defaults OK): + call ale#test#SetFilename('../test-files/openscad/dummy.scad') + + AssertEqual + \ { + \ 'command': ale#Escape('xxx') + \ }, + \ ale#fixers#scadformat#Fix(bufnr('')) diff --git a/test/fixers/test_scalafmt_fixer_callback.vader b/test/fixers/test_scalafmt_fixer_callback.vader new file mode 100644 index 00000000..2b8dc3eb --- /dev/null +++ b/test/fixers/test_scalafmt_fixer_callback.vader @@ -0,0 +1,66 @@ +Before: + Save g:ale_scala_scalafmt_executable + Save g:ale_scala_scalafmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_scala_scalafmt_executable = 'xxxinvalid' + let g:ale_scala_scalafmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The scalafmt callback should return the correct default values): + call ale#test#SetFilename('../test-files/scala/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_scala_scalafmt_executable) + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should use ng with scalafmt automatically): + let g:ale_scala_scalafmt_executable = 'ng' + call ale#test#SetFilename('../test-files/scala/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('ng') + \ . ' scalafmt' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should include custom scalafmt options): + let g:ale_scala_scalafmt_options = '--diff' + call ale#test#SetFilename('../test-files/scala/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_scala_scalafmt_executable) + \ . ' --diff' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should include custom scalafmt options and use ng with scalafmt): + let g:ale_scala_scalafmt_options = '--diff' + let g:ale_scala_scalafmt_executable = 'ng' + call ale#test#SetFilename('../test-files/scala/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('ng') + \ . ' scalafmt' + \ . ' --diff' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) diff --git a/test/fixers/test_shfmt_fixer_callback.vader b/test/fixers/test_shfmt_fixer_callback.vader new file mode 100644 index 00000000..02fce526 --- /dev/null +++ b/test/fixers/test_shfmt_fixer_callback.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_sh_shfmt_executable + Save g:ale_sh_shfmt_options + Save &l:expandtab + Save &l:shiftwidth + Save &l:tabstop + +After: + Restore + +Execute(The shfmt callback should return 'shfmt' as default command): + setlocal noexpandtab + Assert + \ ale#fixers#shfmt#Fix(bufnr('')).command =~# '^' . ale#Escape('shfmt'), + \ "Default command name is expected to be 'shfmt'" + +Execute(The shfmt executable and options should be configurable): + let g:ale_sh_shfmt_executable = 'foobar' + let g:ale_sh_shfmt_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape('foobar') + \ . ' -filename=%s' + \ . ' --some-option', + \ }, + \ ale#fixers#shfmt#Fix(bufnr('')) diff --git a/test/fixers/test_sorbet_fixer_callback.vader b/test/fixers/test_sorbet_fixer_callback.vader new file mode 100644 index 00000000..2694a3dc --- /dev/null +++ b/test/fixers/test_sorbet_fixer_callback.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_ruby_sorbet_executable + Save g:ale_ruby_sorbet_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_sorbet_executable = 'xxxinvalid' + let g:ale_ruby_sorbet_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The sorbet callback should return the correct default values): + call ale#test#SetFilename('../test-files/ruby/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_sorbet_executable) + \ . ' tc --autocorrect --file %t', + \ }, + \ ale#fixers#sorbet#Fix(bufnr('')) + +Execute(The sorbet callback should include custom sorbet options): + let g:ale_ruby_sorbet_options = '--enable-experimental-lsp-hover' + call ale#test#SetFilename('../test-files/ruby/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_sorbet_executable) + \ . ' tc --enable-experimental-lsp-hover' + \ . ' --autocorrect --file %t', + \ }, + \ ale#fixers#sorbet#Fix(bufnr('')) diff --git a/test/fixers/test_sqlfmt_fixer_callback.vader b/test/fixers/test_sqlfmt_fixer_callback.vader new file mode 100644 index 00000000..3046edb3 --- /dev/null +++ b/test/fixers/test_sqlfmt_fixer_callback.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_sql_sqlfmt_executable + Save g:ale_sql_sqlfmt_options + +After: + Restore + +Execute(The sqlfmt callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('sqlfmt') + \ . ' -w', + \ }, + \ ale#fixers#sqlfmt#Fix(bufnr('')) + +Execute(The sqlfmt executable and options should be configurable): + let g:ale_sql_sqlfmt_executable = '/path/to/sqlfmt' + let g:ale_sql_sqlfmt_options = '-u' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/sqlfmt') + \ . ' -w' + \ . ' -u', + \ }, + \ ale#fixers#sqlfmt#Fix(bufnr('')) diff --git a/test/fixers/test_sqlformat_fixer_callback.vader b/test/fixers/test_sqlformat_fixer_callback.vader new file mode 100644 index 00000000..4bace089 --- /dev/null +++ b/test/fixers/test_sqlformat_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_sql_sqlformat_executable + Save g:ale_sql_sqlformat_options + +After: + Restore + +Execute(The sqlformat callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('sqlformat') . ' -' + \ }, + \ ale#fixers#sqlformat#Fix(bufnr('')) + +Execute(The sqlformat executable and options should be configurable): + let g:ale_sql_sqlformat_executable = '/path/to/sqlformat' + let g:ale_sql_sqlformat_options = '-a' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/sqlformat') + \ . ' -a -' + \ }, + \ ale#fixers#sqlformat#Fix(bufnr('')) diff --git a/test/fixers/test_standard_fixer_callback.vader b/test/fixers/test_standard_fixer_callback.vader new file mode 100644 index 00000000..9f5eb0e9 --- /dev/null +++ b/test/fixers/test_standard_fixer_callback.vader @@ -0,0 +1,31 @@ +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() + +Execute(The executable path should be correct): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/standard/bin/cmd.js')) + \ . ' --fix --stdin < %s > %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 --stdin < %s > %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..ff82b8f1 --- /dev/null +++ b/test/fixers/test_standardrb_fixer_callback.vader @@ -0,0 +1,51 @@ +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') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The standardrb callback should return the correct default values): + call ale#test#SetFilename('../test-files/ruby/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#rubocop#PostProcess', + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --fix --force-exclusion --stdin %s', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) + +Execute(The standardrb callback should include configuration files): + call ale#test#SetFilename('../test-files/ruby/with_config/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#rubocop#PostProcess', + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/ruby/with_config/.standard.yml')) + \ . ' --fix --force-exclusion --stdin %s', + \ }, + \ 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('../test-files/ruby/with_config/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#rubocop#PostProcess', + \ 'command': ale#Escape(g:ale_ruby_standardrb_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/ruby/with_config/.standard.yml')) + \ . ' --except Lint/Debugger' + \ . ' --fix --force-exclusion --stdin %s', + \ }, + \ ale#fixers#standardrb#Fix(bufnr('')) diff --git a/test/fixers/test_statix_fixer.vader b/test/fixers/test_statix_fixer.vader new file mode 100644 index 00000000..c55365a6 --- /dev/null +++ b/test/fixers/test_statix_fixer.vader @@ -0,0 +1,18 @@ +Before: + call ale#assert#SetUpFixerTest('nix', 'statix') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The callback should return the correct default values): + AssertFixer { 'command': ale#Escape('statix') . ' fix --stdin' } + +Execute(The callback should include a custom runtime): + let g:ale_nix_statix_fix_executable = 'foo/bar' + + AssertFixer { 'command': ale#Escape('foo/bar') . ' fix --stdin' } + +Execute(The callback should include custom options): + let g:ale_nix_statix_fix_options = '--foobar' + + AssertFixer { 'command': ale#Escape('statix') . ' fix --stdin --foobar' } diff --git a/test/fixers/test_stylelint_fixer_callback.vader b/test/fixers/test_stylelint_fixer_callback.vader new file mode 100644 index 00000000..ee7cfdd4 --- /dev/null +++ b/test/fixers/test_stylelint_fixer_callback.vader @@ -0,0 +1,34 @@ +Before: + Save g:ale_stylelint_options + + let g:ale_stylelint_options = '' + + call ale#assert#SetUpFixerTest('css', 'stylelint') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The stylelint callback should return the correct default values): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir/testfile.css') + + AssertFixer + \ { + \ 'read_temporary_file': 0, + \ 'cwd': '%s:h', + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/stylelint/bin/stylelint.js')) + \ . ' --fix --stdin --stdin-filename %s', + \ } + +Execute(The stylelint callback should include custom stylelint options): + let g:ale_stylelint_options = '--cache' + call ale#test#SetFilename('../test-files/eslint/react-app/subdir/testfile.css') + + AssertFixer + \ { + \ 'read_temporary_file': 0, + \ 'cwd': '%s:h', + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/stylelint/bin/stylelint.js')) + \ . ' --cache --fix --stdin --stdin-filename %s', + \ } diff --git a/test/fixers/test_styler_fixer_callback.vader b/test/fixers/test_styler_fixer_callback.vader new file mode 100644 index 00000000..79f71ba9 --- /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), transformers = ' + \ . 'a_custom_option)"' + \ . ' %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#styler#Fix(bufnr('')) diff --git a/test/fixers/test_stylish_haskell_fixer_callback.vader b/test/fixers/test_stylish_haskell_fixer_callback.vader new file mode 100644 index 00000000..755d3430 --- /dev/null +++ b/test/fixers/test_stylish_haskell_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_haskell_stylish_haskell_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_haskell_stylish_haskell_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The stylish-haskell callback should return the correct default values): + call ale#test#SetFilename('../haskell_files/testfile.hs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' --inplace' + \ . ' %t', + \ }, + \ ale#fixers#stylish_haskell#Fix(bufnr('')) diff --git a/test/fixers/test_stylua_fixer_callback.vader b/test/fixers/test_stylua_fixer_callback.vader new file mode 100644 index 00000000..97572152 --- /dev/null +++ b/test/fixers/test_stylua_fixer_callback.vader @@ -0,0 +1,38 @@ +Before: + call ale#assert#SetUpFixerTest('lua', 'stylua') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The default command should be correct): + AssertFixer {'cwd': '%s:h', 'command': ale#Escape('stylua') . ' --stdin-filepath %s -'} + +Execute(The stylua callback should include custom stylua options): + let g:ale_lua_stylua_executable = 'xxxinvalid' + let g:ale_lua_stylua_options = '--search-parent-directories' + + AssertFixer + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_lua_stylua_options + \ . ' --stdin-filepath %s -', + \ } + +Execute(stylua should detect stylua.toml): + call ale#test#SetFilename('../test-files/stylua/stylua_config_dir/subdir/test.lua') + + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/stylua/stylua_config_dir'), + \ 'command': ale#Escape('stylua') . ' --stdin-filepath %s -', + \ } + +Execute(stylua should detect .stylua.toml): + call ale#test#SetFilename('../test-files/stylua/stylua_dot_config_dir/subdir/test.lua') + + AssertFixer + \ { + \ 'cwd': ale#path#Simplify(g:dir . '/../test-files/stylua/stylua_dot_config_dir'), + \ 'command': ale#Escape('stylua') . ' --stdin-filepath %s -', + \ } diff --git a/test/fixers/test_swiftformat_fixer_callback.vader b/test/fixers/test_swiftformat_fixer_callback.vader new file mode 100644 index 00000000..755c30f6 --- /dev/null +++ b/test/fixers/test_swiftformat_fixer_callback.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_swift_swiftformat_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_swift_swiftformat_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The swiftformat callback should return the correct default values): + call ale#test#SetFilename('../test-files/swift/dummy.swift') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_swiftformat_executable) + \ . ' %t ', + \ }, + \ ale#fixers#swiftformat#Fix(bufnr('')) + +Execute(The swiftformat callback should include any additional options): + call ale#test#SetFilename('../test-files/swift/dummy.swift') + let g:ale_swift_swiftformat_options = '--some-option' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_swiftformat_executable) + \ . ' %t --some-option', + \ }, + \ ale#fixers#swiftformat#Fix(bufnr('')) diff --git a/test/fixers/test_syntax_tree_fixer_callback.vader b/test/fixers/test_syntax_tree_fixer_callback.vader new file mode 100644 index 00000000..de9e69f9 --- /dev/null +++ b/test/fixers/test_syntax_tree_fixer_callback.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_ruby_syntax_tree_executable + Save g:ale_ruby_syntax_tree_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_syntax_tree_executable = 'xxxinvalid' + let g:ale_ruby_syntax_tree_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The syntax_tree callback should return the correct default values): + call ale#test#SetFilename('../test-files/ruby/dummy.rb') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_ruby_syntax_tree_executable) + \ . ' format %t', + \ }, + \ ale#fixers#syntax_tree#Fix(bufnr('')) + +Execute(The syntax_tree callback should include custom options): + let g:ale_ruby_syntax_tree_options = '--print-width=100 --plugins=plugin/trailing_comma' + call ale#test#SetFilename('../test-files/ruby/with_config/dummy.rb') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_ruby_syntax_tree_executable) + \ . ' format --print-width=100 --plugins=plugin/trailing_comma %t', + \ }, + \ ale#fixers#syntax_tree#Fix(bufnr('')) diff --git a/test/fixers/test_terraform_fmt_fixer_callback.vader b/test/fixers/test_terraform_fmt_fixer_callback.vader new file mode 100644 index 00000000..15377a7e --- /dev/null +++ b/test/fixers/test_terraform_fmt_fixer_callback.vader @@ -0,0 +1,34 @@ +Before: + Save g:ale_terraform_fmt_executable + Save g:ale_terraform_fmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_terraform_fmt_executable = 'xxxinvalid' + let g:ale_terraform_fmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The terraform fmt callback should return the correct default values): + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' fmt -', + \ }, + \ ale#fixers#terraform#Fix(bufnr('')) + +Execute(The terraform fmt callback should include custom options): + let g:ale_terraform_fmt_options = "-list=true" + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' fmt' + \ . ' ' . g:ale_terraform_fmt_options + \ . ' -', + \ }, + \ ale#fixers#terraform#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..5b6c5b7a --- /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('../test-files/markdown/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('../test-files/markdown/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/fixers/test_tidy_fixer_callback.vader b/test/fixers/test_tidy_fixer_callback.vader new file mode 100644 index 00000000..25d3d6c3 --- /dev/null +++ b/test/fixers/test_tidy_fixer_callback.vader @@ -0,0 +1,25 @@ +Before: + Save g:ale_html_tidy_executable + + let g:ale_html_tidy_executable = '../test-files/tidy/tidy' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The tidy callback should return 0 if tidy not found): + let g:ale_html_tidy_executable = 'xxxinvalidpath' + AssertEqual + \ 0, + \ ale#fixers#tidy#Fix(bufnr('')) + +Execute(The tidy callback should return the correct default command): + AssertEqual + \ { + \ 'command': ale#Escape('../test-files/tidy/tidy') + \ . ' -q --tidy-mark no --show-errors 0 --show-warnings 0' + \ }, + \ ale#fixers#tidy#Fix(bufnr('')) diff --git a/test/fixers/test_trim_whitespace.vader b/test/fixers/test_trim_whitespace.vader new file mode 100644 index 00000000..2ffbcb04 --- /dev/null +++ b/test/fixers/test_trim_whitespace.vader @@ -0,0 +1,28 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + call ale#test#RestoreDirectory() + +Execute(Should delete all whitespace at the end of different lines): + AssertEqual + \ [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(', + \ 'first_argument,', + \ ' second_argument,', + \ ' third_with_function_call(', + \ 'foo,', + \ ' bar,', + \ '))', + \ ], + \ ale#fixers#generic#TrimWhitespace(bufnr(''), [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(', + \ 'first_argument,', + \ ' second_argument,', + \ ' third_with_function_call(', + \ 'foo,', + \ ' bar,', + \ '))', + \ ]) diff --git a/test/fixers/test_tslint_fixer_callback.vader b/test/fixers/test_tslint_fixer_callback.vader new file mode 100644 index 00000000..43fcc5a4 --- /dev/null +++ b/test/fixers/test_tslint_fixer_callback.vader @@ -0,0 +1,42 @@ +Before: + Save g:ale_typescript_tslint_executable + Save g:ale_typescript_tslint_config_path + + unlet! g:ale_typescript_tslint_executable + unlet! g:ale_typescript_tslint_config_path + unlet! b:ale_typescript_tslint_executable + unlet! b:ale_typescript_tslint_config_path + + call ale#handlers#tslint#InitVariables() + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The tslint callback should return the correct default values): + let g:ale_typescript_tslint_config_path = 'tslint.json' + call ale#test#SetFilename('../test-files/prettier/testfile.ts') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('tslint') + \ . ' -c ' . ale#Escape('tslint.json') + \ . ' --outputAbsolutePaths --fix %t', + \ }, + \ ale#fixers#tslint#Fix(bufnr('')) + +Execute(The tslint callback should include custom tslint config option): + let g:ale_typescript_tslint_config_path = '.tslintrc' + call ale#test#SetFilename('../test-files/prettier/testfile.ts') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('tslint') + \ . ' -c ' . ale#Escape('.tslintrc') + \ . ' --outputAbsolutePaths --fix %t', + \ }, + \ ale#fixers#tslint#Fix(bufnr('')) diff --git a/test/fixers/test_typstyle_fixer_callback.vader b/test/fixers/test_typstyle_fixer_callback.vader new file mode 100644 index 00000000..f8335a79 --- /dev/null +++ b/test/fixers/test_typstyle_fixer_callback.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpFixerTest('typst', 'typstyle', 'typstyle') + +After: + Restore + + call ale#assert#TearDownFixerTest() + +Execute(The typstyle callback should return the correct default command): + AssertEqual + \ {'command': ale#Escape('typstyle') . ' '}, + \ ale#fixers#typstyle#Fix(bufnr('')) + +Execute(The typstyle options should be considered): + call ale#test#SetFilename('../test-files/typstyle/testfile.typ') + let g:ale_typst_typstyle_options = '-c 100' + + AssertFixer + \ { 'command': ale#Escape(g:ale_typst_typstyle_executable) + \ . ' -c 100', + \ } diff --git a/test/fixers/test_uncrustify_fixer_callback.vader b/test/fixers/test_uncrustify_fixer_callback.vader new file mode 100644 index 00000000..c101a31a --- /dev/null +++ b/test/fixers/test_uncrustify_fixer_callback.vader @@ -0,0 +1,108 @@ +Before: + Save g:ale_c_uncrustify_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_c_uncrustify_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The clang-format callback should return the correct default values): + call ale#test#SetFilename('../test-files/c/dummy.c') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l C' + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + +Execute(The uncrustify callback should include any additional options): + call ale#test#SetFilename('../test-files/c/dummy.c') + let b:ale_c_uncrustify_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l C --some-option', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + +Execute(The uncrustify callback should set proper language): + unlet b:ale_c_uncrustify_options + + set filetype=c + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l C', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=cpp + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l CPP', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=cs + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l CS', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=objc + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l OC', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=objcpp + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l OC+', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=d + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l D', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=java + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l JAVA', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=vala + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l VALA', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) + + set filetype=p + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_uncrustify_executable) + \ . ' --no-backup -l PAWN', + \ }, + \ ale#fixers#uncrustify#Fix(bufnr('')) diff --git a/test/fixers/test_vfmt_fixer_callback.vader b/test/fixers/test_vfmt_fixer_callback.vader new file mode 100644 index 00000000..cbab1189 --- /dev/null +++ b/test/fixers/test_vfmt_fixer_callback.vader @@ -0,0 +1,44 @@ +Before: + Save g:ale_v_v_executable + Save g:ale_v_vfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_v_v_executable = 'xxxinvalid' + let g:ale_v_vfmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The vfmt callback should return the correct default values): + call ale#test#SetFilename('../v_files/testfile.v') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' fmt', + \ }, + \ ale#fixers#vfmt#Fix(bufnr('')) + +Execute(The vfmt callback should include custom vfmt options): + let g:ale_v_vfmt_options = "-r '(a) -> a'" + + call ale#test#SetFilename('../v_files/testfile.v') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' fmt ' . g:ale_v_vfmt_options, + \ }, + \ ale#fixers#vfmt#Fix(bufnr('')) + +Execute(The vfmt callback should support Go environment variables): + call ale#test#SetFilename('../v_files/testfile.v') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') . ' fmt', + \ }, + \ ale#fixers#vfmt#Fix(bufnr('')) diff --git a/test/fixers/test_vim_help_tags_alignment_fixer.vader b/test/fixers/test_vim_help_tags_alignment_fixer.vader new file mode 100644 index 00000000..7e18a771 --- /dev/null +++ b/test/fixers/test_vim_help_tags_alignment_fixer.vader @@ -0,0 +1,19 @@ +Before: + Save g:ale_fixers + +After: + Restore + +Given help(A vim help file with badly aligned tags): + foo *foo* + bar *bar* + baz *bar* + +Execute(Tags should be aligned at the right margin): + let g:ale_fixers = {'help': ['align_help_tags']} + ALEFix + +Expect help(Tags should be aligned): + foo *foo* + bar *bar* + baz *bar* diff --git a/test/fixers/test_xmllint_fixer_callback.vader b/test/fixers/test_xmllint_fixer_callback.vader new file mode 100644 index 00000000..392c982e --- /dev/null +++ b/test/fixers/test_xmllint_fixer_callback.vader @@ -0,0 +1,56 @@ +Before: + Save g:ale_xml_xmllint_executable + Save g:ale_xml_xmllint_indentsize + Save g:ale_xml_xmllint_options + + let g:ale_xml_xmllint_executable = '/path/to/xmllint' + let g:ale_xml_xmllint_indentsize = '' + let g:ale_xml_xmllint_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + +Execute(The xmllint callback should return the correct default command with unpersisted buffer): + new + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/xmllint') + \ . ' --format -' + \ }, + \ ale#fixers#xmllint#Fix(bufnr('')) + +Execute(The xmllint callback should return the correct default command): + call ale#test#SetFilename('../test-files/xml/dummy.xml') + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/xmllint') + \ . ' --format -' + \ }, + \ ale#fixers#xmllint#Fix(bufnr('')) + +Execute(The xmllint callback should include the XMLLINT_INDENT variable): + call ale#test#SetFilename('../test-files/xml/dummy.xml') + let g:ale_xml_xmllint_indentsize = 2 + + AssertEqual + \ { + \ 'command': ale#Env('XMLLINT_INDENT', ' ') + \ . ale#Escape('/path/to/xmllint') + \ . ' --format -' + \ }, + \ ale#fixers#xmllint#Fix(bufnr('')) + +Execute(The xmllint callback should include additional options): + call ale#test#SetFilename('../test-files/xml/dummy.xml') + let g:ale_xml_xmllint_options = '--nonet --custom-opt 2' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/xmllint') + \ . ' --format --nonet --custom-opt 2 -' + \ }, + \ ale#fixers#xmllint#Fix(bufnr('')) diff --git a/test/fixers/test_xo_fixer_callback.vader b/test/fixers/test_xo_fixer_callback.vader new file mode 100644 index 00000000..fe2da8cc --- /dev/null +++ b/test/fixers/test_xo_fixer_callback.vader @@ -0,0 +1,45 @@ +Before: + call ale#assert#SetUpFixerTest('javascript', 'xo') + runtime autoload/ale/handlers/xo.vim + set filetype=javascript + +After: + call ale#assert#TearDownFixerTest() + +Execute(The xo callback should return the correct default values): + call ale#test#SetFilename('../test-files/xo/monorepo/packages/a/index.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/xo/monorepo/node_modules/xo/cli.js')) + \ . ' --fix %t', + \ } + +Execute(The xo callback should include custom xo options): + let g:ale_javascript_xo_options = '--space' + call ale#test#SetFilename('../test-files/xo/monorepo/packages/a/index.js') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/xo/monorepo/node_modules/xo/cli.js')) + \ . ' --fix %t' + \ . ' --space', + \ } + +Execute(--stdin should be used when xo is new enough): + let g:ale_javascript_xo_options = '--space' + call ale#test#SetFilename('../test-files/xo/monorepo/packages/a/index.js') + + GivenCommandOutput ['0.30.0'] + AssertFixer + \ { + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/xo/monorepo/node_modules/xo/cli.js')) + \ . ' --stdin --stdin-filename %s' + \ . ' --fix' + \ . ' --space', + \ } diff --git a/test/fixers/test_xots_fixer_callback.vader b/test/fixers/test_xots_fixer_callback.vader new file mode 100644 index 00000000..61a22e62 --- /dev/null +++ b/test/fixers/test_xots_fixer_callback.vader @@ -0,0 +1,45 @@ +Before: + call ale#assert#SetUpFixerTest('typescript', 'xo') + runtime autoload/ale/handlers/xo.vim + set filetype=typescript + +After: + call ale#assert#TearDownFixerTest() + +Execute(The xo callback should return the correct default values): + call ale#test#SetFilename('../test-files/xo/monorepo/packages/a/index.ts') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/xo/monorepo/node_modules/xo/cli.js')) + \ . ' --fix %t', + \ } + +Execute(The xo callback should include custom xo options): + let g:ale_typescript_xo_options = '--space' + call ale#test#SetFilename('../test-files/xo/monorepo/packages/a/index.ts') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/xo/monorepo/node_modules/xo/cli.js')) + \ . ' --fix %t' + \ . ' --space', + \ } + +Execute(--stdin should be used when xo is new enough): + let g:ale_typescript_xo_options = '--space' + call ale#test#SetFilename('../test-files/xo/monorepo/packages/a/index.ts') + + GivenCommandOutput ['0.30.0'] + AssertFixer + \ { + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/xo/monorepo/node_modules/xo/cli.js')) + \ . ' --stdin --stdin-filename %s' + \ . ' --fix' + \ . ' --space', + \ } diff --git a/test/fixers/test_yamlfix_fixer_callback.vader b/test/fixers/test_yamlfix_fixer_callback.vader new file mode 100644 index 00000000..1ae5e335 --- /dev/null +++ b/test/fixers/test_yamlfix_fixer_callback.vader @@ -0,0 +1,33 @@ +Before: + call ale#assert#SetUpFixerTest('yaml', 'yamlfix') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + call ale#assert#TearDownFixerTest() + +Execute(The yamlfix callback should return the correct default values): + AssertEqual + \ 0, + \ ale#fixers#yamlfix#Fix(bufnr('')) + + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.yaml') + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/yamlfix')) . ' -', + \ }, + \ ale#fixers#yamlfix#Fix(bufnr('')) + +Execute(The yamlfix callback should respect custom options): + let g:ale_yaml_yamlfix_options = '--multi-line=3 --trailing-comma' + + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_virtualenv/subdir/foo/bar.yaml') + AssertEqual + \ { + \ 'cwd': '%s:h', + \ 'command': ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/yamlfix')) + \ . ' --multi-line=3 --trailing-comma -', + \ }, + \ ale#fixers#yamlfix#Fix(bufnr('')) diff --git a/test/fixers/test_yamlfmt_fixer_callback.vader b/test/fixers/test_yamlfmt_fixer_callback.vader new file mode 100644 index 00000000..53604e4b --- /dev/null +++ b/test/fixers/test_yamlfmt_fixer_callback.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpFixerTest('yaml', 'yamlfmt') + +After: + Restore + + call ale#assert#TearDownFixerTest() + +Execute(The yamlfmt callback should return the correct default command): + AssertEqual + \ {'command': ale#Escape('yamlfmt') . ' -in'}, + \ ale#fixers#yamlfmt#Fix(bufnr('')) diff --git a/test/fixers/test_yapf_fixer_callback.vader b/test/fixers/test_yapf_fixer_callback.vader new file mode 100644 index 00000000..8d88d423 --- /dev/null +++ b/test/fixers/test_yapf_fixer_callback.vader @@ -0,0 +1,61 @@ +Before: + Save g:ale_python_yapf_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_python_yapf_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The yapf should include the .style.yapf file if present): + call ale#test#SetFilename('../test-files/python/with_virtualenv/dir_with_yapf_config/foo/bar.py') + + AssertEqual + \ { + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/yapf')) + \ . ' --no-local-style' + \ . ' --style ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/dir_with_yapf_config/.style.yapf')), + \ }, + \ ale#fixers#yapf#Fix(bufnr('')) + +Execute(pipenv is detected when python_yapf_auto_pipenv is set): + let g:ale_python_yapf_auto_pipenv = 1 + + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('pipenv') . ' run yapf', + \ }, + \ ale#fixers#yapf#Fix(bufnr('')) + +Execute(Poetry is detected when python_yapf_auto_poetry is set): + let g:ale_python_yapf_auto_poetry = 1 + + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('poetry') . ' run yapf', + \ }, + \ ale#fixers#yapf#Fix(bufnr('')) + +Execute(uv is detected when python_yapf_auto_uv is set): + let g:ale_python_yapf_auto_uv = 1 + + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertEqual + \ { + \ 'command': ale#Escape('uv') . ' run yapf', + \ }, + \ ale#fixers#yapf#Fix(bufnr('')) diff --git a/test/fixers/test_zigfmt_fixer_callback.vader b/test/fixers/test_zigfmt_fixer_callback.vader new file mode 100644 index 00000000..47e3ddee --- /dev/null +++ b/test/fixers/test_zigfmt_fixer_callback.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpFixerTest('zig', 'zigfmt') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The zig callback should return the correct default values): + AssertFixer { + \ 'command': ale#Escape('zig') . ' fmt %t', + \ 'read_temporary_file': 1, + \} + +Execute(The zig callback should allow custom zig executables): + let g:ale_zig_zigfmt_executable = 'foo/bar' + + AssertFixer { + \ 'command': ale#Escape('foo/bar') . ' fmt %t', + \ 'read_temporary_file': 1, + \} + diff --git a/test/handler/test_actionlint_handler.vader b/test/handler/test_actionlint_handler.vader new file mode 100644 index 00000000..abf01b31 --- /dev/null +++ b/test/handler/test_actionlint_handler.vader @@ -0,0 +1,43 @@ +Before: + runtime ale_linters/yaml/actionlint.vim + +After: + call ale#linter#Reset() + +Execute(Problems should be parsed correctly for actionlint): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': '"jobs" section is missing in workflow', + \ 'code': 'syntax-check', + \ }, + \ { + \ 'lnum': 56, + \ 'col': 23, + \ 'type': 'E', + \ 'text': 'property "unknown_input" is not defined in object type {input7: bool; input0: any; input1: any; input2: string; input3: any; input4: any; input5: number; input6: number}', + \ 'code': 'expression', + \ }, + \ ], + \ ale_linters#yaml#actionlint#Handle(bufnr(''), [ + \ '.codecov.yaml:2:1: "jobs" section is missing in workflow [syntax-check]', + \ 'workflow_call_event.yaml:56:23: property "unknown_input" is not defined in object type {input7: bool; input0: any; input1: any; input2: string; input3: any; input4: any; input5: number; input6: number} [expression]', + \ ]) + +Execute(Shellcheck issues should be reported at the line they appear): + AssertEqual + \ [ + \ { + \ 'lnum': 19, + \ 'col': 9, + \ 'type': 'E', + \ 'text': 'Double quote to prevent globbing and word splitting', + \ 'code': 'shellcheck SC2086', + \ }, + \ ], + \ ale_linters#yaml#actionlint#Handle(bufnr(''), [ + \ 'validate.yml:19:9: shellcheck reported issue in this script: SC2086:info:1:15: Double quote to prevent globbing and word splitting [shellcheck]' + \ ]) diff --git a/test/handler/test_ada_gcc_handler.vader b/test/handler/test_ada_gcc_handler.vader new file mode 100644 index 00000000..06ddfe1f --- /dev/null +++ b/test/handler/test_ada_gcc_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/ada/gcc.vim + +After: + call ale#linter#Reset() + +Execute(The gcc handler for Ada should parse input correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 0, + \ 'lnum': 8, + \ 'col': 5, + \ 'type': 'W', + \ 'text': 'variable "X" is assigned but never read', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 6, + \ 'col': 22, + \ 'type': 'E', + \ 'text': 'type definition expected', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 8, + \ 'col': 9, + \ 'type': 'E', + \ 'text': 'aspect specifications not allowed here', + \ }, + \ ], + \ ale_linters#ada#gcc#Handle(0, [ + \ 'foobar.adb:8:05: warning: variable "X" is assigned but never read', + \ 'foobar.ads:6:22: type definition expected', + \ 'foobar.ads:8:09: aspect specifications not allowed here', + \ ]) diff --git a/test/handler/test_alex_handler.vader b/test/handler/test_alex_handler.vader new file mode 100644 index 00000000..eb241f80 --- /dev/null +++ b/test/handler/test_alex_handler.vader @@ -0,0 +1,54 @@ +Execute(The alex handler should handle the example from the alex README): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 5, + \ 'end_lnum': 1, + \ 'end_col': 13, + \ 'type': 'W', + \ 'text': '`boogeyman` may be insensitive, use `boogey` instead (retext-equality)', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 42, + \ 'end_lnum': 1, + \ 'end_col': 47, + \ 'type': 'W', + \ 'text': '`master` / `slaves` may be insensitive, use `primary` / `replica` instead (retext-equality)', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 69, + \ 'end_lnum': 1, + \ 'end_col': 74, + \ 'type': 'W', + \ 'text': 'Don’t use “slaves”, it’s profane (retext-profanities)', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 52, + \ 'end_lnum': 2, + \ 'end_col': 53, + \ 'type': 'W', + \ 'text': '`he` may be insensitive, use `they`, `it` instead (retext-equality)', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 61, + \ 'end_lnum': 2, + \ 'end_col': 67, + \ 'type': 'W', + \ 'text': '`cripple` may be insensitive, use `person with a limp` instead (retext-equality)', + \ }, + \ ], + \ ale#handlers#alex#Handle(bufnr(''), [ + \ 'example.md', + \ ' 1:5-1:14 warning `boogeyman` may be insensitive, use `boogey` instead boogeyman-boogeywoman retext-equality', + \ ' 1:42-1:48 warning `master` / `slaves` may be insensitive, use `primary` / `replica` instead master-slave retext-equality', + \ ' 1:69-1:75 warning Don’t use “slaves”, it’s profane slaves retext-profanities', + \ ' 2:52-2:54 warning `he` may be insensitive, use `they`, `it` instead he-she retext-equality', + \ ' 2:61-2:68 warning `cripple` may be insensitive, use `person with a limp` instead cripple retext-equality', + \ '', + \ '⚠ 5 warnings', + \ ]) 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_ansible_lint_handler.vader b/test/handler/test_ansible_lint_handler.vader new file mode 100644 index 00000000..e0c67ab8 --- /dev/null +++ b/test/handler/test_ansible_lint_handler.vader @@ -0,0 +1,170 @@ +Before: + Save b:ale_warn_about_trailing_whitespace + + runtime ale_linters/ansible/ansible_lint.vim + call ale#test#SetFilename('test_playbook.yml') + + let b:ale_warn_about_trailing_whitespace = 1 + +After: + Restore + + call ale#linter#Reset() + +Execute(The ansible-lint handler for version group <5 should handle basic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 35, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Trailing whitespace', + \ 'code': 'EANSIBLE0002', + \ }, + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [4, 1, 2], [ + \ fnamemodify(tempname(), ':h') . '/test_playbook.yml:35: [EANSIBLE0002] Trailing whitespace', + \ ]) + +Execute(The ansible-lint handler for version group <5 should suppress trailing whitespace output when the option is used): + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [4, 1, 2], [ + \ fnamemodify(tempname(), ':h') . '/test_playbook.yml:35: [EANSIBLE0002] Trailing whitespace', + \ ]) + + +Execute(The ansible-lint handler for version group >=5 should handle basic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 35, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'File permissions unset or incorrect', + \ 'code': 'risky-file-permissions', + \ }, + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [5, 1, 2], [ + \ fnamemodify(tempname(), ':h') . '/test_playbook.yml:35: [risky-file-permissions] [VERY_HIGH] File permissions unset or incorrect', + \ ]) + +Before: + runtime ale_linters/ansible/ansible_lint.vim + call ale#test#SetFilename('test playbook.yml') + +After: + call ale#linter#Reset() + +Execute (The ansible-lint handler for version group <5 should handle names with spaces): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 6, + \ 'type': 'E', + \ 'text': 'indentation is not a multiple of four', + \ 'code': 'E111', + \ }, + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [4, 1, 2], [ + \ fnamemodify(tempname(), ':h') . '/test playbook.yml:6:6: E111 indentation is not a multiple of four', + \ ]) + +Execute (The ansible-lint handler for version group >=5 should handle names with spaces): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 148, + \ 'type': 'E', + \ 'text': "'var' is not a valid attribute for a Play", + \ 'code': 'syntax-check', + \ }, + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [5, 1, 2], [ + \ fnamemodify(tempname(), ':h') . "/test playbook.yml:3:148: [syntax-check] [VERY_HIGH] 'var' is not a valid attribute for a Play", + \ ]) + +Execute (The ansible-lint handler should work with issues with positions and lines members): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 7, + \ 'code': 'major', + \ 'type': 'W', + \ 'text': "syntax-check[specific]", + \ 'detail': 'fakedesc', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 0, + \ 'code': 'major', + \ 'type': 'W', + \ 'text': 'fqcn[action-core]', + \ 'detail': 'fakedesc2' + \ } + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [6, 11, 0], [ + \ '[', + \ ' {', + \ ' "type": "issue",', + \ ' "check_name": "syntax-check[specific]",', + \ ' "categories": [', + \ ' "core",', + \ ' "unskippable"', + \ ' ],', + \ ' "url": "https://ansible-lint.readthedocs.io/rules/syntax-check/",', + \ ' "severity": "major",', + \ ' "description": "fakedesc",', + \ ' "fingerprint": "4",', + \ ' "location": {', + \ ' "path": "test playbook.yml",', + \ ' "positions": {', + \ ' "begin": {', + \ ' "line": 6,', + \ ' "column": 7', + \ ' }', + \ ' }', + \ ' }', + \ ' },', + \ ' {', + \ ' "type": "issue",', + \ ' "check_name": "fqcn[action-core]",', + \ ' "categories": [', + \ ' "formatting"', + \ ' ],', + \ ' "url": "https://ansible-lint.readthedocs.io/rules/fqcn/",', + \ ' "severity": "major",', + \ ' "description": "fakedesc2",', + \ ' "fingerprint": "f",', + \ ' "location": {', + \ ' "path": "test playbook.yml",', + \ ' "lines": {', + \ ' "begin": 6', + \ ' }', + \ ' },', + \ ' "content": {', + \ ' "body": "Use `ansible.builtin.command` or `ansible.legacy.command` instead."', + \ ' }', + \ ' }', + \ ']' + \ ]) + +Execute (The ansible-lint handler should ignore errors from other files): + AssertEqual + \ [ + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [5, 1, 2], [ + \ '/foo/bar/roles/test_playbook.yml:6: [command-instead-of-module] [VERY_LOW] curl used in place of get_url or uri module', + \ ]) + +Execute (The ansible-lint handler should work with empty input): + AssertEqual + \ [ + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [6, 0, 0], []) diff --git a/test/handler/test_appleswiftformat_handler.vader b/test/handler/test_appleswiftformat_handler.vader new file mode 100644 index 00000000..818bd9c5 --- /dev/null +++ b/test/handler/test_appleswiftformat_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/swift/appleswiftformat.vim + +After: + call ale#linter#Reset() + +Execute(The appleswiftformat handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 21, + \ 'type': 'W', + \ 'code': 'DoNotUseSemicolons', + \ 'text': 'remove '';'' and move the next statement to the new line', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 12, + \ 'type': 'W', + \ 'code': 'Spacing', + \ 'text': 'remove 1 space' + \ }, + \ ], + \ ale_linters#swift#appleswiftformat#Handle(bufnr(''), [ + \ 'Sources/main.swift:4:21: warning: [DoNotUseSemicolons] remove '';'' and move the next statement to the new line', + \ 'Sources/main.swift:3:12: warning: [Spacing] remove 1 space', + \ ]) diff --git a/test/handler/test_asm_handler.vader b/test/handler/test_asm_handler.vader new file mode 100644 index 00000000..e28b7ae8 --- /dev/null +++ b/test/handler/test_asm_handler.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/asm/gcc.vim + +After: + call ale#linter#Reset() + +Execute(The asm GCC handler should parse lines from GCC 6.3.1 correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 38, + \ 'text': "too many memory references for `mov'", + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 42, + \ 'text': "incorrect register `%ax' used with `l' suffix", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#asm#gcc#Handle(357, [ + \ "{standard input}: Assembler messages:", + \ "{standard_input}:38: Error: too many memory references for `mov'", + \ "{standard input}:42: Error: incorrect register `%ax' used with `l' suffix", + \ ]) diff --git a/test/handler/test_atools_handler.vader b/test/handler/test_atools_handler.vader new file mode 100644 index 00000000..3c226722 --- /dev/null +++ b/test/handler/test_atools_handler.vader @@ -0,0 +1,85 @@ +Before: + runtime autoload/ale/handlers/atools.vim + +After: + call ale#linter#Reset() + +Execute(The atools handler should handle basic errors or warings): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'text': 'trailing whitespace', + \ 'type': 'E', + \ 'code': 'AL8', + \ }, + \ { + \ 'lnum': 15, + \ 'text': '$pkgname should not be used in the source url', + \ 'type': 'W', + \ 'code': 'AL29', + \ }, + \ ], + \ ale#handlers#atools#Handle(bufnr(''), [ + \ 'IC:[AL8]:APKBUILD:2:trailing whitespace', + \ 'MC:[AL29]:APKBUILD:15:$pkgname should not be used in the source url', + \ ]) + +" Regardless of the severity, if the certainty is [P]ossible and not [C]ertain +" or if regardless of the Certainty the Severity is not [I]mportant or [S]erious +" then it must be a [W]arning +Execute(If we are not Certain or Importantly Serious, be a Warning): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'text': 'This violation is Serious but Possible false positive, I am a Warning!', + \ 'type': 'W', + \ 'code': 'AL', + \ }, + \ { + \ 'lnum': 4, + \ 'text': 'This violation is Important but Possible false positive, I am a Warning!', + \ 'type': 'W', + \ 'code': 'AL', + \ }, + \ { + \ 'lnum': 5, + \ 'text': 'This violation is Minor, I am a Warning!', + \ 'type': 'W', + \ 'code': 'AL', + \ }, + \ { + \ 'lnum': 6, + \ 'text': 'This violation is Style, I am a Warning!', + \ 'type': 'W', + \ 'code': 'AL', + \ }, + \ ], + \ ale#handlers#atools#Handle(bufnr(''), [ + \ 'SP:[AL]:APKBUILD:3:This violation is Serious but Possible false positive, I am a Warning!', + \ 'IP:[AL]:APKBUILD:4:This violation is Important but Possible false positive, I am a Warning!', + \ 'MC:[AL]:APKBUILD:5:This violation is Minor, I am a Warning!', + \ 'TC:[AL]:APKBUILD:6:This violation is Style, I am a Warning!', + \ ]) + +Execute(We should be error if we are Certain it is Serious or Important): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'text': 'This is Certainly Serious, I am an Error!', + \ 'type': 'E', + \ 'code': 'AL', + \ }, + \ { + \ 'lnum': 8, + \ 'text': 'This is Certainly Important, I am an Error!', + \ 'type': 'E', + \ 'code': 'AL', + \ }, + \ ], + \ ale#handlers#atools#Handle(bufnr(''), [ + \ 'SC:[AL]:APKBUILD:7:This is Certainly Serious, I am an Error!', + \ 'IC:[AL]:APKBUILD:8:This is Certainly Important, I am an Error!', + \ ]) diff --git a/test/handler/test_avra_handler.vader b/test/handler/test_avra_handler.vader new file mode 100644 index 00000000..0de83fb8 --- /dev/null +++ b/test/handler/test_avra_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/avra/avra.vim + +After: + call ale#linter#Reset() + +Execute(The avra handler should parse errors correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'text': "Unknown device: atmega3228p", + \ 'type': 'E' + \ }, + \ { + \ 'lnum': 12, + \ 'text': "Unknown directive: .EQ", + \ 'type': 'E' + \ } + \ ], + \ ale_linters#avra#avra#Handle(bufnr(''), [ + \ "main.asm(3) : Error : Unknown device: atmega3228p", + \ "main.asm(12) : Error : Unknown directive: .EQ" + \ ]) 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: ', + \ '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_bashate_handler.vader b/test/handler/test_bashate_handler.vader new file mode 100644 index 00000000..b61bb956 --- /dev/null +++ b/test/handler/test_bashate_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/sh/bashate.vim + +After: + call ale#linter#Reset() + +Execute(The bashate handler should handle basic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 777, + \ 'col': 1, + \ 'text': 'E003 Indent not multiple of 4', + \ }, + \ { + \ 'lnum': 783, + \ 'col': 1, + \ 'text': 'E020 Function declaration not in format ^function name {$', + \ }, + \ { + \ 'lnum': 786, + \ 'col': 1, + \ 'text': 'E010 The "do" should be on same line as for', + \ }, + \ { + \ 'lnum': 791, + \ 'col': 1, + \ 'text': 'E006 Line too long', + \ }, + \ ], + \ ale_linters#sh#bashate#Handle(bufnr(''), [ + \ 'run:777:1: E003 Indent not multiple of 4', + \ 'run:783:1: E020 Function declaration not in format ^function name {$', + \ 'run:786:1: E010 The "do" should be on same line as for', + \ 'run:791:1: E006 Line too long', + \ ]) diff --git a/test/handler/test_bibclean_handler.vader b/test/handler/test_bibclean_handler.vader new file mode 100644 index 00000000..5735065b --- /dev/null +++ b/test/handler/test_bibclean_handler.vader @@ -0,0 +1,86 @@ +Before: + runtime ale_linters/bib/bibclean.vim + +After: + call ale#linter#Reset() + +Execute(The bibclean handler should parse lines from bibclean <= v2.11.4 correctly): + AssertEqual + \ [ + \ { + \ 'lnum': '60', + \ 'type': 'W', + \ 'text': 'Unexpected value in ``month = "09"''''.', + \ 'col': '17' + \ }, + \ { + \ 'lnum': '63', + \ 'type': 'E', + \ 'text': 'Expected comma after last field ``keywords''''.', + \ 'col': ' 1' + \ }, + \ { + \ 'lnum': '176', + \ 'type': 'W', + \ 'text': 'Unexpected DOI in URL value ``"https://doi.org/DOI"'''': move to separate DOI = "..." key/value in this entry.', + \ 'col': '14' + \ } + \ ], + \ ale_linters#bib#bibclean#Handle(255, [ + \ "%% \"stdin\", line 60: Unexpected value in ``month = \"09\"''.", + \ "%% File positions: input [main.bib] output [stdout]", + \ "%% Entry input byte=1681 line=50 column= 1 output byte=1680 line=50 column= 0", + \ "%% Value input byte=2137 line=60 column=17 output byte=2137 line=60 column=17", + \ "%% Current input byte=2139 line=60 column=19 output byte=2137 line=60 column=17", + \ "?? \"stdin\", line 71: Expected comma after last field ``keywords''.", + \ "?? File positions: input [main.bib] output [stdout]", + \ "?? Entry input byte=2145 line=63 column= 1 output byte=2146 line=63 column= 0", + \ "?? Value input byte=2528 line=71 column= 2 output byte=2527 line=70 column=49", + \ "?? Current input byte=2529 line=71 column= 3 output byte=2528 line=70 column=50", + \ "%% \"stdin\", line 176: Unexpected DOI in URL value ``\"https://doi.org/DOI\"'': move to separate DOI = \"...\" key/value in this entry.", + \ "%% File positions: input [stdin] output [stdout]", + \ "%% Entry input byte=6813 line=174 column= 1 output byte=8543 line=227 column= 0", + \ "%% Value input byte=6890 line=176 column=14 output byte=8641 line=229 column=17", + \ "%% Current input byte=6938 line=176 column=62 output byte=8641 line=229 column=17" + \ ]) + +Execute(The bibclean handler should parse lines of bibclean > v2.11.4 correctly): + AssertEqual + \ [ + \ { + \ 'lnum': '60', + \ 'type': 'W', + \ 'text': 'Unexpected value in ``month = "09"''''.', + \ 'col': '17' + \ }, + \ { + \ 'lnum': '63', + \ 'type': 'E', + \ 'text': 'Expected comma after last field ``keywords''''.', + \ 'col': ' 1' + \ }, + \ { + \ 'lnum': '176', + \ 'type': 'W', + \ 'text': 'Unexpected DOI in URL value ``"https://doi.org/DOI"'''': move to separate DOI = "..." key/value in this entry.', + \ 'col': '14' + \ } + \ ], + \ ale_linters#bib#bibclean#Handle(255, [ + \ "%% stdin:60:Unexpected value in ``month = \"09\"''.", + \ "%% File positions: input [main.bib] output [stdout]", + \ "%% Entry input byte=1681 line=50 column= 1 output byte=1680 line=50 column= 0", + \ "%% Value input byte=2137 line=60 column=17 output byte=2137 line=60 column=17", + \ "%% Current input byte=2139 line=60 column=19 output byte=2137 line=60 column=17", + \ "?? stdin:71:Expected comma after last field ``keywords''.", + \ "?? File positions: input [main.bib] output [stdout]", + \ "?? Entry input byte=2145 line=63 column= 1 output byte=2146 line=63 column= 0", + \ "?? Value input byte=2528 line=71 column= 2 output byte=2527 line=70 column=49", + \ "?? Current input byte=2529 line=71 column= 3 output byte=2528 line=70 column=50", + \ "%% stdin:176:Unexpected DOI in URL value ``\"https://doi.org/DOI\"'': move to separate DOI = \"...\" key/value in this entry.", + \ "%% File positions: input [stdin] output [stdout]", + \ "%% Entry input byte=6813 line=174 column= 1 output byte=8543 line=227 column= 0", + \ "%% Value input byte=6890 line=176 column=14 output byte=8641 line=229 column=17", + \ "%% Current input byte=6938 line=176 column=62 output byte=8641 line=229 column=17" + \ ]) + diff --git a/test/handler/test_bicep_az_bicep_handler.vader b/test/handler/test_bicep_az_bicep_handler.vader new file mode 100644 index 00000000..01dbe433 --- /dev/null +++ b/test/handler/test_bicep_az_bicep_handler.vader @@ -0,0 +1,39 @@ +Before: + runtime ale_linters/bicep/az_bicep.vim + +After: + call ale#linter#Reset() + +Execute(The az_bicep handler should handle basic warnings): + AssertEqual + \ [ + \ { + \ 'filename': '/tmp/nvimhxqs5D/1/dns.bicep', + \ 'lnum': 7, + \ 'col': 10, + \ 'type': 'W', + \ 'code': 'no-unused-existing-resources', + \ 'text': 'Existing resource "asdasd" is declared but never used. [https://aka.ms/bicep/linter/no-unused-existing-resources]', + \ }, + \ { + \ 'filename': '/tmp/nvimhxqs5D/1/dns.bicep', + \ 'lnum': 106, + \ 'col': 6, + \ 'type': 'E', + \ 'code': 'BCP019', + \ 'text': 'Expected a new line character at this location.', + \ }, + \ { + \ 'filename': '/tmp/cluster.bicep', + \ 'lnum': 25, + \ 'col': 30, + \ 'type': 'E', + \ 'code': 'BCP334', + \ 'text': 'The provided value has no configured minimum length and may be too short to assign to a target with a configured minimum length of 1.', + \ }, + \ ], + \ ale_linters#bicep#az_bicep#Handle(1, [ + \ '/tmp/nvimhxqs5D/1/dns.bicep(7,10) : Warning no-unused-existing-resources: Existing resource "asdasd" is declared but never used. [https://aka.ms/bicep/linter/no-unused-existing-resources]', + \ '/tmp/nvimhxqs5D/1/dns.bicep(106,6) : Error BCP019: Expected a new line character at this location.', + \ 'ERROR: /tmp/cluster.bicep(25,30) : Warning BCP334: The provided value has no configured minimum length and may be too short to assign to a target with a configured minimum length of 1.', + \ ]) diff --git a/test/handler/test_bicep_bicep_handler.vader b/test/handler/test_bicep_bicep_handler.vader new file mode 100644 index 00000000..82f02737 --- /dev/null +++ b/test/handler/test_bicep_bicep_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/bicep/bicep.vim + +After: + call ale#linter#Reset() + +Execute(The bicep handler should handle basic warnings): + AssertEqual + \ [ + \ { + \ 'filename': '/tmp/nvimhxqs5D/1/dns.bicep', + \ 'lnum': 7, + \ 'col': 10, + \ 'type': 'W', + \ 'code': 'no-unused-existing-resources', + \ 'text': 'Existing resource "asdasd" is declared but never used. [https://aka.ms/bicep/linter/no-unused-existing-resources]', + \ }, + \ { + \ 'filename': '/tmp/nvimhxqs5D/1/dns.bicep', + \ 'lnum': 106, + \ 'col': 6, + \ 'type': 'E', + \ 'code': 'BCP019', + \ 'text': 'Expected a new line character at this location.', + \ }, + \ ], + \ ale_linters#bicep#bicep#Handle(1, [ + \ '/tmp/nvimhxqs5D/1/dns.bicep(7,10) : Warning no-unused-existing-resources: Existing resource "asdasd" is declared but never used. [https://aka.ms/bicep/linter/no-unused-existing-resources]', + \ '/tmp/nvimhxqs5D/1/dns.bicep(106,6) : Error BCP019: Expected a new line character at this location.', + \ ]) diff --git a/test/handler/test_bitbake_oelint_adv_handler.vader b/test/handler/test_bitbake_oelint_adv_handler.vader new file mode 100644 index 00000000..a52e8810 --- /dev/null +++ b/test/handler/test_bitbake_oelint_adv_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/bitbake/oelint_adv.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The oelint_adv handler should handle warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 1234, + \ 'type': 'I', + \ 'code': 'oelint.var.suggestedvar.BUGTRACKER', + \ 'text': 'Variable ''BUGTRACKER'' should be set', + \ }, + \ { + \ 'lnum': 17, + \ 'type': 'E', + \ 'code': 'oelint.var.mandatoryvar.DESCRIPTION', + \ 'text': 'Variable ''DESCRIPTION'' should be set', + \ }, + \ ], + \ ale_linters#bitbake#oelint_adv#Handle(1, [ + \ '/meta-x/recipes-y/example/example_1.0.bb:1234:info:oelint.var.suggestedvar.BUGTRACKER:Variable ''BUGTRACKER'' should be set', + \ 'example2_1.1.bb:17:error:oelint.var.mandatoryvar.DESCRIPTION:Variable ''DESCRIPTION'' should be set', + \ ]) diff --git a/test/handler/test_brakeman_handler.vader b/test/handler/test_brakeman_handler.vader new file mode 100644 index 00000000..3a17ec71 --- /dev/null +++ b/test/handler/test_brakeman_handler.vader @@ -0,0 +1,83 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + + runtime ale_linters/ruby/brakeman.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The brakeman handler should parse JSON correctly): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/app/models/thing.rb') + + AssertEqual + \ [ + \ { + \ 'filename': expand('%:p'), + \ 'lnum': 84, + \ 'text': 'SQL Injection Possible SQL injection (Medium)', + \ 'type': 'W', + \ }, + \ { + \ 'filename': expand('%:p'), + \ 'lnum': 1, + \ 'text': 'Mass Assignment Potentially dangerous attribute available for mass assignment (Weak)', + \ 'type': 'W', + \ } + \ ], + \ ale_linters#ruby#brakeman#Handle(bufnr(''), [ + \ '{', + \ '"warnings": [', + \ '{', + \ '"warning_type": "SQL Injection",', + \ '"warning_code": 0,', + \ '"fingerprint": "1234",', + \ '"check_name": "SQL",', + \ '"message": "Possible SQL injection",', + \ '"file": "' . substitute(ale#path#Simplify('app/models/thing.rb'), '\\', '\\\\', 'g') . '",', + \ '"line": 84,', + \ '"link": "http://brakemanscanner.org/docs/warning_types/sql_injection/",', + \ '"code": "Thing.connection.execute(params[:data])",', + \ '"render_path": null,', + \ '"location": {', + \ '"type": "method",', + \ '"class": "Thing",', + \ '"method": "run_raw_sql_from_internet"', + \ '},', + \ '"user_input": "whatever",', + \ '"confidence": "Medium"', + \ '},', + \ '{', + \ '"warning_type": "Mass Assignment",', + \ '"warning_code": 60,', + \ '"fingerprint": "1235",', + \ '"check_name": "ModelAttrAccessible",', + \ '"message": "Potentially dangerous attribute available for mass assignment",', + \ '"file": "' . substitute(ale#path#Simplify('app/models/thing.rb'), '\\', '\\\\', 'g') . '",', + \ '"line": null,', + \ '"link": "http://brakemanscanner.org/docs/warning_types/mass_assignment/",', + \ '"code": ":name",', + \ '"render_path": null,', + \ '"location": {', + \ '"type": "model",', + \ '"model": "Thing"', + \ '},', + \ '"user_input": null,', + \ '"confidence": "Weak"', + \ '}', + \ ']', + \ '}' + \ ]) + +Execute(The brakeman handler should parse JSON correctly when there is no output from brakeman): + AssertEqual + \ [], + \ ale_linters#ruby#brakeman#Handle(347, [ + \ ]) + +Execute(The brakeman handler should handle garbage output): + AssertEqual + \ [], + \ ale_linters#ruby#brakeman#Handle(347, [ + \ 'No such command in 2.4.1 of ruby', + \ ]) diff --git a/test/handler/test_buildifier_handler.vader b/test/handler/test_buildifier_handler.vader new file mode 100644 index 00000000..7fda0758 --- /dev/null +++ b/test/handler/test_buildifier_handler.vader @@ -0,0 +1,26 @@ +Before: + runtime ale_linters/bzl/buildifier.vim + +After: + call ale#linter#Reset() + +Execute(The buildifier handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 26, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'syntax error near'';'' and move the next statement to the new line', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'unused-variable: Variable "foo" is unused. Please remove it. (https://github.com/bazelbuild/buildtools/blob/master/WARNINGS.md#unused-variable)' + \ }, + \ ], + \ ale_linters#bzl#buildifier#Handle(bufnr(''), [ + \ 'swiftformat/toolchains/assets.bzl:26:1: syntax error near'';'' and move the next statement to the new line', + \ 'swiftformat/toolchains/assets.bzl:7: unused-variable: Variable "foo" is unused. Please remove it. (https://github.com/bazelbuild/buildtools/blob/master/WARNINGS.md#unused-variable)', + \ ]) diff --git a/test/handler/test_cfn_python_lint_handler.vader b/test/handler/test_cfn_python_lint_handler.vader new file mode 100644 index 00000000..2c7ddc62 --- /dev/null +++ b/test/handler/test_cfn_python_lint_handler.vader @@ -0,0 +1,33 @@ +Before: + runtime! ale_linters/cloudformation/cfn_python_lint.vim + call ale#test#SetFilename('sample.template.yaml') + +After: + call ale#linter#Reset() + +Execute(The cfn_python_lint handler should parse items correctly): + AssertEqual + \ [ + \ { + \ 'lnum': '96', + \ 'col': '7', + \ 'end_lnum': '96', + \ 'end_col': '15', + \ 'text': 'Property Resources/Sample/Properties/FromPort should be of type Integer', + \ 'code': 'E3012', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': '97', + \ 'col': '7', + \ 'end_lnum': '97', + \ 'end_col': '15', + \ 'text': 'AllowedPattern and/or AllowedValues for Parameter should be specified at Parameters/SampleIpAddress. Example for AllowedPattern "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"', + \ 'code': 'W2509', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#cloudformation#cfn_python_lint#Handle(bufnr(''), [ + \ fnamemodify(tempname(), ':h') . '/sample.template.yaml:96:7:96:15:E3012:Property Resources/Sample/Properties/FromPort should be of type Integer', + \ fnamemodify(tempname(), ':h') . '/sample.template.yaml:97:7:97:15:W2509:AllowedPattern and/or AllowedValues for Parameter should be specified at Parameters/SampleIpAddress. Example for AllowedPattern "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"', + \ ]) diff --git a/test/handler/test_checkmake_handler.vader b/test/handler/test_checkmake_handler.vader new file mode 100644 index 00000000..bd1a67f8 --- /dev/null +++ b/test/handler/test_checkmake_handler.vader @@ -0,0 +1,21 @@ +Before: + runtime ale_linters/make/checkmake.vim + +After: + call ale#linter#Reset() + +Execute(Parsing checkmake errors should work): + AssertEqual + \ [ + \ { + \ 'bufnr': 42, + \ 'lnum': 1, + \ 'type': 'E', + \ 'code': 'woops', + \ 'text': 'an error has occurred', + \ } + \ ], + \ ale_linters#make#checkmake#Handle(42, [ + \ "This shouldn't match", + \ '1:woops:an error has occurred', + \ ]) diff --git a/test/handler/test_checkov_handler.vader b/test/handler/test_checkov_handler.vader new file mode 100644 index 00000000..9884113c --- /dev/null +++ b/test/handler/test_checkov_handler.vader @@ -0,0 +1,66 @@ +Before: + runtime ale_linters/terraform/checkov.vim + call ale#test#SetFilename('main.tf') + +After: + call ale#linter#Reset() + +Execute(The JSON output of checkov should be handled correctly): + AssertEqual + \ [ + \ { + \ 'filename': '/main.tf', + \ 'lnum': 22, + \ 'end_lnum': 27, + \ 'text': 'Enable VPC Flow Logs and Intranode Visibility [CKV_GCP_61]', + \ 'detail': "CKV_GCP_61: Enable VPC Flow Logs and Intranode Visibility\n" . + \ 'For more information, see: https://docs.bridgecrew.io/docs/enable-vpc-flow-logs-and-intranode-visibility', + \ 'type': 'W', + \ } + \ ], + \ ale_linters#terraform#checkov#Handle(bufnr(''), [ + \'{', + \' "check_type": "terraform",', + \' "results": {', + \' "failed_checks": [', + \' {', + \' "check_id": "CKV_GCP_61",', + \' "bc_check_id": "BC_GCP_KUBERNETES_18",', + \' "check_name": "Enable VPC Flow Logs and Intranode Visibility",', + \' "check_result": {', + \' "result": "FAILED",', + \' "evaluated_keys": [', + \' "enable_intranode_visibility"', + \' ]', + \' },', + \' "file_path": "/main.tf",', + \' "repo_file_path": "/main.tf",', + \' "file_line_range": [', + \' 22,', + \' 27', + \' ],', + \' "resource": "google_container_cluster.cluster-name",', + \' "evaluations": null,', + \' "check_class": "checkov.terraform.checks.resource.gcp.GKEEnableVPCFlowLogs",', + \' "entity_tags": null,', + \' "resource_address": null,', + \' "guideline": "https://docs.bridgecrew.io/docs/enable-vpc-flow-logs-and-intranode-visibility"', + \' }', + \' ]', + \' }', + \'}' + \ ]) + +Execute(Handle output for no findings correctly): + AssertEqual + \ [], + \ ale_linters#terraform#checkov#Handle(bufnr(''), [ + \'{', + \' "passed": 0,', + \' "failed": 0,', + \' "skipped": 0,', + \' "parsing_errors": 0,', + \' "resource_count": 0,', + \' "checkov_version": "2.0.632"', + \'}' + \]) diff --git a/test/handler/test_checkstyle_handler.vader b/test/handler/test_checkstyle_handler.vader new file mode 100644 index 00000000..ea90db3f --- /dev/null +++ b/test/handler/test_checkstyle_handler.vader @@ -0,0 +1,53 @@ +Before: + runtime ale_linters/java/checkstyle.vim + +After: + call ale#linter#Reset() + +Execute(The checkstyle handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 101, + \ 'col': 0, + \ 'text': '''method def rcurly'' has incorrect indentation level 4, expected level should be 2.', + \ 'code': 'Indentation', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 63, + \ 'col': 3, + \ 'text': 'Missing a Javadoc comment.', + \ 'code': 'JavadocMethod', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 11, + \ 'col': 7, + \ 'text': 'WhitespaceAround: ''if'' is not followed by whitespace.', + \ 'code': 'WhitespaceAround', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#java#checkstyle#Handle(666, [ + \ '[WARN] whatever:101: ''method def rcurly'' has incorrect indentation level 4, expected level should be 2. [Indentation]', + \ '[WARN] whatever:63:3: Missing a Javadoc comment. [JavadocMethod]', + \ '[WARN] whatever:11:7: WhitespaceAround: ''if'' is not followed by whitespace. [WhitespaceAround]', + \ ]) + +Execute(The checkstyle handler should parse lines from older checkstyle versions correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 289, + \ 'text': '''method def modifier'' have incorrect indentation level 4, expected level should be 2.', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#java#checkstyle#Handle(666, [ + \ '/home/languitar/src/rsb-java/rsb-java/src/main/java/rsb/Listener.java:289: warning: ''method def modifier'' have incorrect indentation level 4, expected level should be 2.', + \ ]) diff --git a/test/handler/test_circleci_handler.vader b/test/handler/test_circleci_handler.vader new file mode 100644 index 00000000..8ffba360 --- /dev/null +++ b/test/handler/test_circleci_handler.vader @@ -0,0 +1,39 @@ +Before: + runtime ale_linters/yaml/circleci.vim + +After: + call ale#linter#Reset() + +Execute(The circlei handler should return nothing when configs are valid): + AssertEqual + \ [], + \ ale_linters#yaml#circleci#Handle(0, [ + \ 'Config input is valid.', + \ ]) + +Execute(The circlei handler put errors at the top when something is wrong): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': '[#/jobs] expected type: Mapping, found: Integer', + \ 'detail': join([ + \ '[#/jobs] expected type: Mapping, found: Integer', + \ 'Jobs is a map', + \ 'SCHEMA:', + \ ' type: object', + \ 'INPUT:', + \ ' 4', + \ ], "\n"), + \ }, + \ ], + \ ale_linters#yaml#circleci#Handle(0, [ + \ 'Error: ERROR IN CONFIG FILE:', + \ '[#/jobs] expected type: Mapping, found: Integer', + \ 'Jobs is a map', + \ 'SCHEMA:', + \ ' type: object', + \ 'INPUT:', + \ ' 4', + \ ]) diff --git a/test/handler/test_clang_handler.vader b/test/handler/test_clang_handler.vader new file mode 100644 index 00000000..cc8eabd0 --- /dev/null +++ b/test/handler/test_clang_handler.vader @@ -0,0 +1,30 @@ +Execute(clang errors from included files should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'filename': './b.h', + \ 'type': 'E', + \ 'text': 'expected identifier or ''(''', + \ }, + \ { + \ 'lnum': 3, + \ 'text': 'Error found in header. See :ALEDetail', + \ 'detail': join([ + \ 'In file included from :3:', + \ 'In file included from ./a.h:1:', + \ './b.h:1:1: error: expected identifier or ''(''', + \ '{{{', + \ '^', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ 'In file included from :3:', + \ 'In file included from ./a.h:1:', + \ './b.h:1:1: error: expected identifier or ''(''', + \ '{{{', + \ '^', + \ '1 error generated.', + \ ]) 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..9ae70668 --- /dev/null +++ b/test/handler/test_clojure_clj_kondo_handler.vader @@ -0,0 +1,89 @@ +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, [ + \ ':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 )', + \ ]) + +Execute(the clojure clj-kondo handler should be able to lines without row/col): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'error: Unexpected )', + \ }, + \ ], + \ ale_linters#clojure#clj_kondo#HandleCljKondoFormat(0, [ + \ 'test.clj::: error: Unexpected )', + \ ]) diff --git a/test/handler/test_clojure_joker_handler.vader b/test/handler/test_clojure_joker_handler.vader new file mode 100644 index 00000000..460c62e8 --- /dev/null +++ b/test/handler/test_clojure_joker_handler.vader @@ -0,0 +1,75 @@ +Before: + runtime ale_linters/clojure/joker.vim + +After: + call ale#linter#Reset() + +Execute(the clojure joker handler should be able to handle errors): + AssertEqual + \ [ + \ { + \ 'lnum': 123, + \ 'col': 44, + \ 'type': 'E', + \ 'text': 'Read error: Unexpected )', + \ }, + \ ], + \ ale_linters#clojure#joker#HandleJokerFormat(0, [ + \ 'test.clj:123:44: Read error: Unexpected )', + \ ]) + +Execute(the clojure joker handler should be able to handle warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 654, + \ 'col': 321, + \ 'type': 'W', + \ 'text': 'Parse warning: let form with empty body', + \ } + \ ], + \ ale_linters#clojure#joker#HandleJokerFormat(0, [ + \ 'test.clj:654:321: Parse warning: let form with empty body' + \ ]) + +Execute(the clojure joker handler should be able to handle exceptions): + AssertEqual + \ [ + \ { + \ 'lnum': 123, + \ 'col': 321, + \ 'type': 'E', + \ 'text': 'Exception: something horrible happen', + \ } + \ ], + \ ale_linters#clojure#joker#HandleJokerFormat(0, [ + \ 'test.clj:123:321: Exception: something horrible happen' + \ ]) + +Execute(the clojure joker handler should be able to handle errors from stdin): + AssertEqual + \ [ + \ { + \ 'lnum': 16, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Read error: Unexpected )', + \ }, + \ ], + \ ale_linters#clojure#joker#HandleJokerFormat(0, [ + \ ':16:1: Read error: Unexpected )', + \ ]) + +Execute(the clojure joker handler should be able to handle windows files): + AssertEqual + \ [ + \ { + \ 'lnum': 123, + \ 'col': 44, + \ 'type': 'E', + \ 'text': 'Read error: Unexpected )', + \ } + \ ], + \ ale_linters#clojure#joker#HandleJokerFormat(0, [ + \ 'C:\my\operating\system\is\silly\core.clj:123:44: Read error: Unexpected )', + \ ]) diff --git a/test/handler/test_cmake_lint_handler.vader b/test/handler/test_cmake_lint_handler.vader new file mode 100644 index 00000000..31c49c21 --- /dev/null +++ b/test/handler/test_cmake_lint_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/cmake/cmake_lint.vim + +After: + call ale#linter#Reset() + +Execute(The cmake_lint handler should handle basic warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 126, + \ 'col': 0, + \ 'type': 'W', + \ 'code': 'C0301', + \ 'text': 'Line too long (136/80)', + \ }, + \ { + \ 'lnum': 139, + \ 'col': 4, + \ 'type': 'W', + \ 'code': 'C0113', + \ 'text': 'Missing COMMENT in statement which allows it', + \ }, + \ ], + \ ale_linters#cmake#cmake_lint#Handle(1, [ + \ 'CMakeLists.txt:126: [C0301] Line too long (136/80)', + \ 'CMakeLists.txt:139,04: [C0113] Missing COMMENT in statement which allows it', + \ ]) diff --git a/test/handler/test_coffeelint_handler.vader b/test/handler/test_coffeelint_handler.vader new file mode 100644 index 00000000..a061f3a9 --- /dev/null +++ b/test/handler/test_coffeelint_handler.vader @@ -0,0 +1,20 @@ +Before: + runtime ale_linters/coffee/coffeelint.vim + +After: + call ale#linter#Reset() + +Execute(The coffeelint handler should parse lines correctly): + + AssertEqual + \ [ + \ { + \ 'lnum': 125, + \ 'text': "Line exceeds maximum allowed length Length is 122, max is 120.", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#coffee#coffeelint#Handle(347, [ + \ "path,lineNumber,lineNumberEnd,level,message", + \ "stdin,125,,error,Line exceeds maximum allowed length Length is 122, max is 120.", + \ ]) diff --git a/test/handler/test_common_handlers.vader b/test/handler/test_common_handlers.vader new file mode 100644 index 00000000..ee29da36 --- /dev/null +++ b/test/handler/test_common_handlers.vader @@ -0,0 +1,181 @@ +Execute(HandleCSSLintFormat should handle CSS errors): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Expected RBRACE at line 2, col 1.', + \ 'code': 'errors', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 5, + \ 'type': 'W', + \ 'text': 'Expected ... but found ''wat''.', + \ 'code': 'known-properties', + \ }, + \ ], + \ ale#handlers#css#HandleCSSLintFormat(42, [ + \ 'something.css: line 2, col 1, Error - Expected RBRACE at line 2, col 1. (errors)', + \ 'something.css: line 2, col 5, Warning - Expected ... but found ''wat''. (known-properties)', + \ ]) + +Execute(HandleCSSLintFormat should handle CSS errors without groups): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'col': 3, + \ 'type': 'W', + \ 'text': 'Unknown property ''fill''.', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 3, + \ 'type': 'W', + \ 'text': 'Unknown property ''fill-opacity''.', + \ }, + \ ], + \ ale#handlers#css#HandleCSSLintFormat(42, [ + \ 'something.css: line 7, col 3, Warning - Unknown property ''fill''.', + \ 'something.css: line 8, col 3, Warning - Unknown property ''fill-opacity''.', + \ ]) + +Execute (HandleGCCFormat should handle the correct lines of output): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'col': 5, + \ 'type': 'W', + \ 'text': 'conversion lacks type at end of format [-Wformat=]', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 27, + \ 'type': 'E', + \ 'text': 'invalid operands to binary - (have ''int'' and ''char *'')', + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(42, [ + \ ':8:5: warning: conversion lacks type at end of format [-Wformat=]', + \ ':10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’)', + \ ]) + +Execute (HandleGCCFormat should replace Unicode quotes): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'col': 5, + \ 'type': 'W', + \ 'text': "'''' \"\"", + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(42, [':8:5: warning: `´‘’ “”']) + +Execute (HandleUnixFormatAsError should handle some example lines of output): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ }, + \ { + \ 'lnum': 53, + \ 'col': 10, + \ 'type': 'E', + \ 'text': 'if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': '".b" is not a valid class name. Class names must begin with "-", "_" or a letter and can only contain "_", "-", a-z and 0-9.', + \ }, + \ ], + \ ale#handlers#unix#HandleAsError(42, [ + \ 'file.go:27: missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ 'file.go:53:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)', + \ 'test.pug:1:1 ".b" is not a valid class name. Class names must begin with "-", "_" or a letter and can only contain "_", "-", a-z and 0-9.', + \ ]) + +Execute (HandleUnixFormatAsError should handle lines with no space after the colon): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'foo', + \ }, + \ { + \ 'lnum': 53, + \ 'col': 10, + \ 'type': 'E', + \ 'text': 'bar', + \ }, + \ ], + \ ale#handlers#unix#HandleAsError(42, [ + \ 'some_file.xyz:27:foo', + \ 'some_file.xyz:53:10:bar', + \ ]) + +Execute (HandleUnixFormatAsError should handle names with spaces): + AssertEqual + \ [ + \ { + \ 'lnum': 13, + \ 'col': 90, + \ 'type': 'E', + \ 'text': 'leonard.exclamation.30ppm More than 30 ppm of exclamations. Keep them under control.', + \ }, + \ ], + \ ale#handlers#unix#HandleAsError(42, [ + \ '/Users/rrj/Notes/Astro/Taurus December SM.txt:13:90: leonard.exclamation.30ppm More than 30 ppm of exclamations. Keep them under control.', + \ ]) + +Execute (HandleUnixFormatAsWarning should handle some example lines of output): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ }, + \ { + \ 'lnum': 53, + \ 'col': 10, + \ 'type': 'W', + \ 'text': 'if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)', + \ }, + \ ], + \ ale#handlers#unix#HandleAsWarning(42, [ + \ 'file.go:27: missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ 'file.go:53:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)', + \ ]) + +Execute (Unix format functions should handle Windows paths): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'foo', + \ }, + \ { + \ 'lnum': 53, + \ 'col': 10, + \ 'type': 'E', + \ 'text': 'foo', + \ }, + \ ], + \ ale#handlers#unix#HandleAsError(42, [ + \ 'C:\Users\w0rp\AppData\Local\Temp\Xyz123.go:27: foo', + \ 'C:\Users\w0rp\AppData\Local\Temp\Xyz123.go:53:10: foo', + \ ]) diff --git a/test/handler/test_cookstyle_handler.vader b/test/handler/test_cookstyle_handler.vader new file mode 100644 index 00000000..761fc019 --- /dev/null +++ b/test/handler/test_cookstyle_handler.vader @@ -0,0 +1,21 @@ +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_cppcheck_handler.vader b/test/handler/test_cppcheck_handler.vader new file mode 100644 index 00000000..ef161f44 --- /dev/null +++ b/test/handler/test_cppcheck_handler.vader @@ -0,0 +1,93 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + +After: + call ale#test#RestoreDirectory() + +Execute(Basic errors should be handled by cppcheck): + call ale#test#SetFilename('test.cpp') + + AssertEqual + \ [ + \ { + \ 'lnum': 974, + \ 'col' : 6, + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'Array ''n[3]'' accessed at index 3, which is out of bounds.', + \ 'code': 'arrayIndexOutOfBounds' + \ }, + \ { + \ 'lnum': 1185, + \ 'col' : 10, + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'The scope of the variable ''indxStr'' can be reduced.', + \ 'code': 'variableScope' + \ }, + \ ], + \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ + \ 'test.cpp:974:6: error: Array ''n[3]'' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\', + \ ' n[3]=3;', + \ ' ^', + \ 'test.cpp:1185:10: style: The scope of the variable ''indxStr'' can be reduced. [variableScope]\', + \ ' char indxStr[16];', + \ ' ^', + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 974, + \ 'col' : 1, + \ 'type': 'E', + \ 'sub_type': '', + \ 'text': 'inconclusive Array ''n[3]'' accessed at index 3, which is out of bounds.', + \ 'code': 'arrayIndexOutOfBounds' + \ }, + \ { + \ 'lnum': 1185, + \ 'col' : 1, + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'The scope of the variable ''indxStr'' can be reduced.', + \ 'code': 'variableScope' + \ }, + \ ], + \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ + \ 'test.cpp:974:{column}: error:inconclusive Array ''n[3]'' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\', + \ ' n[3]=3;', + \ ' ^', + \ 'test.cpp:1185:{column}: style:{inconclusive:inconclusive} The scope of the variable ''indxStr'' can be reduced. [variableScope]\', + \ ' char indxStr[16];', + \ ' ^', + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col' : 16, + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'misra violation (use --rule-texts= to get proper output)', + \ 'code': 'misra-c2012-2.7' + \ }, + \ ], + \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ + \ 'test.cpp:1:16: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.7]\', + \ 'void test( int parm ) {}', + \ ' ^', + \ ]) + +Execute(Problems from other files should be ignored by cppcheck): + call ale#test#SetFilename('test.cpp') + + AssertEqual + \ [ + \ ], + \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ + \ 'bar.cpp:974:6: error: Array ''n[3]'' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\', + \ ' n[3]=3;', + \ ' ^', + \ ]) diff --git a/test/handler/test_cpplint_handler.vader b/test/handler/test_cpplint_handler.vader new file mode 100644 index 00000000..42518a53 --- /dev/null +++ b/test/handler/test_cpplint_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/cpp/cpplint.vim + +After: + call ale#linter#Reset() + +Execute(cpplint warnings from included files should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 0, + \ 'text': 'Extra space after ( in function call', + \ 'code': 'whitespace/parents', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 120, + \ 'col': 0, + \ 'text': 'At least two spaces is best between code and comments', + \ 'code': 'whitespace/comments', + \ 'type': 'W', + \ }, + \ ], + \ ale#handlers#cpplint#HandleCppLintFormat(347, [ + \ 'test.cpp:5: Extra space after ( in function call [whitespace/parents] [4]', + \ 'keymap_keys.hpp:120: At least two spaces is best between code and comments [whitespace/comments] [2]', + \ ]) diff --git a/test/handler/test_credo_handler.vader b/test/handler/test_credo_handler.vader new file mode 100644 index 00000000..1fd54bb5 --- /dev/null +++ b/test/handler/test_credo_handler.vader @@ -0,0 +1,53 @@ +Before: + runtime ale_linters/elixir/credo.vim + +After: + call ale#linter#Reset() + +Execute(The credo handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 347, + \ 'lnum': 1, + \ 'col': 24, + \ 'text': 'This code can be refactored', + \ 'type': 'W', + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 1, + \ 'col': 4, + \ 'text': 'There is no whitespace around parentheses/brackets most of the time, but here there is.', + \ 'type': 'W', + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 5, + \ 'col': 21, + \ 'text': 'TODO comment', + \ 'type': 'I', + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 26, + \ 'col': 0, + \ 'text': 'If/else blocks should not have a negated condition in `if`.', + \ 'type': 'I', + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 15, + \ 'col': 1, + \ 'text': 'Warning in the code', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#elixir#credo#Handle(347, [ + \ 'This line should be ignored completely', + \ 'lib/my_code/test.ex:1:24: F: This code can be refactored', + \ 'lib/filename.ex:1:4: C: There is no whitespace around parentheses/brackets most of the time, but here there is.', + \ 'lib/my_code/test.ex:5:21: D: TODO comment', + \ 'lib/phoenix/channel.ex:26: R: If/else blocks should not have a negated condition in `if`.', + \ 'lib/my_code/test.ex:15:1: W: Warning in the code', + \ ]) diff --git a/test/handler/test_crystal_handler.vader b/test/handler/test_crystal_handler.vader new file mode 100644 index 00000000..209632e9 --- /dev/null +++ b/test/handler/test_crystal_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/crystal/crystal.vim + +After: + call ale#linter#Reset() + +Execute(The crystal handler should parse lines correctly and add the column if it can): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'unexpected token: EOF' + \ } + \ ], + \ ale_linters#crystal#crystal#Handle(255, [ + \ '[{"file":"/tmp/test.cr","line":2,"column":1,"size":null,"message":"unexpected token: EOF"}]' + \ ]) + +Execute(The crystal handler should not fail when a missing file is required): + AssertEqual + \ [ { 'lnum':1, 'col': 1, 'text': 'while requiring "./nonexistent.cr"' } ], + \ ale_linters#crystal#crystal#Handle(255, + \ json_encode([ + \ { "file":"/tmp/file.cr","line":1,"column":1,"size":0,"message":"while requiring \"./nonexistent.cr\"" }, + \ { "message": "can't find file './nonexistent.cr' relative to '/tmp'" }, + \ ]) + \ ) diff --git a/test/handler/test_csc_handler.vader b/test/handler/test_csc_handler.vader new file mode 100644 index 00000000..c5c0a47e --- /dev/null +++ b/test/handler/test_csc_handler.vader @@ -0,0 +1,98 @@ +Before: + Save g:ale_cs_csc_source + + unlet! g:ale_cs_csc_source + + call ale#test#SetDirectory('/testplugin/test/handler') + call ale#test#SetFilename('Test.cs') + + runtime ale_linters/cs/csc.vim + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The csc handler should work with the default of the buffer's directory): + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col' : 29, + \ 'text': '; expected', + \ 'code': 'CS1001', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(g:dir . '/Test.cs'), + \ }, + \ ], + \ ale_linters#cs#csc#Handle(bufnr(''), [ + \ 'Test.cs(12,29): error CS1001: ; expected', + \ 'Compilation failed: 2 error(s), 1 warnings', + \ ]) + +Execute(The csc handler should handle cannot find symbol errors): + let g:ale_cs_csc_source = '/home/foo/project/bar' + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col' : 29, + \ 'text': '; expected', + \ 'code': 'CS1001', + \ 'type': 'E', + \ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'), + \ }, + \ { + \ 'lnum': 101, + \ 'col': 0, + \ 'text': 'Unexpected processor directive (no #if for this #endif)', + \ 'code': 'CS1028', + \ 'type': 'E', + \ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'), + \ }, + \ { + \ 'lnum': 10, + \ 'col': 12, + \ 'text': 'some warning', + \ 'code': 'CS0123', + \ 'type': 'W', + \ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'), + \ }, + \ ], + \ ale_linters#cs#csc#Handle(bufnr(''), [ + \ 'Test.cs(12,29): error CS1001: ; expected', + \ 'Test.cs(101,0): error CS1028: Unexpected processor directive (no #if for this #endif)', + \ 'Test.cs(10,12): warning CS0123: some warning', + \ 'Compilation failed: 2 error(s), 1 warnings', + \ ]) + +Execute(The csc handler should handle non file specific compiler errors without reporting overal status report as error): + let g:ale_cs_csc_source = '/home/foo/project/bar' + + AssertEqual + \ [ + \ { + \ 'lnum': -1, + \ 'col' : -1, + \ 'text': 'No source files specified.', + \ 'code': 'CS2008', + \ 'type': 'W', + \ 'filename': '', + \ }, + \ { + \ 'lnum': -1, + \ 'col': -1, + \ 'text': 'Outputs without source must have the /out option specified', + \ 'code': 'CS1562', + \ 'type': 'E', + \ 'filename': '', + \ }, + \ ], + \ ale_linters#cs#csc#Handle(bufnr(''), [ + \ 'Microsoft (R) Visual C# Compiler version 2.8.2.62916 (2ad4aabc)', + \ 'Copyright (C) Microsoft Corporation. All rights reserved.', + \ 'warning CS2008: No source files specified.', + \ 'error CS1562: Outputs without source must have the /out option specified', + \ ]) diff --git a/test/handler/test_cspell_handler.vader b/test/handler/test_cspell_handler.vader new file mode 100644 index 00000000..5b18849c --- /dev/null +++ b/test/handler/test_cspell_handler.vader @@ -0,0 +1,14 @@ +Execute(The cspell handler should handle cspell output): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 128, + \ 'end_col': 133, + \ 'type': 'W', + \ 'text': 'Unknown word (Neovim)', + \ }, + \ ], + \ ale#handlers#cspell#Handle(bufnr(''), + \ '/:3:128 - Unknown word (Neovim)' + \) diff --git a/test/handler/test_cucumber_handler.vader b/test/handler/test_cucumber_handler.vader new file mode 100644 index 00000000..2b69a784 --- /dev/null +++ b/test/handler/test_cucumber_handler.vader @@ -0,0 +1,18 @@ +Before: + runtime ale_linters/cucumber/cucumber.vim + +After: + call ale#linter#Reset() + +Execute(The cucumber handler parses JSON correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 13, + \ 'code': 'E', + \ 'text': 'Undefined step' + \ } + \ ], + \ ale_linters#cucumber#cucumber#Handle(bufnr(''), [ + \ '[{"elements": [{"steps": [{"result": {"status": "undefined"},"match": {"location": "features/cuke.feature:13"},"line": 13,"name": "Something undefined","keyword": "Given "},{"result": {"status": "skipped"},"match": {"location": "/var/lib/gems/2.3.0/gems/cucumber-3.1.0/lib/cucumber/step_match.rb:103"},"line": 14,"name": "I visit the profile page for Alice","keyword": "When "}],"type": "scenario","line": 12,"description": "","name": "Another scenario","keyword": "Scenario","id": "a-user-can-view-another-users-profile;another-scenario"}],"line": 1,"description": "","name": "A user can view another users profile","keyword": "Feature","id": "a-user-can-view-another-users-profile","uri": "features/cuke.feature"}]' + \ ]) diff --git a/test/handler/test_cuda_nvcc_handler.vader b/test/handler/test_cuda_nvcc_handler.vader new file mode 100644 index 00000000..40e31922 --- /dev/null +++ b/test/handler/test_cuda_nvcc_handler.vader @@ -0,0 +1,41 @@ +Before: + runtime ale_linters/cuda/nvcc.vim + +After: + call ale#linter#Reset() + +Execute(The cuda nvcc handler should parse errors from multiple files for NVCC 8.0): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': 'this declaration has no storage class or type specifier', + \ 'filename': has('win32') + \ ? 'C:\tmp\cudatest\test.cu' + \ : '/tmp/cudatest/test.cu', + \ }, + \ { + \ 'lnum': 2, + \ 'type': 'E', + \ 'text': 'attribute "global" does not apply here', + \ 'filename': has('win32') + \ ? 'C:\tmp\cudatest\common.h' + \ : '/tmp/cudatest/common.h', + \ }, + \ { + \ 'lnum': 2, + \ 'type': 'E', + \ 'text': 'expected a ";"', + \ 'filename': has('win32') + \ ? 'C:\tmp\cudatest\common.h' + \ : '/tmp/cudatest/common.h', + \ }, + \ ], + \ ale_linters#cuda#nvcc#HandleNVCCFormat(0, [ + \ '/tmp/cudatest/test.cu(1): error: this declaration has no storage class or type specifier', + \ '/tmp/cudatest/common.h(2): error: attribute "global" does not apply here', + \ '/tmp/cudatest/common.h(2): error: expected a ";"', + \ 'At end of source: warning: parsing restarts here after previous syntax error', + \ '3 errors detected in the compilation of "/tmp/tmpxft_00003a9f_00000000-7_test.cpp1.ii".', + \ ]) 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_dafny_handler.vader b/test/handler/test_dafny_handler.vader new file mode 100644 index 00000000..4ca288d2 --- /dev/null +++ b/test/handler/test_dafny_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/dafny/dafny.vim + +After: + call ale#linter#Reset() + +Execute(The Dafny handler should parse output correctly): + AssertEqual + \ [ + \ { + \ 'filename': 'File.dfy', + \ 'col': 45, + \ 'lnum': 123, + \ 'text': 'A precondition for this call might not hold.', + \ 'type': 'E' + \ }, + \ { + \ 'filename': 'File.dfy', + \ 'col': 90, + \ 'lnum': 678, + \ 'text': 'This is the precondition that might not hold.', + \ 'type': 'W' + \ }, + \ { + \ 'filename': 'File.dfy', + \ 'col': 45, + \ 'lnum': 123, + \ 'text': "Verification of 'Impl$$_22_Proof.__default.PutKeepsMapsFull' timed out after 2 seconds", + \ 'type': 'E' + \ }, + \ ], + \ ale_linters#dafny#dafny#Handle(0, [ + \ 'File.dfy(123,45): Error BP5002: A precondition for this call might not hold.', + \ 'File.dfy(678,90): Related location: This is the precondition that might not hold.', + \ "File.dfy(123,45): Verification of 'Impl$$_22_Proof.__default.PutKeepsMapsFull' timed out after 2 seconds", + \ ]) diff --git a/test/handler/test_dart_analyze_handler.vader b/test/handler/test_dart_analyze_handler.vader new file mode 100644 index 00000000..f167582c --- /dev/null +++ b/test/handler/test_dart_analyze_handler.vader @@ -0,0 +1,35 @@ +Before: + runtime ale_linters/dart/dart_analyze.vim + +After: + call ale#linter#Reset() + +Execute(Basic problems should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'type': 'E', + \ 'text': 'expected_token: Expected to find ''}''', + \ 'lnum': 5, + \ 'col': 1, + \ }, + \ { + \ 'type': 'W', + \ 'text': 'invalid_assignment: A value of type ''String'' can''t be assigned to a variable of type ''int''', + \ 'lnum': 2, + \ 'col': 16, + \ }, + \ { + \ 'type': 'I', + \ 'text': 'dead_code: Dead code. Try removing the code, or fixing the code before it so that it can be reached.', + \ 'lnum': 8, + \ 'col': 3, + \ }, + \ ], + \ ale_linters#dart#dart_analyze#Handle(bufnr(''), [ + \ 'Analyzing main.dart...', + \ ' error - main.dart:5:1 - Expected to find ''}'' - expected_token', + \ 'warning - main.dart:2:16 - A value of type ''String'' can''t be assigned to a variable of type ''int'' - invalid_assignment', + \ ' info - main.dart:8:3 - Dead code. Try removing the code, or fixing the code before it so that it can be reached. - dead_code', + \ '3 issues found.', + \ ]) diff --git a/test/handler/test_deadnix_handler.vader b/test/handler/test_deadnix_handler.vader new file mode 100644 index 00000000..508ce0f9 --- /dev/null +++ b/test/handler/test_deadnix_handler.vader @@ -0,0 +1,25 @@ +Execute(The deadnix handler should handle deadnix output): + AssertEqual + \ [ + \ { + \ 'lnum': 23, + \ 'col': 5, + \ 'end_col': 9, + \ 'text': 'Unused lambda pattern: self', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 2, + \ 'end_col': 6, + \ 'text': 'Unused lambda pattern: pkgs', + \ 'type': 'W', + \ }, + \ ], + \ ale#handlers#deadnix#Handle(bufnr(''), [ + \ '{"file":"./flake.nix","results":[{"column":5,"endColumn":9,"line":23,"message":"Unused lambda pattern: self"},{"column":2,"endColumn":6,"line":1,"message":"Unused lambda pattern: pkgs"}]}' + \ ]) + + AssertEqual [], ale#handlers#deadnix#Handle(bufnr(''), ['']) + AssertEqual [], ale#handlers#deadnix#Handle(bufnr(''), ['not json']) + AssertEqual [], ale#handlers#deadnix#Handle(bufnr(''), ['{"results":[{}]}']) diff --git a/test/handler/test_debride_handler.vader b/test/handler/test_debride_handler.vader new file mode 100644 index 00000000..62851468 --- /dev/null +++ b/test/handler/test_debride_handler.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/ruby/debride.vim + +After: + call ale#linter#Reset() + +Execute(The debride linter parses output correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'text': 'Possible unused method: image_tags', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 7, + \ 'text': 'Possible unused method: not_deleted', + \ 'type': 'W', + \ } + \ ], + \ ale_linters#ruby#debride#HandleOutput(0, [ + \ 'These methods MIGHT not be called:', + \ '', + \ 'Image', + \ ' image_tags app/models/image.rb:2', + \ ' not_deleted app/models/image.rb:7' + \]) diff --git a/test/handler/test_desktop_file_validate_handler.vader b/test/handler/test_desktop_file_validate_handler.vader new file mode 100644 index 00000000..88163433 --- /dev/null +++ b/test/handler/test_desktop_file_validate_handler.vader @@ -0,0 +1,26 @@ +Before: + runtime ale_linters/desktop/desktop_file_validate.vim + +After: + call ale#linter#Reset() + +Execute(The desktop-file-validate handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'key "TerminalOptions" in group "Desktop Entry" is deprecated', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'action "new-private-window" is defined, but there is no matching "Desktop Action new-private-window" group', + \ }, + \ ], + \ ale_linters#desktop#desktop_file_validate#Handle(bufnr(''), [ + \ 'foo.desktop: warning: key "TerminalOptions" in group "Desktop Entry" is deprecated', + \ 'foo.desktop: error: action "new-private-window" is defined, but there is no matching "Desktop Action new-private-window" group', + \ ]) diff --git a/test/handler/test_djlint_handler.vader b/test/handler/test_djlint_handler.vader new file mode 100644 index 00000000..3bf916b5 --- /dev/null +++ b/test/handler/test_djlint_handler.vader @@ -0,0 +1,21 @@ +Before: + runtime ale_linters/html/djlint.vim + +After: + call ale#linter#Reset() + +Execute(The Djlint handler should parse output with a column correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 47, + \ 'vcol': 1, + \ 'col': 8, + \ 'code': 'H008', + \ 'text': 'Attributes should be double quoted.', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#djlint#Handle(0, [ + \ 'H008 47:8 Attributes should be double quoted.' + \ ]) diff --git a/test/handler/test_dmd_handler.vader b/test/handler/test_dmd_handler.vader new file mode 100644 index 00000000..32a04982 --- /dev/null +++ b/test/handler/test_dmd_handler.vader @@ -0,0 +1,41 @@ +Before: + runtime ale_linters/d/dmd.vim + call ale#test#SetDirectory('/testplugin/test/dmd') + call ale#test#SetFilename('test.d') + +After: + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(Basic errors should be handled by dmd): + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(g:dir . '/test.d'), + \ 'lnum': '5', + \ 'col' : '8', + \ 'type': 'E', + \ 'text': 'module weak_reference is in file ''dstruct/weak_reference.d'' which cannot be read' + \ }, + \ { + \ 'filename': ale#path#Simplify(g:dir . '/test.d'), + \ 'lnum': '20', + \ 'col' : '10', + \ 'type': 'W', + \ 'text': 'function test.thisoldfunc is deprecated' + \ }, + \ { + \ 'filename': ale#path#Simplify(g:dir . '/foo.d'), + \ 'lnum': '230', + \ 'col' : '9', + \ 'type': 'W', + \ 'text': 'statement is not reachable' + \ } + \ ], + \ ale_linters#d#dmd#Handle(bufnr(''), [ + \ 'test.d(5,8): Error: module weak_reference is in file ''dstruct/weak_reference.d'' which cannot be read', + \ 'import path[0] = source', + \ 'import path[1] = /usr/include/dlang/dmd', + \ ale#path#Simplify(g:dir . '/test.d') . '(20,10): Deprecation: function test.thisoldfunc is deprecated', + \ 'foo.d(230,9): Warning: statement is not reachable', + \ ]) diff --git a/test/handler/test_dockerfile_lint_handler.vader b/test/handler/test_dockerfile_lint_handler.vader new file mode 100644 index 00000000..a73db8cd --- /dev/null +++ b/test/handler/test_dockerfile_lint_handler.vader @@ -0,0 +1,112 @@ +Before: + runtime ale_linters/dockerfile/dockerfile_lint.vim + +After: + call ale#linter#Reset() + +Execute(The dockerfile_lint handler should handle broken JSON): + AssertEqual + \ [], + \ ale_linters#dockerfile#dockerfile_lint#Handle(bufnr(''), ["{asdf"]) + +Execute(The dockerfile_lint handler should handle an empty string response): + AssertEqual + \ [], + \ ale_linters#dockerfile#dockerfile_lint#Handle(bufnr(''), []) + +Execute(The dockerfile_lint handler should handle an empty result, even if it shouldn't happen): + AssertEqual + \ [], + \ ale_linters#dockerfile#dockerfile_lint#Handle(bufnr(''), ["{}"]) + +Execute(The dockerfile_lint handler should handle a normal example): + AssertEqual + \ [ + \ { + \ 'lnum': -1, + \ 'type': 'E', + \ 'text': "Required LABEL name/key 'Name' is not defined", + \ 'detail': "Required LABEL name/key 'Name' is not defined\n\nhttp://docs.projectatomic.io/container-best-practices/#_recommended_labels_for_your_project", + \ }, + \ { + \ 'lnum': -1, + \ 'type': 'E', + \ 'text': "Required LABEL name/key 'Version' is not defined", + \ 'detail': "Required LABEL name/key 'Version' is not defined\n\nhttp://docs.projectatomic.io/container-best-practices/#_recommended_labels_for_your_project", + \ }, + \ { + \ 'lnum': 3, + \ 'type': 'I', + \ 'text': "the MAINTAINER command is deprecated", + \ 'detail': "the MAINTAINER command is deprecated\n\nMAINTAINER is deprecated in favor of using LABEL since Docker v1.13.0\n\nhttps://github.com/docker/cli/blob/master/docs/deprecated.md#maintainer-in-dockerfile", + \ }, + \ { + \ 'lnum': -1, + \ 'type': 'I', + \ 'text': "There is no 'CMD' instruction", + \ 'detail': "There is no 'CMD' instruction\n\nhttps://docs.docker.com/engine/reference/builder/#cmd", + \ }, + \ ], + \ ale_linters#dockerfile#dockerfile_lint#Handle(bufnr(''), [ + \ '{', + \ ' "error": {', + \ ' "count": 2,', + \ ' "data": [', + \ ' {', + \ " \"message\": \"Required LABEL name/key 'Name' is not defined\",", + \ ' "line": -1,', + \ ' "level": "error",', + \ ' "lineContent": "",', + \ ' "reference_url": [', + \ ' "http://docs.projectatomic.io/container-best-practices/#",', + \ ' "_recommended_labels_for_your_project"', + \ ' ]', + \ ' },', + \ ' {', + \ " \"message\": \"Required LABEL name/key 'Version' is not defined\",", + \ ' "line": -1,', + \ ' "level": "error",', + \ ' "lineContent": "",', + \ ' "reference_url": [', + \ ' "http://docs.projectatomic.io/container-best-practices/#",', + \ ' "_recommended_labels_for_your_project"', + \ ' ]', + \ ' }', + \ ' ]', + \ ' },', + \ ' "warn": {', + \ ' "count": 0,', + \ ' "data": []', + \ ' },', + \ ' "info": {', + \ ' "count": 2,', + \ ' "data": [', + \ ' {', + \ ' "label": "maintainer_deprecated",', + \ ' "regex": {},', + \ ' "level": "info",', + \ ' "message": "the MAINTAINER command is deprecated",', + \ ' "description": "MAINTAINER is deprecated in favor of using LABEL since Docker v1.13.0",', + \ ' "reference_url": [', + \ ' "https://github.com/docker/cli/blob/master/docs/deprecated.md",', + \ ' "#maintainer-in-dockerfile"', + \ ' ],', + \ ' "lineContent": "MAINTAINER Alexander Olofsson ",', + \ ' "line": 3', + \ ' },', + \ ' {', + \ ' "instruction": "CMD",', + \ ' "count": 1,', + \ ' "level": "info",', + \ " \"message\": \"There is no 'CMD' instruction\",", + \ ' "description": "None",', + \ ' "reference_url": [', + \ ' "https://docs.docker.com/engine/reference/builder/",', + \ ' "#cmd"', + \ ' ]', + \ ' }', + \ ' ]', + \ ' },', + \ ' "summary": []', + \ '}', + \ ]) diff --git a/test/handler/test_dockerlinter_handler.vader b/test/handler/test_dockerlinter_handler.vader new file mode 100644 index 00000000..de59faac --- /dev/null +++ b/test/handler/test_dockerlinter_handler.vader @@ -0,0 +1,77 @@ +Before: + runtime ale_linters/dockerfile/dockerlinter.vim + +After: + call ale#linter#Reset() + +Execute(The dockerlinter handler should handle broken JSON): + AssertEqual + \ [], + \ ale_linters#dockerfile#dockerlinter#Handle(bufnr(''), ["{asdf"]) + +Execute(The dockerlinter handler should handle an empty string response): + AssertEqual + \ [], + \ ale_linters#dockerfile#dockerlinter#Handle(bufnr(''), []) + +Execute(The dockerlinter handler should handle an empty result, even if it shouldn't happen): + AssertEqual + \ [], + \ ale_linters#dockerfile#dockerlinter#Handle(bufnr(''), ["{}"]) + +Execute(The dockerlinter handler should handle a normal example): + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'type': 'I', + \ 'code': 'ER0002', + \ 'text': "Delete the apt-get lists after installing something", + \ 'detail': "Delete the apt-get lists after installing something\n\nhttps://github.com/buddy-works/dockerfile-linter/blob/master/Rules.md#ER0002", + \ }, + \ { + \ 'lnum': 11, + \ 'type': 'I', + \ 'code': 'ER0010', + \ 'text': "Avoid additional packages by specifying --no-install-recommends", + \ 'detail': "Avoid additional packages by specifying --no-install-recommends\n\nhttps://github.com/buddy-works/dockerfile-linter/blob/master/Rules.md#ER0010", + \ }, + \ { + \ 'lnum': 11, + \ 'type': 'I', + \ 'code': 'ER0012', + \ 'text': "Pin versions in apt get install", + \ 'detail': "Pin versions in apt get install\n\nhttps://github.com/buddy-works/dockerfile-linter/blob/master/Rules.md#ER0012", + \ }, + \ { + \ 'lnum': 30, + \ 'type': 'W', + \ 'code': 'SC2155', + \ 'text': "Declare and assign separately to avoid masking return values.", + \ 'detail': "Declare and assign separately to avoid masking return values.\n\nhttps://www.shellcheck.net/wiki/SC2155", + \ }, + \ { + \ 'lnum': 30, + \ 'type': 'W', + \ 'code': 'SC2046', + \ 'text': "Quote this to prevent word splitting.", + \ 'detail': "Quote this to prevent word splitting.\n\nhttps://www.shellcheck.net/wiki/SC2046", + \ }, + \ { + \ 'lnum': 30, + \ 'type': 'I', + \ 'code': 'SC2086', + \ 'text': "Double quote to prevent globbing and word splitting.", + \ 'detail': "Double quote to prevent globbing and word splitting.\n\nhttps://www.shellcheck.net/wiki/SC2086", + \ }, + \ { + \ 'lnum': 31, + \ 'type': 'W', + \ 'code': 'SC2046', + \ 'text': "Quote this to prevent word splitting.", + \ 'detail': "Quote this to prevent word splitting.\n\nhttps://www.shellcheck.net/wiki/SC2046", + \ }, + \ ], + \ ale_linters#dockerfile#dockerlinter#Handle(bufnr(''), [ + \ '[{"lineNumber":11,"message":"Delete the apt-get lists after installing something","level":"info","code":"ER0002"},{"lineNumber":11,"message":"Avoid additional packages by specifying --no-install-recommends","level":"info","code":"ER0010"},{"lineNumber":11,"message":"Pin versions in apt get install","level":"info","code":"ER0012"},{"lineNumber":30,"message":"Declare and assign separately to avoid masking return values.","level":"warning","code":"SC2155"},{"lineNumber":30,"message":"Quote this to prevent word splitting.","level":"warning","code":"SC2046"},{"lineNumber":30,"message":"Double quote to prevent globbing and word splitting.","level":"info","code":"SC2086"},{"lineNumber":31,"message":"Quote this to prevent word splitting.","level":"warning","code":"SC2046"}]', + \ ]) diff --git a/test/handler/test_dogma_handler.vader b/test/handler/test_dogma_handler.vader new file mode 100644 index 00000000..ad8aec94 --- /dev/null +++ b/test/handler/test_dogma_handler.vader @@ -0,0 +1,29 @@ +Before: + runtime ale_linters/elixir/dogma.vim + +After: + call ale#linter#Reset() + +Execute(The dogma handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 347, + \ 'lnum': 18, + \ 'col': 5, + \ 'text': 'Some error', + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 19, + \ 'col': 7, + \ 'text': 'Some warning', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#elixir#dogma#Handle(347, [ + \ 'This line should be ignored completely', + \ 'lib/filename.ex:18:5: C: Some error', + \ 'lib/filename.ex:19:7: R: Some warning', + \ ]) diff --git a/test/handler/test_drafter_handler.vader b/test/handler/test_drafter_handler.vader new file mode 100644 index 00000000..1524dde1 --- /dev/null +++ b/test/handler/test_drafter_handler.vader @@ -0,0 +1,37 @@ +Before: + runtime! ale_linters/apiblueprint/drafter.vim + +After: + call ale#linter#Reset() + +Execute(drafter handler should handle errors output): + AssertEqual + \ [ + \ { + \ 'lnum': 25, + \ 'col': 3, + \ 'text': "unable to parse response signature, expected 'response [] [()]'", + \ 'type': "W", + \ }, + \ { + \ 'lnum': 25, + \ 'col': 3, + \ 'text': "missing response HTTP status code, assuming 'Response 200'", + \ 'type': "W", + \ }, + \ { + \ 'lnum': 30, + \ 'col': 7, + \ 'end_lnum': 32, + \ 'end_col': 7, + \ 'text': "message-body asset is expected to be a pre-formatted code block, separate it by a newline and indent every of its line by 12 spaces or 3 tabs", + \ 'type': "W", + \ }, + \ ], + \ ale_linters#apiblueprint#drafter#HandleErrors(bufnr(''), [ + \ "", + \ "OK.", + \ "warning: (3) unable to parse response signature, expected 'response [] [()]'; line 25, column 3 - line 25, column 29", + \ "warning: (6) missing response HTTP status code, assuming 'Response 200'; line 25, column 3 - line 25, column 29", + \ "warning: (10) message-body asset is expected to be a pre-formatted code block, separate it by a newline and indent every of its line by 12 spaces or 3 tabs; line 30, column 7 - line 30, column 11; line 31, column 6 - line 31, column 7; line 32, column 6 - line 32, column 7" + \ ]) diff --git a/test/handler/test_elmmake_handler.vader b/test/handler/test_elmmake_handler.vader new file mode 100644 index 00000000..f5906a8b --- /dev/null +++ b/test/handler/test_elmmake_handler.vader @@ -0,0 +1,299 @@ +Before: + runtime ale_linters/elm/make.vim + +After: + unlet! g:config_error_lines + + call ale#linter#Reset() + + +" Elm 0.19 + +Execute(The elm-make handler should parse Elm 0.19 general problems correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "error details\n\nstyled details" + \ } + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ json_encode({ + \ 'type': 'error', + \ 'path': ale#util#Tempname() . '/Module.elm', + \ 'title': 'UNKNOWN IMPORT', + \ 'message': ["error details\n\n", { 'string': 'styled details' }] + \ }), + \ ]) + +Execute(The elm-make handler should parse Elm 0.19 compilation errors correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 404, + \ 'col': 1, + \ 'end_lnum': 408, + \ 'end_col': 18, + \ 'type': 'E', + \ 'text': "error details 1\n\nstyled details" + \ }, + \ { + \ 'lnum': 406, + \ 'col': 5, + \ 'end_lnum': 407, + \ 'end_col': 17, + \ 'type': 'E', + \ 'text': 'error details 2', + \ }, + \ { + \ 'lnum': 406, + \ 'col': 5, + \ 'end_lnum': 406, + \ 'end_col': 93, + \ 'type': 'E', + \ 'text': 'error details 3', + \ }, + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ json_encode({ + \ 'type': 'compile-errors', + \ 'errors': [ + \ { + \ 'path': ale#util#Tempname() . '/Module.elm', + \ 'problems': [ + \ { + \ 'title': 'TYPE MISMATCH', + \ 'message': ["error details 1\n\n", { 'string': 'styled details' }], + \ 'region': { 'start': { 'line': 404, 'column': 1 }, 'end': { 'line': 408, 'column': 18 } } + \ }, + \ { + \ 'title': 'TYPE MISMATCH', + \ 'message': ['error details 2'], + \ 'region': { 'start': {'line': 406, 'column': 5}, 'end': {'line': 407, 'column': 17 } } + \ }, + \ { + \ 'title': 'TYPE MISMATCH', + \ 'message': ['error details 3'], + \ 'region': { 'start': { 'line': 406, 'column': 5}, 'end': {'line': 406, 'column': 93 } } + \ } + \ ] + \ } + \ ] + \ }), + \ ]) + +Execute(The elm-make handler should handle errors in Elm 0.19 imported modules): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "src/Module.elm - error details\n\nstyled details", + \ 'detail': "src/Module.elm ----------\n\nerror details\n\nstyled details" + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "Elm - error details\n\nstyled details", + \ 'detail': "Elm ----------\n\nerror details\n\nstyled details" + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': "src/Module.elm:404 - error details\n\nstyled details", + \ 'detail': "src/Module.elm:404 ----------\n\nerror details\n\nstyled details" + \ }, + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ json_encode({ + \ 'type': 'error', + \ 'path': 'src/Module.elm', + \ 'title': 'UNKNOWN IMPORT', + \ 'message': ["error details\n\n", { 'string': 'styled details' }] + \ }), + \ json_encode({ + \ 'type': 'error', + \ 'path': v:null, + \ 'title': 'UNKNOWN IMPORT', + \ 'message': ["error details\n\n", { 'string': 'styled details' }] + \ }), + \ json_encode({ + \ 'type': 'compile-errors', + \ 'errors': [ + \ { + \ 'path': 'src/Module.elm', + \ 'problems': [ + \ { + \ 'title': 'TYPE MISMATCH', + \ 'message': ["error details\n\n", { 'string': 'styled details' }], + \ 'region': { 'start': { 'line': 404, 'column': 1 }, 'end': { 'line': 408, 'column': 18 } } + \ } + \ ] + \ } + \ ] + \ }), + \ ]) + + +" Elm 0.18 + +Execute(The elm-make handler should parse Elm 0.18 compilation errors correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 33, + \ 'col': 1, + \ 'end_lnum': 33, + \ 'end_col': 19, + \ 'type': 'W', + \ 'text': 'warning overview', + \ 'detail': "warning overview\n\nwarning details", + \ }, + \ { + \ 'lnum': 404, + \ 'col': 1, + \ 'end_lnum': 408, + \ 'end_col': 18, + \ 'type': 'E', + \ 'text': 'error overview 1', + \ 'detail': "error overview 1\n\nerror details 1", + \ }, + \ { + \ 'lnum': 406, + \ 'col': 5, + \ 'end_lnum': 407, + \ 'end_col': 17, + \ 'type': 'E', + \ 'text': 'error overview 2', + \ 'detail': "error overview 2\n\nerror details 2", + \ }, + \ { + \ 'lnum': 406, + \ 'col': 5, + \ 'end_lnum': 406, + \ 'end_col': 93, + \ 'type': 'E', + \ 'text': 'error overview 3', + \ 'detail': "error overview 3\n\nerror details 3", + \ }, + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ json_encode([ + \ { + \ 'tag': 'unused import', + \ 'overview': 'warning overview', + \ 'details': 'warning details', + \ 'region': {'start': { 'line': 33, 'column': 1 }, 'end': { 'line': 33, 'column': 19 } }, + \ 'type': 'warning', + \ 'file': ale#util#Tempname() . '/Module.elm', + \ } + \ ]), + \ json_encode([ + \ { + \ 'tag': 'TYPE MISMATCH', + \ 'overview': 'error overview 1', + \ 'subregion': { 'start': { 'line': 406, 'column': 5 }, 'end': { 'line': 408, 'column': 18 } }, + \ 'details': 'error details 1', + \ 'region': { 'start': { 'line': 404, 'column': 1 }, 'end': { 'line': 408, 'column': 18 } }, + \ 'type': 'error', + \ 'file': ale#util#Tempname() . '/Module.elm', + \ }, + \ { + \ 'tag': 'TYPE MISMATCH', + \ 'overview': 'error overview 2', + \ 'subregion': { 'start': { 'line': 407, 'column': 12 }, 'end': { 'line': 407, 'column': 17 } }, + \ 'details': 'error details 2', + \ 'region': { 'start': { 'line': 406, 'column': 5}, 'end': { 'line': 407, 'column': 17 } }, + \ 'type':'error', + \ 'file': ale#util#Tempname() . '/Module.elm', + \ }, + \ { + \ 'tag': 'TYPE MISMATCH', + \ 'overview': 'error overview 3', + \ 'subregion': { 'start': { 'line': 406, 'column': 88 }, 'end': { 'line': 406, 'column': 93 } }, + \ 'details': 'error details 3', + \ 'region': { 'start': { 'line': 406, 'column': 5 }, 'end': { 'line': 406, 'column': 93 } }, + \ 'type':'error', + \ 'file': ale#util#Tempname() . '/Module.elm', + \ } + \ ]), + \ ]) + +Execute(The elm-make handler should handle errors in Elm 0.18 imported modules): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': 'src/Module.elm:33 - error overview', + \ 'detail': "src/Module.elm:33 ----------\n\nerror overview\n\nerror details" + \ } + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ json_encode([ + \ { + \ 'tag': 'unused import', + \ 'overview': 'warning overview', + \ 'details': 'warning details', + \ 'region': {'start': { 'line': 33, 'column': 1 }, 'end': { 'line': 33, 'column': 19 } }, + \ 'type': 'warning', + \ 'file': 'src/Module.elm', + \ }, + \ { + \ 'tag': 'type error', + \ 'overview': 'error overview', + \ 'details': 'error details', + \ 'region': {'start': { 'line': 33, 'column': 1 }, 'end': { 'line': 33, 'column': 19 } }, + \ 'type': 'error', + \ 'file': 'src/Module.elm', + \ } + \ ]), + \ ]) + +" Generic + +Execute(The elm-make handler should put an error on the first line if a line cannot be parsed): + AssertEqual + \ [ + \ { + \ 'lnum': 404, + \ 'col': 1, + \ 'end_lnum': 408, + \ 'end_col': 18, + \ 'type': 'E', + \ 'text': "error details 1\n\nstyled details" + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': 'Not JSON', + \ 'detail': "Not JSON\nAlso not JSON", + \ }, + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ json_encode({ + \ 'type': 'compile-errors', + \ 'errors': [ + \ { + \ 'path': ale#util#Tempname() . '/Module.elm', + \ 'problems': [ + \ { + \ 'title': 'TYPE MISMATCH', + \ 'message': ["error details 1\n\n", { 'string': 'styled details' }], + \ 'region': { 'start': { 'line': 404, 'column': 1 }, 'end': { 'line': 408, 'column': 18 } } + \ } + \ ] + \ } + \ ] + \ }), + \ 'Not JSON', + \ 'Also not JSON', + \ ]) + +Execute(The elm-make handler should ignore success lines): + AssertEqual + \ [], + \ ale_linters#elm#make#Handle(347, [ + \ 'Successfully generated /dev/null', + \ ]) diff --git a/test/handler/test_embertemplatelint_handler.vader b/test/handler/test_embertemplatelint_handler.vader new file mode 100644 index 00000000..d5394baf --- /dev/null +++ b/test/handler/test_embertemplatelint_handler.vader @@ -0,0 +1,81 @@ +" Author: Adrian Zalewski +Before: + runtime autoload/ale/handlers/embertemplatelint.vim + +After: + call ale#linter#Reset() + +Execute(The ember-template-lint handler should parse lines correctly): + let input_lines = split('{ + \ "/ember-project/app/templates/application.hbs": [ + \ { + \ "moduleId": "app/templates/application", + \ "rule": "bare-strings", + \ "severity": 2, + \ "message": "Non-translated string used", + \ "line": 1, + \ "column": 10, + \ "source": " Bare String\n" + \ }, + \ { + \ "moduleId": "app/templates/application", + \ "rule": "invalid-interactive", + \ "severity": 1, + \ "message": "Interaction added to non-interactive element", + \ "line": 3, + \ "column": 6, + \ "source": "" + \ } + \ ] + \ }', '\n') + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'bare-strings: Non-translated string used', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 6, + \ 'text': 'invalid-interactive: Interaction added to non-interactive element', + \ 'type': 'W', + \ }, + \ ], + \ ale#handlers#embertemplatelint#Handle(347, input_lines) + +Execute(The ember-template-lint handler should handle template parsing error correctly): + let input_lines = split('{ + \ "/ember-project/app/templates/application.hbs": [ + \ { + \ "fatal": true, + \ "moduleId": "app/templates/application", + \ "message": "Parse error on line 5 ...", + \ "line": 5, + \ "column": 3, + \ "source": "Error: Parse error on line 5 ...", + \ "severity": 2 + \ } + \ ] + \ }', '\n') + + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 3, + \ 'text': 'Parse error on line 5 ...', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#embertemplatelint#Handle(347, input_lines) + +Execute(The ember-template-lint handler should handle no lint errors/warnings): + AssertEqual + \ [], + \ ale#handlers#embertemplatelint#Handle(347, []) + AssertEqual + \ [], + \ ale#handlers#embertemplatelint#Handle(347, ['{}']) diff --git a/test/handler/test_erblint_handler.vader b/test/handler/test_erblint_handler.vader new file mode 100644 index 00000000..03836713 --- /dev/null +++ b/test/handler/test_erblint_handler.vader @@ -0,0 +1,64 @@ +Before: + runtime ale_linters/eruby/erblint.vim + +After: + call ale#linter#Reset() + +Execute(The erblint handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 0, + \ 'end_col': 0, + \ 'text': 'Extra blank line detected.', + \ 'code': 'ExtraNewline', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 0, + \ 'end_col': 0, + \ 'text': 'Remove multiple trailing newline at the end of the file.', + \ 'code': 'FinalNewline', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 9, + \ 'end_col': 11, + \ 'text': 'Use 1 space after `<%=` instead of 2 spaces.', + \ 'code': 'SpaceAroundErbTag', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 9, + \ 'end_col': 11, + \ 'text': 'Use 1 space before `%>` instead of 2 spaces.', + \ 'code': 'SpaceAroundErbTag', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 6, + \ 'end_col': 10, + \ 'text': 'Extra whitespace detected at end of line.', + \ 'code': 'TrailingWhitespace', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#eruby#erblint#Handle(347, [ + \ '{"metadata":{"erb_lint_version":"0.1.1","ruby_engine":"ruby","ruby_version":"3.0.2","ruby_patchlevel":"107","ruby_platform":"arm64-darwin20"},"files":[{"path":"demo.html.erb","offenses":[{"linter":"ExtraNewline","message":"Extra blank line detected.","location":{"start_line":3,"start_column":0,"last_line":4,"last_column":0,"length":1}},{"linter":"FinalNewline","message":"Remove multiple trailing newline at the end of the file.","location":{"start_line":6,"start_column":0,"last_line":8,"last_column":0,"length":2}},{"linter":"SpaceAroundErbTag","message":"Use 1 space after `<%=` instead of 2 spaces.","location":{"start_line":4,"start_column":9,"last_line":4,"last_column":11,"length":2}},{"linter":"SpaceAroundErbTag","message":"Use 1 space before `%>` instead of 2 spaces.","location":{"start_line":4,"start_column":9,"last_line":4,"last_column":11,"length":2}},{"linter":"TrailingWhitespace","message":"Extra whitespace detected at end of line.","location":{"start_line":5,"start_column":6,"last_line":5,"last_column":10,"length":4}}]}],"summary":{"offenses":5,"inspected_files":1,"corrected":0}}' + \ ]) + +Execute(The erblint handler should handle when files are checked and no offenses are found): + AssertEqual + \ [], + \ ale_linters#eruby#erblint#Handle(347, [ + \ '{"metadata":{"erb_lint_version":"0.1.1","ruby_engine":"ruby","ruby_version":"3.0.2","ruby_patchlevel":"107","ruby_platform":"arm64-darwin20"},"files":[{"path":"demo.html.erb","offenses":[]}],"summary":{"offenses":0,"inspected_files":1,"corrected":0}}' + \ ]) + +Execute(The erblint handler should handle output without any errors): + AssertEqual [], ale_linters#eruby#erblint#Handle(347, ['{}']) + AssertEqual [], ale_linters#eruby#erblint#Handle(347, []) diff --git a/test/handler/test_erlang_dialyzer_handler.vader b/test/handler/test_erlang_dialyzer_handler.vader new file mode 100644 index 00000000..aa5c2305 --- /dev/null +++ b/test/handler/test_erlang_dialyzer_handler.vader @@ -0,0 +1,21 @@ +Before: + runtime ale_linters/erlang/dialyzer.vim + +After: + call ale#linter#Reset() + +Execute(The dialyzer handler should handle error messages.): + AssertEqual + \[ + \ { + \ 'lnum': 3, + \ 'lcol': 0, + \ 'text': 'Callback info about the provider behaviour is not available', + \ 'type': 'W' + \ } + \], + \ ale_linters#erlang#dialyzer#Handle(bufnr(''), ['erl_tidy_prv_fmt.erl:3: Callback info about the provider behaviour is not available']) + +Execute(The dialyzer handler should handle empty input): + AssertEqual [], ale_linters#erlang#dialyzer#Handle(bufnr(''), []) + AssertEqual [], ale_linters#erlang#dialyzer#Handle(bufnr(''), ['']) diff --git a/test/handler/test_erlang_elvis_handler.vader b/test/handler/test_erlang_elvis_handler.vader new file mode 100644 index 00000000..0a6d756d --- /dev/null +++ b/test/handler/test_erlang_elvis_handler.vader @@ -0,0 +1,40 @@ +Before: + runtime ale_linters/erlang/elvis.vim + +After: + call ale#linter#Reset() + +Execute(Warning messages should be handled): + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'text': "Replace the 'if' expression on line 11 with a 'case' expression or function clauses.", + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 20, + \ 'text': 'Remove the debug call to io:format/1 on line 20.', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#erlang#elvis#Handle(bufnr(''), [ + \ "src/foo.erl:11:no_if_expression:Replace the 'if' expression on line 11 with a 'case' expression or function clauses.", + \ 'src/foo.erl:20:no_debug_call:Remove the debug call to io:format/1 on line 20.', + \ ]) + +Execute(Line length message shouldn't contain the line itself): + AssertEqual + \ [ + \ { + \ 'lnum': 24, + \ 'text': 'Line 24 is too long.', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#erlang#elvis#Handle(bufnr(''), [ + \ 'src/foo.erl:24:line_length:Line 24 is too long: io:format("Look ma, too long!"),.', + \ ]) diff --git a/test/handler/test_eslint_handler.vader b/test/handler/test_eslint_handler.vader new file mode 100644 index 00000000..01dd2c8f --- /dev/null +++ b/test/handler/test_eslint_handler.vader @@ -0,0 +1,438 @@ +Before: + Save g:ale_javascript_eslint_suppress_eslintignore + Save g:ale_javascript_eslint_suppress_missing_config + Save g:ale_warn_about_trailing_whitespace + Save g:ale_warn_about_trailing_blank_lines + + let g:ale_javascript_eslint_suppress_eslintignore = 0 + let g:ale_javascript_eslint_suppress_missing_config = 0 + let g:ale_warn_about_trailing_whitespace = 1 + let g:ale_warn_about_trailing_blank_lines = 1 + unlet! b:ale_warn_about_trailing_whitespace + unlet! b:ale_warn_about_trailing_blank_lines + +After: + Restore + + unlet! b:ale_javascript_eslint_suppress_eslintignore + unlet! b:ale_javascript_eslint_suppress_missing_config + unlet! b:ale_warn_about_trailing_whitespace + unlet! b:ale_warn_about_trailing_blank_lines + unlet! g:config_error_lines + +Execute(The eslint handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 47, + \ 'col': 14, + \ 'text': 'Missing trailing comma.', + \ 'code': 'comma-dangle', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 56, + \ 'col': 41, + \ 'text': 'Missing semicolon.', + \ 'code': 'semi', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 13, + \ 'col': 3, + \ 'text': 'Parsing error: Unexpected token', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ 'This line should be ignored completely', + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ 'This line should be ignored completely', + \ '/path/to/some-filename.js:13:3: Parsing error: Unexpected token', + \ ]) + +Execute(The eslint handler should print a message about a missing configuration file): + let g:config_error_lines = [ + \ '', + \ 'Oops! Something went wrong! :(', + \ '', + \ 'ESLint couldn''t find a configuration file. To set up a configuration file for this project, please run:', + \ ' eslint --init', + \ '', + \ 'ESLint looked for configuration files in /some/path/or/other and its ancestors.', + \ '', + \ 'If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint', + \ '', + \ ] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should allow the missing config error to be suppressed): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ '', + \ 'Oops! Something went wrong! :(', + \ '', + \ 'ESLint couldn''t find a configuration file. To set up a configuration file for this project, please run:', + \ ' eslint --init', + \ '', + \ 'ESLint looked for configuration files in /some/path/or/other and its ancestors.', + \ '', + \ 'If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint', + \ '', + \ ] + + AssertEqual + \ [], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should print a message for config parsing errors): + let g:config_error_lines = [ + \ 'Cannot read config file: /some/path/or/other/.eslintrc.js', + \ 'Error: Unexpected token <<', + \ '/some/path/or/other/.eslintrc.js:1', + \ '(function (exports, require, module, __filename, __dirname) { <<<>>>', + \ ' ^^', + \ 'SyntaxError: Unexpected token <<', + \ ' at Object.exports.runInThisContext (vm.js:76:16)', + \ ' at Module._compile (module.js:528:28)', + \ ' at Object.Module._extensions..js (module.js:565:10)', + \ ' at Module.load (module.js:473:32)', + \ ' at tryModuleLoad (module.js:432:12)', + \ ' at Function.Module._load (module.js:424:3)', + \ ' at Module.require (module.js:483:17)', + \ ' at require (internal/module.js:20:19)', + \ ' at module.exports (/usr/local/lib/node_modules/eslint/node_modules/require-uncached/index.js:14:12)', + \ ' at loadJSConfigFile (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:160:16)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + +Execute(Suppressing missing configs shouldn't suppress parsing errors): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ 'Cannot read config file: /some/path/or/other/.eslintrc.js', + \ 'Error: Unexpected token <<', + \ '/some/path/or/other/.eslintrc.js:1', + \ '(function (exports, require, module, __filename, __dirname) { <<<>>>', + \ ' ^^', + \ 'SyntaxError: Unexpected token <<', + \ ' at Object.exports.runInThisContext (vm.js:76:16)', + \ ' at Module._compile (module.js:528:28)', + \ ' at Object.Module._extensions..js (module.js:565:10)', + \ ' at Module.load (module.js:473:32)', + \ ' at tryModuleLoad (module.js:432:12)', + \ ' at Function.Module._load (module.js:424:3)', + \ ' at Module.require (module.js:483:17)', + \ ' at require (internal/module.js:20:19)', + \ ' at module.exports (/usr/local/lib/node_modules/eslint/node_modules/require-uncached/index.js:14:12)', + \ ' at loadJSConfigFile (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:160:16)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should print a message for invalid configuration settings): + let g:config_error_lines = [ + \ '/home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ 'Error: /home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ ' at validateRuleOptions (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:115:15)', + \ ' at /usr/local/lib/node_modules/eslint/lib/config/config-validator.js:162:13', + \ ' at Array.forEach (native)', + \ ' at Object.validate (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:161:35)', + \ ' at Object.load (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:522:19)', + \ ' at loadConfig (/usr/local/lib/node_modules/eslint/lib/config.js:63:33)', + \ ' at getLocalConfig (/usr/local/lib/node_modules/eslint/lib/config.js:130:29)', + \ ' at Config.getConfig (/usr/local/lib/node_modules/eslint/lib/config.js:256:22)', + \ ' at processText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:224:33)', + \ ' at CLIEngine.executeOnText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:756:26)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + +Execute(Suppressing missing configs shouldn't suppress invalid config errors): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ '/home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ 'Error: /home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ ' at validateRuleOptions (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:115:15)', + \ ' at /usr/local/lib/node_modules/eslint/lib/config/config-validator.js:162:13', + \ ' at Array.forEach (native)', + \ ' at Object.validate (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:161:35)', + \ ' at Object.load (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:522:19)', + \ ' at loadConfig (/usr/local/lib/node_modules/eslint/lib/config.js:63:33)', + \ ' at getLocalConfig (/usr/local/lib/node_modules/eslint/lib/config.js:130:29)', + \ ' at Config.getConfig (/usr/local/lib/node_modules/eslint/lib/config.js:256:22)', + \ ' at processText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:224:33)', + \ ' at CLIEngine.executeOnText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:756:26)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should print a message when import is not used in a module): + let g:config_error_lines = [ + \ 'ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ 'AssertionError: ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ ' at Referencer.ImportDeclaration (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:597:9)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Referencer.Visitor.visitChildren (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:101:38)', + \ ' at Referencer.Program (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:449:14)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Object.analyze (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/index.js:138:16)', + \ ' at EventEmitter.module.exports.api.verify (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/eslint.js:887:40)', + \ ' at processText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:278:31)', + \ ' at CLIEngine.executeOnText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:734:26)', + \ ' at Object.execute (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli.js:171:42) ', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + +Execute(Suppressing missing configs shouldn't suppress module import errors): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ 'ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ 'AssertionError: ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ ' at Referencer.ImportDeclaration (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:597:9)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Referencer.Visitor.visitChildren (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:101:38)', + \ ' at Referencer.Program (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:449:14)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Object.analyze (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/index.js:138:16)', + \ ' at EventEmitter.module.exports.api.verify (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/eslint.js:887:40)', + \ ' at processText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:278:31)', + \ ' at CLIEngine.executeOnText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:734:26)', + \ ' at Object.execute (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli.js:171:42) ', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), g:config_error_lines[:]) + + +Execute(The eslint handler should output end_col values where appropriate): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 3, + \ 'end_col': 15, + \ 'text': 'Parsing error: Unexpected token ''some string''', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 70, + \ 'col': 3, + \ 'end_col': 5, + \ 'text': '''foo'' is not defined.', + \ 'code': 'no-undef', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 71, + \ 'col': 2, + \ 'end_col': 6, + \ 'text': 'Unexpected `await` inside a loop.', + \ 'code': 'no-await-in-loop', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 72, + \ 'col': 6, + \ 'end_col': 10, + \ 'text': 'Redundant use of `await` on a return value.', + \ 'code': 'no-return-await', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 73, + \ 'col': 4, + \ 'end_col': 10, + \ 'text': 'Unexpected console statement', + \ 'code': 'no-console', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 74, + \ 'col': 4, + \ 'end_col': 11, + \ 'text': 'Unexpected ''debugger'' statement.', + \ 'code': 'no-debugger', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ 'app.js:4:3: Parsing error: Unexpected token ''some string'' [Error]', + \ 'app.js:70:3: ''foo'' is not defined. [Error/no-undef]', + \ 'app.js:71:2: Unexpected `await` inside a loop. [Error/no-await-in-loop]', + \ 'app.js:72:6: Redundant use of `await` on a return value. [Error/no-return-await]', + \ 'app.js:73:4: Unexpected console statement [Error/no-console]', + \ 'app.js:74:4: Unexpected ''debugger'' statement. [Error/no-debugger]', + \ ]) + +Execute(The eslint hint about using typescript-eslint-parser): + silent! noautocmd file foo.ts + + AssertEqual + \ [ + \ { + \ 'lnum': 451, + \ 'col': 2, + \ 'end_col': 2, + \ 'text': 'Parsing error (You may need configure typescript-eslint-parser): Unexpected token )', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ 'foo.ts:451:2: Parsing error: Unexpected token ) [Error]', + \ ]) + +Execute(eslint should warn about ignored files by default): + AssertEqual + \ [{ + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.' + \ }], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ '/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 + + AssertEqual + \ [], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ '/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 + \ [ + \ { + \ 'lnum': 59, + \ 'col': 9, + \ 'type': 'E', + \ 'text': 'Property should be placed on the same line as the component declaration', + \ 'code': 'react/jsx-first-prop-new-line', + \ }, + \ ], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ '/path/editor-help.jsx:59:9: Property should be placed on the same line as the component declaration [Error/react/jsx-first-prop-new-line]', + \ ]) + +Execute(Failing to connect to eslint_d should be handled correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'Could not connect to eslint_d. Try updating eslint_d or killing it.', + \ }, + \ ], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ 'Could not connect', + \ ]) + +Execute(Disabling warnings about trailing spaces should work): + silent! noautocmd file foo.ts + + AssertEqual + \ [ + \ { + \ 'lnum': 182, + \ 'col': 22, + \ 'code': 'no-trailing-spaces', + \ 'type': 'E', + \ 'text': 'Trailing spaces not allowed.', + \ }, + \ ], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ 'foo.js:182:22: Trailing spaces not allowed. [Error/no-trailing-spaces]', + \ ]) + + let g:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ 'foo.js:182:22: Trailing spaces not allowed. [Error/no-trailing-spaces]', + \ ]) + + let g:ale_warn_about_trailing_whitespace = 1 + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [], + \ ale#handlers#eslint#Handle(bufnr(''), [ + \ 'foo.js:182:22: Trailing spaces not allowed. [Error/no-trailing-spaces]', + \ ]) diff --git a/test/handler/test_eslint_json_handler.vader b/test/handler/test_eslint_json_handler.vader new file mode 100644 index 00000000..6235794a --- /dev/null +++ b/test/handler/test_eslint_json_handler.vader @@ -0,0 +1,376 @@ +Before: + Save g:ale_javascript_eslint_suppress_eslintignore + Save g:ale_javascript_eslint_suppress_missing_config + Save g:ale_warn_about_trailing_whitespace + Save g:ale_warn_about_trailing_blank_lines + + let g:ale_javascript_eslint_suppress_eslintignore = 0 + let g:ale_javascript_eslint_suppress_missing_config = 0 + let g:ale_warn_about_trailing_whitespace = 1 + let g:ale_warn_about_trailing_blank_lines = 1 + unlet! b:ale_warn_about_trailing_whitespace + unlet! b:ale_warn_about_trailing_blank_lines + +After: + Restore + + unlet! b:ale_javascript_eslint_suppress_eslintignore + unlet! b:ale_javascript_eslint_suppress_missing_config + unlet! b:ale_warn_about_trailing_whitespace + unlet! b:ale_warn_about_trailing_blank_lines + unlet! g:config_error_lines + +Execute(The eslint handler should parse json correctly): + call ale#test#SetFilename('foo.js') + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'end_lnum': 1, + \ 'col': 7, + \ 'end_col': 14, + \ 'text': '''variable'' is assigned a value but never used.', + \ 'code': 'no-unused-vars', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 15, + \ 'text': 'Missing semicolon.', + \ 'code': 'semi', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 7, + \ 'end_lnum': 7, + \ 'col': 7, + \ 'end_col': 14, + \ 'text': '''variable'' is already defined.', + \ 'code': 'no-redeclare', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"foo.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"''variable'' is assigned a value but never used.","line":1,"column":7,"nodeType":"Identifier","endLine":1,"endColumn":15},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":15,"nodeType":"ExpressionStatement","fix":{"range":[46,46],"text":";"}},{"ruleId":"no-redeclare","severity":2,"message":"''variable'' is already defined.","line":7,"column":7,"nodeType":"Identifier","endLine":7,"endColumn":15}],"errorCount":1,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":1,"source":"const variable = {\n a: 3\n};\n\nconsole.log(1)\n\nclass variable {\n}\n"}]' + \ ]) + +Execute(The eslint handler should suppress deprecation warnings): + call ale#test#SetFilename('foo.js') + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 9, + \ 'text': 'Parsing error: Unexpected token Controller', + \ 'type': 'E', + \ } + \ ], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"foo.js","messages":[{"ruleId":null,"fatal":true,"severity":2 ,"message":"Parsing error: Unexpected token Controller","line":1,"column":9}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount": 0,"source":"i:mport Controller from \"@ember/controller\";\nimport listViewControllerMixin from \"elearning/mixins/list-view-controller\";\nimport { inject as service } from \"@ember/service\";\n\nexport default Controller.extend(listViewControllerMixin(), {\n modelName: \"notification\",\n intl: service(),\n\n flatpickrLocale: computed(\"intl.locale\", function() {\n return this.intl.locale.firstObject.split(\"-\")[0];\n })\n});\n"}]', '(node:616989) [ESLINT_LEGACY_OBJECT_REST_SPREAD] DeprecationWarning: The ''parserOptions.ecmaFeatures.experimentalObjectRestSpread'' option is deprecated. Use ''parser Options.ecmaVersion'' instead. (found in "node_modules/eslint-plugin-ember/lib/config/base.js")]' + \ ]) + +Execute(The eslint handler should print a message about a missing configuration file): + let g:config_error_lines = [ + \ '', + \ 'Oops! Something went wrong! :(', + \ '', + \ 'ESLint couldn''t find a configuration file. To set up a configuration file for this project, please run:', + \ ' eslint --init', + \ '', + \ 'ESLint looked for configuration files in /some/path/or/other and its ancestors.', + \ '', + \ 'If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint', + \ '', + \ ] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should allow the missing config error to be suppressed): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ '', + \ 'Oops! Something went wrong! :(', + \ '', + \ 'ESLint couldn''t find a configuration file. To set up a configuration file for this project, please run:', + \ ' eslint --init', + \ '', + \ 'ESLint looked for configuration files in /some/path/or/other and its ancestors.', + \ '', + \ 'If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint', + \ '', + \ ] + + AssertEqual + \ [], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should print a message for config parsing errors): + let g:config_error_lines = [ + \ 'Cannot read config file: /some/path/or/other/.eslintrc.js', + \ 'Error: Unexpected token <<', + \ '/some/path/or/other/.eslintrc.js:1', + \ '(function (exports, require, module, __filename, __dirname) { <<<>>>', + \ ' ^^', + \ 'SyntaxError: Unexpected token <<', + \ ' at Object.exports.runInThisContext (vm.js:76:16)', + \ ' at Module._compile (module.js:528:28)', + \ ' at Object.Module._extensions..js (module.js:565:10)', + \ ' at Module.load (module.js:473:32)', + \ ' at tryModuleLoad (module.js:432:12)', + \ ' at Function.Module._load (module.js:424:3)', + \ ' at Module.require (module.js:483:17)', + \ ' at require (internal/module.js:20:19)', + \ ' at module.exports (/usr/local/lib/node_modules/eslint/node_modules/require-uncached/index.js:14:12)', + \ ' at loadJSConfigFile (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:160:16)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(Suppressing missing configs shouldn't suppress parsing errors): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ 'Cannot read config file: /some/path/or/other/.eslintrc.js', + \ 'Error: Unexpected token <<', + \ '/some/path/or/other/.eslintrc.js:1', + \ '(function (exports, require, module, __filename, __dirname) { <<<>>>', + \ ' ^^', + \ 'SyntaxError: Unexpected token <<', + \ ' at Object.exports.runInThisContext (vm.js:76:16)', + \ ' at Module._compile (module.js:528:28)', + \ ' at Object.Module._extensions..js (module.js:565:10)', + \ ' at Module.load (module.js:473:32)', + \ ' at tryModuleLoad (module.js:432:12)', + \ ' at Function.Module._load (module.js:424:3)', + \ ' at Module.require (module.js:483:17)', + \ ' at require (internal/module.js:20:19)', + \ ' at module.exports (/usr/local/lib/node_modules/eslint/node_modules/require-uncached/index.js:14:12)', + \ ' at loadJSConfigFile (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:160:16)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should print a message for invalid configuration settings): + let g:config_error_lines = [ + \ '/home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ 'Error: /home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ ' at validateRuleOptions (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:115:15)', + \ ' at /usr/local/lib/node_modules/eslint/lib/config/config-validator.js:162:13', + \ ' at Array.forEach (native)', + \ ' at Object.validate (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:161:35)', + \ ' at Object.load (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:522:19)', + \ ' at loadConfig (/usr/local/lib/node_modules/eslint/lib/config.js:63:33)', + \ ' at getLocalConfig (/usr/local/lib/node_modules/eslint/lib/config.js:130:29)', + \ ' at Config.getConfig (/usr/local/lib/node_modules/eslint/lib/config.js:256:22)', + \ ' at processText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:224:33)', + \ ' at CLIEngine.executeOnText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:756:26)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(Suppressing missing configs shouldn't suppress invalid config errors): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ '/home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ 'Error: /home/w0rp/git/wazoku/wazoku-spotlight/.eslintrc.js:', + \ ' Configuration for rule "indent" is invalid:', + \ ' Value "off" is the wrong type.', + \ '', + \ ' at validateRuleOptions (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:115:15)', + \ ' at /usr/local/lib/node_modules/eslint/lib/config/config-validator.js:162:13', + \ ' at Array.forEach (native)', + \ ' at Object.validate (/usr/local/lib/node_modules/eslint/lib/config/config-validator.js:161:35)', + \ ' at Object.load (/usr/local/lib/node_modules/eslint/lib/config/config-file.js:522:19)', + \ ' at loadConfig (/usr/local/lib/node_modules/eslint/lib/config.js:63:33)', + \ ' at getLocalConfig (/usr/local/lib/node_modules/eslint/lib/config.js:130:29)', + \ ' at Config.getConfig (/usr/local/lib/node_modules/eslint/lib/config.js:256:22)', + \ ' at processText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:224:33)', + \ ' at CLIEngine.executeOnText (/usr/local/lib/node_modules/eslint/lib/cli-engine.js:756:26)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should print a message when import is not used in a module): + let g:config_error_lines = [ + \ 'ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ 'AssertionError: ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ ' at Referencer.ImportDeclaration (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:597:9)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Referencer.Visitor.visitChildren (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:101:38)', + \ ' at Referencer.Program (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:449:14)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Object.analyze (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/index.js:138:16)', + \ ' at EventEmitter.module.exports.api.verify (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/eslint.js:887:40)', + \ ' at processText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:278:31)', + \ ' at CLIEngine.executeOnText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:734:26)', + \ ' at Object.execute (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli.js:171:42) ', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(Suppressing missing configs shouldn't suppress module import errors): + let b:ale_javascript_eslint_suppress_missing_config = 1 + let g:config_error_lines = [ + \ 'ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ 'AssertionError: ImportDeclaration should appear when the mode is ES6 and in the module context.', + \ ' at Referencer.ImportDeclaration (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:597:9)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Referencer.Visitor.visitChildren (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:101:38)', + \ ' at Referencer.Program (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/referencer.js:449:14)', + \ ' at Referencer.Visitor.visit (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/esrecurse/esrecurse.js:122:34)', + \ ' at Object.analyze (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint-scope/lib/index.js:138:16)', + \ ' at EventEmitter.module.exports.api.verify (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/eslint.js:887:40)', + \ ' at processText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:278:31)', + \ ' at CLIEngine.executeOnText (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli-engine.js:734:26)', + \ ' at Object.execute (/home/w0rp/git/wazoku/wazoku-spotlight/spotlight/static/node_modules/eslint/lib/cli.js:171:42) ', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(g:config_error_lines, "\n"), + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) + +Execute(The eslint handler should hint about using typescript-eslint-parser): + call ale#test#SetFilename('foo.ts') + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'Parsing error (You may need configure typescript-eslint-parser): The keyword ''interface'' is reserved', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"foo.ts","messages":[{"ruleId":null,"fatal":true,"severity":2,"message":"Parsing error: The keyword ''interface'' is reserved","line":2,"column":1}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\ninterface test {}\n"}]', + \ ]) + +Execute(eslint should warn about ignored files by default): + AssertEqual + \ [{ + \ 'lnum': 0, + \ 'type': 'W', + \ 'text': 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.' + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"/path/to/some/ignored/file.js","messages":[{"fatal":false,"severity":1,"message":"File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0}]', + \ ]) + + AssertEqual + \ [{ + \ 'lnum': 0, + \ 'type': 'W', + \ 'text': 'File ignored by default. Use "--ignore-pattern ''!node_modules/*''" to override.', + \ }], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"/path/to/some/ignored/file.js","messages":[{"fatal":false,"severity":1,"message":"File ignored by default. Use \"--ignore-pattern ''!node_modules/*''\" to override."}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0}]', + \ ]) + +Execute(eslint should not warn about ignored files when explicitly disabled): + let g:ale_javascript_eslint_suppress_eslintignore = 1 + + AssertEqual + \ [], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"/path/to/some/ignored/file.js","messages":[{"fatal":false,"severity":1,"message":"File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0}]', + \ ]) + + AssertEqual + \ [], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"/path/to/some/ignored/file.js","messages":[{"fatal":false,"severity":1,"message":"File ignored by default. Use \"--ignore-pattern ''!node_modules/*''\" to override."}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0}]', + \ ]) + +Execute(Failing to connect to eslint_d should be handled correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'Could not connect to eslint_d. Try updating eslint_d or killing it.', + \ }, + \ ], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ 'Could not connect', + \ ]) + +Execute(Disabling warnings about trailing spaces should work): + call ale#test#SetFilename('foo.js') + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 16, + \ 'code': 'no-trailing-spaces', + \ 'type': 'W', + \ 'text': 'Trailing spaces not allowed.', + \ }, + \ ], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"foo.js","messages":[{"ruleId":"no-trailing-spaces","severity":1,"message":"Trailing spaces not allowed.","line":2,"column":16,"nodeType":"Program","fix":{"range":[16,17],"text":""}}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":1,"source":"\nconsole.log(1); \n"}]' + \ ]) + + let g:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"foo.js","messages":[{"ruleId":"no-trailing-spaces","severity":1,"message":"Trailing spaces not allowed.","line":2,"column":16,"nodeType":"Program","fix":{"range":[16,17],"text":""}}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":1,"source":"\nconsole.log(1); \n"}]' + \ ]) + + let g:ale_warn_about_trailing_whitespace = 1 + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"foo.js","messages":[{"ruleId":"no-trailing-spaces","severity":1,"message":"Trailing spaces not allowed.","line":2,"column":16,"nodeType":"Program","fix":{"range":[16,17],"text":""}}],"errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":1,"source":"\nconsole.log(1); \n"}]' + \ ]) 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_fish_handler.vader b/test/handler/test_fish_handler.vader new file mode 100644 index 00000000..25942abb --- /dev/null +++ b/test/handler/test_fish_handler.vader @@ -0,0 +1,61 @@ +Before: + runtime ale_linters/fish/fish.vim + +After: + call ale#linter#Reset() + +Execute(The fish handler should handle basic warnings and syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 20, + \ 'col': 23, + \ 'text': "Unsupported use of '||'. In fish, please use 'COMMAND; or COMMAND'.", + \ }, + \ { + \ 'lnum': 26, + \ 'col': 7, + \ 'text': "Illegal command name '(prompt_pwd)'", + \ }, + \ { + \ 'lnum': 36, + \ 'col': 1, + \ 'text': "'end' outside of a block", + \ }, + \ ], + \ ale_linters#fish#fish#Handle(1, [ + \ "fish_prompt.fish (line 20): Unsupported use of '||'. In fish, please use 'COMMAND; or COMMAND'.", + \ 'if set -q SSH_CLIENT || set -q SSH_TTY', + \ ' ^', + \ "fish_prompt.fish (line 26): Illegal command name '(prompt_pwd)'", + \ ' (prompt_pwd) \', + \ ' ^', + \ "fish_prompt.fish (line 36): 'end' outside of a block", + \ 'end', + \ '^', + \ 'config.fish (line 45):', + \ "abbr --add p 'cd ~/Projects'", + \ '^', + \ ]) + +Execute(The fish handler should handle problems where the problem before before the line with the line number): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 23, + \ 'text': 'Unsupported use of ''||''. In fish, please use ''COMMAND; or COMMAND''.', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 1, + \ 'text': 'wat', + \ }, + \ ], + \ ale_linters#fish#fish#Handle(bufnr(''), [ + \ 'Unsupported use of ''||''. In fish, please use ''COMMAND; or COMMAND''.', + \ '/tmp/vLz620o/258/test.fish (line 2): if set -q SSH_CLIENT || set -q SSH_TTY', + \ ' ^', + \ '/tmp/vLz620o/258/test.fish (line 5): wat', + \ ' ^', + \ ]) diff --git a/test/handler/test_flake8_handler.vader b/test/handler/test_flake8_handler.vader new file mode 100644 index 00000000..1c9956fa --- /dev/null +++ b/test/handler/test_flake8_handler.vader @@ -0,0 +1,276 @@ +Before: + Save g:ale_warn_about_trailing_blank_lines + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_blank_lines = 1 + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/python/flake8.vim + +After: + Restore + + unlet! b:ale_warn_about_trailing_blank_lines + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() + +Execute(The flake8 handler should handle basic warnings and syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 6, + \ 'vcol': 1, + \ 'type': 'E', + \ 'text': 'indentation is not a multiple of four', + \ 'code': 'E111', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 6, + \ 'vcol': 1, + \ 'type': 'W', + \ 'text': 'some warning', + \ 'code': 'W123', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 3, + \ 'vcol': 1, + \ 'type': 'E', + \ 'text': 'SyntaxError: invalid syntax', + \ 'code': 'E999', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(1, [ + \ 'stdin:6:6: E111 indentation is not a multiple of four', + \ 'stdin:7:6: W123 some warning', + \ 'stdin:8:3: E999 SyntaxError: invalid syntax', + \ ]) + +Execute(The flake8 handler should set end column indexes for certain errors): + AssertEqual + \ [ + \ { + \ 'lnum': 25, + \ 'col': 1, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 3, + \ 'text': 'undefined name ''foo''', + \ 'code': 'F821', + \ }, + \ { + \ 'lnum': 28, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': 'hello may be undefined, or defined from star imports: x', + \ 'code': 'F405', + \ }, + \ { + \ 'lnum': 104, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 12, + \ 'text': '''continue'' not properly in loop', + \ 'code': 'F999', + \ }, + \ { + \ 'lnum': 106, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': '''break'' outside loop', + \ 'code': 'F999', + \ }, + \ { + \ 'lnum': 109, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 8, + \ 'text': 'local variable ''test'' is assigned to but never used', + \ 'code': 'F841', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(1, [ + \ 'foo.py:25:1: F821 undefined name ''foo''', + \ 'foo.py:28:5: F405 hello may be undefined, or defined from star imports: x', + \ 'foo.py:104:5: F999 ''continue'' not properly in loop', + \ 'foo.py:106:5: F999 ''break'' outside loop', + \ 'foo.py:109:5: F841 local variable ''test'' is assigned to but never used', + \ ]) + +Execute(The flake8 handler should handle stack traces): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'ImportError: No module named parser (See :ALEDetail)', + \ 'detail': join([ + \ 'Traceback (most recent call last):', + \ ' File "/usr/local/bin/flake8", line 7, in ', + \ ' from flake8.main.cli import main', + \ ' File "/usr/local/lib/python2.7/dist-packages/flake8/main/cli.py", line 2, in ', + \ ' from flake8.main import application', + \ ' File "/usr/local/lib/python2.7/dist-packages/flake8/main/application.py", line 17, in ', + \ ' from flake8.plugins import manager as plugin_manager', + \ ' File "/usr/local/lib/python2.7/dist-packages/flake8/plugins/manager.py", line 5, in ', + \ ' import pkg_resources', + \ ' File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 35, in ', + \ ' import email.parser', + \ 'ImportError: No module named parser', + \ ], "\n"), + \ }, + \ ], + \ ale_linters#python#flake8#Handle(42, [ + \ 'Traceback (most recent call last):', + \ ' File "/usr/local/bin/flake8", line 7, in ', + \ ' from flake8.main.cli import main', + \ ' File "/usr/local/lib/python2.7/dist-packages/flake8/main/cli.py", line 2, in ', + \ ' from flake8.main import application', + \ ' File "/usr/local/lib/python2.7/dist-packages/flake8/main/application.py", line 17, in ', + \ ' from flake8.plugins import manager as plugin_manager', + \ ' File "/usr/local/lib/python2.7/dist-packages/flake8/plugins/manager.py", line 5, in ', + \ ' import pkg_resources', + \ ' File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 35, in ', + \ ' import email.parser', + \ 'ImportError: No module named parser', + \ ]) + +Execute(The flake8 handler should handle names with spaces): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 6, + \ 'vcol': 1, + \ 'type': 'E', + \ 'text': 'indentation is not a multiple of four', + \ 'code': 'E111', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(42, [ + \ 'C:\something\with spaces.py:6:6: E111 indentation is not a multiple of four', + \ ]) + +Execute(Warnings about trailing whitespace should be reported by default): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'W291', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'who cares', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'W293', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'who cares', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: W291 who cares', + \ 'foo.py:6:1: W293 who cares', + \ ]) + +Execute(Disabling trailing whitespace warnings should work): + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: W291 who cares', + \ 'foo.py:6:1: W293 who cares', + \ ]) + +Execute(Warnings about trailing blank lines should be reported by default): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'W391', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'blank line at end of file', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: W391 blank line at end of file', + \ ]) + +Execute(Disabling trailing blank line warnings should work): + let b:ale_warn_about_trailing_blank_lines = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: W391 blank line at end of file', + \ ]) + +Execute(F401 should be a warning): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'F401', + \ 'type': 'W', + \ 'text': 'module imported but unused', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: F401 module imported but unused', + \ ]) + +Execute(E112 should be a syntax error): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'E112', + \ 'type': 'E', + \ 'text': 'expected an indented block', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: E112 expected an indented block', + \ ]) + +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_flakehell_handler.vader b/test/handler/test_flakehell_handler.vader new file mode 100644 index 00000000..1f77bd96 --- /dev/null +++ b/test/handler/test_flakehell_handler.vader @@ -0,0 +1,276 @@ +Before: + Save g:ale_warn_about_trailing_blank_lines + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_blank_lines = 1 + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/python/flakehell.vim + +After: + Restore + + unlet! b:ale_warn_about_trailing_blank_lines + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() + +Execute(The flakehell handler should handle basic warnings and syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 6, + \ 'vcol': 1, + \ 'type': 'E', + \ 'text': 'indentation is not a multiple of four', + \ 'code': 'E111', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 6, + \ 'vcol': 1, + \ 'type': 'W', + \ 'text': 'some warning', + \ 'code': 'W123', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 3, + \ 'vcol': 1, + \ 'type': 'E', + \ 'text': 'SyntaxError: invalid syntax', + \ 'code': 'E999', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(1, [ + \ 'stdin:6:6: E111 indentation is not a multiple of four', + \ 'stdin:7:6: W123 some warning', + \ 'stdin:8:3: E999 SyntaxError: invalid syntax', + \ ]) + +Execute(The flakehell handler should set end column indexes for certain errors): + AssertEqual + \ [ + \ { + \ 'lnum': 25, + \ 'col': 1, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 3, + \ 'text': 'undefined name ''foo''', + \ 'code': 'F821', + \ }, + \ { + \ 'lnum': 28, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': 'hello may be undefined, or defined from star imports: x', + \ 'code': 'F405', + \ }, + \ { + \ 'lnum': 104, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 12, + \ 'text': '''continue'' not properly in loop', + \ 'code': 'F999', + \ }, + \ { + \ 'lnum': 106, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': '''break'' outside loop', + \ 'code': 'F999', + \ }, + \ { + \ 'lnum': 109, + \ 'col': 5, + \ 'vcol': 1, + \ 'type': 'E', + \ 'end_col': 8, + \ 'text': 'local variable ''test'' is assigned to but never used', + \ 'code': 'F841', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(1, [ + \ 'foo.py:25:1: F821 undefined name ''foo''', + \ 'foo.py:28:5: F405 hello may be undefined, or defined from star imports: x', + \ 'foo.py:104:5: F999 ''continue'' not properly in loop', + \ 'foo.py:106:5: F999 ''break'' outside loop', + \ 'foo.py:109:5: F841 local variable ''test'' is assigned to but never used', + \ ]) + +Execute(The flakehell handler should handle stack traces): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'ImportError: No module named parser (See :ALEDetail)', + \ 'detail': join([ + \ 'Traceback (most recent call last):', + \ ' File "/usr/local/bin/flakehell", line 7, in ', + \ ' from flakehell.main.cli import main', + \ ' File "/usr/local/lib/python2.7/dist-packages/flakehell/main/cli.py", line 2, in ', + \ ' from flakehell.main import application', + \ ' File "/usr/local/lib/python2.7/dist-packages/flakehell/main/application.py", line 17, in ', + \ ' from flakehell.plugins import manager as plugin_manager', + \ ' File "/usr/local/lib/python2.7/dist-packages/flakehell/plugins/manager.py", line 5, in ', + \ ' import pkg_resources', + \ ' File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 35, in ', + \ ' import email.parser', + \ 'ImportError: No module named parser', + \ ], "\n"), + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(42, [ + \ 'Traceback (most recent call last):', + \ ' File "/usr/local/bin/flakehell", line 7, in ', + \ ' from flakehell.main.cli import main', + \ ' File "/usr/local/lib/python2.7/dist-packages/flakehell/main/cli.py", line 2, in ', + \ ' from flakehell.main import application', + \ ' File "/usr/local/lib/python2.7/dist-packages/flakehell/main/application.py", line 17, in ', + \ ' from flakehell.plugins import manager as plugin_manager', + \ ' File "/usr/local/lib/python2.7/dist-packages/flakehell/plugins/manager.py", line 5, in ', + \ ' import pkg_resources', + \ ' File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 35, in ', + \ ' import email.parser', + \ 'ImportError: No module named parser', + \ ]) + +Execute(The flakehell handler should handle names with spaces): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 6, + \ 'vcol': 1, + \ 'type': 'E', + \ 'text': 'indentation is not a multiple of four', + \ 'code': 'E111', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(42, [ + \ 'C:\something\with spaces.py:6:6: E111 indentation is not a multiple of four', + \ ]) + +Execute(Warnings about trailing whitespace should be reported by default): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'W291', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'who cares', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'W293', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'who cares', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(bufnr(''), [ + \ 'foo.py:6:1: W291 who cares', + \ 'foo.py:6:1: W293 who cares', + \ ]) + +Execute(Disabling trailing whitespace warnings should work): + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#python#flakehell#Handle(bufnr(''), [ + \ 'foo.py:6:1: W291 who cares', + \ 'foo.py:6:1: W293 who cares', + \ ]) + +Execute(Warnings about trailing blank lines should be reported by default): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'W391', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'blank line at end of file', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(bufnr(''), [ + \ 'foo.py:6:1: W391 blank line at end of file', + \ ]) + +Execute(Disabling trailing blank line warnings should work): + let b:ale_warn_about_trailing_blank_lines = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#python#flakehell#Handle(bufnr(''), [ + \ 'foo.py:6:1: W391 blank line at end of file', + \ ]) + +Execute(F401 should be a warning): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'F401', + \ 'type': 'W', + \ 'text': 'module imported but unused', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(bufnr(''), [ + \ 'foo.py:6:1: F401 module imported but unused', + \ ]) + +Execute(E112 should be a syntax error): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'E112', + \ 'type': 'E', + \ 'text': 'expected an indented block', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(bufnr(''), [ + \ 'foo.py:6:1: E112 expected an indented block', + \ ]) + +Execute(Compatibility with hacking which uses older style flakehell): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'vcol': 1, + \ 'code': 'H306', + \ 'type': 'W', + \ 'text': 'imports not in alphabetical order (smtplib, io)', + \ }, + \ ], + \ ale_linters#python#flakehell#Handle(bufnr(''), [ + \ 'foo.py:6:1: H306: imports not in alphabetical order (smtplib, io)', + \ ]) diff --git a/test/handler/test_flawfinder_handler.vader b/test/handler/test_flawfinder_handler.vader new file mode 100644 index 00000000..01dd1698 --- /dev/null +++ b/test/handler/test_flawfinder_handler.vader @@ -0,0 +1,47 @@ +Before: + Save b:ale_c_flawfinder_error_severity + runtime ale_linters/c/flawfinder.vim + +After: + Restore + call ale#linter#Reset() + +Execute(The Flawfinder handler should work): + AssertEqual + \ [ + \ { + \ 'lnum': 31, + \ 'col': 4, + \ 'type': 'W', + \ 'text': "(buffer) strncpy: Easily used incorrectly", + \ }, + \ ], + \ ale#handlers#flawfinder#HandleFlawfinderFormat(347, [ + \ ':31:4: [1] (buffer) strncpy:Easily used incorrectly', + \ 'foo', + \ 'bar', + \ 'baz', + \ ]) + +Execute(The Flawfinder error severity level should be configurable): + let b:ale_c_flawfinder_error_severity = 2 + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col': 4, + \ 'type': 'E', + \ 'text': "(buffer) char: Statically-sized arrays can be bad", + \ }, + \ { + \ 'lnum': 31, + \ 'col': 4, + \ 'type': 'W', + \ 'text': "(buffer) strncpy: Easily used incorrectly", + \ }, + \ ], + \ ale#handlers#flawfinder#HandleFlawfinderFormat(bufnr(''), [ + \ ':12:4: [2] (buffer) char:Statically-sized arrays can be bad', + \ ':31:4: [1] (buffer) strncpy:Easily used incorrectly', + \ ]) diff --git a/test/handler/test_flow_handler.vader b/test/handler/test_flow_handler.vader new file mode 100644 index 00000000..055ba026 --- /dev/null +++ b/test/handler/test_flow_handler.vader @@ -0,0 +1,507 @@ +Before: + runtime ale_linters/javascript/flow.vim + +After: + unlet! g:flow_output + unlet! g:expected + unlet! g:actual + call ale#linter#Reset() + +Execute(The flow handler should throw away non-JSON lines): + AssertEqual + \ [], + \ ale_linters#javascript#flow#Handle(bufnr(''), [ + \ 'Already up-to-date.', + \ '{"flowVersion":"0.50.0","errors":[],"passed":true}', + \ ]) + AssertEqual + \ [], + \ ale_linters#javascript#flow#Handle(bufnr(''), [ + \ 'foo', + \ 'bar', + \ 'baz', + \ '{"flowVersion":"0.50.0","errors":[],"passed":true}', + \ ]) + +Execute(The flow handler should process errors correctly.): + silent! noautocmd file /home/w0rp/Downloads/graphql-js/src/language/parser.js + + let g:flow_output = { + \ "flowVersion": "0.39.0", + \ "errors": [ + \ { + \ "kind": "infer", + \ "level": "error", + \ "message": [ + \ { + \ "context": " return 1", + \ "descr": "number", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 417, + \ "column": 10, + \ "offset": 9503 + \ }, + \ "end": { + \ "line": 417, + \ "column": 10, + \ "offset": 9504 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 417, + \ "endline": 417, + \ "start": 10, + \ "end": 10 + \ }, + \ { + \ "context": v:null, + \ "descr": "This type is incompatible with the expected return type of", + \ "type": "Comment", + \ "path": "", + \ "line": 0, + \ "endline": 0, + \ "start": 1, + \ "end": 0 + \ }, + \ { + \ "context": "function parseArguments(lexer: Lexer<*>): Array {", + \ "descr": "array type", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 416, + \ "column": 43, + \ "offset": 9472 + \ }, + \ "end": { + \ "line": 416, + \ "column": 61, + \ "offset": 9491 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 416, + \ "endline": 416, + \ "start": 43, + \ "end": 61 + \ } + \ ] + \ }, + \ { + \ "kind": "infer", + \ "level": "warning", + \ "message": [ + \ { + \ "context": " return peek(lexer, TokenKind.PAREN_L) ?", + \ "descr": "unreachable code", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 419, + \ "column": 3, + \ "offset": 9508 + \ }, + \ "end": { + \ "line": 421, + \ "column": 7, + \ "offset": 9626 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 419, + \ "endline": 421, + \ "start": 3, + \ "end": 7 + \ } + \ ] + \ } + \ ], + \ "passed": v:false + \} + + let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) + let g:expected = [ + \ { + \ 'lnum': 417, + \ 'type': 'E', + \ 'col': 10, + \ 'text': 'number: This type is incompatible with the expected return type of array type', + \ }, + \ { + \ 'lnum': 419, + \ 'type': 'W', + \ 'col': 3, + \ 'text': 'unreachable code:', + \ }, + \] + + AssertEqual g:expected, g:actual + +Execute(The flow handler should fetch the correct location for the currently opened file, even when it's not in the first message.): + silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js + + let g:flow_output = { + \ "flowVersion": "0.44.0", + \ "errors": [{ + \ "operation": { + \ "context": " , document.getElementById('foo')", + \ "descr": "React element `Foo`", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 6, + \ "endline": 6, + \ "start": 3, + \ "end": 18 + \ }, + \ "kind": "infer", + \ "level": "error", + \ "message": [{ + \ "context": "module.exports = function(props: Props) {", + \ "descr": "property `bar`", + \ "type": "Blame", + \ "loc": { + \ "source": "/Users/rav/Projects/vim-ale-flow/foo.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 9, + \ "column": 34, + \ "offset": 121 + \ }, + \ "end": { + \ "line": 9, + \ "column": 38, + \ "offset": 126 + \ } + \ }, + \ "path": "/Users/rav/Projects/vim-ale-flow/foo.js", + \ "line": 9, + \ "endline": 9, + \ "start": 34, + \ "end": 38 + \ }, { + \ "context": v:null, + \ "descr": "Property not found in", + \ "type": "Comment", + \ "path": "", + \ "line": 0, + \ "endline": 0, + \ "start": 1, + \ "end": 0 + \ }, { + \ "context": " , document.getElementById('foo')", + \ "descr": "props of React element `Foo`", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 6, + \ "endline": 6, + \ "start": 3, + \ "end": 18 + \ }] + \ }], + \ "passed": v:false + \} + + let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) + let g:expected = [ + \ { + \ 'lnum': 6, + \ 'col': 3, + \ 'type': 'E', + \ 'text': 'property `bar`: Property not found in props of React element `Foo` See also: React element `Foo`', + \ } + \] + + AssertEqual g:expected, g:actual + +Execute(The flow handler should handle relative paths): + silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js + + let g:flow_output = { + \ "flowVersion": "0.44.0", + \ "errors": [{ + \ "operation": { + \ "context": " , document.getElementById('foo')", + \ "descr": "React element `Foo`", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 6, + \ "endline": 6, + \ "start": 3, + \ "end": 18 + \ }, + \ "kind": "infer", + \ "level": "error", + \ "message": [{ + \ "context": "module.exports = function(props: Props) {", + \ "descr": "property `bar`", + \ "type": "Blame", + \ "loc": { + \ "source": "vim-ale-flow/foo.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 9, + \ "column": 34, + \ "offset": 121 + \ }, + \ "end": { + \ "line": 9, + \ "column": 38, + \ "offset": 126 + \ } + \ }, + \ "path": "vim-ale-flow/foo.js", + \ "line": 9, + \ "endline": 9, + \ "start": 34, + \ "end": 38 + \ }, { + \ "context": v:null, + \ "descr": "Property not found in", + \ "type": "Comment", + \ "path": "", + \ "line": 0, + \ "endline": 0, + \ "start": 1, + \ "end": 0 + \ }, { + \ "context": " , document.getElementById('foo')", + \ "descr": "props of React element `Foo`", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 6, + \ "endline": 6, + \ "start": 3, + \ "end": 18 + \ }] + \ }], + \ "passed": v:false + \} + + let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) + let g:expected = [ + \ { + \ 'lnum': 6, + \ 'col': 3, + \ 'type': 'E', + \ 'text': 'property `bar`: Property not found in props of React element `Foo` See also: React element `Foo`', + \ } + \] + + AssertEqual g:expected, g:actual + +Execute(The flow handler should handle extra errors): + silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js + + let g:flow_output = { + \ "flowVersion": "0.54.0", + \ "errors": [{ + \ "extra": [{ + \ "message": [{ + \ "context": v:null, + \ "descr": "Property \`setVector\` is incompatible:", + \ "type": "Blame ", + \ "path": "", + \ "line": 0, + \ "endline": 0, + \ "start": 1, + \ "end": 0 + \ }], + \ "children": [{ + \ "message": [{ + \ "context": "setVector = \{2\}", + \ "descr": "number ", + \ "type": "Blame ", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile ", + \ "start": { + \ "line": 90, + \ "column": 30, + \ "offset": 2296 + \ }, + \ "end": { + \ "line": 90, + \ "column": 30, + \ "offset": 2297 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 90, + \ "endline": 90, + \ "start": 30, + \ "end": 30 + \ }, { + \ "context": v:null, + \ "descr": "This type is incompatible with ", + \ "type": "Comment ", + \ "path": "", + \ "line": 0, + \ "endline": 0, + \ "start": 1, + \ "end": 0 + \ }, { + \ "context": "setVector: VectorType => void,", + \ "descr": "function type ", + \ "type": "Blame ", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 9, + \ "column": 14, + \ "offset": 252 + \ }, + \ "end": { + \ "line": 9, + \ "column": 31, + \ "offset": 270 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 9, + \ "endline": 9, + \ "start": 14, + \ "end": 31 + \ }] + \ }] + \ }], + \ "kind": "infer", + \ "level": "error", + \ "suppressions": [], + \ "message": [{ + \ "context": " < New ", + \ "descr": "props of React element `New`", + \ "type": "Blame", + \ "loc": { + \ "source": "vim-ale-flow/foo.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 89, + \ "column": 17, + \ "offset": 2262 + \ }, + \ "end": { + \ "line": 94, + \ "column": 18, + \ "offset": 2488 + \ } + \ }, + \ "path": "", + \ "line": 89, + \ "endline": 94, + \ "start": 17, + \ "end": 18 + \ }, { + \ "context": v:null, + \ "descr": "This type is incompatible with", + \ "type": "Comment", + \ "path": "", + \ "line": 0, + \ "endline": 0, + \ "start": 1, + \ "end": 0 + \ }, { + \ "context": "class New extends React.Component < NewProps,NewState > {", + \ "descr": "object type", + \ "type": "Blame", + \ "loc": { + \ "source": expand('%:p'), + \ "type": "SourceFile", + \ "start": { + \ "line": 20, + \ "column": 35, + \ "offset": 489 + \ }, + \ "end": { + \ "line": 20, + \ "column": 42, + \ "offset": 497 + \ } + \ }, + \ "path": expand('%:p'), + \ "line": 20, + \ "endline": 20, + \ "start": 35, + \ "end": 42 + \ }] + \ }], + \ "passed": v:false + \} + + let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) + let g:expected = [ + \ { + \ 'lnum': 20, + \ 'col': 35, + \ 'type': 'E', + \ 'text': 'props of React element `New`: This type is incompatible with object 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 ", + \ } + \] + + AssertEqual g:expected, g:actual diff --git a/test/handler/test_foodcritic_handler.vader b/test/handler/test_foodcritic_handler.vader new file mode 100644 index 00000000..67cb6cab --- /dev/null +++ b/test/handler/test_foodcritic_handler.vader @@ -0,0 +1,44 @@ +Before: + runtime ale_linters/chef/foodcritic.vim + +After: + call ale#linter#Reset() + +Execute(Basic warnings should be handled): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'code': 'CINK001', + \ 'type': 'W', + \ 'text': 'Missing CHANGELOG in markdown format', + \ 'filename': '/foo/bar/CHANGELOG.md', + \ }, + \ { + \ 'lnum': 1, + \ 'code': 'FC011', + \ 'type': 'W', + \ 'text': 'Missing README in markdown format', + \ 'filename': '/foo/bar/README.md', + \ }, + \ { + \ 'lnum': 1, + \ 'code': 'FC031', + \ 'type': 'W', + \ 'text': 'Cookbook without metadata.rb file', + \ 'filename': '/foo/bar/metadata.rb', + \ }, + \ { + \ 'lnum': 1, + \ 'code': 'FC071', + \ 'type': 'W', + \ 'text': 'Missing LICENSE file', + \ 'filename': '/foo/bar/LICENSE', + \ }, + \ ], + \ ale_linters#chef#foodcritic#Handle(bufnr(''), [ + \ 'CINK001: Missing CHANGELOG in markdown format: /foo/bar/CHANGELOG.md:1', + \ 'FC011: Missing README in markdown format: /foo/bar/README.md:1', + \ 'FC031: Cookbook without metadata.rb file: /foo/bar/metadata.rb:1', + \ 'FC071: Missing LICENSE file: /foo/bar/LICENSE:1', + \ ]) diff --git a/test/handler/test_fortran_handler.vader b/test/handler/test_fortran_handler.vader new file mode 100644 index 00000000..50ed5a18 --- /dev/null +++ b/test/handler/test_fortran_handler.vader @@ -0,0 +1,32 @@ +Before: + runtime ale_linters/fortran/gcc.vim + +After: + call ale#linter#Reset() + +Execute(The fortran handler should parse lines from GCC 6.3.1 correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 337, + \ 'lnum': 3, + \ 'col': 12, + \ 'text': "Symbol ‘a’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': 337, + \ 'lnum': 4, + \ 'col': 12, + \ 'text': "Symbol ‘b’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#fortran#gcc#Handle(337, [ + \ ":3:12:", + \ "", + \ "Error: Symbol ‘a’ at (1) has no IMPLICIT type", + \ ":4:12:", + \ "", + \ "Error: Symbol ‘b’ at (1) has no IMPLICIT type", + \ ]) diff --git a/test/handler/test_gawk_handler.vader b/test/handler/test_gawk_handler.vader new file mode 100644 index 00000000..52d01da8 --- /dev/null +++ b/test/handler/test_gawk_handler.vader @@ -0,0 +1,38 @@ +Before: + runtime ale_linters/awk/gawk.vim + +After: + call ale#linter#Reset() + +Execute(gawk syntax errors should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'text': "invalid char ''' in expression", + \ 'code': 0, + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 0, + \ 'text': 'unterminated string', + \ 'code': 0, + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 0, + \ 'text': "escape sequence `\u' treated as plain `u'", + \ 'code': 0, + \ 'type': 'W', + \ }, + \ ], + \ ale#handlers#gawk#HandleGawkFormat(347, [ + \ "gawk: something.awk:1: BEGIN { system('touch aaaaaaaaa') }", + \ "gawk: something.awk:1: ^ invalid char ''' in expression", + \ 'gawk: something.awk:5: { x = "aaaaaaaaaaa', + \ 'gawk: something.awk:5: ^ unterminated string', + \ "gawk: something.awk:10: warning: escape sequence `\u' treated as plain `u'", + \ ]) diff --git a/test/handler/test_gcc_handler.vader b/test/handler/test_gcc_handler.vader new file mode 100644 index 00000000..e95734bd --- /dev/null +++ b/test/handler/test_gcc_handler.vader @@ -0,0 +1,312 @@ +Execute(The GCC handler should ignore other lines of output): + AssertEqual + \ [], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, ['foo', 'bar', 'baz']) + +Execute(GCC errors from included files should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'filename': 'broken.h', + \ 'type': 'E', + \ 'text': 'expected identifier or ''('' before ''{'' token', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 2, + \ 'text': 'Error found in header. See :ALEDetail', + \ 'detail': join([ + \ 'In file included from :3:2:', + \ 'broken.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ 'In file included from :3:2:', + \ 'broken.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ 'compilation terminated.', + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'filename': 'b.h', + \ 'type': 'E', + \ 'text': 'expected identifier or ''('' before ''{'' token', + \ }, + \ { + \ 'lnum': 5, + \ 'text': 'Error found in header. See :ALEDetail', + \ 'detail': join([ + \ 'In file included from a.h:1:0,', + \ ' from :5:', + \ 'b.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ 'In file included from a.h:1:0,', + \ ' from :5:', + \ 'b.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ 'compilation terminated.', + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'filename': 'b.h', + \ 'type': 'E', + \ 'text': 'unknown type name ''bad_type''', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'filename': 'b.h', + \ 'type': 'E', + \ 'text': 'unknown type name ''other_bad_type''', + \ }, + \ { + \ 'lnum': 3, + \ 'text': 'Error found in header. See :ALEDetail', + \ 'detail': join([ + \ 'In file included from a.h:1:0,', + \ ' from :3:', + \ 'b.h:1:1: error: unknown type name ‘bad_type’', + \ ' bad_type x;', + \ ' ^', + \ 'b.h:2:1: error: unknown type name ‘other_bad_type’', + \ ' other_bad_type y;', + \ ' ^', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ 'In file included from a.h:1:0,', + \ ' from :3:', + \ 'b.h:1:1: error: unknown type name ‘bad_type’', + \ ' bad_type x;', + \ ' ^', + \ 'b.h:2:1: error: unknown type name ‘other_bad_type’', + \ ' other_bad_type y;', + \ ' ^', + \ 'compilation terminated.', + \ ]) + +Execute(The GCC handler shouldn't complain about #pragma once for headers): + silent file! test.h + + AssertEqual + \ [], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ ':1:1: warning: #pragma once in main file [enabled by default]', + \ ]) + + silent file! test.hpp + + AssertEqual + \ [], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ ':1:1: warning: #pragma once in main file [enabled by default]', + \ ]) + +Execute(The GCC handler should handle syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 12, + \ 'type': 'E', + \ 'text': 'invalid suffix "p" on integer constant' + \ }, + \ { + \ 'lnum': 17, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'invalid suffix "n" on integer constant' + \ }, + \ { + \ 'lnum': 4, + \ 'type': 'E', + \ 'text': 'variable or field ''foo'' declared void' + \ }, + \ { + \ 'lnum': 4, + \ 'type': 'E', + \ 'text': '''cat'' was not declared in this scope' + \ }, + \ { + \ 'lnum': 12, + \ 'type': 'E', + \ 'text': 'expected '';'' before ''o''' + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ ':6:12: error: invalid suffix "p" on integer constant', + \ ':17:5: error: invalid suffix "n" on integer constant', + \ ':4: error: variable or field ''foo'' declared void', + \ ':4: error: ''cat'' was not declared in this scope', + \ ':12: error: expected `;'' before ''o''', + \ ]) + +Execute(The GCC handler should handle notes with no previous message): + AssertEqual + \ [], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ ':1:1: note: x', + \ ':1:1: note: x', + \ ]) + +Execute(The GCC handler should attach notes to previous messages): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 12, + \ 'type': 'E', + \ 'text': 'Some error', + \ 'detail': "Some error\n:1:1: note: x", + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ '-:6:12: error: Some error', + \ ':1:1: note: x', + \ ]) + +Execute(The GCC handler should interpret - as being the current file): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 12, + \ 'type': 'E', + \ 'text': 'Some error', + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ '-:6:12: error: Some error', + \ ]) + +Execute(The GCC handler should handle fatal error messages due to missing files): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 12, + \ 'type': 'E', + \ 'text': 'foo.h: No such file or directory' + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ ':3:12: fatal error: foo.h: No such file or directory', + \ ]) + +Execute(The GCC handler should handle errors for inlined header functions): + AssertEqual + \ [ + \ { + \ 'lnum': 50, + \ 'col': 4, + \ 'filename': '/usr/include/bits/fcntl2.h', + \ 'type': 'E', + \ 'text': 'call to ''__open_missing_mode'' declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments', + \ }, + \ { + \ 'lnum': 44, + \ 'col': 5, + \ 'filename': '/usr/include/bits/fcntl2.h', + \ 'type': 'E', + \ 'text': 'call to ''__open_too_many_args'' declared with attribute error: open can be called either with 2 or 3 arguments, not more', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 10, + \ 'type': 'E', + \ 'text': 'call to ''__open_missing_mode'' declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments', + \ }, + \ { + \ 'lnum': 13, + \ 'col': 11, + \ 'type': 'E', + \ 'text': 'call to ''__open_too_many_args'' declared with attribute error: open can be called either with 2 or 3 arguments, not more', + \ }, + \ { + \ 'lnum': 1, + \ 'text': 'Error found in header. See :ALEDetail', + \ 'detail': join([ + \ 'In file included from /usr/include/fcntl.h:328,', + \ ' from :1:', + \ 'In function ‘open’,', + \ ' inlined from ‘main’ at :7:10:', + \ '/usr/include/bits/fcntl2.h:50:4: error: call to ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments', + \ ' __open_missing_mode ();', + \ ' ^~~~~~~~~~~~~~~~~~~~~~', + \ 'In function ‘open’,', + \ ' inlined from ‘main’ at :13:11:', + \ '/usr/include/bits/fcntl2.h:44:5: error: call to ‘__open_too_many_args’ declared with attribute error: open can be called either with 2 or 3 arguments, not more', + \ ' __open_too_many_args ();', + \ ], "\n") + \ }, + \], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ 'In file included from /usr/include/fcntl.h:328,', + \ ' from :1:', + \ 'In function ‘open’,', + \ ' inlined from ‘main’ at :7:10:', + \ '/usr/include/bits/fcntl2.h:50:4: error: call to ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments', + \ ' __open_missing_mode ();', + \ ' ^~~~~~~~~~~~~~~~~~~~~~', + \ 'In function ‘open’,', + \ ' inlined from ‘main’ at :13:11:', + \ '/usr/include/bits/fcntl2.h:44:5: error: call to ‘__open_too_many_args’ declared with attribute error: open can be called either with 2 or 3 arguments, not more', + \ ' __open_too_many_args ();', + \ ' ^~~~~~~~~~~~~~~~~~~~~~~', + \ ]) + +Execute(The GCC handler should handle macro expansion errors in current file): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 19, + \ 'type': 'E', + \ 'text': 'error message', + \ 'detail': "error message\n:1:19: note: in expansion of macro 'TEST'", + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ ': error: error message', + \ ':1:19: note: in expansion of macro ‘TEST’', + \ ' 1 | std::string str = TEST;', + \ ' | ^~~~', + \ ]) + +Execute(The GCC handler should handle macro expansion errors in other files): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'type': 'E', + \ 'text': 'Error found in macro expansion. See :ALEDetail', + \ 'detail': "error message\ninc.h:1:19: note: in expansion of macro 'TEST'", + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ ': error: error message', + \ 'inc.h:1:19: note: in expansion of macro ‘TEST’', + \ ' 1 | std::string str = TEST;', + \ ' | ^~~~', + \ ]) diff --git a/test/handler/test_ghc_handler.vader b/test/handler/test_ghc_handler.vader new file mode 100644 index 00000000..73cd725f --- /dev/null +++ b/test/handler/test_ghc_handler.vader @@ -0,0 +1,180 @@ +After: + unlet! g:detail + +Execute(The ghc handler should handle hdevtools output): + call ale#test#SetFilename('foo.hs') + + AssertEqual + \ [ + \ { + \ 'lnum': 147, + \ 'type': 'W', + \ 'col': 62, + \ 'text': "• Couldn't match type ‘a -> T.Text’ with ‘T.Text’ Expected type: [T.Text]", + \ 'detail': join([ + \ "• Couldn't match type ‘a -> T.Text’ with ‘T.Text’", + \ ' Expected type: [T.Text]', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ 'foo.hs:147:62: warning:', + \ "• Couldn't match type ‘a -> T.Text’ with ‘T.Text’", + \ ' Expected type: [T.Text]', + \ ]) + +Execute(The ghc handler should handle ghc 8 output): + call ale#test#SetFilename('src/Appoint/Lib.hs') + + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'type': 'E', + \ 'col': 1, + \ 'text': 'Failed to load interface for ‘GitHub.Data’ Use -v to see a list of the files searched for.', + \ 'detail': join([ + \ ' Failed to load interface for ‘GitHub.Data’', + \ ' Use -v to see a list of the files searched for.', + \ ], "\n"), + \ }, + \ { + \ 'lnum': 7, + \ 'type': 'W', + \ 'col': 1, + \ 'text': 'Failed to load interface for ‘GitHub.Endpoints.PullRequests’ Use -v to see a list of the files searched for.', + \ 'detail': join([ + \ ' Failed to load interface for ‘GitHub.Endpoints.PullRequests’', + \ ' Use -v to see a list of the files searched for.', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ '', + \ ale#path#Simplify('src/Appoint/Lib.hs') . ':6:1: error:', + \ ' Failed to load interface for ‘GitHub.Data’', + \ ' Use -v to see a list of the files searched for.', + \ '', + \ ale#path#Simplify('src/Appoint/Lib.hs') . ':7:1: warning:', + \ ' Failed to load interface for ‘GitHub.Endpoints.PullRequests’', + \ ' Use -v to see a list of the files searched for.', + \ ]) + +Execute(The ghc handler should handle ghc 7 output): + call ale#test#SetFilename('src/Main.hs') + + AssertEqual + \ [ + \ { + \ 'lnum': 168, + \ 'type': 'E', + \ 'col': 1, + \ 'text': 'parse error (possibly incorrect indentation or mismatched brackets)', + \ 'detail': join([ + \ ' parse error (possibly incorrect indentation or mismatched brackets)', + \ ], "\n"), + \ }, + \ { + \ 'lnum': 84, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'Top-level binding with no type signature: myLayout :: Choose Tall (Choose (Mirror Tall) Full) a', + \ 'detail': join([ + \ ' Top-level binding with no type signature:', + \ ' myLayout :: Choose Tall (Choose (Mirror Tall) Full) a', + \ ], "\n"), + \ }, + \ { + \ 'lnum': 94, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'Some other error', + \ 'detail': join([ + \ ' Some other error', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ ale#path#Simplify('src/Main.hs') . ':168:1:', + \ ' parse error (possibly incorrect indentation or mismatched brackets)', + \ ale#path#Simplify('src/Main.hs') . ':84:1:Warning:', + \ ' Top-level binding with no type signature:', + \ ' myLayout :: Choose Tall (Choose (Mirror Tall) Full) a', + \ ale#path#Simplify('src/Main.hs') . ':94:5:Error:', + \ ' Some other error', + \ ]) + +Execute(The ghc handler should handle stack 1.5.1 output): + call ale#test#SetFilename('src/Main.hs') + + AssertEqual + \ [ + \ { + \ 'lnum': 160, + \ 'col': 14, + \ 'type': 'E', + \ 'text': '• Expecting one fewer arguments to ‘Exp’ Expected kind ‘k0 -> *’, but ‘Exp’ has kind ‘*’ • In the type ‘Exp a’ | 160 | pattern F :: Exp a | ^^^^^', + \ 'detail': join([ + \ ' • Expecting one fewer arguments to ‘Exp’', + \ ' Expected kind ‘k0 -> *’, but ‘Exp’ has kind ‘*’', + \ ' • In the type ‘Exp a’', + \ ' |', + \ ' 160 | pattern F :: Exp a', + \ ' | ^^^^^', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ ' ' . ale#path#Simplify('src/Main.hs') . ':160:14: error:', + \ ' • Expecting one fewer arguments to ‘Exp’', + \ ' Expected kind ‘k0 -> *’, but ‘Exp’ has kind ‘*’', + \ ' • In the type ‘Exp a’', + \ ' |', + \ ' 160 | pattern F :: Exp a', + \ ' | ^^^^^', + \ ]) + +Execute(The ghc handler should handle ghc panic): + let g:detail = [ + \ '[15 of 15] Compiling SizedTypes.List', + \ 'ghc: panic! (the ''impossible'' happened)', + \ ' (GHC version 8.10.3:', + \ ' src/SizedTypes/List.hs:(46,19)-(50,0) Specified type does not refine Haskell type for `SizedTypes.List.out` (Plugged Init types new)', + \ ' The Liquid type', + \ ' .', + \ ' GHC.Types.Int -> (SizedTypes.List.List a) -> (_, (SizedTypes.List.List a))', + \ ' .', + \ ' is inconsistent with the Haskell type', + \ ' .', + \ ' forall p a ->', + \ 'p -> SizedTypes.List.List a -> (a, SizedTypes.List.List a)', + \ ' .', + \ ' defined at src/SizedTypes/List.hs:52:1-3', + \ ' .', + \ ' Specifically, the Liquid component', + \ ' .', + \ ' {VV##0 : GHC.Types.Int | VV##0 >= 0}', + \ ' .', + \ ' is inconsistent with the Haskell component', + \ ' .', + \ ' p', + \ ' .', + \ ' ', + \ ' HINT: Use the hole ''_'' instead of the mismatched component (in the Liquid specification)', + \ '', + \ 'Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug', + \ '', + \ '' + \ ] + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'ghc panic!', + \ 'detail': join(g:detail[1:-3], "\n"), + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), g:detail) + unlet g:detail diff --git a/test/handler/test_ghc_mod_handler.vader b/test/handler/test_ghc_mod_handler.vader new file mode 100644 index 00000000..bed5b13c --- /dev/null +++ b/test/handler/test_ghc_mod_handler.vader @@ -0,0 +1,37 @@ +Execute(HandleGhcFormat should handle ghc-mod problems): + call ale#test#SetFilename('check2.hs') + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Failed to load interface for ‘Missing’Use -v to see a list of the files searched for.', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Suggestion: Use camelCaseFound: my_variable = ...Why not: myVariable = ...', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'Eta reduceFound: myFunc x = succ xWhy not: myFunc = succ', + \ }, + \ { + \ 'lnum': 28, + \ 'col': 28, + \ 'type': 'W', + \ 'text': 'Defaulting the following constraints to type ‘Integer’ (Num a0) arising from the literal ‘3’ at check2.hs:28:28 (Eq a0) arising from a use of ‘lookup’ at check2.hs:28:21-28 • In the first argument of ‘lookup’, namely ‘3’ In the expression: lookup 3 In the second argument of ‘fmap’, namely ‘(lookup 3 $ zip [1, 2, 3] [4, 5, 6])''’' + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ 'check2.hs:2:1:Failed to load interface for ‘Missing’Use -v to see a list of the files searched for.', + \ 'check2.hs:2:1: Suggestion: Use camelCaseFound: my_variable = ...Why not: myVariable = ...', + \ 'check2.hs:6:1: Warning: Eta reduceFound: myFunc x = succ xWhy not: myFunc = succ', + \ 'xxx.hs:6:1: Warning: Eta reduceFound: myFunc x = succ xWhy not: myFunc = succ', + \ printf("check2.hs:28:28: Warning: Defaulting the following constraints to type ‘Integer’ (Num a0) arising from the literal ‘3’ at %s/check2.hs:28:28 (Eq a0) arising from a use of ‘lookup’ at %s/check2.hs:28:21-28 • In the first argument of ‘lookup’, namely ‘3’ In the expression: lookup 3 In the second argument of ‘fmap’, namely ‘(lookup 3 $ zip [1, 2, 3] [4, 5, 6])'’", tempname(), tempname()), + \ ]) 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_gitlablint_handler.vader b/test/handler/test_gitlablint_handler.vader new file mode 100644 index 00000000..d61766bd --- /dev/null +++ b/test/handler/test_gitlablint_handler.vader @@ -0,0 +1,34 @@ +Before: + runtime! ale_linters/yaml/gitlablint.vim + +After: + call ale#linter#Reset() + +Execute(Problems should be parsed correctly for gitlablint): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'root config contains unknown keys: efore_script', + \ }, + \ { + \ 'lnum': 77, + \ 'col': 3, + \ 'type': 'E', + \ 'text': '(): could not find expected : while scanning a simple key', + \ }, + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'build:dev:rest job: undefined need: chck:dev', + \ }, + \ ], + \ ale_linters#yaml#gitlablint#Handle(bufnr(''), [ + \ 'GitLab CI configuration is invalid', + \ 'root config contains unknown keys: efore_script', + \ '(): could not find expected : while scanning a simple key at line 77 column 3', + \ 'build:dev:rest job: undefined need: chck:dev', + \ ]) diff --git a/test/handler/test_gitlint_handler.vader b/test/handler/test_gitlint_handler.vader new file mode 100644 index 00000000..5c531664 --- /dev/null +++ b/test/handler/test_gitlint_handler.vader @@ -0,0 +1,89 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/gitcommit/gitlint.vim + +After: + Restore + + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() + +Execute(The gitlint handler should handle basic warnings and syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': 'Body message is missing', + \ 'code': 'B6', + \ }, + \ { + \ 'lnum': 2, + \ 'type': 'E', + \ 'text': 'Second line is not empty: "to send to upstream"', + \ 'code': 'B4', + \ }, + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': 'Body message is too short (19<20): "to send to upstream"', + \ 'code': 'B5', + \ }, + \ { + \ 'lnum': 8, + \ 'type': 'E', + \ 'text': 'Title exceeds max length (92>72): "some very long commit subject line where the author can''t wait to explain what he just fixed"', + \ 'code': 'T1', + \ }, + \ ], + \ ale_linters#gitcommit#gitlint#Handle(1, [ + \ '1: B6 Body message is missing', + \ '2: B4 Second line is not empty: "to send to upstream"', + \ '3: B5 Body message is too short (19<20): "to send to upstream"', + \ '8: T1 Title exceeds max length (92>72): "some very long commit subject line where the author can''t wait to explain what he just fixed"' + \ ]) + +Execute(Disabling trailing whitespace warnings should work): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'type': 'E', + \ 'text': 'Trailing whitespace', + \ 'code': 'T2', + \ }, + \ ], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: T2 Trailing whitespace', + \]) + + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'type': 'E', + \ 'text': 'Trailing whitespace', + \ 'code': 'B2', + \ }, + \ ], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: B2 Trailing whitespace', + \]) + + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: T2 Trailing whitespace', + \ ]) + + AssertEqual + \ [], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: B2 Trailing whitespace', + \ ]) diff --git a/test/handler/test_glslang_handler.vader b/test/handler/test_glslang_handler.vader new file mode 100644 index 00000000..9952c0a8 --- /dev/null +++ b/test/handler/test_glslang_handler.vader @@ -0,0 +1,48 @@ +Before: + runtime ale_linters/glsl/glslang.vim + +Execute(The glsl glslang handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 0, + \ 'type': 'E', + \ 'text': '''gl_ModelViewProjectionMatrix'' : undeclared identifier', + \ }, + \ { + \ 'lnum': 121, + \ 'col': 0, + \ 'type': 'W', + \ 'text': '''switch'' : last case/default label not followed by statements', + \ }, + \ ], + \ ale_linters#glsl#glslang#Handle(bufnr(''), [ + \ 'ERROR: 0:4: ''gl_ModelViewProjectionMatrix'' : undeclared identifier', + \ 'WARNING: 0:121: ''switch'' : last case/default label not followed by statements', + \ 'ERROR: 2 compilation errors. No code generated.', + \ ]) + +Execute(The glsl glslang handler should parse lines with options -V or -G correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'col': 0, + \ 'type': 'E', + \ 'text': '''non-opaque uniforms outside a block'' : not allowed when using GLSL for Vulkan', + \ }, + \ { + \ 'lnum': 14, + \ 'col': 0, + \ 'type': 'W', + \ 'text': '''__shininess'' : identifiers containing consecutive underscores ("__") are reserved', + \ }, + \ ], + \ ale_linters#glsl#glslang#Handle(bufnr(''), [ + \ 'shader.vert', + \ 'ERROR: shader.vert:7: ''non-opaque uniforms outside a block'' : not allowed when using GLSL for Vulkan', + \ 'WARNING: shader.vert:14: ''__shininess'' : identifiers containing consecutive underscores ("__") are reserved', + \ 'ERROR: 1 compilation errors. No code generated.', + \ 'SPIR-V is not generated for failed compile or link', + \ ]) diff --git a/test/handler/test_go_generic_handler.vader b/test/handler/test_go_generic_handler.vader new file mode 100644 index 00000000..2b17fdcb --- /dev/null +++ b/test/handler/test_go_generic_handler.vader @@ -0,0 +1,38 @@ +Execute(The golang handler should return the correct filenames): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'text': 'some error', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.go'), + \ }, + \ { + \ 'lnum': 27, + \ 'col': 5, + \ 'text': 'some error with a column', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/other.go'), + \ }, + \ { + \ 'lnum': 18, + \ 'col': 0, + \ 'text': 'random error', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/go1.14.go'), + \ }, + \ { + \ 'lnum': 36, + \ 'col': 2, + \ 'text': 'another random error', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/anothergo1.14.go'), + \ }, + \ ], + \ ale#handlers#go#Handler(bufnr(''), [ + \ 'test.go:27: some error', + \ 'other.go:27:5: some error with a column', + \ 'vet: go1.14.go:18:0: random error', + \ 'vet: anothergo1.14.go:36:2: another random error', + \ ]) diff --git a/test/handler/test_gobuild_handler.vader b/test/handler/test_gobuild_handler.vader new file mode 100644 index 00000000..17608c3a --- /dev/null +++ b/test/handler/test_gobuild_handler.vader @@ -0,0 +1,45 @@ +Before: + runtime ale_linters/go/gobuild.vim + +After: + call ale#linter#Reset() + +Execute (The gobuild handler should handle names with spaces): + " We can't test Windows paths with the path resovling on Linux, but we can + " test the regex. + AssertEqual + \ [ + \ [ + \ 'C:\something\file with spaces.go', + \ '27', + \ '', + \ 'missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ ], + \ [ + \ 'C:\something\file with spaces.go', + \ '5', + \ '2', + \ 'expected declaration, found ''STRING'' "log"', + \ ], + \ ], + \ map(ale_linters#go#gobuild#GetMatches([ + \ 'C:\something\file with spaces.go:27: missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ 'C:\something\file with spaces.go:5:2: expected declaration, found ''STRING'' "log"', + \ ]), 'v:val[1:4]') + +Execute (The gobuild handler should handle relative paths correctly): + call ale#test#SetFilename('app/test.go') + + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'text': 'missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.go'), + \ }, + \ ], + \ ale_linters#go#gobuild#Handler(bufnr(''), [ + \ 'test.go:27: missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ ]) diff --git a/test/handler/test_golangci_lint_handler.vader b/test/handler/test_golangci_lint_handler.vader new file mode 100644 index 00000000..34f51d78 --- /dev/null +++ b/test/handler/test_golangci_lint_handler.vader @@ -0,0 +1,146 @@ +Before: + runtime ale_linters/go/golangci_lint.vim + +After: + call ale#linter#Reset() + +Execute (The golangci-lint handler should handle only typecheck lines as errors): + call ale#test#SetFilename('app/main.go') + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'text': 'typecheck - found packages main (main.go) and validator (validation.go) in ', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/main.go'), + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'typecheck - package validator_test; expected package main_test', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/validation_encoder_test.go'), + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'typecheck - package validator_test; expected package main_test', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/validation_error_test.go'), + \ }, + \ { + \ 'lnum': 505, + \ 'col': 75, + \ 'text': 'gomnd - Magic number: 404, in detected', + \ 'type': 'W', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/main.go'), + \ } + \ ], + \ ale_linters#go#golangci_lint#Handler(bufnr(''), [ + \ '{', + \ ' "Issues": [', + \ ' {', + \ ' "FromLinter": "typecheck",', + \ ' "Text": "found packages main (main.go) and validator (validation.go) in ",', + \ ' "Severity": "",', + \ ' "SourceLines": [', + \ ' "package main"', + \ ' ],', + \ ' "Pos": {', + \ ' "Filename": "main.go",', + \ ' "Offset": 0,', + \ ' "Line": 1,', + \ ' "Column": 0', + \ ' },', + \ ' "ExpectNoLint": false,', + \ ' "ExpectedNoLintLinter": ""', + \ ' },', + \ ' {', + \ ' "FromLinter": "typecheck",', + \ ' "Text": "package validator_test; expected package main_test",', + \ ' "Severity": "",', + \ ' "SourceLines": [', + \ ' "package validator_test"', + \ ' ],', + \ ' "Pos": {', + \ ' "Filename": "validation_encoder_test.go",', + \ ' "Offset": 0,', + \ ' "Line": 1,', + \ ' "Column": 1', + \ ' },', + \ ' "ExpectNoLint": false,', + \ ' "ExpectedNoLintLinter": ""', + \ ' },', + \ ' {', + \ ' "FromLinter": "typecheck",', + \ ' "Text": "package validator_test; expected package main_test",', + \ ' "Severity": "",', + \ ' "SourceLines": [', + \ ' "package validator_test"', + \ ' ],', + \ ' "Pos": {', + \ ' "Filename": "validation_error_test.go",', + \ ' "Offset": 0,', + \ ' "Line": 1,', + \ ' "Column": 1', + \ ' },', + \ ' "ExpectNoLint": false,', + \ ' "ExpectedNoLintLinter": ""', + \ ' },', + \ ' {', + \ ' "FromLinter": "gomnd",', + \ ' "Text": "Magic number: 404, in detected",', + \ ' "Severity": "",', + \ ' "SourceLines": [', + \ ' "package validator_test"', + \ ' ],', + \ ' "Pos": {', + \ ' "Filename": "main.go",', + \ ' "Offset": 0,', + \ ' "Line": 505,', + \ ' "Column": 75', + \ ' },', + \ ' "ExpectNoLint": false,', + \ ' "ExpectedNoLintLinter": ""', + \ ' }', + \ ' ]', + \ '}', + \ ]) + +Execute (The golangci-lint handler should set proper filename): + call ale#test#SetFilename('app/cmd/server/main.go') + + AssertEqual + \ [ + \ { + \ 'lnum': 198, + \ 'col': 19, + \ 'text': 'funlen - Function getConfig has too many statements (51 > 50)', + \ 'type': 'W', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/main.go'), + \ }, + \ ], + \ ale_linters#go#golangci_lint#Handler(bufnr(''), [ + \ '{', + \ ' "Issues": [', + \ ' {', + \ ' "FromLinter": "funlen",', + \ ' "Text": "Function getConfig has too many statements (51 > 50)",', + \ ' "Severity": "",', + \ ' "SourceLines": [', + \ ' "package main"', + \ ' ],', + \ ' "Pos": {', + \ ' "Filename": "cmd/server/main.go",', + \ ' "Offset": 5374,', + \ ' "Line": 198,', + \ ' "Column": 19', + \ ' },', + \ ' "ExpectNoLint": false,', + \ ' "ExpectedNoLintLinter": ""', + \ ' }', + \ ' ]', + \ '}', + \ ]) diff --git a/test/handler/test_hadolint.vader b/test/handler/test_hadolint.vader new file mode 100644 index 00000000..5a8034f8 --- /dev/null +++ b/test/handler/test_hadolint.vader @@ -0,0 +1,59 @@ +Before: + runtime ale_linters/dockerfile/hadolint.vim + +After: + call ale#linter#Reset() + +Execute(The hadolint handler should handle an empty string response): + AssertEqual + \ [], + \ ale_linters#dockerfile#hadolint#Handle(bufnr(''), []) + +Execute(The hadolint handler should handle a normal example): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'type': 'W', + \ 'code': 'DL3006', + \ 'text': 'Always tag the version of an image explicitly', + \ 'detail': "DL3006 ( https://github.com/hadolint/hadolint/wiki/DL3006 )\n\nAlways tag the version of an image explicitly", + \ }, + \ { + \ 'lnum': 4, + \ 'col': 0, + \ 'type': 'W', + \ 'code': 'DL3033', + \ 'text': 'Specify version with `yum install -y -`.', + \ 'detail': "DL3033 ( https://github.com/hadolint/hadolint/wiki/DL3033 )\n\nSpecify version with `yum install -y -`.", + \ }, + \ { + \ 'lnum': 12, + \ 'col': 0, + \ 'type': 'W', + \ 'code': 'SC2039', + \ 'text': 'In POSIX sh, brace expansion is undefined.', + \ 'detail': "SC2039 ( https://github.com/koalaman/shellcheck/wiki/SC2039 )\n\nIn POSIX sh, brace expansion is undefined.", + \ }, + \ ], + \ ale_linters#dockerfile#hadolint#Handle(bufnr(''), [ + \ '-:1 DL3006 warning: Always tag the version of an image explicitly', + \ '-:4 DL3033 warning: Specify version with `yum install -y -`.', + \ '-:12 SC2039 warning: In POSIX sh, brace expansion is undefined.', + \ ]) + +Execute(The hadolint handler should handle parsing errors): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': "unexpected 'b' expecting '#', ADD, ARG, CMD, COPY, ENTRYPOINT, ENV, EXPOSE, FROM, HEALTHCHECK, LABEL, MAINTAINER, ONBUILD, RUN, SHELL, STOPSIGNAL, USER, VOLUME, WORKDIR, or end of input", + \ 'detail': "hadolint could not parse the file because of a syntax error.", + \ }, + \ ], + \ ale_linters#dockerfile#hadolint#Handle(bufnr(''), [ + \ '/dev/stdin:1:1 unexpected ''b'' expecting ''#'', ADD, ARG, CMD, COPY, ENTRYPOINT, ENV, EXPOSE, FROM, HEALTHCHECK, LABEL, MAINTAINER, ONBUILD, RUN, SHELL, STOPSIGNAL, USER, VOLUME, WORKDIR, or end of input', + \ ]) diff --git a/test/handler/test_haskell_stack_handler.vader b/test/handler/test_haskell_stack_handler.vader new file mode 100644 index 00000000..07e7e69c --- /dev/null +++ b/test/handler/test_haskell_stack_handler.vader @@ -0,0 +1,7 @@ +Before: + runtime ale/handlers/haskell_stack.vim + +Execute(Escape stack should correctly identify a stack exec command): + AssertEqual + \ ale#Escape('stack') . ' exec ' . ale#Escape('hlint') . ' --', + \ ale#handlers#haskell_stack#EscapeExecutable('stack', 'hlint') diff --git a/test/handler/test_hlint_handler.vader b/test/handler/test_hlint_handler.vader new file mode 100644 index 00000000..915e1748 --- /dev/null +++ b/test/handler/test_hlint_handler.vader @@ -0,0 +1,80 @@ +Before: + runtime! ale_linters/haskell/hlint.vim + +After: + call ale#linter#Reset() + +Execute(The hlint handler should parse items correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 4, + \ 'end_lnum': 3, + \ 'end_col': 2, + \ 'text': 'Error: Do something. Found: [Char] Why not: String', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 7, + \ 'end_col': 2, + \ 'text': 'Warning: Do something. Found: [Char] Why not: String', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 73, + \ 'col': 25, + \ 'end_lnum': 73, + \ 'end_col': 31, + \ 'text': 'Suggestion: Use String. Found: [Char] Why not: String', + \ 'type': 'I', + \ }, + \ ], + \ ale_linters#haskell#hlint#Handle(bufnr(''), [json_encode([ + \ { + \ 'module': 'Main', + \ 'decl': 'foo', + \ 'severity': 'Error', + \ 'hint': 'Do something', + \ 'file': '-', + \ 'startLine': 1, + \ 'startColumn': 4, + \ 'endLine': 3, + \ 'endColumn': 2, + \ 'from': '[Char]', + \ 'to': 'String', + \ }, + \ { + \ 'module': 'Main', + \ 'decl': 'foo', + \ 'severity': 'Warning', + \ 'hint': 'Do something', + \ 'file': '-', + \ 'startLine': 2, + \ 'startColumn': 4, + \ 'endLine': 7, + \ 'endColumn': 2, + \ 'from': '[Char]', + \ 'to': 'String', + \ }, + \ { + \ 'module': 'Main', + \ 'decl': 'myFocusedBorderColor', + \ 'severity': 'Suggestion', + \ 'hint': 'Use String', + \ 'file': '-', + \ 'startLine': 73, + \ 'startColumn': 25, + \ 'endLine': 73, + \ 'endColumn': 31, + \ 'from': '[Char]', + \ 'to': 'String', + \ }, + \ ])]) + +Execute(The hlint handler should handle empty output): + AssertEqual + \ [], + \ ale_linters#haskell#hlint#Handle(bufnr(''), []) diff --git a/test/handler/test_hurlfmt_handler.vader b/test/handler/test_hurlfmt_handler.vader new file mode 100644 index 00000000..3da686d7 --- /dev/null +++ b/test/handler/test_hurlfmt_handler.vader @@ -0,0 +1,29 @@ +Before: + runtime ale_linters/hurl/hurlfmt.vim + +After: + call ale#linter#Reset() + +Execute(The hurlfmt handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'bufnr': 345, + \ 'col': 48, + \ 'end_col': 48, + \ 'text': 'Parsing space : expecting a space ', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#hurl#hurlfmt#HandleOutput(345, [ + \ 'error: Parsing space', + \ '--> test.hurl:11:48', + \ ' |', + \ '8 " | header "Content-Type"= "application/json; charset=utf-8"', + \ ' | ^ expecting a space', + \ ' |', + \ ]) + +Execute(The rubocop handler should handle empty output): + AssertEqual [], ale_linters#hurl#hurlfmt#HandleOutput(347, []) diff --git a/test/handler/test_ibm_openapi_validator_handler.vader b/test/handler/test_ibm_openapi_validator_handler.vader new file mode 100644 index 00000000..e136d5d2 --- /dev/null +++ b/test/handler/test_ibm_openapi_validator_handler.vader @@ -0,0 +1,49 @@ +Before: + runtime! ale_linters/openapi/ibm_validator.vim + +After: + call ale#linter#Reset() + +Execute(Problems should be parsed correctly for openapi-ibm-validator): + AssertEqual + \ [ + \ { + \ 'lnum': 54, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Items with a description must have content in it.', + \ }, + \ { + \ 'lnum': 24, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'Operations must have a non-empty `operationId`.', + \ }, + \ { + \ 'lnum': 40, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'operationIds must follow case convention: lower_snake_case', + \ }, + \ ], + \ ale_linters#openapi#ibm_validator#Handle(bufnr(''), [ + \ '', + \ '[Warning] No .validaterc file found. The validator will run in default mode.', + \ 'To configure the validator, create a .validaterc file.', + \ '', + \ 'errors', + \ '', + \ ' Message : Items with a description must have content in it.', + \ ' Path : paths./settings.patch.description', + \ ' Line : 54', + \ '', + \ 'warnings', + \ '', + \ ' Message : Operations must have a non-empty `operationId`.', + \ ' Path : paths./stats.get.operationId', + \ ' Line : 24', + \ '', + \ ' Message : operationIds must follow case convention: lower_snake_case', + \ ' Path : paths./settings.get.operationId', + \ ' Line : 40' + \ ]) diff --git a/test/handler/test_idris_handler.vader b/test/handler/test_idris_handler.vader new file mode 100644 index 00000000..6a032ea6 --- /dev/null +++ b/test/handler/test_idris_handler.vader @@ -0,0 +1,66 @@ +Before: + Save $TMPDIR + + " Set TMPDIR so the temporary file checks work. + let $TMPDIR = '/tmp' + + runtime ale_linters/idris/idris.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The idris handler should parse messages that reference a single column): + if has('win32') + call ale#test#SetFilename($TEMP . '\foo.idr') + else + call ale#test#SetFilename('/tmp/foo.idr') + endif + + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'When checking right hand side of main with expected type IO () When checking an application of function Prelude.Monad.>>=: Type mismatch between IO () (Type of putStrLn _) and _ -> _ (Is putStrLn _ applied to too many arguments?) Specifically: Type mismatch between IO and \uv => _ -> uv' + \ } + \ ], + \ ale_linters#idris#idris#Handle(bufnr(''), [ + \ expand('%:p') . ':4:5:', + \ 'When checking right hand side of main with expected type', + \ ' IO ()', + \ '', + \ 'When checking an application of function Prelude.Monad.>>=:', + \ ' Type mismatch between', + \ ' IO () (Type of putStrLn _)', + \ ' and', + \ ' _ -> _ (Is putStrLn _ applied to too many arguments?)', + \ '', + \ ' Specifically:', + \ ' Type mismatch between', + \ ' IO', + \ ' and', + \ ' \uv => _ -> uv', + \ ]) + +Execute(The idris handler should parse messages that reference a column range): + call ale#test#SetFilename('/tmp/foo.idr') + + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'col': 11, + \ 'type': 'E', + \ 'text': 'When checking right hand side of Main.case block in main at /tmp/foo.idr:10:10 with expected type IO () Last statement in do block must be an expression' + \ } + \ ], + \ ale_linters#idris#idris#Handle(bufnr(''), [ + \ expand('%:p') . ':11:11-13:', + \ 'When checking right hand side of Main.case block in main at /tmp/foo.idr:10:10 with expected type', + \ ' IO ()', + \ '', + \ 'Last statement in do block must be an expression', + \ ]) diff --git a/test/handler/test_inko_handler.vader b/test/handler/test_inko_handler.vader new file mode 100644 index 00000000..6621d2d6 --- /dev/null +++ b/test/handler/test_inko_handler.vader @@ -0,0 +1,54 @@ +Before: + runtime ale_linters/inko/inko.vim + +After: + call ale#linter#Reset() + +Execute(The inko handler should parse errors correctly): + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify('/tmp/foo.inko'), + \ 'lnum': 4, + \ 'col': 5, + \ 'text': 'this is an error', + \ 'type': 'E', + \ } + \ ], + \ ale#handlers#inko#Handle(bufnr(''), [ + \ '[', + \ ' {', + \ ' "file": "/tmp/foo.inko",', + \ ' "line": 4,', + \ ' "column": 5,', + \ ' "message": "this is an error",', + \ ' "level": "error"', + \ ' }', + \ ']' + \ ]) + +Execute(The inko handler should parse warnings correctly): + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify('/tmp/foo.inko'), + \ 'lnum': 4, + \ 'col': 5, + \ 'text': 'this is a warning', + \ 'type': 'W', + \ } + \ ], + \ ale#handlers#inko#Handle(bufnr(''), [ + \ '[', + \ ' {', + \ ' "file": "/tmp/foo.inko",', + \ ' "line": 4,', + \ ' "column": 5,', + \ ' "message": "this is a warning",', + \ ' "level": "warning"', + \ ' }', + \ ']' + \ ]) + +Execute(The inko handler should handle empty output): + AssertEqual [], ale#handlers#inko#Handle(bufnr(''), []) diff --git a/test/handler/test_ispc_ispc_handler.vader b/test/handler/test_ispc_ispc_handler.vader new file mode 100644 index 00000000..619773fe --- /dev/null +++ b/test/handler/test_ispc_ispc_handler.vader @@ -0,0 +1,90 @@ +Before: + runtime ale_linters/ispc/ispc.vim + +After: + call ale#linter#Reset() + +Execute(The ispc handler should parse input correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 0, + \ 'lnum': 33, + \ 'col': 14, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected ''int'', expecting '','' or '';''.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 36, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected ''for''.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 51, + \ 'col': 9, + \ 'type': 'E', + \ 'text': '''foobar.h'' file not found', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 79, + \ 'col': 52, + \ 'type': 'W', + \ 'text': 'Modulus operator with varying types is very inefficient.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 85, + \ 'col': 13, + \ 'type': 'W', + \ 'text': 'Undefined behavior: all program instances are writing to the same location!', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 93, + \ 'col': 19, + \ 'type': 'W', + \ 'text': 'Gather required to load value.', + \ }, + \ { + \ 'bufnr': 0, + \ 'lnum': 93, + \ 'col': 9, + \ 'type': 'W', + \ 'text': 'Scatter required to store value.', + \ }, + \ ], + \ ale_linters#ispc#ispc#Handle(0, [ + \ 'Warning: No output file or header file name specified. Program will be compiled and warnings/errors will be issued, but no output will be generated. ', + \ 'Warning: No --target specified on command-line. Using default system target "avx2-i32x8".', + \ 'mandelbrot.ispc:33:14: Error: syntax error, unexpected ''int'', expecting '','' or '';''.', + \ 'static iline int mandel(float c_re, float c_im, int count) {', + \ ' ^^^', + \ '', + \ 'mandelbrot.ispc:36:5: Error: syntax error, unexpected ''for''.', + \ ' for (i = 0; i < count; ++i) {', + \ ' ^^^', + \ '', + \ 'mandelbrot.ispc:51:9: fatal error: ''foobar.h'' file not found', + \ '#include', + \ ' ^~~~~~~~~~', + \ 'mandelbrot.ispc:79:52: Performance Warning: Modulus operator with varying types is very inefficient.', + \ ' double x = x0 + i * (dx + epsilon*(k%2)*delta);', + \ ' ^^^', + \ '', + \ 'mandelbrot.ispc:85:13: Warning: Undefined behavior: all program instances are writing to the same location!', + \ ' output[index] = (NNN) / sample_size;', + \ ' ^^^^^^^^^^^^^', + \ '', + \ 'mandelbrot.ispc:93:19: Performance Warning: Gather required to load value.', + \ ' A[i*8] *= A[i*8];', + \ ' ^^^^^^', + \ '', + \ 'mandelbrot.ispc:93:9: Performance Warning: Scatter required to store value.', + \ ' A[i*8] *= A[i*8];', + \ ' ^^^^^^', + \ '', + \ ]) diff --git a/test/handler/test_javac_handler.vader b/test/handler/test_javac_handler.vader new file mode 100644 index 00000000..3dc245af --- /dev/null +++ b/test/handler/test_javac_handler.vader @@ -0,0 +1,97 @@ +Before: + runtime ale_linters/java/javac.vim + + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.java') + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The javac handler should handle cannot find symbol errors): + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), + \ 'lnum': 1, + \ 'text': 'error: some error', + \ 'type': 'E', + \ }, + \ { + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), + \ 'lnum': 2, + \ 'col': 5, + \ 'text': 'error: cannot find symbol: BadName', + \ 'type': 'E', + \ }, + \ { + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), + \ 'lnum': 34, + \ 'col': 5, + \ 'text': 'error: cannot find symbol: BadName2', + \ 'type': 'E', + \ }, + \ { + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), + \ 'lnum': 37, + \ 'text': 'warning: some warning', + \ 'type': 'W', + \ }, + \ { + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), + \ 'lnum': 42, + \ 'col': 11, + \ 'text': 'error: cannot find symbol: bar()', + \ 'type': 'E', + \ }, + \ { + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), + \ 'lnum': 58, + \ 'col': 19, + \ 'text': 'error: incompatible types: Bar cannot be converted to Foo', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#java#javac#Handle(bufnr(''), [ + \ '/tmp/vLPr4Q5/33/foo.java:1: error: some error', + \ '/tmp/vLPr4Q5/33/foo.java:2: error: cannot find symbol', + \ ' BadName foo() {', + \ ' ^', + \ ' symbol: class BadName', + \ ' location: class Bar', + \ '/tmp/vLPr4Q5/33/foo.java:34: error: cannot find symbol', + \ ' BadName2 foo() {', + \ ' ^', + \ ' symbol: class BadName2', + \ ' location: class Bar', + \ '/tmp/vLPr4Q5/33/foo.java:37: warning: some warning', + \ '/tmp/vLPr4Q5/33/foo.java:42: error: cannot find symbol', + \ ' this.bar();', + \ ' ^', + \ ' symbol: method bar()', + \ '/tmp/vLPr4Q5/33/foo.java:58: error: incompatible types: Bar cannot be converted to Foo', + \ ' this.setFoo(bar);', + \ ' ^', + \ '6 errors', + \ ]) + +Execute(The javac handler should resolve files from different directories): + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(g:dir . '/Foo.java'), + \ 'lnum': 1, + \ 'text': 'error: some error', + \ 'type': 'E', + \ }, + \ { + \ 'filename': ale#path#Simplify(g:dir . '/Bar.java'), + \ 'lnum': 1, + \ 'text': 'error: some error', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#java#javac#Handle(bufnr(''), [ + \ './Foo.java:1: error: some error', + \ './Bar.java:1: error: some error', + \ ]) diff --git a/test/handler/test_jq_handler.vader b/test/handler/test_jq_handler.vader new file mode 100644 index 00000000..1653bd3d --- /dev/null +++ b/test/handler/test_jq_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/json/jq.vim + +After: + call ale#linter#Reset() + +Execute (Should parse error correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 9, + \ 'text': 'Expected another array element', + \ } + \ ], + \ ale_linters#json#jq#Handle(0, [ + \ 'parse error: Expected another array element at line 1, column 9' + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 9, + \ 'text': 'Expected another array element', + \ } + \ ], + \ ale_linters#json#jq#Handle(0, [ + \ 'jq: parse error: Expected another array element at line 1, column 9' + \ ]) diff --git a/test/handler/test_jscs_handler.vader b/test/handler/test_jscs_handler.vader new file mode 100644 index 00000000..5566116f --- /dev/null +++ b/test/handler/test_jscs_handler.vader @@ -0,0 +1,39 @@ +Before: + runtime ale_linters/javascript/jscs.vim + +After: + call ale#linter#Reset() + +Execute(jscs should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 7, + \ 'text': 'Variable declarations should use `let` or `const` not `var`', + \ 'code': 'disallowVar', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 21, + \ 'text': 'Illegal trailing whitespace', + \ 'code': 'disallowTrailingWhitespace', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 9, + \ 'text': 'Variable `hello` is not used', + \ 'code': 'disallowUnusedVariables', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'Expected indentation of 1 characters', + \ }, + \ ], + \ ale_linters#javascript#jscs#Handle(347, [ + \ 'foobar.js: line 1, col 7, disallowVar: Variable declarations should use `let` or `const` not `var`', + \ 'foobar.js: line 3, col 21, disallowTrailingWhitespace: Illegal trailing whitespace', + \ 'foobar.js: line 5, col 9, disallowUnusedVariables: Variable `hello` is not used', + \ 'foobar.js: line 2, col 1, Expected indentation of 1 characters', + \ ]) 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..5d1b6ace --- /dev/null +++ b/test/handler/test_lacheck_handler.vader @@ -0,0 +1,34 @@ +Before: + runtime ale_linters/tex/lacheck.vim + call ale#test#SetDirectory('/testplugin/test/handler') + +After: + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The lacheck handler should parse lines correctly): + call ale#test#SetFilename('../test-files/tex/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_lessc_handler.vader b/test/handler/test_lessc_handler.vader new file mode 100644 index 00000000..31de5593 --- /dev/null +++ b/test/handler/test_lessc_handler.vader @@ -0,0 +1,69 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + call ale#test#SetFilename('testfile.less') + + runtime ale_linters/less/lessc.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The lessc handler should handle errors for the current file correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Unrecognised input. Possibly missing something', + \ }, + \ ], + \ ale_linters#less#lessc#Handle(bufnr(''), [ + \ 'ParseError: Unrecognised input. Possibly missing something in - on line 2, column 1:', + \ '1 vwewww', + \ '2 ', + \]) + +Execute(The lessc handler should handle errors for other files in the same directory correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Unrecognised input. Possibly missing something', + \ 'filename': ale#path#Simplify(g:dir . '/imported.less') + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Unrecognised input. Possibly missing something', + \ 'filename': ale#path#Simplify(g:dir . '/imported.less') + \ }, + \ ], + \ ale_linters#less#lessc#Handle(bufnr(''), [ + \ 'ParseError: Unrecognised input. Possibly missing something in imported.less on line 2, column 1:', + \ '1 vwewww', + \ '2 ', + \ 'ParseError: Unrecognised input. Possibly missing something in ./imported.less on line 2, column 1:', + \ '1 vwewww', + \ '2 ', + \]) + +Execute(The lessc handler should handle errors for files in directories above correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Unrecognised input. Possibly missing something', + \ 'filename': ale#path#Simplify(g:dir . '/../imported2.less') + \ }, + \ ], + \ ale_linters#less#lessc#Handle(bufnr(''), [ + \ 'ParseError: Unrecognised input. Possibly missing something in ../imported2.less on line 2, column 1:', + \ '1 vwewww', + \ '2 ', + \]) diff --git a/test/handler/test_llc_handler.vader b/test/handler/test_llc_handler.vader new file mode 100644 index 00000000..bbe686f2 --- /dev/null +++ b/test/handler/test_llc_handler.vader @@ -0,0 +1,58 @@ +Before: + runtime! ale_linters/llvm/llc.vim + +After: + call ale#linter#Reset() + +Execute(llc handler should parse errors output for STDIN): + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'col': 7, + \ 'text': "error: value doesn't match function result type 'i32'", + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 13, + \ 'text': "error: use of undefined value '@foo'", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#llvm#llc#HandleErrors(bufnr(''), [ + \ "llc: :10:7: error: value doesn't match function result type 'i32'", + \ 'ret i64 0', + \ ' ^', + \ '', + \ "llc: :10:13: error: use of undefined value '@foo'", + \ 'call void @foo(i64 %0)', + \ ' ^', + \ ]) + +Execute(llc handler should parse errors output for some file): + call ale#test#SetFilename('test.ll') + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'col': 7, + \ 'text': "error: value doesn't match function result type 'i32'", + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 13, + \ 'text': "error: use of undefined value '@foo'", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#llvm#llc#HandleErrors(bufnr(''), [ + \ "llc: /path/to/test.ll:10:7: error: value doesn't match function result type 'i32'", + \ 'ret i64 0', + \ ' ^', + \ '', + \ "llc: /path/to/test.ll:10:13: error: use of undefined value '@foo'", + \ 'call void @foo(i64 %0)', + \ ' ^', + \ ]) diff --git a/test/handler/test_llvm_mc_handler.vader b/test/handler/test_llvm_mc_handler.vader new file mode 100644 index 00000000..990de525 --- /dev/null +++ b/test/handler/test_llvm_mc_handler.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/asm/llvm_mc.vim + +After: + call ale#linter#Reset() + +Execute(The asm llvm-mc handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'col' : 15, + \ 'text': "invalid operand for instruction", + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 11, + \ 'col' : 2, + \ 'text': "invalid instruction mnemonic 'lpaq'", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#asm#llvm_mc#Handle(357, [ + \ "xorq %rbp, %rbp", + \ "{standard_input}:10:15: error: invalid operand for instruction", + \ "{standard input}:11:2: error: invalid instruction mnemonic 'lpaq'", + \ ]) diff --git a/test/handler/test_lua_selene_handler.vader b/test/handler/test_lua_selene_handler.vader new file mode 100644 index 00000000..e9410ae9 --- /dev/null +++ b/test/handler/test_lua_selene_handler.vader @@ -0,0 +1,38 @@ +Before: + runtime ale_linters/lua/selene.vim + +After: + Restore + call ale#linter#Reset() + +Execute(The selene handler for Lua should parse input correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'end_lnum': 2, + \ 'col': 1, + \ 'end_col': 3, + \ 'text': 'empty if block', + \ 'code': 'empty_if', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 1, + \ 'end_lnum': 1, + \ 'col': 4, + \ 'end_col': 11, + \ 'text': 'comparing things to nan directly is not allowed', + \ 'code': 'compare_nan', + \ 'type': 'E', + \ 'detail': "comparing things to nan directly is not allowed\n\ntry: `x ~= x` instead" + \ }, + \ ], + \ ale_linters#lua#selene#Handle(0, [ + \ '{"severity":"Warning","code":"empty_if","message":"empty if block","primary_label":{"span":{"start":0,"start_line":0,"start_column":0,"end":20,"end_line":1,"end_column":3},"message":""},"notes":[],"secondary_labels":[]}', + \ '{"severity":"Error","code":"compare_nan","message":"comparing things to nan directly is not allowed","primary_label":{"span":{"start":3,"start_line":0,"start_column":3,"end":11,"end_line":0,"end_column":11},"message":""},"notes":["try: `x ~= x` instead"],"secondary_labels":[]}', + \ 'Results:', + \ '1 errors', + \ '1 warnings', + \ '0 parse errors', + \ ]) diff --git a/test/handler/test_luac_handler.vader b/test/handler/test_luac_handler.vader new file mode 100644 index 00000000..6bb51c34 --- /dev/null +++ b/test/handler/test_luac_handler.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/lua/luac.vim + +After: + Restore + call ale#linter#Reset() + +Execute(The luac handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'line contains trailing whitespace', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 3, + \ 'text': 'unexpected symbol near ''-''', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 5, + \ 'text': '''='' expected near '')''', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#lua#luac#Handle(347, [ + \ 'luac /file/path/here.lua:1: line contains trailing whitespace', + \ 'luac /file/path/here.lua:3: unexpected symbol near ''-''', + \ 'luac /file/path/here.lua:5: ''='' expected near '')''', + \ ]) diff --git a/test/handler/test_luacheck_handler.vader b/test/handler/test_luacheck_handler.vader new file mode 100644 index 00000000..7cebb017 --- /dev/null +++ b/test/handler/test_luacheck_handler.vader @@ -0,0 +1,62 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/lua/luacheck.vim + +After: + Restore + call ale#linter#Reset() + +Execute(The luacheck handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 8, + \ 'text': 'line contains trailing whitespace', + \ 'code': 'W612', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'text': 'unused loop variable ''k''', + \ 'code': 'W213', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 19, + \ 'text': 'accessing undefined variable ''x''', + \ 'code': 'W113', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#lua#luacheck#Handle(347, [ + \ ' /file/path/here.lua:1:8: (W612) line contains trailing whitespace', + \ ' /file/path/here.lua:3:5: (W213) unused loop variable ''k''', + \ ' /file/path/here.lua:3:19: (W113) accessing undefined variable ''x''', + \ ]) + +Execute(The luacheck handler should respect the warn_about_trailing_whitespace option): + let g:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 43, + \ 'text': 'unused argument ''g''', + \ 'code': 'W212', + \ 'type': 'W', + \ } + \ ], + \ ale_linters#lua#luacheck#Handle(347, [ + \ '/file/path/here.lua:15:97: (W614) trailing whitespace in a comment', + \ '/file/path/here.lua:16:60: (W612) line contains trailing whitespace', + \ '/file/path/here.lua:17:1: (W611) line contains only whitespace', + \ '/file/path/here.lua:27:57: (W613) trailing whitespace in a string', + \ '/file/path/here.lua:5:43: (W212) unused argument ''g''', + \ ]) diff --git a/test/handler/test_markdownlint_handler.vader b/test/handler/test_markdownlint_handler.vader new file mode 100644 index 00000000..f49dba97 --- /dev/null +++ b/test/handler/test_markdownlint_handler.vader @@ -0,0 +1,34 @@ +Before: + runtime ale_linters/markdown/markdownlint.vim + +After: + call ale#linter#Reset() + +Execute(The Markdownlint handler should parse output with a column correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'col': 20, + \ 'code': 'MD013/line-length', + \ 'text': 'Line length [Expected: 80; Actual: 114]', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md:10:20 MD013/line-length Line length [Expected: 80; Actual: 114]' + \ ]) + +Execute(The Markdownlint handler should parse output with multiple slashes in rule name correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'code': 'MD022/blanks-around-headings/blanks-around-headers', + \ 'text': 'Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Above] [Context: "### something"]', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md:10 MD022/blanks-around-headings/blanks-around-headers Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Above] [Context: "### something"]' + \ ]) diff --git a/test/handler/test_mcs_handler.vader b/test/handler/test_mcs_handler.vader new file mode 100644 index 00000000..3defc328 --- /dev/null +++ b/test/handler/test_mcs_handler.vader @@ -0,0 +1,37 @@ +Before: + runtime ale_linters/cs/mcs.vim + +After: + call ale#linter#Reset() + +Execute(The mcs handler should handle cannot find symbol errors): + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col' : 29, + \ 'text': '; expected', + \ 'code': 'CS1001', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 101, + \ 'col': 0, + \ 'text': 'Unexpected processor directive (no #if for this #endif)', + \ 'code': 'CS1028', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 12, + \ 'text': 'some warning', + \ 'code': 'CS0123', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#cs#mcs#Handle(347, [ + \ 'Tests.cs(12,29): error CS1001: ; expected', + \ 'Tests.cs(101,0): error CS1028: Unexpected processor directive (no #if for this #endif)', + \ 'Tests.cs(10,12): warning CS0123: some warning', + \ 'Compilation failed: 2 error(s), 1 warnings', + \ ]) diff --git a/test/handler/test_mcsc_handler.vader b/test/handler/test_mcsc_handler.vader new file mode 100644 index 00000000..c04f7d27 --- /dev/null +++ b/test/handler/test_mcsc_handler.vader @@ -0,0 +1,88 @@ +Before: + Save g:ale_cs_mcsc_source + + unlet! g:ale_cs_mcsc_source + + call ale#test#SetDirectory('/testplugin/test/handler') + call ale#test#SetFilename('Test.cs') + + runtime ale_linters/cs/mcsc.vim + +After: + unlet! g:ale_cs_mcsc_source + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The mcs handler should work with the default of the buffer's directory): + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col' : 29, + \ 'text': '; expected', + \ 'code': 'CS1001', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(g:dir . '/Test.cs'), + \ }, + \ ], + \ ale_linters#cs#mcsc#Handle(bufnr(''), [ + \ 'Test.cs(12,29): error CS1001: ; expected', + \ 'Compilation failed: 2 error(s), 1 warnings', + \ ]) + +Execute(The mcs handler should handle cannot find symbol errors): + let g:ale_cs_mcsc_source = '/home/foo/project/bar' + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col' : 29, + \ 'text': '; expected', + \ 'code': 'CS1001', + \ 'type': 'E', + \ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'), + \ }, + \ { + \ 'lnum': 101, + \ 'col': 0, + \ 'text': 'Unexpected processor directive (no #if for this #endif)', + \ 'code': 'CS1028', + \ 'type': 'E', + \ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'), + \ }, + \ { + \ 'lnum': 10, + \ 'col': 12, + \ 'text': 'some warning', + \ 'code': 'CS0123', + \ 'type': 'W', + \ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'), + \ }, + \ ], + \ ale_linters#cs#mcsc#Handle(bufnr(''), [ + \ 'Test.cs(12,29): error CS1001: ; expected', + \ 'Test.cs(101,0): error CS1028: Unexpected processor directive (no #if for this #endif)', + \ 'Test.cs(10,12): warning CS0123: some warning', + \ 'Compilation failed: 2 error(s), 1 warnings', + \ ]) + +Execute(The mcsc handler should handle non file specific compiler errors without reporting overal status report as error): + let g:ale_cs_mcsc_source = '/home/foo/project/bar' + + AssertEqual + \ [ + \ { + \ 'lnum': -1, + \ 'col' : -1, + \ 'text': 'No files to compile were specified', + \ 'code': 'CS2008', + \ 'type': 'E', + \ 'filename': '', + \ }, + \ ], + \ ale_linters#cs#mcsc#Handle(bufnr(''), [ + \ 'error CS2008: No files to compile were specified', + \ 'Compilation failed: 1 error(s), 0 warnings', + \ ]) diff --git a/test/handler/test_mdl_handler.vader b/test/handler/test_mdl_handler.vader new file mode 100644 index 00000000..d01b52af --- /dev/null +++ b/test/handler/test_mdl_handler.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/markdown/mdl.vim + +After: + call ale#linter#Reset() + +Execute(The mdl handler should parse output correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'code': 'MD002/first-header-h1', + \ 'text': 'First header should be a top level header', + \ 'type': 'W' + \ }, + \ { + \ 'lnum': 18, + \ 'code': 'MD033/no-inline-html', + \ 'text': 'Inline HTML', + \ 'type': 'W' + \ } + \ ], + \ ale_linters#markdown#mdl#Handle(0, [ + \ '[{"filename":"README.md","line":1,"rule":"MD002","aliases":["first-header-h1"],"description":"First header should be a top level header"},{"filename":"README.md","line":18,"rule":"MD033","aliases":["no-inline-html"],"description":"Inline HTML"}]' + \ ]) diff --git a/test/handler/test_mercury_mmc_handler.vader b/test/handler/test_mercury_mmc_handler.vader new file mode 100644 index 00000000..e862f287 --- /dev/null +++ b/test/handler/test_mercury_mmc_handler.vader @@ -0,0 +1,58 @@ +Before: + runtime ale_linters/mercury/mmc.vim + +After: + call ale#linter#Reset() + +Execute(The mmc handler should handle syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': "Syntax error at token ',': operator precedence error." + \ } + \ ], + \ ale_linters#mercury#mmc#Handle(1, [ + \ "file_name.m:003: Syntax error at token ',': operator precedence error." + \ ]) + +Execute(The mmc handler should handle warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'type': 'W', + \ 'text': 'Warning: reference to uninitialized state variable !.X.' + \ }, + \ { + \ 'lnum': 12, + \ 'type': 'W', + \ 'text': 'warning: determinism declaration could be tighter.' + \ } + \ ], + \ ale_linters#mercury#mmc#Handle(1, [ + \ 'file_name.m:010: Warning: reference to uninitialized state variable !.X.', + \ "file_name.m:012: In `some_predicate':", + \ 'file_name.m:012: warning: determinism declaration could be tighter.' + \ ]) + +Execute(The mmc handler should handle semantic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'type': 'E', + \ 'text': "error: undefined type `bar'/0." + \ }, + \ { + \ 'lnum': 15, + \ 'type': 'E', + \ 'text': "Error: circular equivalence type `file_name.foo'/0." + \ } + \ ], + \ ale_linters#mercury#mmc#Handle(1, [ + \ "file_name.m:007: In clause for predicate `foldit'/4:", + \ "file_name.m:007: error: undefined type `bar'/0.", + \ "file_name.m:015: Error: circular equivalence type `file_name.foo'/0." + \ ]) diff --git a/test/handler/test_mix_handler.vader b/test/handler/test_mix_handler.vader new file mode 100644 index 00000000..fc04f8f7 --- /dev/null +++ b/test/handler/test_mix_handler.vader @@ -0,0 +1,20 @@ +Before: + runtime ale_linters/elixir/mix.vim + +After: + call ale#linter#Reset() + +Execute(The mix handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 347, + \ 'lnum': 87, + \ 'col': 0, + \ 'text': 'undefined function update_in/4', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#elixir#mix#Handle(347, [ + \ '** (CompileError) apps/sim/lib/sim/server.ex:87: undefined function update_in/4' + \ ]) diff --git a/test/handler/test_msgfmt_hander.vader b/test/handler/test_msgfmt_hander.vader new file mode 100644 index 00000000..1a67dbc6 --- /dev/null +++ b/test/handler/test_msgfmt_hander.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/po/msgfmt.vim + +After: + call ale#linter#Reset() + +Execute(Duplicate messages should be made easier to navigate): + AssertEqual + \ [ + \ {'lnum': 14, 'col': 0, 'type': 'W', 'text': 'some other thing'}, + \ {'lnum': 1746, 'col': 0, 'type': 'W', 'text': 'duplicate of message at line 262'}, + \ {'lnum': 262, 'col': 0, 'type': 'W', 'text': 'first location of duplicate of message at line 1746'}, + \ {'lnum': 666, 'col': 0, 'type': 'W', 'text': 'duplicate message definition...'}, + \ {'lnum': 888, 'col': 0, 'type': 'W', 'text': 'some other thing'}, + \ {'lnum': 999, 'col': 0, 'type': 'W', 'text': '...this is the location of the first definition'}, + \ ], + \ ale_linters#po#msgfmt#Handle(bufnr(''), [ + \ '/tmp/v6GMUFf/16/foo.po:14: some other thing', + \ '/tmp/v6GMUFf/16/foo.po:1746: duplicate message definition...', + \ '/tmp/v6GMUFf/16/foo.po:262: ...this is the location of the first definition', + \ '/tmp/v6GMUFf/16/foo.po:666: duplicate message definition...', + \ '/tmp/v6GMUFf/16/foo.po:888: some other thing', + \ '/tmp/v6GMUFf/16/foo.po:999: ...this is the location of the first definition', + \ ]) diff --git a/test/handler/test_mypy_handler.vader b/test/handler/test_mypy_handler.vader new file mode 100644 index 00000000..039155a2 --- /dev/null +++ b/test/handler/test_mypy_handler.vader @@ -0,0 +1,141 @@ +Before: + Save g:ale_python_mypy_ignore_invalid_syntax + Save g:ale_python_mypy_show_notes + + unlet! g:ale_python_mypy_show_notes + unlet! g:ale_python_mypy_ignore_invalid_syntax + + runtime ale_linters/python/mypy.vim + + call ale#test#SetDirectory('/testplugin/test/handler') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The mypy handler should parse lines correctly): + call ale#test#SetFilename('__init__.py') + + let g:ale_python_mypy_show_notes = 0 + + AssertEqual + \ [ + \ { + \ 'lnum': 768, + \ 'col': 38, + \ 'filename': ale#path#Simplify(g:dir . '/baz.py'), + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''SOME_SYMBOL''', + \ }, + \ { + \ 'lnum': 821, + \ 'col': 38, + \ 'filename': ale#path#Simplify(g:dir . '/baz.py'), + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''SOME_SYMBOL''', + \ }, + \ { + \ 'lnum': 38, + \ 'col': 44, + \ 'filename': ale#path#Simplify(g:dir . '/other.py'), + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''ANOTHER_SYMBOL''', + \ }, + \ { + \ 'lnum': 15, + \ 'col': 3, + \ 'filename': ale#path#Simplify(g:dir . '/__init__.py'), + \ 'type': 'E', + \ 'text': 'Argument 1 to "somefunc" has incompatible type "int"; expected "str"' + \ }, + \ { + \ 'lnum': 72, + \ 'col': 1, + \ 'filename': ale#path#Simplify(g:dir . '/__init__.py'), + \ 'type': 'W', + \ 'text': 'Some warning', + \ }, + \ ], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ 'baz.py: note: In class "SomeClass":', + \ 'baz.py:768:38: error: Cannot determine type of ''SOME_SYMBOL''', + \ 'baz.py: note: In class "AnotherClass":', + \ 'baz.py:821:38: error: Cannot determine type of ''SOME_SYMBOL''', + \ '__init__.py:92: note: In module imported here:', + \ 'other.py: note: In class "YetAnotherClass":', + \ 'other.py:38:44: error: Cannot determine type of ''ANOTHER_SYMBOL''', + \ '__init__.py: note: At top level:', + \ '__init__.py:15:3: error: Argument 1 to "somefunc" has incompatible type "int"; expected "str"', + \ 'another_module/bar.py:14: note: In module imported here,', + \ 'another_module/__init__.py:16: note: ... from here,', + \ '__init__.py:72:1: warning: Some warning', + \ ]) + +Execute(The mypy handler should show notes if enabled): + call ale#test#SetFilename('__init__.py') + + AssertEqual + \ [ + \ { + \ 'lnum': 72, + \ 'col': 1, + \ 'filename': ale#path#Simplify(g:dir . '/__init__.py'), + \ 'type': 'I', + \ 'text': 'A note', + \ }, + \ ], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ '__init__.py:72:1: note: A note', + \ ]) + + let g:ale_python_mypy_show_notes = 0 + + AssertEqual + \ [], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ '__init__.py:72:1: note: A note', + \ ]) + +Execute(The mypy handler should handle Windows names with spaces): + " This test works on Unix, where this is seen as a single filename + silent file C:\\something\\with\ spaces.py + + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 0, + \ 'filename': ale#path#Simplify('C:\something\with spaces.py'), + \ 'type': 'E', + \ 'text': 'No library stub file for module ''django.db''', + \ }, + \ ], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ 'C:\something\with spaces.py:4: error: No library stub file for module ''django.db''', + \ ]) + +Execute(The mypy syntax errors shouldn't be ignored by default): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 0, + \ 'filename': ale#path#Simplify(g:dir . '/foo.py'), + \ 'type': 'E', + \ 'text': 'invalid syntax', + \ }, + \ ], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ 'foo.py:4: error: invalid syntax', + \ ]) + +Execute(The mypy syntax errors should be ignored when the option is on): + let g:ale_python_mypy_ignore_invalid_syntax = 1 + + AssertEqual + \ [], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ 'foo.py:4: error: invalid syntax', + \ ]) diff --git a/test/handler/test_naga_handler.vader b/test/handler/test_naga_handler.vader new file mode 100644 index 00000000..48b8c281 --- /dev/null +++ b/test/handler/test_naga_handler.vader @@ -0,0 +1,23 @@ +Before: + runtime ale_linters/wgsl/naga.vim + +After: + call ale#linter#Reset() + +Execute(Error handler should parse error message and position from input): + let example_output = [ + \ "error: expected global item ('struct', 'let', 'var', 'type', ';', 'fn') or the end of the file, found '['", + \ " ┌─ wgsl:5:1", + \ " │", + \ "5 │ [[group(1), binding(0)]]", + \ " │ ^ expected global item ('struct', 'let', 'var', 'type', ';', 'fn') or the end of the file", + \ "Could not parse WGSL", + \ ] + let actual = ale#handlers#naga#Handle(0, example_output) + let expected = [{ + \ "text": "expected global item ('struct', 'let', 'var', 'type', ';', 'fn') or the end of the file, found '['", + \ "lnum": 5, + \ "col": 1, + \ "type": "E", + \ }] + AssertEqual actual, expected diff --git a/test/handler/test_nagelfar_handler.vader b/test/handler/test_nagelfar_handler.vader new file mode 100644 index 00000000..ceaee19c --- /dev/null +++ b/test/handler/test_nagelfar_handler.vader @@ -0,0 +1,174 @@ +Before: + runtime ale_linters/tcl/nagelfar.vim + +After: + call ale#linter#Reset() + +Execute(The nagelfar handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'type': 'W', + \ 'text': 'Found constant "bepa" which is also a variable.' + \ }, + \ { + \ 'lnum': 7, + \ 'type': 'E', + \ 'text': 'Unknown variable "cep"' + \ }, + \ { + \ 'lnum': 7, + \ 'type': 'W', + \ 'text': 'Unknown command "se"' + \ }, + \ { + \ 'lnum': 8, + \ 'type': 'E', + \ 'text': 'Unknown variable "epa"' + \ }, + \ { + \ 'lnum': 10, + \ 'type': 'E', + \ 'text': 'Unknown variable "depa"' + \ }, + \ { + \ 'lnum': 10, + \ 'type': 'W', + \ 'text': 'Suspicious variable name "$depa"' + \ }, + \ { + \ 'lnum': 11, + \ 'type': 'W', + \ 'text': 'Suspicious variable name "$cepa"' + \ }, + \ { + \ 'lnum': 13, + \ 'type': 'E', + \ 'text': 'Wrong number of arguments (3) to "set"' + \ }, + \ { + \ 'lnum': 13, + \ 'type': 'W', + \ 'text': 'Found constant "bepa" which is also a variable.' + \ }, + \ { + \ 'lnum': 13, + \ 'type': 'W', + \ 'text': 'Found constant "cepa" which is also a variable.' + \ }, + \ { + \ 'lnum': 18, + \ 'type': 'E', + \ 'text': 'Badly formed if statement' + \ }, + \ { + \ 'lnum': 24, + \ 'type': 'E', + \ 'text': 'Unknown subcommand "gurka" to "info"' + \ }, + \ { + \ 'lnum': 31, + \ 'type': 'W', + \ 'text': 'Switch pattern starting with #. This could be a bad comment.' + \ }, + \ { + \ 'lnum': 31, + \ 'type': 'W', + \ 'text': 'Unknown command "This"' + \ }, + \ { + \ 'lnum': 31, + \ 'type': 'W', + \ 'text': 'Unknown command "bad"' + \ }, + \ { + \ 'lnum': 34, + \ 'type': 'W', + \ 'text': 'Unknown command "miffo"' + \ }, + \ { + \ 'lnum': 55, + \ 'type': 'W', + \ 'text': 'Suspicious variable name "$bepa"' + \ }, + \ { + \ 'lnum': 56, + \ 'type': 'W', + \ 'text': 'Suspicious variable name "$apa"' + \ }, + \ { + \ 'lnum': 61, + \ 'type': 'E', + \ 'text': 'Could not complete statement.' + \ }, + \ { + \ 'lnum': 67, + \ 'type': 'E', + \ 'text': 'Could not complete statement.' + \ }, + \ { + \ 'lnum': 70, + \ 'type': 'E', + \ 'text': 'Wrong number of arguments (4) to "proc"' + \ }, + \ { + \ 'lnum': 72, + \ 'type': 'E', + \ 'text': 'Wrong number of arguments (1) to "if"' + \ }, + \ { + \ 'lnum': 75, + \ 'type': 'E', + \ 'text': 'Unbalanced close brace found' + \ }, + \ { + \ 'lnum': 82, + \ 'type': 'E', + \ 'text': 'Unbalanced close brace found' + \ }, + \ { + \ 'lnum': 88, + \ 'type': 'E', + \ 'text': 'Could not complete statement.' + \ }, + \ { + \ 'lnum': 90, + \ 'type': 'E', + \ 'text': 'Wrong number of arguments (1) to "if"' + \ }, + \ { + \ 'lnum': 93, + \ 'type': 'W', + \ 'text': 'Close brace not aligned with line 90 (4 0)' + \ }, + \ ], + \ ale_linters#tcl#nagelfar#Handle(bufnr(''), [ + \ 'Line 5: W Found constant "bepa" which is also a variable.', + \ 'Line 7: E Unknown variable "cep"', + \ 'Line 7: W Unknown command "se"', + \ 'Line 8: E Unknown variable "epa"', + \ 'Line 10: E Unknown variable "depa"', + \ 'Line 10: N Suspicious variable name "$depa"', + \ 'Line 11: N Suspicious variable name "$cepa"', + \ 'Line 13: E Wrong number of arguments (3) to "set"', + \ 'Line 13: W Found constant "bepa" which is also a variable.', + \ 'Line 13: W Found constant "cepa" which is also a variable.', + \ 'Line 18: E Badly formed if statement', + \ 'Line 24: E Unknown subcommand "gurka" to "info"', + \ 'Line 31: W Switch pattern starting with #. This could be a bad comment.', + \ 'Line 31: W Unknown command "This"', + \ 'Line 31: W Unknown command "bad"', + \ 'Line 34: W Unknown command "miffo"', + \ 'Line 55: N Suspicious variable name "$bepa"', + \ 'Line 56: N Suspicious variable name "$apa"', + \ 'Line 61: E Could not complete statement.', + \ 'Line 67: E Could not complete statement.', + \ 'Line 70: E Wrong number of arguments (4) to "proc"', + \ 'Line 72: E Wrong number of arguments (1) to "if"', + \ 'Line 75: E Unbalanced close brace found', + \ 'Line 82: E Unbalanced close brace found', + \ 'Line 88: E Could not complete statement.', + \ 'Line 90: E Wrong number of arguments (1) to "if"', + \ 'Line 93: N Close brace not aligned with line 90 (4 0)', + \ ]) diff --git a/test/handler/test_nasm_handler.vader b/test/handler/test_nasm_handler.vader new file mode 100644 index 00000000..9c7d9650 --- /dev/null +++ b/test/handler/test_nasm_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/nasm/nasm.vim + +After: + call ale#linter#Reset() + +Execute(The nasm handler should parse GCC style output from nasm correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'text': "label alone on a line without a colon might be in error", + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'text': "invalid combination of opcode and operands", + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 7, + \ 'text': "unable to open include file `bar.asm'", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#nasm#nasm#Handle(bufnr(''), [ + \ "tmp.asm:2: warning: label alone on a line without a colon might be in error", + \ "tmp.asm:4: error: invalid combination of opcode and operands", + \ "tmp.asm:7: fatal: unable to open include file `bar.asm'" + \ ]) diff --git a/test/handler/test_nim_handler.vader b/test/handler/test_nim_handler.vader new file mode 100644 index 00000000..b6784d84 --- /dev/null +++ b/test/handler/test_nim_handler.vader @@ -0,0 +1,80 @@ +Before: + runtime ale_linters/nim/nimcheck.vim + +After: + call ale#linter#Reset() + +Execute(Parsing nim errors should work): + silent file foobar.nim + + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'col': 8, + \ 'text': 'use {.base.} for base methods; baseless methods are deprecated', + \ 'code': 'UseBase', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 12, + \ 'col': 2, + \ 'text': 'identifier expected, but found ''a.barfoo''', + \ 'type': 'E', + \ 'end_col': 9, + \ }, + \ { + \ 'lnum': 2, + \ 'col': 5, + \ 'text': '''NotUsed'' is declared but not used', + \ 'code': 'XDeclaredButNotUsed', + \ 'type': 'W', + \ 'end_col': 11, + \ }, + \ { + \ 'lnum': 12, + \ 'col': 2, + \ 'text': 'with : character', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 8, + \ 'text': 'imported and not used: ''strutils''', + \ 'code': 'UnusedImport', + \ 'type': 'W', + \ 'end_col': 15, + \ }, + \ { + \ 'lnum': 12, + \ 'col': 9, + \ 'text': 'undeclared identifier: ''total''', + \ 'type': 'E', + \ 'end_col': 13, + \ }, + \ { + \ 'lnum': 14, + \ 'col': 1, + \ 'text': '''sum'' cannot be assigned to', + \ 'type': 'E', + \ 'end_col': 3, + \ }, + \ { + \ 'lnum': 15, + \ 'col': 1, + \ 'text': 'redefinition of ''getName''; previous declaration here: /nested/folder/foobar.nim(14, 6)', + \ 'type': 'E', + \ 'end_col': 7, + \ }, + \ ], + \ ale_linters#nim#nimcheck#Handle(bufnr(''), [ + \ 'Line with wrong( format)', + \ 'foobar.nim(8, 8) Warning: use {.base.} for base methods; baseless methods are deprecated [UseBase]', + \ 'foobar.nim(12, 2) Error: identifier expected, but found ''a.barfoo''', + \ '/nested/folder/foobar.nim(2, 5) Hint: ''NotUsed'' is declared but not used [XDeclaredButNotUsed]', + \ 'foobar.nim(12, 2) Error: with : character', + \ 'foobar.nim(1, 8) Warning: imported and not used: ''strutils'' [UnusedImport]', + \ 'foobar.nim(12, 9) Error: undeclared identifier: ''total''', + \ 'foobar.nim(14, 1) Error: ''sum'' cannot be assigned to', + \ 'foobar.nim(15, 1) Error: redefinition of ''getName''; previous declaration here: /nested/folder/foobar.nim(14, 6)', + \ ]) diff --git a/test/handler/test_nix_handler.vader b/test/handler/test_nix_handler.vader new file mode 100644 index 00000000..56d88b29 --- /dev/null +++ b/test/handler/test_nix_handler.vader @@ -0,0 +1,117 @@ +Before: + runtime ale_linters/nix/nix.vim + +After: + call ale#linter#Reset() + +Execute(The nix handler should parse nix-instantiate error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 3, + \ 'type': 'E', + \ 'text': "syntax error, unexpected ']', expecting ';'", + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'type': 'E', + \ 'text': "undefined variable 'foo'", + \ }, + \ + \ ], + \ ale_linters#nix#nix#Handle(bufnr(''), [ + \ "@nix {\"line\":6,\"column\":3,\"raw_msg\":\"syntax error, unexpected ']', expecting ';'\"}", + \ "@nix {\"line\":3,\"column\":5,\"raw_msg\":\"undefined variable 'foo'\"}", + \ "@nix {\"unrelated\":\"message\"}" + \ ]) + +Execute(The nix handler should parse nix-instantiate error messages with ANSI color codes correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'type': 'E', + \ 'text': "undefined variable 'foo'", + \ }, + \ + \ ], + \ ale_linters#nix#nix#Handle(bufnr(''), [ + \ "@nix {\"line\":3,\"column\":5,\"raw_msg\":\"undefined variable '\\u001b[35;1mfoo\\u001b[0m'\"}", + \ "@nix {\"unrelated\":\"message\"}" + \ ]) + +Execute(The nix handler should parse message from old nix-instantiate correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 23, + \ 'col': 14, + \ 'text': 'error: syntax error, unexpected IN', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 12, + \ 'text': 'error: syntax error, unexpected ''='', expecting '';''', + \ 'type': 'E', + \ }, + \ + \ ], + \ ale_linters#nix#nix#Handle(47, [ + \ 'This line should be ignored', + \ 'error: syntax error, unexpected IN, at /path/to/filename.nix:23:14', + \ 'error: syntax error, unexpected ''='', expecting '';'', at /path/to/filename.nix:3:12', + \ ]) + +Execute(The nix command should not add 'log-format' option for nix version 2.3): + AssertEqual + \ 'nix-instantiate --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.3.0'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.4): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.4.1'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.5): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.5.0pre20211206_d1aaa7e'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.6): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.6.0pre20211206_ignored'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.7): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.7.0pre20211206_ignored'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.8): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.8.0pre20211206_ignored'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.9): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.9.0pre20211206_ignored'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.10): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.10.0pre20221221_ignored'], '') + +Execute(The nix command should add 'log-format' option for nix version 2.20): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 2.20.0pre20221221_ignored'], '') + +Execute(The nix command should add 'log-format' option for nix version 3.0): + AssertEqual + \ 'nix-instantiate --log-format internal-json --parse -', + \ ale_linters#nix#nix#Command('', ['nix-instantiate (Nix) 3.0.0pre20211206_ignored'], '') diff --git a/test/handler/test_npmgroovylint_handler.vader b/test/handler/test_npmgroovylint_handler.vader new file mode 100644 index 00000000..e0dae6d2 --- /dev/null +++ b/test/handler/test_npmgroovylint_handler.vader @@ -0,0 +1,63 @@ +Before: + runtime ale_linters/groovy/npmgroovylint.vim + +After: + call ale#linter#Reset() + +Execute(The npm-groovy-lint handler should parse JSON): + AssertEqual + \ [ + \ { + \ 'col': 0, + \ 'end_col': 1, + \ 'end_lnum': 2, + \ 'filename': 'test2.groovy', + \ 'lnum': 2, + \ 'text': 'Some error message', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'test.groovy', + \ 'lnum': 1, + \ 'text': 'Some warning message', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#groovy#npmgroovylint#Handle(bufnr(''), [ + \ '{', + \ ' "files" : {', + \ ' "test.groovy" : {', + \ ' "errors" : [', + \ ' {', + \ ' "id" : 0,', + \ ' "line" : 1,', + \ ' "msg" : "Some warning message",', + \ ' "rule" : "SomeRule",', + \ ' "severity" : "warning"', + \ ' }', + \ ' ]', + \ ' },', + \ ' "test2.groovy": {', + \ ' "errors": [', + \ ' {', + \ ' "id" : 1,', + \ ' "line" : 2,', + \ ' "msg" : "Some error message",', + \ ' "range" : {', + \ ' "end" : {', + \ ' "character" : 1,', + \ ' "line" : 2', + \ ' },', + \ ' "start" : {', + \ ' "character" : 0,', + \ ' "line" : 2', + \ ' }', + \ ' },', + \ ' "rule" : "SomeOtherRule",', + \ ' "severity" : "error"', + \ ' }', + \ ' ]', + \ ' }', + \ ' }', + \ '}', + \ ]) diff --git a/test/handler/test_openscad_handler.vader b/test/handler/test_openscad_handler.vader new file mode 100644 index 00000000..aaecc588 --- /dev/null +++ b/test/handler/test_openscad_handler.vader @@ -0,0 +1,76 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + + " Load sca2d + runtime ale_linters/openscad/sca2d.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The openscad handler should handle sca2d output): + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(g:dir . '/awesome_project.scad'), + \ 'lnum': 7, + \ 'type': 'E', + \ 'col': 42, + \ 'text': 'Module `corcle` used but never defined.', + \ 'detail': 'E2002: Module `corcle` used but never defined.', + \ }, + \ ], + \ ale#handlers#openscad#SCA2D_callback(bufnr(''), [ + \ 'awesome_project.scad:7:42: E2002: Module `corcle` used but never defined.', + \ '', + \ 'SCA2D message summary', + \ '=====================', + \ 'Fatal errors: 0', + \ 'Errors: 1', + \ 'Warnings: 0', + \ 'Info: 0', + \ 'Depreciated 0', + \ ]) + + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(g:dir . '/awesome_project.scad'), + \ 'lnum': 1, + \ 'type': 'E', + \ 'col': 37, + \ 'text': 'Cannot read file due to syntax error: - No terminal matches ''}'' in the current parser context', + \ 'detail': 'F0001: Cannot read file due to syntax error: - No terminal matches ''}'' in the current parser context', + \ }, + \ ], + \ ale#handlers#openscad#SCA2D_callback(bufnr(''), [ + \ 'awesome_project.scad:1:1: F0001: Cannot read file due to syntax error:', + \ ' - No terminal matches ''}'' in the current parser context, at line 1 col 37', + \ ' - ', + \ ' - translate([ 0, 0, 0 ]) { circle(10) }', + \ ' - ^', + \ ' - Expected one of: ', + \ ' - * IF', + \ ' - * LET', + \ ' - * FOR', + \ ' - * FUNC_CALL_NAME', + \ ' - * TERMINATION', + \ ' - * STAR', + \ ' - * LBRACE', + \ ' - * BANG', + \ ' - * ASSIGN', + \ ' - * PERCENT', + \ ' - * HASH', + \ ' - * INTERSECTION_FOR', + \ ' - ', + \ 'If you believe this is a bug in SCA2D please report it to us.', + \ '', + \ '', + \ 'SCA2D message summary', + \ '=====================', + \ 'Fatal errors: 1', + \ 'Errors: 0', + \ 'Warnings: 0', + \ 'Info: 0', + \ 'Depreciated 0', + \ ]) diff --git a/test/handler/test_packwerk_handler.vader b/test/handler/test_packwerk_handler.vader new file mode 100644 index 00000000..b95b7556 --- /dev/null +++ b/test/handler/test_packwerk_handler.vader @@ -0,0 +1,29 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + cd .. + + runtime ale_linters/ruby/packwerk.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The packwerk handler should parse lines correctly): + call ale#test#SetFilename('test-files/ruby/valid_rails_app/app/models/thing.rb') + + AssertEqual + \ [ + \ { + \ 'lnum': 36, + \ 'col': 100, + \ 'text': 'Dependency violation: ::Edi::Source belongs to ''edi'', but ''billing'' does not specify a dependency on ''edi''.', + \ } + \ ], + \ ale_linters#ruby#packwerk#Handle(bufnr(''), [ + \ '/Users/JaneDoe/src/github.com/sample-project/billing/app/jobs/document_processing_job.rb:36:100', + \ 'Dependency violation: ::Edi::Source belongs to ''edi'', but ''billing'' does not specify a dependency on ''edi''.', + \ 'Are we missing an abstraction?', + \ 'Is the code making the reference, and the referenced constant, in the right packages?', + \ '', + \ 'Inference details: ''Edi::Source'' refers to ::Edi::Source which seems to be defined in edi/app/models/edi/source.rb.', + \ ]) diff --git a/test/handler/test_perl6_handler.vader b/test/handler/test_perl6_handler.vader new file mode 100644 index 00000000..452a9174 --- /dev/null +++ b/test/handler/test_perl6_handler.vader @@ -0,0 +1,277 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + + runtime ale_linters/perl6/perl6.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The Perl6 linter should handle empty output): + call ale#test#SetFilename('bar.pl6') + + AssertEqual [], ale_linters#perl6#perl6#Handle(bufnr(''), []) + +Execute(The Perl6 linter should complain about undeclared variables): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [ + \ { + \ 'lnum': '6', + \ 'text': 'Variable ''$tes'' is not declared. Did you mean any of these? $res $test ', + \ 'type': 'E', + \ 'col': '', + \ 'end_lnum': '', + \ 'code': 'X::Undeclared', + \ } + \ ], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ '{ + \ "X::Undeclared" : { + \ "highexpect" : [ ], + \ "is-compile-time" : 1, + \ "modules" : [ ], + \ "column" : null, + \ "pos" : 18, + \ "symbol" : "$tes", + \ "filename" : "bar.pl6", + \ "what" : "Variable", + \ "pre" : "my $test = 0; say ", + \ "post" : "$tes", + \ "suggestions" : [ + \ "$res", + \ "$test" + \ ], + \ "line" : 6, + \ "message" : "Variable ''$tes'' is not declared. Did you mean any of these?\n $res\n $test\n" + \ } + \ }' + \ ]) + +Execute(The Perl6 linter should complain about Comp::AdHoc errors): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [ + \ { + \ 'lnum': '3', + \ 'type': 'E', + \ 'text': 'is repr(...) trait needs a parameter', + \ 'col': '', + \ 'end_lnum': '', + \ 'code': 'X::Comp::AdHoc', + \ } + \ ], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ '{ + \ "X::Comp::AdHoc" : { + \ "pre" : "class test is repr", + \ "message" : "is repr(...) trait needs a parameter", + \ "line" : 3, + \ "post" : " {}", + \ "is-compile-time" : true, + \ "pos" : 19, + \ "highexpect" : [ ], + \ "payload" : "is repr(...) trait needs a parameter", + \ "filename" : "bar.pl6", + \ "column" : null, + \ "modules" : [ ] + \ } + \ }' + \]) + +Execute(The Perl6 linter should be able to extract a line number from an error message): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [ + \ { + \ 'lnum': '3', + \ 'text': 'Could not find Module::Does::not::exist at line 3 in: /usr/share/perl6/site /usr/share/perl6/vendor /usr/share/perl6 CompUnit::Repository::AbsolutePath<94023691448416> CompUnit::Repository::NQP<94023670532736> CompUnit::Repository::Perl5<94023670532776>', + \ 'col': '', + \ 'type': 'E', + \ 'end_lnum': '', + \ 'code': 'X::CompUnit::UnsatisfiedDependency', + \ } + \ ], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ '{ + \ "X::CompUnit::UnsatisfiedDependency" : { + \ "message" : "Could not find Module::Does::not::exist at line 3 in:\n /usr/share/perl6/site\n /usr/share/perl6/vendor\n /usr/share/perl6\n CompUnit::Repository::AbsolutePath<94023691448416>\n CompUnit::Repository::NQP<94023670532736>\n CompUnit::Repository::Perl5<94023670532776>", + \ "specification" : "Module::Does::not::exist" + \ } + \ }' + \ ]) + +Execute(The Perl6 linter should be able to differentiate between warnings and errors): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [ + \ { + \ 'lnum': '1', + \ 'col': '', + \ 'code': 'X::Syntax::Regex::Unterminated', + \ 'end_lnum': '', + \ 'type': 'E', + \ 'text': 'Regex not terminated.', + \ }, + \ { + \ 'lnum': '1', + \ 'col': '', + \ 'code': 'X::Comp::AdHoc', + \ 'end_lnum': '', + \ 'type': 'W', + \ 'text': 'Space is not significant here; please use quotes or :s (:sigspace) modifier (or, to suppress this warning, omit the space, or otherwise change the spacing)', + \ } + \ ], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ '{ + \ "X::Comp::Group" : { + \ "message" : "Regex not terminated.\nUnable to parse regex; couldn''t find final ''/''\nSpace is not significant here; please use quotes or :s (:sigspace) modifier (or, to suppress this warning, omit the space, or otherwise change the spacing)", + \ "panic" : "Unable to parse regex; couldn''t find final ''/''", + \ "sorrows" : [ + \ { + \ "X::Syntax::Regex::Unterminated" : { + \ "highexpect" : [ + \ "infix stopper" + \ ], + \ "pos" : 6, + \ "is-compile-time" : 1, + \ "modules" : [ ], + \ "post" : "", + \ "message" : "Regex not terminated.", + \ "line" : 1, + \ "filename" : "bar.pl6", + \ "column" : null, + \ "pre" : "/win 3" + \ } + \ } + \ ], + \ "worries" : [ + \ { + \ "X::Comp::AdHoc" : { + \ "filename" : "bar.pl6", + \ "line" : 1, + \ "column" : null, + \ "pre" : "/win", + \ "highexpect" : [ ], + \ "payload" : "Space is not significant here; please use quotes or :s (:sigspace) modifier (or, to suppress this warning, omit the space, or otherwise change the spacing)", + \ "post" : " 3", + \ "message" : "Space is not significant here; please use quotes or :s (:sigspace) modifier (or, to suppress this warning, omit the space, or otherwise change the spacing)", + \ "modules" : [ ], + \ "is-compile-time" : true, + \ "pos" : 4 + \ } + \ } + \ ] + \ } + \ }' + \]) + +Execute(The Perl6 linter should gracefully handle non-JSON messages): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [ + \ { + \ 'lnum': '1', + \ 'text': 'Received output in the default Perl6 error format. See :ALEDetail for details', + \ 'type': 'W', + \ 'detail': join([ + \ 'Potential difficulties:', + \ ' Redeclaration of symbol ''$_''', + \ ' at /home/travis/perl6-error-fail/insanity-test.pl6:1', + \ ' ------> sub foo($_) {.say}; my $_ = 1; .&foo;', + \ ' Space is not significant here; please use quotes or :s (:sigspace) modifier (or, to suppress this warning, omit the space, or otherwise change the spacing)', + \ ' at /home/travis/perl6-error-fail/insanity-test.pl6:4', + \ ' ------> /win 3/', + \ 'Syntax OK',], "\n") + \ } + \ ], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ 'Potential difficulties:', + \ ' Redeclaration of symbol ''$_''', + \ ' at /home/travis/perl6-error-fail/insanity-test.pl6:1', + \ ' ------> sub foo($_) {.say}; my $_ = 1; .&foo;', + \ ' Space is not significant here; please use quotes or :s (:sigspace) modifier (or, to suppress this warning, omit the space, or otherwise change the spacing)', + \ ' at /home/travis/perl6-error-fail/insanity-test.pl6:4', + \ ' ------> /win 3/', + \ 'Syntax OK' + \ ]) + +Execute(The Perl6 linter should gracefully handle messages without a line number): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [ + \ { + \ 'lnum': '1', + \ 'end_lnum': '', + \ 'text': 'Cannot find method ''has_compile_time_value'' on object of type NQPMu', + \ 'type': 'E', + \ 'col' : '', + \ 'code': 'X::AdHoc', + \ } + \ ], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ '{', + \ '"X::AdHoc" : {', + \ '"message" : "Cannot find method ''has_compile_time_value'' on object of type NQPMu",', + \ '"payload" : "Cannot find method ''has_compile_time_value'' on object of type NQPMu"', + \ '}', + \ '}', + \ ]) + +Execute(The Perl6 linter should not include errors from a known separate file): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ '{ + \ "X::Undeclared" : { + \ "highexpect" : [ ], + \ "is-compile-time" : 1, + \ "modules" : [ ], + \ "column" : null, + \ "pos" : 18, + \ "symbol" : "$tes", + \ "filename" : "foo.pl6", + \ "what" : "Variable", + \ "pre" : "my $test = 0; say ", + \ "post" : "$tes", + \ "suggestions" : [ + \ "$res", + \ "$test" + \ ], + \ "line" : 6, + \ "message" : "Variable ''$tes'' is not declared. Did you mean any of these?\n $res\n $test\n" + \ } + \ }' + \ ]) + +Execute(The Perl6 linter should not ignore errors without a filename): + call ale#test#SetFilename('bar.pl6') + + AssertEqual + \ [ + \ { + \ 'lnum': '3', + \ 'end_lnum': '', + \ 'text': 'Cannot find method ''has_compile_time_value'' on object of type NQPMu', + \ 'type': 'E', + \ 'col' : '', + \ 'code': 'X::AdHoc', + \ } + \ ], + \ ale_linters#perl6#perl6#Handle(bufnr(''), [ + \ '{', + \ '"X::AdHoc" : {', + \ '"line" : 3,', + \ '"message" : "Cannot find method ''has_compile_time_value'' on object of type NQPMu",', + \ '"payload" : "Cannot find method ''has_compile_time_value'' on object of type NQPMu"', + \ '}', + \ '}', + \ ]) diff --git a/test/handler/test_perl_handler.vader b/test/handler/test_perl_handler.vader new file mode 100644 index 00000000..060b1ffe --- /dev/null +++ b/test/handler/test_perl_handler.vader @@ -0,0 +1,109 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + + runtime ale_linters/perl/perl.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The Perl linter should handle empty output): + call ale#test#SetFilename('bar.pl') + + AssertEqual [], ale_linters#perl#perl#Handle(bufnr(''), []) + +Execute(The Perl linter should ignore errors from other files): + call ale#test#SetFilename('bar.pl') + + AssertEqual + \ [ + \ {'lnum': '2', 'type': 'E', 'text': 'Compilation failed in require'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ 'syntax error at ' . ale#path#Simplify(g:dir . '/foo.pm') . ' line 4, near "aklsdfjmy "', + \ 'Compilation failed in require at ' . ale#path#Simplify(g:dir . '/bar.pl') . ' line 2.', + \ 'BEGIN failed--compilation aborted at ' . ale#path#Simplify(g:dir . '/bar.pl') . ' line 2.', + \ ]) + +Execute(The Perl linter should complain about failing to locate modules): + AssertEqual + \ [ + \ { + \ 'lnum': '23', + \ 'type': 'E', + \ 'text': 'Can''t locate JustOneDb.pm in @INC (you may need to install the JustOneDb module) (@INC contains: /home/local/sean/work/PostgreSQL/6616/../../../../lib /home/local/sean/work/PostgreSQL/6616/lib lib /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .)', + \ }, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ 'Can''t locate JustOneDb.pm in @INC (you may need to install the JustOneDb module) (@INC contains: /home/local/sean/work/PostgreSQL/6616/../../../../lib /home/local/sean/work/PostgreSQL/6616/lib lib /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .) at - line 23.', + \ 'BEGIN failed--compilation aborted at - line 23.', + \ ]) + + +Execute(The Perl linter should complain about failing to locate modules): + AssertEqual + \ [ + \ {'lnum': '8', 'type': 'E', 'text': 'BEGIN failed--compilation aborted'}, + \ {'lnum': '10', 'type': 'E', 'text': 'BEGIN failed--compilation aborted'} + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ 'Unable to build `ro` accessor for slot `path` in `App::CPANFileUpdate` because the slot cannot be found. at /extlib/Method/Traits.pm line 189.', + \ 'BEGIN failed--compilation aborted at - line 8.', + \ 'Unable to build `ro` accessor for slot `path` in `App::CPANFileUpdate` because the slot cannot be found. at /extlib/Method/Traits.pm line 189.', + \ 'BEGIN failed--compilation aborted at - line 10.', + \ ]) + +Execute(The Perl linter should not report warnings as errors): + AssertEqual + \ [ + \ {'lnum': '5', 'type': 'W', 'text': '"my" variable $foo masks earlier declaration in same scope'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ '"my" variable $foo masks earlier declaration in same scope at - line 5.', + \ 't.pl syntax OK', + \ ]) + +Execute(The Perl linter does not default to reporting generic error): + AssertEqual + \ [ + \ {'lnum': '8', 'type': 'E', 'text': 'Missing right curly or square bracket'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ 'Missing right curly or square bracket at - line 8, at end of line', + \ 'syntax error at - line 8, at EOF', + \ 'Execution of t.pl aborted due to compilation errors.', + \ ]) + +" The first "error" is actually a warning, but the current implementation +" doesn't have a good way of teasing out the warnings from amongst the +" errors. If we're able to do this in future, then we'll want to switch +" the first "E" to a "W". + +Execute(The Perl linter reports errors even when mixed with warnings): + AssertEqual + \ [ + \ {'lnum': '5', 'type': 'E', 'text': '"my" variable $foo masks earlier declaration in same scope'}, + \ {'lnum': '8', 'type': 'E', 'text': 'Missing right curly or square bracket'}, + \ ], + \ ale_linters#perl#perl#Handle(bufnr(''), [ + \ '"my" variable $foo masks earlier declaration in same scope at - line 5.', + \ 'Missing right curly or square bracket at - line 8, at end of line', + \ 'syntax error at - line 8, at EOF', + \ 'Execution of t.pl aborted due to compilation errors.', + \ ]) + +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 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, line 1.', + \ 'Execution of t.pl aborted due to compilation errors.', + \ ]) diff --git a/test/handler/test_perlcritic_handler.vader b/test/handler/test_perlcritic_handler.vader new file mode 100644 index 00000000..f165a32f --- /dev/null +++ b/test/handler/test_perlcritic_handler.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/perl/perlcritic.vim + +After: + call ale#linter#Reset() + +Execute(The Perl::Critic handler should create all issues as warnings): + AssertEqual + \ [ + \ { + \ 'lnum': '21', + \ 'col': '17', + \ 'text': 'Regular expression without "/m" flag', + \ 'type': 'W', + \ } + \ ], + \ ale_linters#perl#perlcritic#Handle(99, [ + \ '21:17 Regular expression without "/m" flag' + \ ]) diff --git a/test/handler/test_php_handler.vader b/test/handler/test_php_handler.vader new file mode 100644 index 00000000..6fe9b323 --- /dev/null +++ b/test/handler/test_php_handler.vader @@ -0,0 +1,93 @@ +Before: + runtime ale_linters/php/php.vim + +After: + call ale#linter#Reset() + +Given (Some invalid lines of PHP): + [foo;] + class Foo { / } + $foo) + ['foo' 'bar'] + function count() {} + +Execute(The php handler should calculate column numbers): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 5, + \ 'end_col': 5, + \ 'text': "syntax error, unexpected ';', expecting ']'", + \ }, + \ { + \ 'lnum': 2, + \ 'col': 13, + \ 'end_col': 13, + \ 'text': "syntax error, unexpected '/', expecting function (T_FUNCTION) or const (T_CONST)", + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'end_col': 5, + \ 'text': "syntax error, unexpected ')'", + \ }, + \ { + \ 'lnum': 4, + \ 'col': 8, + \ 'end_col': 12, + \ 'text': "syntax error, unexpected ''bar'' (T_CONSTANT_ENCAPSED_STRING), expecting ']'", + \ }, + \ ], + \ ale_linters#php#php#Handle(347, [ + \ "This line should be ignored completely", + \ "Parse error: syntax error, unexpected ';', expecting ']' in - on line 1", + \ "Parse error: syntax error, unexpected '/', expecting function (T_FUNCTION) or const (T_CONST) in - on line 2", + \ "Parse error: syntax error, unexpected ')' in - on line 3", + \ "Parse error: syntax error, unexpected ''bar'' (T_CONSTANT_ENCAPSED_STRING), expecting ']' in - on line 4", + \ ]) + +Execute (The php handler should ignore lines starting with 'PHP Parse error'): + AssertEqual + \ [], + \ ale_linters#php#php#Handle(347, [ + \ "PHP Parse error: syntax error, This line should be ignored completely in - on line 1", + \ ]) + +Execute (The php handler should handle lines containing 'Standard input code'): + AssertEqual + \ [ + \ { + \ 'lnum': 47, + \ 'col': 0, + \ 'text': "Invalid numeric literal", + \ }, + \ ], + \ ale_linters#php#php#Handle(347, [ + \ "Parse error: Invalid numeric literal in Standard input code on line 47", + \ ]) +Execute (The php handler should parse lines without column indication): + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 0, + \ 'text': "Cannot redeclare count()", + \ }, + \ { + \ 'lnum': 21, + \ 'col': 0, + \ 'text': "syntax error, unexpected end of file", + \ }, + \ { + \ 'lnum': 47, + \ 'col': 0, + \ 'text': "Invalid numeric literal", + \ }, + \ ], + \ ale_linters#php#php#Handle(347, [ + \ "This line should be ignored completely", + \ "Fatal error: Cannot redeclare count() in - on line 5", + \ "Parse error: syntax error, unexpected end of file in - on line 21", + \ "Parse error: Invalid numeric literal in - on line 47", + \ ]) diff --git a/test/handler/test_php_phan_handler.vader b/test/handler/test_php_phan_handler.vader new file mode 100644 index 00000000..bbdae5dd --- /dev/null +++ b/test/handler/test_php_phan_handler.vader @@ -0,0 +1,26 @@ +Before: + runtime ale_linters/php/phan.vim + +After: + call ale#linter#Reset() + +Execute(The php static analyzer handler should parse errors from phan): + AssertEqual + \ [ + \ { + \ 'lnum': 25, + \ 'type': 'W', + \ 'text': 'Return type of getValidator is undeclared type \Respect\Validation\Validator', + \ 'filename': 'example.php', + \ }, + \ { + \ 'lnum': 66, + \ 'type': 'W', + \ 'text': 'Call to method string from undeclared class \Respect\Validation\Validator', + \ 'filename': 'example.php', + \ }, + \ ], + \ ale_linters#php#phan#Handle(347, [ + \ "example.php:25 PhanUndeclaredTypeReturnType Return type of getValidator is undeclared type \\Respect\\Validation\\Validator", + \ "example.php:66 PhanUndeclaredClassMethod Call to method string from undeclared class \\Respect\\Validation\\Validator", + \ ]) diff --git a/test/handler/test_php_phpmd_handler.vader b/test/handler/test_php_phpmd_handler.vader new file mode 100644 index 00000000..f161d731 --- /dev/null +++ b/test/handler/test_php_phpmd_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/php/phpmd.vim + +After: + call ale#linter#Reset() + +Execute(The php static analyzer handler should parse errors from phpmd): + AssertEqual + \ [ + \ { + \ 'lnum': 22, + \ 'type': 'W', + \ 'text': "Avoid unused local variables such as '$response'.", + \ }, + \ { + \ 'lnum': 14, + \ 'type': 'W', + \ 'text': "The method test uses an else expression. Else is never necessary and you can simplify the code to work without else.", + \ }, + \ ], + \ ale_linters#php#phpmd#Handle(347, [ + \ "example.php:22 Avoid unused local variables such as '$response'.", + \ "example.php:14 The method test uses an else expression. Else is never necessary and you can simplify the code to work without else.", + \ ]) diff --git a/test/handler/test_phpcs_handler.vader b/test/handler/test_phpcs_handler.vader new file mode 100644 index 00000000..26d35cb8 --- /dev/null +++ b/test/handler/test_phpcs_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/php/phpcs.vim + +After: + call ale#linter#Reset() + +Execute(phpcs errors should be handled): + AssertEqual + \ [ + \ { + \ 'lnum': 18, + \ 'col': 3, + \ 'type': 'E', + \ 'sub_type': 'style', + \ 'text': 'Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)', + \ }, + \ { + \ 'lnum': 22, + \ 'col': 3, + \ 'type': 'E', + \ 'sub_type': 'style', + \ 'text': 'All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks)', + \ }, + \ ], + \ ale_linters#php#phpcs#Handle(bufnr(''), [ + \ '/path/to/some-filename.php:18:3: error - Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)', + \ "/path/to/some-filename.php:22:3: error - All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '\"\n'.", + \ ]) diff --git a/test/handler/test_phpstan_handler.vader b/test/handler/test_phpstan_handler.vader new file mode 100644 index 00000000..3599178f --- /dev/null +++ b/test/handler/test_phpstan_handler.vader @@ -0,0 +1,49 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + + runtime ale_linters/php/phpstan.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Output without errors should be parsed correctly): + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + + AssertEqual + \ [], + \ ale_linters#php#phpstan#Handle(bufnr(''), [ + \ '{"totals":{"errors":0,"file_errors":0},"files":[],"errors":[]}', + \ ]) + +Execute(Output with some errors should be parsed correctly): + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + + AssertEqual + \ [ + \ { + \ 'lnum': 9, + \ 'text': 'Call to method format() on an unknown class DateTimeImutable.', + \ 'type': 'E' + \ }, + \ { + \ 'lnum': 16, + \ 'text': 'Sample message.', + \ 'type': 'E' + \ }, + \ { + \ 'lnum': 192, + \ 'text': 'Invalid command testCommand.', + \ 'type': 'E' + \ } + \ ], + \ ale_linters#php#phpstan#Handle(bufnr(''), [json_encode( + \ { + \ "totals":{"errors": 0, "file_errors": 11}, + \ "files":{expand('%:p'): {"errors": 3, "messages": [ + \ {"message": "Call to method format() on an unknown class DateTimeImutable.", "line":9}, + \ {"message": "Sample message.", "line":16}, + \ {"message": "Invalid command testCommand.", "line": 192} + \ ]}} + \ }, + \)]) diff --git a/test/handler/test_pmd_handler.vader b/test/handler/test_pmd_handler.vader new file mode 100644 index 00000000..4f64c9ca --- /dev/null +++ b/test/handler/test_pmd_handler.vader @@ -0,0 +1,42 @@ +Before: + runtime ale_linters/java/pmd.vim + +After: + call ale#linter#Reset() + +Execute(The pmd handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 18, + \ 'text': 'Each class should declare at least one constructor', + \ 'code': 'Code Style - AtLeastOneConstructor', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 36, + \ 'text': 'Local variable ''node'' could be declared final', + \ 'code': 'Code Style - LocalVariableCouldBeFinal', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#java#pmd#Handle(666, [ + \ '"Problem","Package","File","Priority","Line","Description","Rule set","Rule"', + \ '"1","rsb.performance.test.ros","/home/languitar/src/rsb-performance-test-api-ros/src/main/java/rsb/performance/test/ros/NodeHolder.java","3","18","Each class should declare at least one constructor","Code Style","AtLeastOneConstructor"', + \ '"2","rsb.performance.test.ros","/home/languitar/src/rsb-performance-test-api-ros/src/main/java/rsb/performance/test/ros/NodeHolder.java","1","36","Local variable ''node'' could be declared final","Code Style","LocalVariableCouldBeFinal"' + \ ]) + +Execute(The pmd handler should parse lines correctly for java files that use unnamed packages): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'text': 'Avoid unused local variables such as ''stest''.', + \ 'code': 'Best Practices - UnusedLocalVariable', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#java#pmd#Handle(666, [ + \ '"Problem","Package","File","Priority","Line","Description","Rule set","Rule"', + \ '"1","","/Users/diego/Projects/github.com/dlresende/kata-fizz-buzz/src/main/java/App.java","3","8","Avoid unused local variables such as ''stest''.","Best Practices","UnusedLocalVariable"' + \ ]) diff --git a/test/handler/test_pony_handler.vader b/test/handler/test_pony_handler.vader new file mode 100644 index 00000000..25a8254b --- /dev/null +++ b/test/handler/test_pony_handler.vader @@ -0,0 +1,21 @@ +Execute(The pony handler should handle ponyc output): + call ale#test#SetFilename('foo.pony') + + AssertEqual + \ [ + \ { + \ 'filename': '/home/projects/Wombat.pony', + \ 'lnum': 22, + \ 'type': 'E', + \ 'col': 30, + \ 'text': 'can''t lookup private fields from outside the type', + \ }, + \ ], + \ ale#handlers#pony#HandlePonycFormat(bufnr(''), [ + \ 'Building builtin -> /usr/lib/pony/0.21.3/packages/builtin', + \ 'Building . -> /home/projects', + \ 'Error:', + \ '/home/projects/Wombat.pony:22:30: can''t lookup private fields from outside the type', + \ ' env.out.print(defaultWombat._hunger_level)', + \ ' ^', + \ ]) diff --git a/test/handler/test_powershell_handler.vader b/test/handler/test_powershell_handler.vader new file mode 100644 index 00000000..77c3dc65 --- /dev/null +++ b/test/handler/test_powershell_handler.vader @@ -0,0 +1,109 @@ +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' + \ ]) +Execute(The powershell handler should process unexecpected token that contains a newline character): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 8, + \ 'type': 'E', + \ 'text': 'The string is missing the terminator: ".', + \ 'code': 'ParseException' + \ }, + \ { + \ 'lnum': 2, + \ 'col': 8, + \ 'type': 'E', + \ 'text': 'Unexpected token ''"', + \ 'code': 'ParseException' + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Missing closing ''}'' in statement block or type definition.', + \ 'code': 'ParseException' + \ } + \ ], + \ ale_linters#powershell#powershell#Handle(bufnr(''), [ + \ 'At line:2 char:8', + \ '+ "" "', + \ '+ ~', + \ 'The string is missing the terminator: ".', + \ 'At line:2 char:8', + \ '+ "" "', + \ '+ ~', + \ 'Unexpected token ''"', + \ '', + \ ' }'' in expression or statement.', + \ '', + \ 'At line:1 char:1', + \ '+ {', + \ '+ ~', + \ 'Missing closing ''}'' in statement block or type definition.', + \ 'At C:\Users\jpharris\AppData\Local\Temp\VIAA777.tmp\script.ps1:1 char:150', + \ '+ ... ontents); [void]$ExecutionContext.InvokeCommand.NewScriptBlock($Con ...', + \ '+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', + \ ' + CategoryInfo : NotSpecified: (:) [], ParseException', + \ ' + FullyQualifiedErrorId : ParseException' + \ ]) diff --git a/test/handler/test_prospector_handler.vader b/test/handler/test_prospector_handler.vader new file mode 100644 index 00000000..2e9a9918 --- /dev/null +++ b/test/handler/test_prospector_handler.vader @@ -0,0 +1,161 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/python/prospector.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(Basic prospector errors should be handle): + AssertEqual + \ [ + \ { + \ 'lnum': 20, + \ 'col': 1, + \ 'text': 'Trailing whitespace', + \ 'code': '(pylint) trailing-whitespace', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Missing module docstring', + \ 'code': '(pylint) missing-docstring', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'Missing function docstring', + \ 'code': '(pylint) missing-docstring', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'text': '''break'' not properly in loop', + \ 'code': '(pylint) not-in-loop', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 5, + \ 'text': 'Unreachable code', + \ 'code': '(pylint) unreachable', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 33, + \ 'text': 'No exception type(s) specified', + \ 'code': '(pylint) bare-except', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#python#prospector#Handle(bufnr(''), [ + \ '{', + \ ' "messages": [', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "trailing-whitespace",', + \ ' "message": "Trailing whitespace",', + \ ' "location": {', + \ ' "character": 0,', + \ ' "line": 20', + \ ' }', + \ ' },', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "missing-docstring",', + \ ' "message": "Missing module docstring",', + \ ' "location": {', + \ ' "character": 0,', + \ ' "line": 1', + \ ' }', + \ ' },', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "missing-docstring",', + \ ' "message": "Missing function docstring",', + \ ' "location": {', + \ ' "character": 0,', + \ ' "line": 2', + \ ' }', + \ ' },', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "not-in-loop",', + \ ' "message": "''break'' not properly in loop",', + \ ' "location": {', + \ ' "character": 4,', + \ ' "line": 3', + \ ' }', + \ ' },', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "unreachable",', + \ ' "message": "Unreachable code",', + \ ' "location": {', + \ ' "character": 4,', + \ ' "line": 4', + \ ' }', + \ ' },', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "bare-except",', + \ ' "message": "No exception type(s) specified",', + \ ' "location": {', + \ ' "character": 32,', + \ ' "line": 7', + \ ' }', + \ ' }', + \ ' ]', + \ '}', + \ ]) + +Execute(Ignoring trailing whitespace messages should work): + let g:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Missing module docstring', + \ 'code': '(pylint) missing-docstring', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#python#prospector#Handle(bufnr(''), [ + \ '{', + \ ' "messages": [', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "trailing-whitespace",', + \ ' "message": "Trailing whitespace",', + \ ' "location": {', + \ ' "character": 0,', + \ ' "line": 4', + \ ' }', + \ ' },', + \ ' {', + \ ' "source": "pylint",', + \ ' "code": "missing-docstring",', + \ ' "message": "Missing module docstring",', + \ ' "location": {', + \ ' "character": 0,', + \ ' "line": 1', + \ ' }', + \ ' }', + \ ' ]', + \ '}', + \ ]) + +Execute(The prospector handler should handle empty output): + AssertEqual + \ [], + \ ale_linters#python#prospector#Handle(bufnr(''), []) 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_puglint_handler.vader b/test/handler/test_puglint_handler.vader new file mode 100644 index 00000000..f37518bb --- /dev/null +++ b/test/handler/test_puglint_handler.vader @@ -0,0 +1,45 @@ +Before: + runtime ale_linters/pug/puglint.vim + +After: + call ale#linter#Reset() + +Execute(Regular errors should be handled): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'Static attribute "class" must be written as class literal', + \ }, + \ ], + \ ale_linters#pug#puglint#Handle(0, [ + \ '/tmp/vwYwsJA/233/test.pug:1:5 Static attribute "class" must be written as class literal', + \ ]) + +Execute(syntax errors in the configuration file should be handled): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'puglint configuration error (type :ALEDetail for more information)', + \ 'detail': join( + \ [ + \ 'undefined:2', + \ ' disallowClassAttributeWithStaticValue: true', + \ ' ^', + \ 'SyntaxError: Unexpected token d in JSON at position 4', + \ ' at JSON.parse ()', + \ ], + \ "\n" + \ ), + \ }, + \ ], + \ ale_linters#pug#puglint#Handle(0, [ + \ 'undefined:2', + \ ' disallowClassAttributeWithStaticValue: true', + \ ' ^', + \ 'SyntaxError: Unexpected token d in JSON at position 4', + \ ' at JSON.parse ()', + \ ]) diff --git a/test/handler/test_puppet_handler.vader b/test/handler/test_puppet_handler.vader new file mode 100644 index 00000000..03adc9f0 --- /dev/null +++ b/test/handler/test_puppet_handler.vader @@ -0,0 +1,77 @@ +Before: + runtime ale_linters/puppet/puppet.vim + +After: + call ale#linter#Reset() + +Execute(The puppet handler should parse lines correctly when no column is supplied): + " Line Error + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 0, + \ 'text': "Syntax error at '='; expected '}'" + \ }, + \ { + \ 'lnum': 3, + \ 'col': 0, + \ 'text': "Syntax error at '='; expected '}'" + \ }, + \ ], + \ ale_linters#puppet#puppet#Handle(255, [ + \ "Error: Could not parse for environment production: Syntax error at '='; expected '}' at /root/puppetcode/modules/pancakes/manifests/init.pp:5", + \ "Error: Could not parse for environment production: Syntax error at '='; expected '}' at C:/puppet/modules/pancakes/manifests/init.pp:3", + \ ]) + +Execute(The puppet handler should parse lines and column correctly): + " Line Error + AssertEqual + \ [ + \ { + \ 'lnum': 43, + \ 'col': 12, + \ 'text': "Syntax error at ':'" + \ }, + \ { + \ 'lnum': 54, + \ 'col': 9, + \ 'text': "Syntax error at ':'" + \ }, + \ { + \ 'lnum': 45, + \ 'col': 12, + \ 'text': "Syntax error at 'parameter1'" + \ }, + \ ], + \ ale_linters#puppet#puppet#Handle(255, [ + \ "Error: Could not parse for environment production: Syntax error at ':' at /root/puppetcode/modules/nginx/manifests/init.pp:43:12", + \ "Error: Could not parse for environment production: Syntax error at ':' at C:/puppet/modules/nginx/manifests/init.pp:54:9", + \ "Error: Could not parse for environment production: Syntax error at 'parameter1' (file: /tmp/modules/mariadb/manifests/slave.pp, line: 45, column: 12)", + \ ]) + +Execute(The puppet handler should correctly parse errors that are reported before even trying to parse for an environment): + " Line Error + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 11, + \ 'text': "Illegal attempt to assign to 'a Name'. Not an assignable reference" + \ }, + \ ], + \ ale_linters#puppet#puppet#Handle(255, [ + \ "Error: Illegal attempt to assign to 'a Name'. Not an assignable reference (file: /tmp/modules/waffles/manifests/syrup.pp, line: 5, column: 11)", + \ ]) +Execute(The puppet handler should parse lines when end of input is the location): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'text': "Syntax error at end of input" + \ }, + \ ], + \ ale_linters#puppet#puppet#Handle(255, [ + \ "Error: Could not parse for environment production: Syntax error at end of input (file: /tmp//modules/test/manifests/init.pp)", + \ ]) diff --git a/test/handler/test_pycodestyle_handler.vader b/test/handler/test_pycodestyle_handler.vader new file mode 100644 index 00000000..a2efde23 --- /dev/null +++ b/test/handler/test_pycodestyle_handler.vader @@ -0,0 +1,153 @@ +Before: + Save g:ale_warn_about_trailing_blank_lines + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_blank_lines = 1 + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/python/pycodestyle.vim + +After: + Restore + + unlet! b:ale_warn_about_trailing_blank_lines + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() + +Execute(The pycodestyle handler should parse output): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'col': 3, + \ 'type': 'E', + \ 'text': 'SyntaxError: invalid syntax', + \ 'code': 'E999', + \ }, + \ { + \ 'lnum': 69, + \ 'col': 11, + \ 'text': 'multiple imports on one line', + \ 'code': 'E401', + \ 'type': 'E', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 77, + \ 'col': 1, + \ 'text': 'expected 2 blank lines, found 1', + \ 'code': 'E302', + \ 'type': 'E', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 88, + \ 'col': 5, + \ 'text': 'expected 1 blank line, found 0', + \ 'code': 'E301', + \ 'type': 'E', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 222, + \ 'col': 34, + \ 'text': 'deprecated form of raising exception', + \ 'code': 'W602', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 544, + \ 'col': 21, + \ 'text': '.has_key() is deprecated, use ''in''', + \ 'code': 'W601', + \ 'type': 'W', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'stdin:8:3: E999 SyntaxError: invalid syntax', + \ 'stdin:69:11: E401 multiple imports on one line', + \ 'stdin:77:1: E302 expected 2 blank lines, found 1', + \ 'stdin:88:5: E301 expected 1 blank line, found 0', + \ 'stdin:222:34: W602 deprecated form of raising exception', + \ 'example.py:544:21: W601 .has_key() is deprecated, use ''in''', + \ ]) + +Execute(Warnings about trailing whitespace should be reported by default): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'W291', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'who cares', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'W293', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'who cares', + \ }, + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'foo.py:6:1: W291 who cares', + \ 'foo.py:6:1: W293 who cares', + \ ]) + +Execute(Disabling trailing whitespace warnings should work): + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'foo.py:6:1: W291 who cares', + \ 'foo.py:6:1: W293 who cares', + \ ]) + +Execute(Warnings about trailing blank lines should be reported by default): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'W391', + \ 'type': 'W', + \ 'sub_type': 'style', + \ 'text': 'blank line at end of file', + \ }, + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'foo.py:6:1: W391 blank line at end of file', + \ ]) + +Execute(Disabling trailing blank line warnings should work): + let b:ale_warn_about_trailing_blank_lines = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'foo.py:6:1: W391 blank line at end of file', + \ ]) + +Execute(E112 should be a syntax error): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'E112', + \ 'type': 'E', + \ 'text': 'expected an indented block', + \ }, + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'foo.py:6:1: E112 expected an indented block', + \ ]) diff --git a/test/handler/test_pydocstyle_handler.vader b/test/handler/test_pydocstyle_handler.vader new file mode 100644 index 00000000..ec5ff9da --- /dev/null +++ b/test/handler/test_pydocstyle_handler.vader @@ -0,0 +1,116 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/python/pydocstyle.vim + +After: + Restore + + call ale#linter#Reset() + + silent file something_else.py + +" File sample.py +" # sample.py file +" +" def main(): +" """ +" This is a multi-line description that should produce multiple errors to be +" tested by the handler +" """ +" return False +" +" +" if __name__ == '__main__': +" main() +" +" The command to generate the handler input is: +" +" $ python -m pydocstyle --verbose --source --explain sample.py +" [...] +" $ + +Execute(Basic pydocstyle warnings should be handled): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Missing docstring in public module', + \ 'code': 'D100', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'text': '1 blank line required between summary line and description (found 0)', + \ 'code': 'D205', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'text': 'First line should end with a period (not ''e'')', + \ 'code': 'D400', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'text': 'First line should be in imperative mood; try rephrasing (found ''This'')', + \ 'code': 'D401', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#python#pydocstyle#Handle(bufnr(''), [ + \ 'Checking file ' . fnamemodify(bufname(bufnr('')), ':p') . '.', + \ './mydir/myfile.py:1 at module level:', + \ ' D100: Missing docstring in public module', + \ '', + \ ' All modules should normally have docstrings. [...] all functions and', + \ ' classes exported by a module should also have docstrings. Public', + \ ' methods (including the __init__ constructor) should also have', + \ ' docstrings.', + \ ' Note: Public (exported) definitions are either those with names listed', + \ ' in __all__ variable (if present), or those that do not start', + \ ' with a single underscore.', + \ '', + \ ' 1: # 2: 3: s 4: a 5: m 6: p 7: l ...', + \ '', + \ '', + \ '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', + \ ' docstring, followed by a blank line, followed by a more elaborate', + \ ' description. The summary line may be used by automatic indexing tools;', + \ ' it is important that it fits on one line and is separated from the', + \ ' rest of the docstring by a blank line.', + \ '', + \ ' 3: d 4: e 5: f 6: 7: m 8: a 9: i ...', + \ '', + \ '', + \ '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.', + \ '', + \ ' 3: d 4: e 5: f 6: 7: m 8: a 9: i ...', + \ '', + \ '', + \ ale#Escape(fnamemodify(bufname(bufnr('')), ':t')) . ':4 in public function `main`:', + \ ' D401: First line should be in imperative mood; try rephrasing (found ''This'')', + \ '', + \ ' [Docstring] prescribes the function or method''s effect as a command:', + \ ' ("Do this", "Return that"), not as a description; e.g. don''t write', + \ ' "Returns the pathname ...".', + \ '', + \ ' 3: d 4: e 5: f 6: 7: m 8: a 9: i ...', + \ ]) + +Execute(Handler should handle empty output): + AssertEqual + \ [], + \ ale_linters#python#pydocstyle#Handle(bufnr(''), []) diff --git a/test/handler/test_pyflakes_handler.vader b/test/handler/test_pyflakes_handler.vader new file mode 100644 index 00000000..ab4fab49 --- /dev/null +++ b/test/handler/test_pyflakes_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/python/pyflakes.vim + +After: + call ale#linter#Reset() + +Execute(The pyflakes handler should handle basic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'text': 'undefined name ''foo''', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 7, + \ 'text': 'invalid syntax', + \ }, + \ ], + \ ale_linters#python#pyflakes#Handle(bufnr(''), [ + \ 'test.py:1: undefined name ''foo''', + \ 'test.py:1:7: invalid syntax', + \ ]) diff --git a/test/handler/test_pylama_handler.vader b/test/handler/test_pylama_handler.vader new file mode 100644 index 00000000..3d1151b5 --- /dev/null +++ b/test/handler/test_pylama_handler.vader @@ -0,0 +1,268 @@ +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 with version older than 8.1.4): + AssertEqual [], ale_linters#python#pylama#Handle(bufnr(''), [8, 0, 5], []) + +Execute(The pylama handler should handle no messages with version newer or equal than 8.1.4): + AssertEqual [], ale_linters#python#pylama#Handle(bufnr(''), [8, 2, 0], []) + +Execute(The pylama handler should handle basic warnings and syntax errors with version older than 8.1.4): + 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(''), [8, 0, 5], [ + \ '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 basic warnings and syntax errors with version newer than 8.1.4): + 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(''), [8, 2, 0], [ + \ '[{"source":"pyflakes","col":1,"lnum":8,"etype":"W","message":"''foo'' imported but unused","filename":"index.py","number":"W0611"},{"source":"pylint","col":0,"lnum":8,"etype":"E","message":"Unable to import ''foo''","filename":"index.py","number":"E0401"},{"source":"pycodestyle","col":1,"lnum":10,"etype":"E","message":"expected 2 blank lines, found 1","filename":"index.py","number":"E302"},{"source":"pydocstyle","col":1,"lnum":11,"etype":"D","message":"First line should be in imperative mood (''Get'', not ''Gets'')","filename":"index.py","number":"D401"},{"source":"pycodestyle","col":81,"lnum":15,"etype":"E","message":"line too long (96 > 80 characters)","filename":"index.py","number":"E501"},{"source":"pydocstyle","col":1,"lnum":16,"etype":"D","message":"1 blank line required before class docstring (found 0)","filename":"index.py","number":"D203"},{"source":"pydocstyle","col":1,"lnum":18,"etype":"D","message":"Missing docstring in __init__","filename":"index.py","number":"D107"},{"source":"pylint","col":0,"lnum":20,"etype":"C","message":"Invalid string quote \", should be ''","filename":"index.py","number":"C4001"}]', + \ ]) + +Execute(The pylama handler should handle tracebacks with parsable messages with version older than 8.1.4): + 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(''), [8, 0, 5], [ + \ '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 with version older than 8.1.4): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'code': '', + \ 'type': 'W', + \ 'sub_type': '', + \ 'text': 'Incorrectly sorted imports. [isort]' + \ }, + \ ], + \ ale_linters#python#pylama#Handle(bufnr(''), [8, 0, 5], [ + \ '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 with version older than 8.1.4): + 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(''), [8, 0, 5], [ + \ 'index.py:31:1: E800: Found commented out code: # needs_sphinx = ''1.0'' [eradicate]', + \ ]) diff --git a/test/handler/test_pylint_handler.vader b/test/handler/test_pylint_handler.vader new file mode 100644 index 00000000..ce7322f3 --- /dev/null +++ b/test/handler/test_pylint_handler.vader @@ -0,0 +1,135 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/python/pylint.vim + +After: + Restore + + call ale#linter#Reset() + + silent file something_else.py + +Execute(Basic pylint errors should be handle): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'text': 'Trailing whitespace', + \ 'code': 'trailing-whitespace', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Missing module docstring', + \ 'code': 'missing-docstring', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'Missing function docstring', + \ 'code': 'missing-docstring', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'text': '''break'' not properly in loop', + \ 'code': 'not-in-loop', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 5, + \ 'text': 'Unreachable code', + \ 'code': 'unreachable', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 33, + \ 'text': 'No exception type(s) specified', + \ 'code': 'bare-except', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#python#pylint#Handle(bufnr(''), [ + \ 'No config file found, using default configuration', + \ '************* Module test', + \ 'test.py:4:0: C0303 (trailing-whitespace) Trailing whitespace', + \ 'test.py:1:0: C0111 (missing-docstring) Missing module docstring', + \ 'test.py:2:0: C0111 (missing-docstring) Missing function docstring', + \ 'test.py:3:4: E0103 (not-in-loop) ''break'' not properly in loop', + \ 'test.py:4:4: W0101 (unreachable) Unreachable code', + \ 'test.py:7:32: W0702 (bare-except) No exception type(s) specified', + \ '', + \ '------------------------------------------------------------------', + \ 'Your code has been rated at 0.00/10 (previous run: 2.50/10, -2.50)', + \ ]) + +Execute(Ignoring trailing whitespace messages should work): + let g:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Missing module docstring', + \ 'code': 'missing-docstring', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#python#pylint#Handle(bufnr(''), [ + \ 'No config file found, using default configuration', + \ '************* Module test', + \ 'test.py:4:0: C0303 (trailing-whitespace) Trailing whitespace', + \ 'test.py:1:0: C0111 (missing-docstring) Missing module docstring', + \ '', + \ '------------------------------------------------------------------', + \ 'Your code has been rated at 0.00/10 (previous run: 2.50/10, -2.50)', + \ ]) + +Execute(The pylint handler should parse Windows filenames): + AssertEqual + \ [ + \ { + \ 'lnum': 13, + \ 'col': 6, + \ 'text': 'Undefined variable ''x''', + \ 'code': 'undefined-variable', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#python#pylint#Handle(bufnr(''), [ + \ '************* Module test', + \ 'D:\acm\github\vim\tools\test.py:13:5: E0602 (undefined-variable) Undefined variable ''x''', + \ '', + \ '------------------------------------------------------------------', + \ 'Your code has been rated at 5.83/10 (previous run: 5.83/10, +0.00)', + \ ]) + +Execute(Use msg_id): + let g:ale_python_pylint_use_msg_id = 1 + AssertEqual + \ [ + \ { + \ 'lnum': 13, + \ 'col': 6, + \ 'text': 'Undefined variable ''x''', + \ 'code': 'E0602', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#python#pylint#Handle(bufnr(''), [ + \ '************* Module test', + \ 'D:\acm\github\vim\tools\test.py:13:5: E0602 (undefined-variable) Undefined variable ''x''', + \ '', + \ '------------------------------------------------------------------', + \ 'Your code has been rated at 5.83/10 (previous run: 5.83/10, +0.00)', + \ ]) diff --git a/test/handler/test_pyrex_cython_handler.vader b/test/handler/test_pyrex_cython_handler.vader new file mode 100644 index 00000000..fd0f9a8b --- /dev/null +++ b/test/handler/test_pyrex_cython_handler.vader @@ -0,0 +1,26 @@ +Before: + runtime ale_linters/pyrex/cython.vim + +After: + call ale#linter#Reset() + +Execute(The cython handler should handle warnings and errors): + AssertEqual + \ [ + \ { + \ 'lnum': 42, + \ 'col': 7, + \ 'text': 'some warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 777, + \ 'col': 21, + \ 'text': 'some error', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#pyrex#cython#Handle(347, [ + \ 'warning: file:42:7: some warning', + \ 'file:777:21: some error', + \ ]) diff --git a/test/handler/test_qmlfmt_handler.vader b/test/handler/test_qmlfmt_handler.vader new file mode 100644 index 00000000..fc8ef355 --- /dev/null +++ b/test/handler/test_qmlfmt_handler.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/qml/qmlfmt.vim + +After: + call ale#linter#Reset() + +Execute(The qmlfmt handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 22, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Expected token ''}''.' + \ } + \ ], + \ ale_linters#qml#qmlfmt#Handle(1, [ + \ 'Error:22:1: Expected token ''}''.' + \ ]) diff --git a/test/handler/test_qmllint_handler.vader b/test/handler/test_qmllint_handler.vader new file mode 100644 index 00000000..fcc65eb5 --- /dev/null +++ b/test/handler/test_qmllint_handler.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/qml/qmllint.vim + +After: + call ale#linter#Reset() + +Execute(The qmllint handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Expected token ''}''' + \ } + \ ], + \ ale_linters#qml#qmllint#Handle(1, [ + \ '/tmp/ab34cd56/Test.qml:2 : Expected token ''}''' + \ ]) diff --git a/test/handler/test_raco_handler.vader b/test/handler/test_raco_handler.vader new file mode 100644 index 00000000..565fd795 --- /dev/null +++ b/test/handler/test_raco_handler.vader @@ -0,0 +1,27 @@ +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 + \ [ + \ { + \ 'filename': 'foo.rkt', + \ '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_rails_best_practices_handler.vader b/test/handler/test_rails_best_practices_handler.vader new file mode 100644 index 00000000..f6d69eee --- /dev/null +++ b/test/handler/test_rails_best_practices_handler.vader @@ -0,0 +1,52 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + cd .. + + runtime ale_linters/ruby/rails_best_practices.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The rails_best_practices handler should parse JSON correctly): + call ale#test#SetFilename('test-files/ruby/valid_rails_app/app/models/thing.rb') + + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'text': 'use local variable', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 10, + \ 'text': 'other advice', + \ 'type': 'W', + \ } + \ ], + \ ale_linters#ruby#rails_best_practices#Handle(bufnr(''), [ + \ '[', + \ '{', + \ '"message": "use local variable",', + \ '"line_number": "5",', + \ printf('"filename": "%s"', substitute(expand('%:p'), '\\', '\\\\', 'g')), + \ '},{', + \ '"message": "other advice",', + \ '"line_number": "10",', + \ printf('"filename": "%s"', substitute(expand('%:p'), '\\', '\\\\', 'g')), + \ '}', + \ ']' + \ ]) + +Execute(The rails_best_practices handler should parse JSON correctly when there is no output from the tool): + AssertEqual + \ [], + \ ale_linters#ruby#rails_best_practices#Handle(347, [ + \ ]) + +Execute(The rails_best_practices handler should handle garbage output): + AssertEqual + \ [], + \ ale_linters#ruby#rails_best_practices#Handle(347, [ + \ 'No such command in 2.4.1 of ruby', + \ ]) diff --git a/test/handler/test_redpen_handler.vader b/test/handler/test_redpen_handler.vader new file mode 100644 index 00000000..0b030e2d --- /dev/null +++ b/test/handler/test_redpen_handler.vader @@ -0,0 +1,98 @@ +Before: + runtime! ale_linters/markdown/redpen.vim + +After: + call ale#linter#Reset() + +Execute(redpen handler should handle errors output): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'end_lnum': 1, + \ 'end_col': 15, + \ 'text': 'Found possibly misspelled word "plugin".', + \ 'type': 'W', + \ 'code': 'Spelling', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Found possibly misspelled word "NeoVim".', + \ 'type': 'W', + \ 'code': 'Spelling', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 35, + \ 'end_lnum': 1, + \ 'end_col': 55, + \ 'text': 'Found possibly misspelled word "コードチェック".', + \ 'type': 'W', + \ 'code': 'Spelling', + \ }, + \ ], + \ ale#handlers#redpen#HandleRedpenOutput(bufnr(''), [ + \ '[', + \ ' {', + \ ' "document": "test.md",', + \ ' "errors": [', + \ ' {', + \ ' "sentence": "ALE is a plugin for providing linting in NeoVim and Vim 8 while you edit your text files.",', + \ ' "endPosition": {', + \ ' "offset": 15,', + \ ' "lineNum": 1', + \ ' },', + \ ' "validator": "Spelling",', + \ ' "lineNum": 1,', + \ ' "sentenceStartColumnNum": 0,', + \ ' "message": "Found possibly misspelled word \"plugin\".",', + \ ' "startPosition": {', + \ ' "offset": 9,', + \ ' "lineNum": 1', + \ ' }', + \ ' },', + \ ' {', + \ ' "sentence": "ALE is a plugin for providing linting in NeoVim and Vim 8 while you edit your text files.",', + \ ' "validator": "Spelling",', + \ ' "lineNum": 1,', + \ ' "sentenceStartColumnNum": 0,', + \ ' "message": "Found possibly misspelled word \"NeoVim\"."', + \ ' },', + \ ' {', + \ ' "sentence": "ALEはNeoVimとVim8で非同期のコードチェックを実現するプラグインです。",', + \ ' "endPosition": {', + \ ' "offset": 27,', + \ ' "lineNum": 1', + \ ' },', + \ ' "validator": "Spelling",', + \ ' "lineNum": 1,', + \ ' "sentenceStartColumnNum": 0,', + \ ' "message": "Found possibly misspelled word \"コードチェック\".",', + \ ' "startPosition": {', + \ ' "offset": 20,', + \ ' "lineNum": 1', + \ ' }', + \ ' }', + \ ' ]', + \ ' }', + \ ']', + \ ]) + +Execute(The redpen handler should handle an empty error list): + AssertEqual + \ [], + \ ale#handlers#redpen#HandleRedpenOutput(bufnr(''), [ + \ '[', + \ ' {', + \ ' "document": "test.md",', + \ ' "errors": []', + \ ' }', + \ ']', + \ ]) + +Execute(The redpen handler should handle totally empty output): + AssertEqual + \ [], + \ ale#handlers#redpen#HandleRedpenOutput(bufnr(''), []) diff --git a/test/handler/test_reek_handler.vader b/test/handler/test_reek_handler.vader new file mode 100644 index 00000000..98c2b736 --- /dev/null +++ b/test/handler/test_reek_handler.vader @@ -0,0 +1,85 @@ +Before: + Save g:ale_ruby_reek_show_context + Save g:ale_ruby_reek_show_wiki_link + + let g:ale_ruby_reek_show_context = 0 + let g:ale_ruby_reek_show_wiki_link = 0 + + runtime ale_linters/ruby/reek.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The reek handler should parse JSON correctly, with only context enabled): + let g:ale_ruby_reek_show_context = 1 + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'text': 'Context#method violates rule number one', + \ 'code': 'Rule1', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 34, + \ 'text': 'Context#method violates rule number two', + \ 'code': 'Rule2', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 56, + \ 'text': 'Context#method violates rule number two', + \ 'code': 'Rule2', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#ruby#reek#Handle(347, [ + \ '[{"context":"Context#method","lines":[12],"message":"violates rule number one","smell_type":"Rule1","source":"/home/user/file.rb","parameter":"bad parameter","wiki_link":"https://example.com/Rule1.md"},{"context":"Context#method","lines":[34, 56],"message":"violates rule number two","smell_type":"Rule2","source":"/home/user/file.rb","name":"bad code","count":2,"wiki_link":"https://example.com/Rule1.md"}]' + \ ]) + +Execute(The reek handler should parse JSON correctly, with no context or wiki links): + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'text': 'violates rule number one', + \ 'code': 'Rule1', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#ruby#reek#Handle(347, [ + \ '[{"context":"Context#method","lines":[12],"message":"violates rule number one","smell_type":"Rule1","source":"/home/user/file.rb","parameter":"bad parameter","wiki_link":"https://example.com/Rule1.md"}]' + \ ]) + +Execute(The reek handler should parse JSON correctly, with both context and wiki links): + let g:ale_ruby_reek_show_context = 1 + let g:ale_ruby_reek_show_wiki_link = 1 + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'text': 'Context#method violates rule number one [https://example.com/Rule1.md]', + \ 'code': 'Rule1', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#ruby#reek#Handle(347, [ + \ '[{"context":"Context#method","lines":[12],"message":"violates rule number one","smell_type":"Rule1","source":"/home/user/file.rb","parameter":"bad parameter","wiki_link":"https://example.com/Rule1.md"}]' + \ ]) + +Execute(The reek handler should parse JSON correctly when there is no output from reek): + AssertEqual + \ [], + \ ale_linters#ruby#reek#Handle(347, [ + \ ]) + +Execute(The reek handler should handle garbage output): + AssertEqual + \ [], + \ ale_linters#ruby#reek#Handle(347, [ + \ 'No such command in 2.4.1 of ruby', + \ ]) diff --git a/test/handler/test_remark_lint_handler.vader b/test/handler/test_remark_lint_handler.vader new file mode 100644 index 00000000..0794d51c --- /dev/null +++ b/test/handler/test_remark_lint_handler.vader @@ -0,0 +1,39 @@ +Before: + runtime ale_linters/markdown/remark_lint.vim + +After: + call ale#linter#Reset() + +Execute(Warning and error messages should be handled correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 4, + \ 'type': 'W', + \ 'text': 'Incorrect list-item indent: add 1 space list-item-indent remark-lint', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'Incorrect list-item indent: remove 1 space list-item-indent remark-lint', + \ }, + \ { + \ 'lnum': 18, + \ 'col': 71, + \ 'end_lnum': 19, + \ 'end_col': 1, + \ 'type': 'E', + \ 'text': 'Missing new line after list item list-item-spacing remark-lint', + \ }, + \ ], + \ ale_linters#markdown#remark_lint#Handle(1, [ + \ 'foo.md', + \ ' 1:4 warning Incorrect list-item indent: add 1 space list-item-indent remark-lint', + \ ' 3:5 error Incorrect list-item indent: remove 1 space list-item-indent remark-lint', + \ ' 18:71-19:1 error Missing new line after list item list-item-spacing remark-lint', + \ '', + \ '⚠ 1 warnings', + \ '✘ 2 errors', + \]) diff --git a/test/handler/test_rflint_handler.vader b/test/handler/test_rflint_handler.vader new file mode 100644 index 00000000..8fc6296c --- /dev/null +++ b/test/handler/test_rflint_handler.vader @@ -0,0 +1,32 @@ +Before: + runtime ale_linters/robot/rflint.vim + +After: + call ale#linter#Reset() + +Execute(Warning and error messages should be handled correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 1, + \ 'filename': 'test.robot', + \ 'type': 'W', + \ 'lnum': 1, + \ 'col': 2, + \ 'text': 'RequireSuiteDocumentation', + \ 'detail': 'No suite documentation', + \ }, + \ { + \ 'bufnr': 1, + \ 'filename': 'test.robot', + \ 'type': 'E', + \ 'lnum': 3, + \ 'col': 4, + \ 'text': 'RequireTestDocumentation', + \ 'detail': 'No testcase documentation', + \ }, + \ ], + \ ale_linters#robot#rflint#Handle(1, [ + \ 'test.robot:W:1:2:RequireSuiteDocumentation:No suite documentation', + \ 'test.robot:E:3:4:RequireTestDocumentation:No testcase documentation' + \]) diff --git a/test/handler/test_rpmlint_handler.vader b/test/handler/test_rpmlint_handler.vader new file mode 100644 index 00000000..2ea9e5cf --- /dev/null +++ b/test/handler/test_rpmlint_handler.vader @@ -0,0 +1,33 @@ +Before: + runtime ale_linters/spec/rpmlint.vim + +After: + call ale#linter#Reset() + +Execute(The rpmlint handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 42, + \ 'lnum': 23, + \ 'text': 'macro-in-comment %version', + \ 'type': 'W', + \ }, + \ { + \ 'bufnr': 42, + \ 'lnum': 17, + \ 'text': 'hardcoded-library-path in %_prefix/lib/%name', + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': 42, + \ 'lnum': 1, + \ 'text': 'specfile-error warning: bogus date in %changelog: Mon Oct 1 2005 - Foo', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#spec#rpmlint#Handle(42, [ + \ 'cyrus-imapd.spec:23: W: macro-in-comment %version', + \ 'cyrus-imapd.spec:17: E: hardcoded-library-path in %_prefix/lib/%name', + \ 'apcupsd.spec: E: specfile-error warning: bogus date in %changelog: Mon Oct 1 2005 - Foo', + \ ]) diff --git a/test/handler/test_rstcheck_lint_handler.vader b/test/handler/test_rstcheck_lint_handler.vader new file mode 100644 index 00000000..c65c15eb --- /dev/null +++ b/test/handler/test_rstcheck_lint_handler.vader @@ -0,0 +1,42 @@ +Before: + runtime ale_linters/rst/rstcheck.vim + +After: + call ale#linter#Reset() + +Execute(Warning and error messages should be handled correctly): + " For some reason we can't set the directory such that the filenames are + " correct here when running the tests from the Docker image, so we have to + " just check the basenames of the files instead. + AssertEqual + \ [ + \ { + \ 'filename': 'bad_python.rst', + \ 'lnum': 7, + \ 'col': 0, + \ 'type': 'W', + \ 'text': '(python) unexpected EOF while parsing', + \ }, + \ { + \ 'filename': 'bad_cpp.rst', + \ 'lnum': 9, + \ 'col': 0, + \ 'type': 'W', + \ 'text': '(cpp) error: ''x'' was not declared in this scope', + \ }, + \ { + \ 'filename': 'bad_rst.rst', + \ 'lnum': 1, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Title overline & underline mismatch.', + \ }, + \ ], + \ map( + \ ale_linters#rst#rstcheck#Handle(1, [ + \ 'bad_python.rst:7: (ERROR/3) (python) unexpected EOF while parsing', + \ 'bad_cpp.rst:9: (ERROR/3) (cpp) error: ''x'' was not declared in this scope', + \ 'bad_rst.rst:1: (SEVERE/4) Title overline & underline mismatch.', + \ ]), + \ 'extend(v:val, {''filename'': fnamemodify(v:val.filename, '':t'')})' + \ ) diff --git a/test/handler/test_rubocop_handler.vader b/test/handler/test_rubocop_handler.vader new file mode 100644 index 00000000..a7db471e --- /dev/null +++ b/test/handler/test_rubocop_handler.vader @@ -0,0 +1,63 @@ +Before: + runtime ale_linters/ruby/rubocop.vim + +After: + call ale#linter#Reset() + +Execute(The rubocop handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 83, + \ 'col': 29, + \ 'end_col': 35, + \ 'text': 'Prefer single-quoted strings...', + \ 'code': 'Style/SomeCop', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 12, + \ 'col': 2, + \ 'end_col': 2, + \ 'text': 'Some error', + \ 'code': 'Style/SomeOtherCop', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 5, + \ 'end_col': 12, + \ 'text': 'Regular warning', + \ 'code': 'Style/WarningCop', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 11, + \ 'col': 1, + \ 'end_col': 1, + \ 'text': 'Another error', + \ 'code': 'Style/SpaceBeforeBlockBraces', + \ 'type': 'E', + \ }, + \ ], + \ 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#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#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}}' + \ ]) + +Execute(The rubocop handler should handle empty output): + AssertEqual [], ale#ruby#HandleRubocopOutput(347, ['{}']) + AssertEqual [], ale#ruby#HandleRubocopOutput(347, []) diff --git a/test/handler/test_ruby_handler.vader b/test/handler/test_ruby_handler.vader new file mode 100644 index 00000000..824d8c58 --- /dev/null +++ b/test/handler/test_ruby_handler.vader @@ -0,0 +1,38 @@ +Before: + runtime ale_linters/ruby/ruby.vim + +After: + call ale#linter#Reset() + +Execute(The ruby handler should parse lines correctly and add the column if it can): + " Point Error + " Warning + " Line Error + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 13, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected '';''' + \ }, + \ { + \ 'lnum': 9, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'warning: statement not reached' + \ }, + \ { + \ 'lnum': 12, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected end-of-input, expecting keyword_end' + \ } + \ ], + \ ale#handlers#ruby#HandleSyntaxErrors(255, [ + \ "test.rb:6: syntax error, unexpected ';'", + \ " t = ;", + \ " ^", + \ "test.rb:9: warning: statement not reached", + \ "test.rb:12: syntax error, unexpected end-of-input, expecting keyword_end", + \ ]) diff --git a/test/handler/test_ruff_handler.vader b/test/handler/test_ruff_handler.vader new file mode 100644 index 00000000..82012706 --- /dev/null +++ b/test/handler/test_ruff_handler.vader @@ -0,0 +1,43 @@ +Before: + runtime ale_linters/python/ruff.vim + +After: + call ale#linter#Reset() + +Execute(We should handle basic output of ruff correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'code': 'F821', + \ 'type': 'W', + \ 'end_col': 7, + \ 'end_lnum': 2, + \ 'text': 'Undefined name example', + \ }, + \ ], + \ ale_linters#python#ruff#Handle(bufnr(''), [ + \ '{"cell":null,"code":"F821","end_location":{"column":8,"row":2},"filename":"/home/eduardo/Code/Python/test.py","fix":null,"location":{"column":1,"row":2},"message":"Undefined name example","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}', + \ ]) + +Execute(We should handle totally broken output from ruff): + AssertEqual [], ale_linters#python#ruff#Handle(bufnr(''), ['ERROR: oh noes!']) + +Execute(We should handle mixed error lines and JSON output from ruff): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'code': 'F821', + \ 'type': 'W', + \ 'end_col': 7, + \ 'end_lnum': 2, + \ 'text': 'Undefined name example', + \ }, + \ ], + \ ale_linters#python#ruff#Handle(bufnr(''), [ + \ 'ERROR: oh noes!', + \ '{"cell":null,"code":"F821","end_location":{"column":8,"row":2},"filename":"/home/eduardo/Code/Python/test.py","fix":null,"location":{"column":1,"row":2},"message":"Undefined name example","noqa_row":2,"url":"https://docs.astral.sh/ruff/rules/undefined-name"}', + \ ]) diff --git a/test/handler/test_rust_handler.vader b/test/handler/test_rust_handler.vader new file mode 100644 index 00000000..bbd12f3e --- /dev/null +++ b/test/handler/test_rust_handler.vader @@ -0,0 +1,491 @@ +Before: + Save g:ale_rust_ignore_secondary_spans + + let g:ale_rust_ignore_secondary_spans = 0 + +After: + Restore + +Execute(The Rust handler should handle rustc output): + call ale#test#SetFilename('src/playpen.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 15, + \ 'end_lnum': 15, + \ 'type': 'E', + \ 'col': 5, + \ 'end_col': 7, + \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ }, + \ { + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'type': 'E', + \ 'col': 7, + \ 'end_col': 9, + \ 'text': 'no method named `wat` found for type `std::string::String` in the current scope', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ '', + \ 'ignore this', + \ json_encode({ + \ 'message': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ 'code': v:null, + \ 'level': 'error', + \ 'spans': [ + \ { + \ 'file_name': '', + \ 'byte_start': 418, + \ 'byte_end': 421, + \ 'line_start': 15, + \ 'line_end': 15, + \ 'column_start': 5, + \ 'column_end': 8, + \ 'is_primary': v:true, + \ 'label': v:null, + \ }, + \ ], + \ }), + \ json_encode({ + \ 'message': 'main function not found', + \ 'code': v:null, + \ 'level': 'error', + \ 'spans': [], + \ }), + \ json_encode({ + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'no method named `wat` found for type `std::string::String` in the current scope', + \ 'spans': [ + \ { + \ 'byte_end': 410, + \ 'byte_start': 407, + \ 'column_end': 10, + \ 'column_start': 7, + \ 'file_name': '', + \ 'is_primary': v:true, + \ 'label': v:null, + \ 'line_end': 13, + \ 'line_start': 13, + \ } + \ ] + \ }), + \ json_encode({ + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'aborting due to previous error', + \ 'spans': [ + \ ] + \ }), + \ ]) + +Execute(The Rust handler should handle cargo output): + call ale#test#SetFilename('src/playpen.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 15, + \ 'end_lnum': 15, + \ 'type': 'E', + \ 'col': 5, + \ 'end_col': 7, + \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ }, + \ { + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'type': 'E', + \ 'col': 7, + \ 'end_col': 9, + \ 'text': 'no method named `wat` found for type `std::string::String` in the current scope', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ '', + \ 'ignore this', + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ 'spans': [ + \ { + \ 'byte_end': 11508, + \ 'byte_start': 11505, + \ 'column_end': 8, + \ 'column_start': 5, + \ 'file_name': ale#path#Simplify('src/playpen.rs'), + \ 'is_primary': v:true, + \ 'label': v:null, + \ 'line_end': 15, + \ 'line_start': 15, + \ } + \ ] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'no method named `wat` found for type `std::string::String` in the current scope', + \ 'spans': [ + \ { + \ 'byte_end': 11497, + \ 'byte_start': 11494, + \ 'column_end': 10, + \ 'column_start': 7, + \ 'file_name': ale#path#Simplify('src/playpen.rs'), + \ 'is_primary': v:true, + \ 'label': v:null, + \ 'line_end': 13, + \ 'line_start': 13, + \ } + \ ] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'aborting due to previous error', + \ 'spans': [ + \ ] + \ }, + \ }), + \ ]) + +Execute(The Rust handler should should errors from expansion spans): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'end_lnum': 4, + \ 'type': 'E', + \ 'col': 21, + \ 'end_col': 22, + \ 'text': 'mismatched types: expected bool, found integral variable', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'mismatched types', + \ 'spans': [ + \ { + \ 'byte_end': 1, + \ 'byte_start': 1, + \ 'column_end': 1, + \ 'column_start': 1, + \ 'file_name': ale#path#Simplify('src/other.rs'), + \ 'is_primary': v:true, + \ 'label': 'some other error', + \ 'line_end': 4, + \ 'line_start': 4, + \ 'expansion': { + \ 'span': { + \ 'byte_end': 54, + \ 'byte_start': 52, + \ 'column_end': 23, + \ 'column_start': 21, + \ 'file_name': ale#path#Simplify('src/playpen.rs'), + \ 'is_primary': v:true, + \ 'label': 'expected bool, found integral variable', + \ 'line_end': 4, + \ 'line_start': 4, + \ } + \ } + \ } + \ ] + \ }, + \ }), + \ ]) + +Execute(The Rust handler should show detailed errors): + call ale#test#SetFilename('src/playpen.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'end_lnum': 4, + \ 'type': 'E', + \ 'col': 21, + \ 'end_col': 22, + \ 'text': 'mismatched types: expected bool, found integral variable', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ '', + \ 'ignore this', + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'mismatched types', + \ 'spans': [ + \ { + \ 'byte_end': 54, + \ 'byte_start': 52, + \ 'column_end': 23, + \ 'column_start': 21, + \ 'expansion': v:null, + \ 'file_name': ale#path#Simplify('src/playpen.rs'), + \ 'is_primary': v:true, + \ 'label': 'expected bool, found integral variable', + \ 'line_end': 4, + \ 'line_start': 4, + \ } + \ ] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'aborting due to previous error(s)', + \ 'spans': [ + \ ] + \ }, + \ }), + \ ]) + +Execute(The Rust handler should show detailed clippy errors with rendered field if it's available): + call ale#test#SetFilename('src/playpen.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'end_lnum': 4, + \ 'type': 'E', + \ 'col': 21, + \ 'end_col': 22, + \ 'text': 'mismatched types: expected bool, found integral variable', + \ 'detail': 'this is a detailed description', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ '', + \ 'ignore this', + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'mismatched types', + \ 'rendered': 'this is a detailed description', + \ 'spans': [ + \ { + \ 'byte_end': 54, + \ 'byte_start': 52, + \ 'column_end': 23, + \ 'column_start': 21, + \ 'expansion': v:null, + \ 'file_name': ale#path#Simplify('src/playpen.rs'), + \ 'is_primary': v:true, + \ 'label': 'expected bool, found integral variable', + \ 'line_end': 4, + \ 'line_start': 4, + \ } + \ ] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'aborting due to previous error(s)', + \ 'spans': [ + \ ] + \ }, + \ }), + \ ]) + +Execute(The Rust handler should find correct files): + call ale#test#SetFilename('src/noerrors/mod.rs') + + AssertEqual + \ [], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ '', + \ 'ignore this', + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'unresolved import `Undefined`', + \ 'spans': [ + \ { + \ 'byte_end': 103, + \ 'byte_start': 94, + \ 'column_end': 14, + \ 'column_start': 5, + \ 'file_name': 'src/haserrors/mod.rs', + \ 'is_primary': v:true, + \ 'label': 'no `Undefined` in the root', + \ 'line_end': 1, + \ 'line_start': 1, + \ } + \ ] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'aborting due to previous error', + \ 'spans': [ + \ ] + \ }, + \ }), + \ ]) + +Execute(The Rust handler should remove secondary spans if set): + call ale#test#SetFilename('src/noerrors/mod.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'end_lnum': 1, + \ 'type': 'E', + \ 'end_col': 20, + \ 'col': 1, + \ 'text': 'this function takes 1 parameter but 0 were supplied: defined here', + \ }, + \ { + \ 'lnum': 1, + \ 'end_lnum': 1, + \ 'type': 'E', + \ 'end_col': 45, + \ 'col': 40, + \ 'text': 'this function takes 1 parameter but 0 were supplied: expected 1 parameter', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ '', + \ 'fn test(x: u8) -> u8 { x } fn main() { x(); }', + \ json_encode({ + \ 'message': { + \ 'code': { + \ 'code': 'E0061', + \ 'explanation': 'Dummy explanation; not used' + \ }, + \ 'level': 'error', + \ 'message': 'this function takes 1 parameter but 0 were supplied', + \ 'spans': [ + \ { + \ 'byte_end': 20, + \ 'byte_start': 0, + \ 'column_end': 21, + \ 'column_start': 1, + \ 'file_name': 'src/noerrors/mod.rs', + \ 'is_primary': v:false, + \ 'label': 'defined here', + \ 'line_end': 1, + \ 'line_start': 1, + \ }, + \ { + \ 'byte_end': 45, + \ 'byte_start': 39, + \ 'column_end': 46, + \ 'column_start': 40, + \ 'file_name': '', + \ 'is_primary': v:true, + \ 'label': 'expected 1 parameter', + \ 'line_end': 1, + \ 'line_start': 1, + \ }, + \ ] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'aborting due to previous error', + \ 'spans': [] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'For more information about this error, try `rustc --explain E0061`.', + \ 'spans': [] + \ }, + \ }), + \ ]) + + let g:ale_rust_ignore_secondary_spans = 1 + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'end_lnum': 1, + \ 'type': 'E', + \ 'end_col': 45, + \ 'col': 40, + \ 'text': 'this function takes 1 parameter but 0 were supplied: expected 1 parameter', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrors(bufnr(''), [ + \ '', + \ 'fn test(x: u8) -> u8 { x } fn main() { x(); }', + \ json_encode({ + \ 'message': { + \ 'code': { + \ 'code': 'E0061', + \ 'explanation': 'Dummy explanation; not used' + \ }, + \ 'level': 'error', + \ 'message': 'this function takes 1 parameter but 0 were supplied', + \ 'spans': [ + \ { + \ 'byte_end': 20, + \ 'byte_start': 0, + \ 'column_end': 21, + \ 'column_start': 1, + \ 'file_name': 'src/noerrors/mod.rs', + \ 'is_primary': v:false, + \ 'label': 'defined here', + \ 'line_end': 1, + \ 'line_start': 1, + \ }, + \ { + \ 'byte_end': 45, + \ 'byte_start': 39, + \ 'column_end': 46, + \ 'column_start': 40, + \ 'file_name': '', + \ 'is_primary': v:true, + \ 'label': 'expected 1 parameter', + \ 'line_end': 1, + \ 'line_start': 1, + \ }, + \ ] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'aborting due to previous error', + \ 'spans': [] + \ }, + \ }), + \ json_encode({ + \ 'message': { + \ 'code': v:null, + \ 'level': 'error', + \ 'message': 'For more information about this error, try `rustc --explain E0061`.', + \ 'spans': [] + \ }, + \ }), + \ ]) diff --git a/test/handler/test_salt_salt_lint.vader b/test/handler/test_salt_salt_lint.vader new file mode 100644 index 00000000..7e234785 --- /dev/null +++ b/test/handler/test_salt_salt_lint.vader @@ -0,0 +1,34 @@ +Before: + runtime ale_linters/salt/salt_lint.vim + +After: + call ale#linter#Reset() + +Execute(The salt handler should parse lines correctly and show error in severity HIGH): + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'code': 207, + \ 'text': 'File modes should always be encapsulated in quotation marks', + \ 'type': 'E' + \ } + \ ], + \ ale_linters#salt#salt_lint#Handle(255, [ + \ '[{"id": "207", "message": "File modes should always be encapsulated in quotation marks", "filename": "test.sls", "linenumber": 5, "line": " - mode: 0755", "severity": "HIGH"}]' + \ ]) + + +Execute(The salt handler should parse lines correctly and show error in severity not HIGH): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'code': 204, + \ 'text': 'Lines should be no longer that 160 chars', + \ 'type': 'W' + \ } + \ ], + \ ale_linters#salt#salt_lint#Handle(255, [ + \ '[{"id": "204", "message": "Lines should be no longer that 160 chars", "filename": "test2.sls", "linenumber": 27, "line": "this line is definitely longer than 160 chars, this line is definitely longer than 160 chars, this line is definitely longer than 160 chars", "severity": "VERY_LOW"}]' + \ ]) diff --git a/test/handler/test_scala_handler.vader b/test/handler/test_scala_handler.vader new file mode 100644 index 00000000..d983d84a --- /dev/null +++ b/test/handler/test_scala_handler.vader @@ -0,0 +1,29 @@ +Execute(The handler should return an empty list with empty input): + AssertEqual [], ale#handlers#scala#HandleScalacLintFormat(bufnr(''), []) + +Execute(The handler should correctly parse error messages): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 8, + \ 'text': ''':'' expected but identifier found.', + \ 'type': 'E' + \ }, + \ { + \ 'lnum': 6, + \ 'col': 2, + \ 'text': 'identifier expected but eof found.', + \ 'type': 'E' + \ } + \ ], + \ ale#handlers#scala#HandleScalacLintFormat(bufnr(''), + \ [ + \ "hi.scala:4: error: ':' expected but identifier found.", + \ " Some stupid scala code", + \ " ^", + \ "hi.scala:6: error: identifier expected but eof found.", + \ ")", + \ " ^", + \ "two errors found", + \ ]) diff --git a/test/handler/test_scalastyle_handler.vader b/test/handler/test_scalastyle_handler.vader new file mode 100644 index 00000000..32da79c0 --- /dev/null +++ b/test/handler/test_scalastyle_handler.vader @@ -0,0 +1,53 @@ +Before: + runtime! ale_linters/scala/scalastyle.vim + +After: + call ale#linter#Reset() + +Execute(The scalastyle handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 190, + \ 'text': 'Missing or badly formed ScalaDoc: Missing @param str', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 200, + \ 'col': 34, + \ 'text': 'There should be a space before the plus (+) sign', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 200, + \ 'col': 1, + \ 'text': 'There should be a space before the plus (+) sign', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#scala#scalastyle#Handle(347, [ + \ 'Starting scalastyle', + \ 'start file /home/test/Doop.scala', + \ 'warning file=/home/test/Doop.scala message=Missing or badly formed ScalaDoc: Missing @param str line=190', + \ 'error file=/home/test/Doop.scala message=There should be a space before the plus (+) sign line=200 column=33', + \ 'error file=/home/test/Doop.scala message=There should be a space before the plus (+) sign line=200 column=0', + \ 'end file /home/test/Doop.scala', + \ 'Processed 1 file(s)', + \ 'Found 0 errors', + \ 'Found 3 warnings', + \ 'Finished in 934 ms', + \ ]) + +Execute(The scalastyle linter should complain when there is no configuration file): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': '(See :help ale-scala-scalastyle) No scalastyle configuration file was found.', + \ }, + \ ], + \ ale_linters#scala#scalastyle#Handle(347, [ + \ 'scalastyle 1.0.0', + \ 'Usage: scalastyle [options] ', + \ ' -c, --config FILE configuration file (required)', + \ ]) diff --git a/test/handler/test_scarb_handler.vader b/test/handler/test_scarb_handler.vader new file mode 100644 index 00000000..96c5f765 --- /dev/null +++ b/test/handler/test_scarb_handler.vader @@ -0,0 +1,20 @@ +Before: + runtime ale_linters/cairo/scarb.vim + +After: + call ale#linter#Reset() + +Execute(Check scarb output parsing): + AssertEqual + \ [ + \ { + \ 'lnum': 40, + \ 'col': 48, + \ 'text': 'Skipped tokens. Expected: Const/Module/Use/FreeFunction/ExternFunction/ExternType/Trait/Impl/Struct/Enum/TypeAlias/InlineMacro or an attribute.', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#cairo#HandleCairoErrors(bufnr(''), [ + \ 'error: Skipped tokens. Expected: Const/Module/Use/FreeFunction/ExternFunction/ExternType/Trait/Impl/Struct/Enum/TypeAlias/InlineMacro or an attribute.', + \ ' --> /path/to/file.cairo:40:48', + \ ]) diff --git a/test/handler/test_shell_handler.vader b/test/handler/test_shell_handler.vader new file mode 100644 index 00000000..c61cf37d --- /dev/null +++ b/test/handler/test_shell_handler.vader @@ -0,0 +1,177 @@ +Before: + runtime ale_linters/sh/shell.vim + +After: + call ale#linter#Reset() + +Execute(The shell handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 13, + \ 'text': 'syntax error near unexpected token d', + \ }, + \ { + \ 'lnum': 7, + \ 'text': 'line 42: line 36:', + \ }, + \ { + \ 'lnum': 11, + \ 'text': 'Syntax error: "(" unexpected', + \ }, + \ { + \ 'lnum': 95, + \ 'text': 'parse error near `out=$(( $1 / 1024. )...', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ { + \ 'lnum': 9, + \ 'text': '`done'' unexpected', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ 'bash: line 13: syntax error near unexpected token d', + \ 'bash: line 7: line 42: line 36:', + \ 'sh: 11: Syntax error: "(" unexpected', + \ 'qfm:95: parse error near `out=$(( $1 / 1024. )...', + \ 'qfm:22: :11: :33: :44:', + \ 'foo.sh: syntax error at line 9: `done'' unexpected', + \ ]) + +Execute(The shell handler should parse Simplified Chinese lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': '未预期的符号“done”附近有语法错误', + \ }, + \ { + \ 'lnum': 90, + \ 'text': '寻找匹配的“"”时遇到了未预期的文件结束符', + \ }, + \ { + \ 'lnum': 111, + \ 'text': '语法错误: 未预期的文件结尾', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh:行0: 未预期的符号“done”附近有语法错误', + \ '/tmp/nvimWL5sOL/2/a.sh:行90: 寻找匹配的“"”时遇到了未预期的文件结束符', + \ '/tmp/nvimWL5sOL/2/a.sh:行111: 语法错误: 未预期的文件结尾', + \ '/tmp/nvimWL5sOL/2/a.sh:行22: :11: :33: :44:', + \ ]) + +Execute(The shell handler should parse Traditional Chinese lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': '未預期的字組「(」附近有語法錯誤', + \ }, + \ { + \ 'lnum': 90, + \ 'text': '尋找匹配的「"」時遇到了未預期的檔案結束符', + \ }, + \ { + \ 'lnum': 111, + \ 'text': '語法錯誤: 未預期的檔案結尾', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh: 列 0: 未預期的字組「(」附近有語法錯誤', + \ '/tmp/nvimWL5sOL/2/a.sh: 列 90: 尋找匹配的「"」時遇到了未預期的檔案結束符', + \ '/tmp/nvimWL5sOL/2/a.sh: 列 111: 語法錯誤: 未預期的檔案結尾', + \ '/tmp/nvimWL5sOL/2/a.sh: 列 22: :11: :33: :44:', + \ ]) + +Execute(The shell handler should parse Japanese lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': "予期しないトークン `(' 周辺に構文エラーがあります", + \ }, + \ { + \ 'lnum': 90, + \ 'text': "予期しないトークン `done' 周辺に構文エラーがあります", + \ }, + \ { + \ 'lnum': 111, + \ 'text': "対応する `\"' を探索中に予期しないファイル終了 (EOF) です", + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ "/tmp/nvimWL5sOL/2/a.sh: 行 0: 予期しないトークン `(' 周辺に構文エラーがあります", + \ "/tmp/nvimWL5sOL/2/a.sh: 行 90: 予期しないトークン `done' 周辺に構文エラーがあります", + \ "/tmp/nvimWL5sOL/2/a.sh: 行 111: 対応する `\"' を探索中に予期しないファイル終了 (EOF) です", + \ "/tmp/nvimWL5sOL/2/a.sh: 行 22: :11: :33: :44:", + \ ]) + +Execute(The shell handler should parse Greek lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': 'συντακτικό σφάλμα κοντά στο μη αναμενόμενο σύμβολο «done»', + \ }, + \ { + \ 'lnum': 90, + \ 'text': 'syntax error: μη αναμενόμενο τέλος αρχείου', + \ }, + \ { + \ 'lnum': 111, + \ 'text': 'μη αναμενόμενο EOF κατά την αναζήτηση «"»', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh: γραμμή 0: συντακτικό σφάλμα κοντά στο μη αναμενόμενο σύμβολο «done»', + \ '/tmp/nvimWL5sOL/2/a.sh: γραμμή 90: syntax error: μη αναμενόμενο τέλος αρχείου', + \ '/tmp/nvimWL5sOL/2/a.sh: γραμμή 111: μη αναμενόμενο EOF κατά την αναζήτηση «"»', + \ "/tmp/nvimWL5sOL/2/a.sh: γραμμή 22: :11: :33: :44:", + \ ]) + +Execute(The shell handler should parse Russian lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': 'синтаксическая ошибка рядом с неожиданным маркером «done»', + \ }, + \ { + \ 'lnum': 90, + \ 'text': 'синтаксическая ошибка: неожиданный конец файла', + \ }, + \ { + \ 'lnum': 111, + \ 'text': 'неожиданный конец файла во время поиска «"»', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh: строка 0: синтаксическая ошибка рядом с неожиданным маркером «done»', + \ '/tmp/nvimWL5sOL/2/a.sh: строка 90: синтаксическая ошибка: неожиданный конец файла', + \ '/tmp/nvimWL5sOL/2/a.sh: строка 111: неожиданный конец файла во время поиска «"»', + \ '/tmp/nvimWL5sOL/2/a.sh: строка 22: :11: :33: :44:', + \ ]) diff --git a/test/handler/test_shellcheck_handler.vader b/test/handler/test_shellcheck_handler.vader new file mode 100644 index 00000000..e5c972d7 --- /dev/null +++ b/test/handler/test_shellcheck_handler.vader @@ -0,0 +1,152 @@ +Before: + runtime ale_linters/shell/shellcheck.vim + +After: + call ale#linter#Reset() + +Execute(The shellcheck handler should handle basic errors or warnings <0.7.0): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'In POSIX sh, ''let'' is not supported.', + \ 'code': 'SC2039', + \ 'detail': 'In POSIX sh, ''let'' is not supported.' . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . 'SC2039', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'type': 'E', + \ 'text': 'Don''t put spaces around the = in assignments.', + \ 'code': 'SC1068', + \ 'detail': 'Don''t put spaces around the = in assignments.' . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . 'SC1068', + \ }, + \ ], + \ ale#handlers#shellcheck#Handle(bufnr(''), [0, 6, 0], [ + \ '-:2:1: warning: In POSIX sh, ''let'' is not supported. [SC2039]', + \ '-:2:3: error: Don''t put spaces around the = in assignments. [SC1068]', + \ ]) + +Execute(The shellcheck handler should handle notes <0.7.0): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 3, + \ 'type': 'I', + \ 'text': 'Double quote to prevent globbing and word splitting.', + \ 'code': 'SC2086', + \ 'detail': 'Double quote to prevent globbing and word splitting.' . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . 'SC2086', + \ }, + \ ], + \ ale#handlers#shellcheck#Handle(bufnr(''), [0, 6, 0], [ + \ '-:3:3: note: Double quote to prevent globbing and word splitting. [SC2086]', + \ ]) + +Execute(The shellcheck handler should handle basic errors or warnings >=0.7.0): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'end_lnum': 3, + \ 'col': 1, + \ 'end_col': 1, + \ 'type': 'W', + \ 'text': 'In POSIX sh, ''let'' is not supported.', + \ 'code': 'SC2039', + \ 'detail': 'In POSIX sh, ''let'' is not supported.' . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . 'SC2039', + \ }, + \ { + \ 'lnum': 2, + \ 'end_lnum': 3, + \ 'col': 3, + \ 'end_col': 3, + \ 'type': 'E', + \ 'text': 'Don''t put spaces around the = in assignments.', + \ 'code': 'SC1068', + \ 'detail': 'Don''t put spaces around the = in assignments.' . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . 'SC1068', + \ }, + \ ], + \ ale#handlers#shellcheck#Handle(bufnr(''), [0, 7, 0], [ + \ '{ + \ "comments": [ + \ { + \ "file":"-", + \ "line":2, + \ "endLine":3, + \ "column":1, + \ "endColumn":2, + \ "level":"warning", + \ "code":2039, + \ "message":"In POSIX sh, ''let'' is not supported.", + \ "fix": null + \ }, + \ { + \ "file":"-", + \ "line":2, + \ "endLine":3, + \ "column":3, + \ "endColumn":4, + \ "level":"error", + \ "code":1068, + \ "message":"Don''t put spaces around the = in assignments.", + \ "fix": null + \ } + \ ] + \ }' + \ ]) + +Execute(The shellcheck handler should handle info and style >=0.7.0): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'end_lnum': 5, + \ 'col': 3, + \ 'end_col': 4, + \ 'type': 'I', + \ 'text': 'Double quote to prevent globbing and word splitting.', + \ 'code': 'SC2086', + \ 'detail': 'Double quote to prevent globbing and word splitting.' . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . 'SC2086', + \ }, + \ { + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'col': 17, + \ 'end_col': 27, + \ 'type': 'I', + \ 'text': '$/${} is unnecessary on arithmetic variables.', + \ 'code': 'SC2004', + \ 'detail': '$/${} is unnecessary on arithmetic variables.' . "\n\nFor more information:\n https://www.shellcheck.net/wiki/" . 'SC2004', + \ } + \ ], + \ ale#handlers#shellcheck#Handle(bufnr(''), [0, 7, 0], [ + \ '{ + \ "comments": [ + \ { + \ "file": "-", + \ "line": 3, + \ "endLine": 5, + \ "column": 3, + \ "endColumn": 5, + \ "level": "info", + \ "code": 2086, + \ "message": "Double quote to prevent globbing and word splitting.", + \ "fix": null + \ }, + \ { + \ "file": "-", + \ "line": 13, + \ "endLine": 13, + \ "column": 17, + \ "endColumn": 28, + \ "level": "style", + \ "code": 2004, + \ "message": "$/${} is unnecessary on arithmetic variables.", + \ "fix": null + \ } + \ ] + \ }' + \ ]) diff --git a/test/handler/test_sierra_handler.vader b/test/handler/test_sierra_handler.vader new file mode 100644 index 00000000..889ac49c --- /dev/null +++ b/test/handler/test_sierra_handler.vader @@ -0,0 +1,20 @@ +Before: + runtime ale_linters/cairo/sierra.vim + +After: + call ale#linter#Reset() + +Execute(The starknet handler should handle error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 16, + \ 'col': 25, + \ 'text': 'Plugin diagnostic: Type not found', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#cairo#sierra#Handle(bufnr(''), [ + \ 'error: Plugin diagnostic: Type not found', + \ ' --> lib.cairo:16:25', + \ ]) diff --git a/test/handler/test_slang_handler.vader b/test/handler/test_slang_handler.vader new file mode 100644 index 00000000..529eb718 --- /dev/null +++ b/test/handler/test_slang_handler.vader @@ -0,0 +1,26 @@ +Before: + runtime ale_linters/verilog/slang.vim + +After: + call ale#linter#Reset() + +Execute(The slang handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'extra '';'' has no effect [-Wempty-member]', + \ }, + \ { + \ 'lnum': 24, + \ 'col': 12, + \ 'type': 'E', + \ 'text': 'cannot mix continuous and procedural assignments to variable ''data_o''', + \ }, + \ ], + \ ale_linters#verilog#slang#Handle(bufnr(''), [ + \ 'foo.sv:11:1: warning: extra '';'' has no effect [-Wempty-member]', + \ 'foo.sv:24:12: error: cannot mix continuous and procedural assignments to variable ''data_o''', + \ ]) diff --git a/test/handler/test_slim_handler.vader b/test/handler/test_slim_handler.vader new file mode 100644 index 00000000..d16082e0 --- /dev/null +++ b/test/handler/test_slim_handler.vader @@ -0,0 +1,33 @@ +" Author: Markus Doits +Before: + runtime ale_linters/slim/slimlint.vim + +After: + call ale#linter#Reset() + +Execute(The slim handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': '`div` is redundant when class attribute shortcut is present', + \ 'code': 'RedundantDiv', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'text': 'Line is too long. [136/80]', + \ 'code': 'LineLength', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'text': 'Invalid syntax', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#slim#slimlint#Handle(347, [ + \ 'inv.slim:1 [W] RedundantDiv: `div` is redundant when class attribute shortcut is present', + \ 'inv.slim:2 [W] LineLength: Line is too long. [136/80]', + \ 'inv.slim:3 [E] Invalid syntax', + \ ]) diff --git a/test/handler/test_sml_handler.vader b/test/handler/test_sml_handler.vader new file mode 100644 index 00000000..ef93cc47 --- /dev/null +++ b/test/handler/test_sml_handler.vader @@ -0,0 +1,119 @@ +Execute (Testing on EOF error): + AssertEqual [ + \ { + \ 'filename': 'a.sml', + \ 'lnum': 2, + \ 'col': 15, + \ 'type': 'E', + \ 'text': 'Error: syntax error found at EOF', + \ }, + \], + \ ale#handlers#sml#Handle(42, [ + \ "Standard ML of New Jersey v110.78 [built: Thu Jul 23 11:21:58 2015]", + \ "[opening a.sml]", + \ "a.sml:2.16 Error: syntax error found at EOF", + \ '/usr/lib/smlnj/bin/sml: Fatal error -- Uncaught exception Compile with "syntax error" raised at ../compiler/Parse/main/smlfile.sml:15.24-15.46', + \]) + +Execute (Testing if the handler can handle multiple errors on the same line): + AssertEqual [ + \ { + \ 'filename': 'a.sml', + \ 'lnum': 1, + \ 'col': 5, + \ 'type': 'E', + \ 'text': "Error: can't find function arguments in clause", + \ }, + \ { + \ 'filename': 'a.sml', + \ 'lnum': 1, + \ 'col': 12, + \ 'type': 'E', + \ 'text': 'Error: unbound variable or constructor: wow', + \ }, + \], + \ ale#handlers#sml#Handle(42, [ + \ "Standard ML of New Jersey v110.78 [built: Thu Jul 23 11:21:58 2015]", + \ "[opening test.sml]", + \ "a.sml:1.6-1.10 Error: can't find function arguments in clause", + \ "a.sml:1.13-1.16 Error: unbound variable or constructor: wow", + \ "/usr/lib/smlnj/bin/sml: Fatal error -- Uncaught exception Error with 0", + \ "raised at ../compiler/TopLevel/interact/evalloop.sml:66.19-66.27", + \]) + +Execute (Testing rarer errors): + AssertEqual [ + \ { + \ 'filename': 'a.sml', + \ 'lnum': 5, + \ 'col': 18, + \ 'type': 'E', + \ 'text': "Error: syntax error found at ID", + \ }, + \ { + \ 'filename': 'a.sml', + \ 'lnum': 7, + \ 'col': 0, + \ 'type': 'E', + \ 'text': "Error: value type in structure doesn't match signature spec", + \ }, + \], + \ ale#handlers#sml#Handle(42, [ + \ "Standard ML of New Jersey v110.78 [built: Thu Jul 23 11:21:58 2015]", + \ "[opening test.sml]", + \ "a.sml:5.19 Error: syntax error found at ID", + \ "a.sml:7.1-9.27 Error: value type in structure doesn't match signature spec", + \ "/usr/lib/smlnj/bin/sml: Fatal error -- Uncaught exception Error with 0", + \ "raised at ../compiler/TopLevel/interact/evalloop.sml:66.19-66.27", + \]) + +Execute (Testing a warning): + AssertEqual [ + \ { + \ 'filename': 'a.sml', + \ 'lnum': 4, + \ 'col': 4, + \ 'type': 'W', + \ 'text': "Warning: match nonexhaustive", + \ }, + \], + \ ale#handlers#sml#Handle(42, [ + \ "Standard ML of New Jersey v110.78 [built: Thu Jul 23 11:21:58 2015]", + \ "[opening a.sml]", + \ "a.sml:4.5-4.12 Warning: match nonexhaustive", + \ "0 => ...", + \ "val f = fn : int -> int", + \ "-", + \]) + +Execute (Testing stdIn): + AssertEqual [ + \ { + \ 'bufnr': 42, + \ 'lnum': 1, + \ 'col': 5, + \ 'type': 'E', + \ 'text': "Error: operator and operand don't agree [overload conflict]", + \ }, + \ { + \ 'bufnr': 42, + \ 'lnum': 2, + \ 'col': 4, + \ 'type': 'E', + \ 'text': "Error: operator and operand don't agree [overload conflict]", + \ }, + \], + \ ale#handlers#sml#Handle(42, [ + \ "Standard ML of New Jersey v110.79 [built: Sat Oct 26 12:27:04 2019]", + \ "- = stdIn:1.6-1.21 Error: operator and operand don't agree [overload conflict]", + \ " operator domain: [+ ty] * [+ ty]", + \ " operand: string * [int ty]", + \ " in expression:", + \ ' "abc" + 123', + \ "stdIn:2.5-2.20 Error: operator and operand don't agree [overload conflict]", + \ " operator domain: [+ ty] * [+ ty]", + \ " operand: [+ ty] * string", + \ " in expression:", + \ ' 890 + "xyz"', + \ "-", + \]) diff --git a/test/handler/test_solc_handler.vader b/test/handler/test_solc_handler.vader new file mode 100644 index 00000000..dcaa8b2d --- /dev/null +++ b/test/handler/test_solc_handler.vader @@ -0,0 +1,34 @@ +Before: + runtime ale_linters/solidity/solc.vim + +After: + call ale#linter#Reset() + +Execute(Check solc output parsing): + AssertEqual + \ [ + \ { + \ 'lnum': 40, + \ 'col': 48, + \ 'text': 'This declaration shadows an existing declaration.', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 23, + \ 'col': 16, + \ 'text': 'Member "getSinleSignature" not found or not visible after argument-dependent lookup in type(contract OneToN).', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#solidity#solc#Handle(bufnr(''), [ + \ 'Warning: This declaration shadows an existing declaration.', + \ ' --> /path/to/file.sol:40:48:', + \ ' |', + \ '40 | function decimals() external view returns (uint8 decimals);', + \ ' | ^------------^', + \ 'Error: Member "getSinleSignature" not found or not visible after argument-dependent lookup in type(contract OneToN).', + \ ' --> /path/to/file.sol:23:16: ', + \ ' | ', + \ '23 | return OneToN.getSinleSignature(signatures, i);', + \ ' | ^----------------------^', + \ ]) diff --git a/test/handler/test_solhint_handler.vader b/test/handler/test_solhint_handler.vader new file mode 100644 index 00000000..b9cdacc7 --- /dev/null +++ b/test/handler/test_solhint_handler.vader @@ -0,0 +1,83 @@ +Before: + runtime ale_linters/solidity/solhint.vim + +After: + call ale#linter#Reset() + +Execute(The solhint handler should parse linter error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 17, + \ 'text': 'Compiler version must be fixed', + \ 'code': 'compiler-fixed', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 8, + \ 'text': 'Use double quotes for string literals', + \ 'code': 'quotes', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 8, + \ 'text': 'Use double quotes for string literals', + \ 'code': 'quotes', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 13, + \ 'col': 3, + \ 'text': 'Expected indentation of 4 spaces but found 2', + \ 'code': 'indent', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 14, + \ 'col': 3, + \ 'text': 'Expected indentation of 4 spaces but found 2', + \ 'code': 'indent', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 47, + \ 'col': 3, + \ 'text': 'Function order is incorrect, public function can not go after internal function.', + \ 'code': 'func-order', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#solidity#solhint#Handle(bufnr(''), [ + \ 'contracts/Bounty.sol:1:17: Compiler version must be fixed [Warning/compiler-fixed]', + \ 'contracts/Bounty.sol:4:8: Use double quotes for string literals [Error/quotes]', + \ 'contracts/Bounty.sol:5:8: Use double quotes for string literals [Error/quotes]', + \ 'contracts/Bounty.sol:13:3: Expected indentation of 4 spaces but found 2 [Error/indent]', + \ 'contracts/Bounty.sol:14:3: Expected indentation of 4 spaces but found 2 [Error/indent]', + \ 'contracts/Bounty.sol:47:3: Function order is incorrect, public function can not go after internal function. [Error/func-order]', + \ ]) + +Execute(The solhint handler should parse syntax error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 30, + \ 'col': 4, + \ 'text': "missing ';' at 'uint248'", + \ 'code': 'Parse error', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 203, + \ 'col': 4, + \ 'text': "no viable alternative at input '_loserStakeMultiplier}'", + \ 'code': 'Parse error', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#solidity#solhint#Handle(bufnr(''), [ + \ "contracts/Bounty.sol:30:4: Parse error: missing ';' at 'uint248' [Error]", + \ "contracts/Bounty.sol:203:4: Parse error: no viable alternative at input '_loserStakeMultiplier}' [Error]", + \ ]) diff --git a/test/handler/test_spectral_handler.vader b/test/handler/test_spectral_handler.vader new file mode 100644 index 00000000..89a3ff1b --- /dev/null +++ b/test/handler/test_spectral_handler.vader @@ -0,0 +1,52 @@ +Before: + runtime ale_linters/yaml/spectral.vim + +After: + call ale#linter#Reset() + +Execute(spectral handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'code': 'oas3-api-servers', + \ 'text': 'OpenAPI `servers` must be present and non-empty array.', + \ 'type': 'W' + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'code': 'oas3-schema', + \ 'text': 'Object should have required property `paths`.', + \ 'type': 'E' + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'code': 'openapi-tags', + \ 'text': 'OpenAPI object should have non-empty `tags` array.', + \ 'type': 'W' + \ }, + \ { + \ 'lnum': 3, + \ 'col': 6, + \ 'code': 'info-contact', + \ 'text': 'Info object should contain `contact` object.', + \ 'type': 'W' + \ }, + \ { + \ 'lnum': 3, + \ 'col': 6, + \ 'code': 'oas3-schema', + \ 'text': '`info` property should have required property `version`.', + \ 'type': 'E' + \ }, + \ ], + \ ale#handlers#spectral#HandleSpectralOutput(bufnr(''), [ + \ 'openapi.yml:1:1 warning oas3-api-servers "OpenAPI `servers` must be present and non-empty array."', + \ 'openapi.yml:1:1 error oas3-schema "Object should have required property `paths`."', + \ 'openapi.yml:1:1 warning openapi-tags "OpenAPI object should have non-empty `tags` array."', + \ 'openapi.yml:3:6 warning info-contact "Info object should contain `contact` object."', + \ 'openapi.yml:3:6 error oas3-schema "`info` property should have required property `version`."', + \ ]) diff --git a/test/handler/test_sql_sqlfluff_handler.vader b/test/handler/test_sql_sqlfluff_handler.vader new file mode 100644 index 00000000..74508ee9 --- /dev/null +++ b/test/handler/test_sql_sqlfluff_handler.vader @@ -0,0 +1,83 @@ +Before: + runtime ale_linters/sql/sqlfluff.vim + +After: + call ale#linter#Reset() + +Execute(The sqlfluff handler should handle basic warnings with version older than 3.0.0): + AssertEqual + \ [ + \ { + \ 'filename': 'schema.sql', + \ 'lnum': 1, + \ 'col': 8, + \ 'type': 'W', + \ 'code': 'L010', + \ 'text': 'Keywords must be consistently upper case.', + \ }, + \ { + \ 'filename': 'schema.sql', + \ 'lnum': 13, + \ 'col': 2, + \ 'type': 'W', + \ 'code': 'L003', + \ 'text': 'Expected 1 indentation, found 0 [compared to line 12]', + \ }, + \ { + \ 'filename': 'schema.sql', + \ 'lnum': 16, + \ 'col': 1, + \ 'type': 'W', + \ 'code': 'L009', + \ 'text': 'Files must end with a single trailing newline.', + \ }, + \ ], + \ ale_linters#sql#sqlfluff#Handle(bufnr(''), [2, 3, 5], [ + \ '[{"filepath": "schema.sql", "violations": [{"line_no": 1, "line_pos": 8, "code": "L010", "description": "Keywords must be consistently upper case."}, {"line_no": 13, "line_pos": 2, "code": "L003", "description": "Expected 1 indentation, found 0 [compared to line 12]"}, {"line_no": 16, "line_pos": 1, "code": "L009", "description": "Files must end with a single trailing newline."}]}]', + \ ]) + +Execute(The sqlfluff handler should handle basic warnings with version newer than 3.0.0): + AssertEqual + \ [ + \ { + \ 'filename': 'schema.sql', + \ 'lnum': 1, + \ 'end_lnum': 1, + \ 'col': 8, + \ 'end_col': 12, + \ 'type': 'W', + \ 'code': 'L010', + \ 'text': 'Keywords must be consistently upper case.', + \ }, + \ { + \ 'filename': 'schema.sql', + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'col': 2, + \ 'end_col': 20, + \ 'type': 'W', + \ 'code': 'L003', + \ 'text': 'Expected 1 indentation, found 0 [compared to line 12]', + \ }, + \ { + \ 'filename': 'schema.sql', + \ 'lnum': 16, + \ 'end_lnum': 16, + \ 'col': 1, + \ 'end_col': 5, + \ 'type': 'W', + \ 'code': 'L009', + \ 'text': 'Files must end with a single trailing newline.', + \ }, + \ { + \ 'filename': 'schema.sql', + \ 'lnum': 3, + \ 'col': 21, + \ 'type': 'W', + \ 'code': 'TMP', + \ 'text': "Undefined jinja template variable: 'a_jinja_templated_table'", + \ }, + \ ], + \ ale_linters#sql#sqlfluff#Handle(bufnr(''), [3, 0, 0], [ + \ '[{"filepath": "schema.sql", "violations": [{"start_line_no": 1, "end_line_no":1, "start_line_pos": 8, "end_line_pos":12, "code": "L010", "description": "Keywords must be consistently upper case."}, {"start_line_no": 13, "end_line_no":13, "start_line_pos": 2, "end_line_pos":20, "code": "L003", "description": "Expected 1 indentation, found 0 [compared to line 12]"}, {"start_line_no": 16, "end_line_no":16, "start_line_pos": 1, "end_line_pos":5, "code": "L009", "description": "Files must end with a single trailing newline."}, {"start_line_no": 3, "start_line_pos": 21, "code": "TMP", "description": "Undefined jinja template variable: ''a_jinja_templated_table''"}]}]', + \ ]) diff --git a/test/handler/test_sqlint_handler.vader b/test/handler/test_sqlint_handler.vader new file mode 100644 index 00000000..5567ca41 --- /dev/null +++ b/test/handler/test_sqlint_handler.vader @@ -0,0 +1,34 @@ +Before: + runtime! ale_linters/sql/sqlint.vim + +After: + call ale#linter#Reset() + +Execute(The sqlint handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 1, + \ 'text': 'syntax error at or near "WIBBLE"', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 47, + \ 'col': 11, + \ 'text': 'unterminated quoted string at or near "''', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 50, + \ 'col': 12, + \ 'text': 'some warning at end of input', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#sql#sqlint#Handle(347, [ + \ 'This line should be ignored completely', + \ 'stdin:3:1:ERROR syntax error at or near "WIBBLE"', + \ 'stdin:47:11:ERROR unterminated quoted string at or near "''', + \ 'stdin:50:12:WARNING some warning at end of input', + \ ]) diff --git a/test/handler/test_sqllint_handler.vader b/test/handler/test_sqllint_handler.vader new file mode 100644 index 00000000..2f2283c8 --- /dev/null +++ b/test/handler/test_sqllint_handler.vader @@ -0,0 +1,23 @@ +Before: + " Load the file which defines the linter. + runtime ale_linters/sql/sqllint.vim + +After: + " Unload all linters again. + call ale#linter#Reset() + +Execute (The output should be correct): + + " Test that the right loclist items are parsed from the handler. + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'type': '', + \ 'text': 'stdin:1 [ER_NO_DB_ERROR] No database selected' + \ }, + \ ], + \ ale_linters#sql#sqllint#Handle(bufnr(''), [ + \ 'stdin:1 [ER_NO_DB_ERROR] No database selected' + \ ]) diff --git a/test/handler/test_standard_handler.vader b/test/handler/test_standard_handler.vader new file mode 100644 index 00000000..31e3a36b --- /dev/null +++ b/test/handler/test_standard_handler.vader @@ -0,0 +1,37 @@ +Before: + Save g:ale_javascript_eslint_suppress_eslintignore + + let g:ale_javascript_eslint_suppress_eslintignore = 0 + +After: + Restore + +Execute(The standard handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 47, + \ 'col': 14, + \ 'text': 'Expected indentation of 2 spaces but found 4.', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 56, + \ 'col': 41, + \ 'text': 'Strings must use singlequote.', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 13, + \ 'col': 3, + \ 'text': 'Parsing error: Unexpected token', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#eslint#Handle(347, [ + \ 'This line should be ignored completely', + \ '/path/to/some-filename.js:47:14: Expected indentation of 2 spaces but found 4.', + \ '/path/to/some-filename.js:56:41: Strings must use singlequote.', + \ 'This line should be ignored completely', + \ '/path/to/some-filename.js:13:3: Parsing error: Unexpected token', + \ ]) diff --git a/test/handler/test_starknet_handler.vader b/test/handler/test_starknet_handler.vader new file mode 100644 index 00000000..767cb211 --- /dev/null +++ b/test/handler/test_starknet_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/cairo/starknet.vim + +After: + call ale#linter#Reset() + +Execute(The starknet handler should handle error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 6, + \ 'text': 'Could not find module "starkware.cairo.commo.cairo_builtins". Searched in the following paths:', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#cairo#starknet#Handle(bufnr(''), [ + \ 'contract.cairo:3:6: Could not find module "starkware.cairo.commo.cairo_builtins". Searched in the following paths:', + \ 'from starkware.cairo.commo.cairo_builtins import HashBuiltin', + \ ' ^**********************************^', + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 21, + \ 'col': 2, + \ 'text': 'Unsupported decorator: "vie".', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#cairo#starknet#Handle(bufnr(''), [ + \ 'contract.cairo:21:2: Unsupported decorator: "vie".', + \ '@vie', + \ ' ^*^', + \ ]) diff --git a/test/handler/test_statix_handler.vader b/test/handler/test_statix_handler.vader new file mode 100644 index 00000000..f2a105ee --- /dev/null +++ b/test/handler/test_statix_handler.vader @@ -0,0 +1,16 @@ +Execute(The statix handler should handle statix output): + call ale#test#SetFilename('flake.nix') + + AssertEqual + \ [ + \ { + \ 'lnum': 46, + \ 'type': 'W', + \ 'col': 13, + \ 'code': '3', + \ 'text': 'This assignment is better written with `inherit`' + \ }, + \ ], + \ ale#handlers#statix#Handle(bufnr(''), + \ '>46:13:W:3:This assignment is better written with `inherit`' + \) diff --git a/test/handler/test_steep_handler.vader b/test/handler/test_steep_handler.vader new file mode 100644 index 00000000..98875229 --- /dev/null +++ b/test/handler/test_steep_handler.vader @@ -0,0 +1,100 @@ +Before: + runtime ale_linters/ruby/steep.vim + +After: + call ale#linter#Reset() + +Execute(The steep handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 400, + \ 'col': 18, + \ 'end_col': 45, + \ 'text': 'Method parameters are incompatible with declaration `(untyped, untyped, *untyped, **untyped) { () -> untyped } -> untyped`', + \ 'code': 'Ruby::MethodArityMismatch', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 20, + \ 'col': 9, + \ 'end_col': 17, + \ 'text': 'Cannot find implementation of method `::Frobz::FooBarBaz#method_name`', + \ 'code': 'Ruby::MethodDefinitionMissing', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 30, + \ 'col': 9, + \ 'end_col': 17, + \ 'text': 'Cannot find implementation of method `::Frobz::FooBarBaz#method_name`', + \ 'code': 'Ruby::MethodDefinitionMissing', + \ 'type': 'I', + \ }, + \ { + \ 'lnum': 40, + \ 'col': 9, + \ 'end_col': 17, + \ 'text': 'Cannot find implementation of method `::Frobz::FooBarBaz#method_name`', + \ 'code': 'Ruby::MethodDefinitionMissing', + \ 'type': 'I', + \ }, + \ ], + \ ale_linters#ruby#steep#HandleOutput(347, [ + \ '# Type checking files:', + \ '', + \ '...............................................................................................................................F..........F.F...F.', + \ '', + \ 'lib/frobz/foobar_baz.rb:400:17: [error] Method parameters are incompatible with declaration `(untyped, untyped, *untyped, **untyped) { () -> untyped } -> untyped`', + \ '│ Diagnostic ID: Ruby::MethodArityMismatch', + \ '│', + \ '└ def frobz(obj, suffix, *args, &block)', + \ ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~', + \ '', + \ 'lib/frobz/foobar_baz.rb:20:8: [warning] Cannot find implementation of method `::Frobz::FooBarBaz#method_name`', + \ '│ Diagnostic ID: Ruby::MethodDefinitionMissing', + \ '│', + \ '└ class FooBarBaz', + \ ' ~~~~~~~~~', + \ '', + \ 'lib/frobz/foobar_baz.rb:30:8: [information] Cannot find implementation of method `::Frobz::FooBarBaz#method_name`', + \ '│ Diagnostic ID: Ruby::MethodDefinitionMissing', + \ '│', + \ '└ class FooBarBaz', + \ ' ~~~~~~~~~', + \ '', + \ 'lib/frobz/foobar_baz.rb:40:8: [hint] Cannot find implementation of method `::Frobz::FooBarBaz#method_name`', + \ '│ Diagnostic ID: Ruby::MethodDefinitionMissing', + \ '│', + \ '└ class FooBarBaz', + \ ' ~~~~~~~~~', + \ '', + \ 'Detected 4 problems from 1 file', + \ ]) + +Execute(The steep handler should handle when files are checked and no offenses are found): + AssertEqual + \ [], + \ ale_linters#ruby#steep#HandleOutput(347, [ + \ '# Type checking files:', + \ '', + \ '.............................................................................................................................................', + \ '', + \ 'No type error detected. 🧉', + \ ]) + +Execute(The steep handler should handle when no files are checked): + AssertEqual + \ [], + \ ale_linters#ruby#steep#HandleOutput(347, [ + \ '# Type checking files:', + \ '', + \ '', + \ '', + \ 'No type error detected. 🧉', + \ ]) + +Execute(The steep handler should handle empty output): + AssertEqual [], ale_linters#ruby#steep#HandleOutput(347, ['']) + AssertEqual [], ale_linters#ruby#steep#HandleOutput(347, []) + diff --git a/test/handler/test_stylelint_handler.vader b/test/handler/test_stylelint_handler.vader new file mode 100644 index 00000000..5cb34601 --- /dev/null +++ b/test/handler/test_stylelint_handler.vader @@ -0,0 +1,43 @@ +After: + unlet! g:error_lines + +Execute (stylelint errors should be handled correctly): + " Stylelint includes trailing spaces for output. This needs to be taken into + " account for parsing errors. + AssertEqual + \ [ + \ { + \ 'lnum': 108, + \ 'col': 10, + \ 'type': 'E', + \ 'text': 'Unexpected leading zero', + \ 'code': 'number-leading-zero', + \ }, + \ { + \ 'lnum': 116, + \ 'col': 20, + \ 'type': 'E', + \ 'text': 'Expected a trailing semicolon', + \ 'code': 'declaration-block-trailing-semicolon', + \ }, + \ ], + \ ale#handlers#css#HandleStyleLintFormat(42, [ + \ 'src/main.css', + \ ' 108:10 ✖ Unexpected leading zero number-leading-zero ', + \ ' 116:20 ✖ Expected a trailing semicolon declaration-block-trailing-semicolon', + \ ]) + +Execute (stylelint should complain when no configuration file is used): + let g:error_lines = [ + \ 'Error: No configuration provided for /home/w0rp/.vim/bundle/ale/test.stylus', + \ ' at module.exports (/home/w0rp/.vim/bundle/ale/node_modules/stylelint/lib/utils/configurationError.js:8:27)', + \ ' at stylelint._fullExplorer.load.then.then.config (/home/w0rp/.vim/bundle/ale/node_modules/stylelint/lib/getConfigForFile.js:39:13)', + \] + + AssertEqual + \ [{ + \ 'lnum': 1, + \ 'text': 'stylelint exception thrown (type :ALEDetail for more information)', + \ 'detail': join(g:error_lines, "\n"), + \ }], + \ ale#handlers#css#HandleStyleLintFormat(347, g:error_lines[:]) diff --git a/test/handler/test_swaglint_handler.vader b/test/handler/test_swaglint_handler.vader new file mode 100644 index 00000000..7ab10439 --- /dev/null +++ b/test/handler/test_swaglint_handler.vader @@ -0,0 +1,68 @@ +Before: + runtime ale_linters/yaml/swaglint.vim + +After: + call ale#linter#Reset() + +Execute(The swaglint handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Missing required property: info', + \ 'code': 'sway_object_missing_required_property', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 9, + \ 'text': 'Not a valid response definition', + \ 'code': 'sway_one_of_missing', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 11, + \ 'text': 'Missing required property: description', + \ 'code': 'sway_object_missing_required_property', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 11, + \ 'text': 'Missing required property: $ref', + \ 'code': 'sway_object_missing_required_property', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'Expected type string but found type integer', + \ 'code': 'sway_invalid_type', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'No enum match for: 2', + \ 'code': 'sway_enum_mismatch', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 14, + \ 'col': 3, + \ 'text': 'Definition is not used: #/definitions/Foo', + \ 'code': 'sway_unused_definition', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#yaml#swaglint#Handle(347, [ + \ 'swagger.yaml: error @ 1:1 - Missing required property: info (sway_object_missing_required_property)', + \ 'swagger.yaml: error @ 6:9 - Not a valid response definition (sway_one_of_missing)', + \ 'swagger.yaml: error @ 7:11 - Missing required property: description (sway_object_missing_required_property)', + \ 'swagger.yaml: error @ 7:11 - Missing required property: $ref (sway_object_missing_required_property)', + \ 'swagger.yaml: error @ 1:10 - Expected type string but found type integer (sway_invalid_type)', + \ 'swagger.yaml: error @ 1:10 - No enum match for: 2 (sway_enum_mismatch)', + \ 'swagger.yaml: warning @ 14:3 - Definition is not used: #/definitions/Foo (sway_unused_definition)', + \ ]) diff --git a/test/handler/test_swiftlint_handler.vader b/test/handler/test_swiftlint_handler.vader new file mode 100644 index 00000000..725ff97c --- /dev/null +++ b/test/handler/test_swiftlint_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/swift/swiftlint.vim + +After: + call ale#linter#Reset() + +Execute(The swiftint handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 7, + \ 'text': 'Operator Usage Whitespace Violation: Operators should be surrounded by a single whitespace when they are being used.', + \ 'code': 'operator_usage_whitespace', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 11, + \ 'text': 'Operator Usage Whitespace Violation: Operators should be surrounded by a single whitespace when they are being used.', + \ 'code': 'operator_usage_whitespace', + \ 'type': 'W', + \ }, + \ + \ ], + \ ale_linters#swift#swiftlint#Handle(bufnr(''), [ + \ 'This line should be ignored', + \ ':1:7: warning: Operator Usage Whitespace Violation: Operators should be surrounded by a single whitespace when they are being used. (operator_usage_whitespace)', + \ ':1:11: warning: Operator Usage Whitespace Violation: Operators should be surrounded by a single whitespace when they are being used. (operator_usage_whitespace)', + \ ]) diff --git a/test/handler/test_swipl_handler.vader b/test/handler/test_swipl_handler.vader new file mode 100644 index 00000000..81b8b9e5 --- /dev/null +++ b/test/handler/test_swipl_handler.vader @@ -0,0 +1,155 @@ +Before: + runtime ale_linters/prolog/swipl.vim + +After: + call ale#linter#Reset() + +Execute (The swipl handler should handle oneline warning / error): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 1, + \ 'text': 'Syntax error: Operator expected', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'ERROR: /path/to/test.pl:5:1: Syntax error: Operator expected', + \ ]) + +Execute (The swipl handler should handle a warning / error of two lines): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [ + \ { + \ 'lnum': 9, + \ 'col': 0, + \ 'text': 'Singleton variables: [M]', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'Warning: /path/to/test.pl:9:', + \ ' Singleton variables: [M]', + \ ]) + +Execute (The swipl handler should handle a warning / error of two lines in the new format): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [ + \ { + \ 'lnum': 9, + \ 'col': 0, + \ 'text': 'Singleton variables: [M]', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'Warning: /path/to/test.pl:9:', + \ 'Warning: Singleton variables: [M]', + \ ]) + +Execute (The swipl handler should join three or more lines with '. '): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'col': 0, + \ 'text': 'Clauses of fib/2 are not together in the source-file. Earlier definition at /path/to/test.pl:7. Current predicate: f/0. Use :- discontiguous fib/2. to suppress this message', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'Warning: /path/to/test.pl:10:', + \ ' Clauses of fib/2 are not together in the source-file', + \ ' Earlier definition at /path/to/test.pl:7', + \ ' Current predicate: f/0', + \ ' Use :- discontiguous fib/2. to suppress this message', + \ ]) + +Execute (The swipl handler should ignore warnings / errors 'No permission to call sandboxed ...'): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'ERROR: /path/to/test.pl:11:', + \ ' No permission to call sandboxed `''$set_predicate_attribute''(_G3416:_G3417,_G3413,_G3414)''', + \ ' Reachable from:', + \ ' system:''$set_pattr''(A,B,C,D)', + \ ' system:''$set_pattr''(vimscript:A,B,C)', + \ ' vimscript: (multifile A)', + \ 'ERROR: /path/to/test.pl:12:', + \ ' No permission to call sandboxed `''$set_predicate_attribute''(_G205:_G206,_G202,_G203)''', + \ ' Reachable from:', + \ ' system:''$set_pattr''(A,B,C,D)', + \ ' system:''$set_pattr''(vimscript:A,B,C)', + \ ' vimscript: (multifile A)', + \ 'ERROR: /path/to/test.pl:13:', + \ ' No permission to call sandboxed `''$set_predicate_attribute''(_G1808:_G1809,_G1805,_G1806)''', + \ ' Reachable from:', + \ ' system:''$set_pattr''(A,B,C,D)', + \ ' system:''$set_pattr''(vimscript:A,B,C)', + \ ' vimscript: (multifile A)', + \ ]) + +Execute (The swipl handler should join three or more lines with '. ' on latest swipl): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'col': 0, + \ 'text': 'Clauses of fib/2 are not together in the source-file. Earlier definition at /path/to/test.pl:7. Current predicate: f/0. Use :- discontiguous fib/2. to suppress this message', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'Warning: /path/to/test.pl:10:', + \ 'Warning: Clauses of fib/2 are not together in the source-file', + \ 'Warning: Earlier definition at /path/to/test.pl:7', + \ 'Warning: Current predicate: f/0', + \ 'Warning: Use :- discontiguous fib/2. to suppress this message', + \ ]) + +Execute (The swipl handler should ignore warnings / errors 'No permission to call sandboxed with latest swpl...'): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'ERROR: /path/to/test.pl:11:', + \ 'ERROR: No permission to call sandboxed `''$set_predicate_attribute''(_G3416:_G3417,_G3413,_G3414)''', + \ 'ERROR: Reachable from:', + \ 'ERROR: system:''$set_pattr''(A,B,C,D)', + \ 'ERROR: system:''$set_pattr''(vimscript:A,B,C)', + \ 'ERROR: vimscript: (multifile A)', + \ 'ERROR: /path/to/test.pl:12:', + \ 'ERROR: No permission to call sandboxed `''$set_predicate_attribute''(_G205:_G206,_G202,_G203)''', + \ 'ERROR: Reachable from:', + \ 'ERROR: system:''$set_pattr''(A,B,C,D)', + \ 'ERROR: system:''$set_pattr''(vimscript:A,B,C)', + \ 'ERROR: vimscript: (multifile A)', + \ 'ERROR: /path/to/test.pl:13:', + \ 'ERROR: No permission to call sandboxed `''$set_predicate_attribute''(_G1808:_G1809,_G1805,_G1806)''', + \ 'ERROR: Reachable from:', + \ 'ERROR: system:''$set_pattr''(A,B,C,D)', + \ 'ERROR: system:''$set_pattr''(vimscript:A,B,C)', + \ 'ERROR: vimscript: (multifile A)', + \ ]) + +Execute (The swipl handler should handle a warning / error with no line number): + call ale#test#SetFilename('test.pl') + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'text': 'Exported procedure module_name:pred/0 is not defined', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#prolog#swipl#Handle(bufnr(''), [ + \ 'ERROR: Exported procedure module_name:pred/0 is not defined', + \ ]) diff --git a/test/handler/test_syntaxerl_handler.vader b/test/handler/test_syntaxerl_handler.vader new file mode 100644 index 00000000..95f2bfef --- /dev/null +++ b/test/handler/test_syntaxerl_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/erlang/syntaxerl.vim + +After: + call ale#linter#Reset() + +Execute (Handle SyntaxErl output): + AssertEqual + \ [ + \ { + \ 'lnum': 42, + \ 'text': "syntax error before: ','", + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 42, + \ 'text': 'function foo/0 is unused', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#erlang#syntaxerl#Handle(bufnr(''), [ + \ "/tmp/v2wDixk/1/module.erl:42: syntax error before: ','", + \ '/tmp/v2wDixk/2/module.erl:42: warning: function foo/0 is unused', + \ ]) diff --git a/test/handler/test_systemd_analyze_handler.vader b/test/handler/test_systemd_analyze_handler.vader new file mode 100644 index 00000000..c7d668e0 --- /dev/null +++ b/test/handler/test_systemd_analyze_handler.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/systemd/systemd_analyze.vim + +After: + call ale#linter#Reset() + +Execute(The systemd-analyze handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 9, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'Unknown key name ''Wat'' in section ''Service'', ignoring.', + \ }, + \ ], + \ ale_linters#systemd#systemd_analyze#Handle(bufnr(''), [ + \ '/home/user/.config/systemd/user/foo.service:9: Unknown key name ''Wat'' in section ''Service'', ignoring.', + \ ]) diff --git a/test/handler/test_terraform_handler.vader b/test/handler/test_terraform_handler.vader new file mode 100644 index 00000000..4be89cb1 --- /dev/null +++ b/test/handler/test_terraform_handler.vader @@ -0,0 +1,138 @@ +Before: + " Load the file which defines the linter. + runtime ale_linters/terraform/terraform.vim + call ale#test#SetDirectory('/testplugin/test/test-files/terraform') + call ale#test#SetFilename('providers.tf') + +After: + " Unload all linters again. + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The output should be correct): + AssertEqual + \ [ + \ { + \ 'lnum': 17, + \ 'col': 13, + \ 'filename': ale#path#Simplify(g:dir . '/providers.tf'), + \ 'type': 'W', + \ 'text': 'Terraform 0.13 and earlier allowed provider version', + \ }, + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'filename': ale#path#Simplify(g:dir . '/providers.tf'), + \ 'type': 'E', + \ 'text': 'Plugin reinitialization required. Please run "terraform"', + \ } + \ ], + \ ale_linters#terraform#terraform#Handle(bufnr(''), [ + \ '{', + \ '"valid": false,', + \ '"error_count": 1,', + \ '"warning_count": 1,', + \ '"diagnostics": [', + \ ' {', + \ ' "severity": "warning",', + \ ' "summary": "Version constraints inside provider configuration blocks are deprecated",', + \ ' "detail": "Terraform 0.13 and earlier allowed provider version",', + \ ' "range": {', + \ ' "filename": "providers.tf",', + \ ' "start": {', + \ ' "line": 17,', + \ ' "column": 13,', + \ ' "byte": 669', + \ ' },', + \ ' "end": {', + \ ' "line": 17,', + \ ' "column": 24,', + \ ' "byte": 680', + \ ' }', + \ ' }', + \ ' },', + \ ' {', + \ ' "severity": "error",', + \ ' "summary": "Could not load plugin",', + \ ' "detail": "Plugin reinitialization required. Please run \"terraform\""', + \ ' }', + \ ' ]', + \ '}', + \ ]) + +Execute(Should use summary if detail not available): + AssertEqual + \ [ + \ { + \ 'lnum': 91, + \ 'col': 41, + \ 'filename': ale#path#Simplify(g:dir . '/main.tf'), + \ 'type': 'E', + \ 'text': 'storage_os_disk: required field is not set', + \ } + \ ], + \ ale_linters#terraform#terraform#Handle(bufnr(''), [ + \ '{', + \ ' "valid": false,', + \ ' "error_count": 1,', + \ ' "warning_count": 0,', + \ ' "diagnostics": [', + \ ' {', + \ ' "severity": "error",', + \ ' "summary": "storage_os_disk: required field is not set",', + \ ' "range": {', + \ ' "filename": "main.tf",', + \ ' "start": {', + \ ' "line": 91,', + \ ' "column": 41,', + \ ' "byte": 2381', + \ ' },', + \ ' "end": {', + \ ' "line": 91,', + \ ' "column": 41,', + \ ' "byte": 2381', + \ ' }', + \ ' }', + \ ' }', + \ ' ]', + \ '}' + \ ]) + +Execute(Should use summary if detail available but empty): + AssertEqual + \ [ + \ { + \ 'lnum': 91, + \ 'col': 41, + \ 'filename': ale#path#Simplify(g:dir . '/main.tf'), + \ 'type': 'E', + \ 'text': 'storage_os_disk: required field is not set', + \ } + \ ], + \ ale_linters#terraform#terraform#Handle(bufnr(''), [ + \ '{', + \ ' "valid": false,', + \ ' "error_count": 1,', + \ ' "warning_count": 0,', + \ ' "diagnostics": [', + \ ' {', + \ ' "severity": "error",', + \ ' "summary": "storage_os_disk: required field is not set",', + \ ' "detail": "",', + \ ' "range": {', + \ ' "filename": "main.tf",', + \ ' "start": {', + \ ' "line": 91,', + \ ' "column": 41,', + \ ' "byte": 2381', + \ ' },', + \ ' "end": {', + \ ' "line": 91,', + \ ' "column": 41,', + \ ' "byte": 2381', + \ ' }', + \ ' }', + \ ' }', + \ ' ]', + \ '}' + \ ]) diff --git a/test/handler/test_textlint_handler.vader b/test/handler/test_textlint_handler.vader new file mode 100644 index 00000000..b6d73648 --- /dev/null +++ b/test/handler/test_textlint_handler.vader @@ -0,0 +1,40 @@ +Before: + runtime! ale_linters/markdown/textlint.vim + +After: + call ale#linter#Reset() + +Execute(textlint handler should handle errors output): + AssertEqual + \ [ + \ { + \ 'lnum': 16, + \ 'col': 50, + \ 'text': 'Found possibly misspelled word "NeoVim".', + \ 'type': 'W', + \ 'code': 'preset-japanese/no-doubled-joshi', + \ }, + \ ], + \ ale#handlers#textlint#HandleTextlintOutput(bufnr(''), [ + \ '[', + \ ' {', + \ ' "filePath": "test.md",', + \ ' "messages": [', + \ ' {', + \ ' "type": "lint",', + \ ' "ruleId": "preset-japanese/no-doubled-joshi",', + \ ' "index": 1332,', + \ ' "line": 16,', + \ ' "column": 50,', + \ ' "severity": 2,', + \ ' "message": "Found possibly misspelled word \"NeoVim\"."', + \ ' }', + \ ' ]', + \ ' }', + \ ']', + \ ]) + +Execute(textlint handler should no error output): + AssertEqual + \ [], + \ ale#handlers#textlint#HandleTextlintOutput(bufnr(''), []) diff --git a/test/handler/test_tflint_handler.vader b/test/handler/test_tflint_handler.vader new file mode 100644 index 00000000..dfff85ac --- /dev/null +++ b/test/handler/test_tflint_handler.vader @@ -0,0 +1,73 @@ +Before: + runtime! ale_linters/terraform/tflint.vim + +After: + call ale#linter#Reset() + +Execute(The tflint handler should parse items correctly): + AssertEqual + \ [ + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 1, + \ 'col': 30, + \ 'end_lnum': 2, + \ 'end_col': 1, + \ 'text': 'A block definition must have block content delimited by "{" and "}", starting on the same line as the block header.', + \ 'code': 'Invalid block definition', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 2, + \ 'col': 3, + \ 'end_lnum': 2, + \ 'end_col': 6, + \ 'text': 'An argument named "ami" is not expected here.', + \ 'code': 'Unsupported argument', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 3, + \ 'col': 3, + \ 'end_lnum': 1, + \ 'end_col': 6, + \ 'text': 'An argument named "instance_type" is not expected here.', + \ 'code': 'Unsupported argument', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_db_instance.tf', + \ 'lnum': 12, + \ 'col': 11, + \ 'end_lnum': 12, + \ 'end_col': 21, + \ 'text': 'be warned, traveller', + \ 'code': 'aws_db_instance_readable_password', + \ 'type': 'W', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_elasticache_cluster.tf', + \ 'lnum': 9, + \ 'col': 29, + \ 'end_lnum': 9, + \ 'end_col': 29, + \ 'text': 'error message', + \ 'code': 'aws_elasticache_cluster_invalid_type', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 5, + \ 'col': 15, + \ 'end_lnum': 5, + \ 'end_col': 25, + \ 'text': 'just so ya know', + \ 'code': 'aws_instance_not_specified_iam_profile', + \ 'type': 'I', + \ }, + \ ], + \ ale_linters#terraform#tflint#Handle(123, [ + \ '{"issues":[{"rule":{"name":"aws_db_instance_readable_password","severity":"WARNING","link":"https://github.com/wata727/tflint/blob/master/docs/aws_db_instance_readable_password.md"},"message":"be warned, traveller","range":{"filename":"github.com/wata727/example-module/aws_db_instance.tf","start":{"line":12,"column":11},"end":{"line":12,"column":21},"callers":[]}},{"rule":{"name":"aws_elasticache_cluster_invalid_type","severity":"ERROR","link":"https://github.com/wata727/tflint/blob/master/docs/aws_elasticache_cluster_invalid_type.md"},"message":"error message","range":{"filename":"github.com/wata727/example-module/aws_elasticache_cluster.tf","start":{"line":9,"column":29},"end":{"line":9,"column":29},"callers":[]}},{"rule":{"name":"aws_instance_not_specified_iam_profile","severity":"NOTICE","link":"https://github.com/wata727/tflint/blob/master/docs/aws_instance_not_specified_iam_profile.md"},"message":"just so ya know","range":{"filename":"github.com/wata727/example-module/aws_instance.tf","start":{"line":5,"column":15},"end":{"line":5,"column":25},"callers":[]}}],"errors":[{"message":"github.com/wata727/example-module/aws_instance.tf:1,30-2,1: Invalid block definition; A block definition must have block content delimited by \"{\" and \"}\", starting on the same line as the block header."},{"message":"github.com/wata727/example-module/aws_instance.tf:2,3-6: Unsupported argument; An argument named \"ami\" is not expected here."},{"message":"github.com/wata727/example-module/aws_instance.tf:3,3-16: Unsupported argument; An argument named \"instance_type\" is not expected here."}]}' + \ ]) diff --git a/test/handler/test_tfsec_handler.vader b/test/handler/test_tfsec_handler.vader new file mode 100644 index 00000000..f6566986 --- /dev/null +++ b/test/handler/test_tfsec_handler.vader @@ -0,0 +1,52 @@ +Before: + runtime ale_linters/terraform/tfsec.vim + +After: + call ale#linter#Reset() + +Execute(The tfsec handler should handle empty output): + AssertEqual + \ [], + \ ale_linters#terraform#tfsec#Handle(bufnr(''), ['{"results": null}']) + +Execute(The tfsec handler should parse results correctly): + AssertEqual + \ [ + \ { + \ 'filename': '/test/main.tf', + \ 'lnum': 10, + \ 'end_lnum': 12, + \ 'text': "IAM policy document uses sensitive action 'iam:PassRole' on wildcarded resource '*'", + \ 'code': 'aws-iam-no-policy-wildcards', + \ 'type': 'W', + \ }, + \], + \ ale_linters#terraform#tfsec#Handle(bufnr(''), json_encode( + \ { + \ "results": [ + \ { + \ "rule_id": "AVD-AWS-0057", + \ "long_id": "aws-iam-no-policy-wildcards", + \ "rule_description": "IAM policy should avoid use of wildcards and instead apply the principle of least privilege", + \ "rule_provider": "aws", + \ "rule_service": "iam", + \ "impact": "Overly permissive policies may grant access to sensitive resources", + \ "resolution": "Specify the exact permissions required, and to which resources they should apply instead of using wildcards.", + \ "links": [ + \ "https://aquasecurity.github.io/tfsec/v1.28.0/checks/aws/iam/no-policy-wildcards/", + \ "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document" + \ ], + \ "description": "IAM policy document uses sensitive action 'iam:PassRole' on wildcarded resource '*'", + \ "severity": "HIGH", + \ "warning": v:false, + \ "status": 0, + \ "resource": "data.aws_iam_policy_document.default", + \ "location": { + \ "filename": "/test/main.tf", + \ "start_line": 10, + \ "end_line": 12 + \ } + \ } + \ ] + \ } + \)) diff --git a/test/handler/test_thrift_handler.vader b/test/handler/test_thrift_handler.vader new file mode 100644 index 00000000..c87986da --- /dev/null +++ b/test/handler/test_thrift_handler.vader @@ -0,0 +1,63 @@ +Before: + runtime ale_linters/thrift/thrift.vim + +After: + call ale#linter#Reset() + +Execute(The thrift handler should handle basic warnings and errors): + AssertEqual + \ [ + \ { + \ 'lnum': 17, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'The "byte" type is a compatibility alias for "i8". Use i8" to emphasize the signedness of this type.', + \ }, + \ { + \ 'lnum': 20, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'Could not find include file include.thrift', + \ }, + \ { + \ 'lnum': 83, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Enum FOO is already defined!', + \ }, + \ ], + \ ale_linters#thrift#thrift#Handle(1, [ + \ '[WARNING:/path/filename.thrift:17] The "byte" type is a compatibility alias for "i8". Use i8" to emphasize the signedness of this type.', + \ '[WARNING:/path/filename.thrift:20] Could not find include file include.thrift', + \ '[FAILURE:/path/filename.thrift:83] Enum FOO is already defined!', + \ ]) + +Execute(The thrift handler should handle multiline errors): + AssertEqual + \ [ + \ { + \ 'lnum': 75, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'This integer is too big: "11111111114213213453243"', + \ }, + \ { + \ 'lnum': 76, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Implicit field keys are deprecated and not allowed with -strict', + \ }, + \ { + \ 'lnum': 77, + \ 'col': 0, + \ 'type': 'E', + \ 'text': "Unknown error (last token was ';')", + \ }, + \ ], + \ ale_linters#thrift#thrift#Handle(1, [ + \ "[ERROR:/path/filename.thrift:75] (last token was '11111111114213213453243')", + \ 'This integer is too big: "11111111114213213453243"', + \ "[ERROR:/path/filename.thrift:76] (last token was ';')", + \ 'Implicit field keys are deprecated and not allowed with -strict', + \ "[ERROR:/path/filename.thrift:77] (last token was ';')", + \ ]) diff --git a/test/handler/test_thriftcheck_handler.vader b/test/handler/test_thriftcheck_handler.vader new file mode 100644 index 00000000..33811f77 --- /dev/null +++ b/test/handler/test_thriftcheck_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/thrift/thriftcheck.vim + +After: + call ale#linter#Reset() + +Execute(The thriftcheck handler should handle basic warnings and errors): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': '"py" namespace must match "^idl\\."', + \ 'code': 'namespace.pattern', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'type': 'W', + \ 'text': '64-bit integer constant -2147483649 may not work in all languages', + \ 'code': 'int.64bit', + \ }, + \ ], + \ ale_linters#thrift#thriftcheck#Handle(1, [ + \ 'file.thrift:1:1: error: "py" namespace must match "^idl\\." (namespace.pattern)', + \ 'file.thrift:3:5: warning: 64-bit integer constant -2147483649 may not work in all languages (int.64bit)', + \ ]) diff --git a/test/handler/test_tlint_handler.vader b/test/handler/test_tlint_handler.vader new file mode 100644 index 00000000..e146346c --- /dev/null +++ b/test/handler/test_tlint_handler.vader @@ -0,0 +1,34 @@ +Before: + runtime ale_linters/php/tlint.vim + +After: + call ale#linter#Reset() + +Execute(The tlint handler should calculate line numbers): + AssertEqual + \ [ + \ { + \ 'lnum': '5', + \ 'col': 0, + \ 'sub_type': + \ 'style', + \ 'type': 'W', + \ 'text': ['! There should be no unused imports.', 'There should be no unused imports.', '', '', '', '', '', '', '', ''] + \ }, + \ { + \ 'lnum': '15', + \ 'col': 0, + \ 'sub_type': + \ 'style', + \ 'type': 'W', + \ 'text': ['! There should be no method visibility in test methods.', 'There should be no method visibility in test methods.', '', '', '', '', '', '', '', ''] + \ }, + \ ], + \ ale_linters#php#tlint#Handle(347, [ + \ "Lints for /Users/jose/Code/Tighten/tester/tests/Unit/ExampleTest.php", + \ "============", + \ "! There should be no unused imports.", + \ "5 : `use Illuminate\Foundation\Testing\RefreshDatabase;`", + \ "! There should be no method visibility in test methods.", + \ "15 : ` public function testBasicTest()`", + \ ]) diff --git a/test/handler/test_tslint_handler.vader b/test/handler/test_tslint_handler.vader new file mode 100644 index 00000000..3653e776 --- /dev/null +++ b/test/handler/test_tslint_handler.vader @@ -0,0 +1,315 @@ +Before: + Save g:ale_typescript_tslint_ignore_empty_files + + unlet! g:ale_typescript_tslint_ignore_empty_files + unlet! b:ale_typescript_tslint_ignore_empty_files + + runtime ale_linters/typescript/tslint.vim + + call ale#test#SetDirectory('/testplugin/test/handler') + +After: + Restore + + unlet! b:ale_typescript_tslint_ignore_empty_files + unlet! b:relative_to_root + unlet! b:tempname_suffix + unlet! b:relative_tempname + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The tslint handler should parse lines correctly): + call ale#test#SetFilename('app/test.ts') + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 15, + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.ts'), + \ 'end_lnum': 1, + \ 'type': 'E', + \ 'end_col': 15, + \ 'text': 'Missing semicolon', + \ 'code': 'semicolon', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 8, + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.ts'), + \ 'end_lnum': 3, + \ 'type': 'W', + \ 'end_col': 12, + \ 'text': 'Something else', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 8, + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/something-else.ts'), + \ 'end_lnum': 3, + \ 'type': 'W', + \ 'end_col': 12, + \ 'text': 'Something else', + \ 'code': 'something', + \ }, + \ { + \ 'lnum': 31, + \ 'col': 9, + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.ts'), + \ 'end_lnum': 31, + \ 'type': 'E', + \ 'end_col': 20, + \ 'text': 'Calls to console.log are not allowed.', + \ 'code': 'no-console', + \ }, + \ ] , + \ ale_linters#typescript#tslint#Handle(bufnr(''), [json_encode([ + \ { + \ 'endPosition': { + \ 'character': 14, + \ 'line': 0, + \ 'position': 1000 + \ }, + \ 'failure': 'Missing semicolon', + \ 'fix': { + \ 'innerLength': 0, + \ 'innerStart': 14, + \ 'innerText': ';' + \ }, + \ 'name': 'test.ts', + \ 'ruleName': 'semicolon', + \ 'ruleSeverity': 'ERROR', + \ 'startPosition': { + \ 'character': 14, + \ 'line': 0, + \ 'position': 1000 + \ } + \ }, + \ { + \ 'endPosition': { + \ 'character': 11, + \ 'line': 2, + \ 'position': 1000 + \ }, + \ 'failure': 'Something else', + \ 'fix': { + \ 'innerLength': 0, + \ 'innerStart': 14, + \ 'innerText': ';' + \ }, + \ 'name': 'test.ts', + \ 'ruleSeverity': 'WARNING', + \ 'startPosition': { + \ 'character': 7, + \ 'line': 1, + \ 'position': 1000 + \ } + \ }, + \ { + \ 'endPosition': { + \ 'character': 11, + \ 'line': 2, + \ 'position': 22 + \ }, + \ 'failure': 'Something else', + \ 'fix': { + \ 'innerLength': 0, + \ 'innerStart': 14, + \ 'innerText': ';' + \ }, + \ 'name': 'something-else.ts', + \ 'ruleName': 'something', + \ 'ruleSeverity': 'WARNING', + \ 'startPosition': { + \ 'character': 7, + \ 'line': 1, + \ 'position': 14 + \ } + \ }, + \ { + \ "endPosition": { + \ "character": 19, + \ "line": 30, + \ "position": 14590 + \ }, + \ "failure": "Calls to console.log are not allowed.", + \ 'name': 'test.ts', + \ "ruleName": "no-console", + \ "startPosition": { + \ "character": 8, + \ "line": 30, + \ "position": 14579 + \ } + \ }, + \])]) + +Execute(The tslint handler should handle empty output): + AssertEqual + \ [], + \ ale_linters#typescript#tslint#Handle(bufnr(''), []) + +Execute(The tslint handler report errors for empty files by default): + call ale#test#SetFilename('app/test.ts') + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.ts'), + \ 'end_lnum': 2, + \ 'type': 'E', + \ 'end_col': 1, + \ 'text': 'Consecutive blank lines are forbidden', + \ 'code': 'no-consecutive-blank-lines', + \ }, + \ ], + \ ale_linters#typescript#tslint#Handle(bufnr(''), [json_encode([{ + \ 'endPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ }, + \ 'failure': 'Consecutive blank lines are forbidden', + \ 'fix': [{ + \ 'innerStart': 0, + \ 'innerLength': 1, + \ 'innerText': '' + \ }], + \ 'name': 'test.ts', + \ 'ruleName': 'no-consecutive-blank-lines', + \ 'ruleSeverity': 'ERROR', + \ 'startPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ } + \ }])]) + +Execute(The tslint handler should not report errors for empty files when the ignore option is on): + let b:ale_typescript_tslint_ignore_empty_files = 1 + call ale#test#SetFilename('app/test.ts') + + AssertEqual + \ [ + \ ], + \ ale_linters#typescript#tslint#Handle(bufnr(''), [json_encode([{ + \ 'endPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ }, + \ 'failure': 'Consecutive blank lines are forbidden', + \ 'fix': [{ + \ 'innerStart': 0, + \ 'innerLength': 1, + \ 'innerText': '' + \ }], + \ 'name': 'test.ts', + \ 'ruleName': 'no-consecutive-blank-lines', + \ 'ruleSeverity': 'ERROR', + \ 'startPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ } + \ }])]) + +Given typescript(A file with extra blank lines): + const x = 3 + + + const y = 4 + +Execute(The tslint handler should report errors when the ignore option is on, but the file is not empty): + let b:ale_typescript_tslint_ignore_empty_files = 1 + call ale#test#SetFilename('app/test.ts') + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.ts'), + \ 'end_lnum': 2, + \ 'type': 'E', + \ 'end_col': 1, + \ 'text': 'Consecutive blank lines are forbidden', + \ 'code': 'no-consecutive-blank-lines', + \ }, + \ ], + \ ale_linters#typescript#tslint#Handle(bufnr(''), [json_encode([{ + \ 'endPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ }, + \ 'failure': 'Consecutive blank lines are forbidden', + \ 'fix': [{ + \ 'innerStart': 0, + \ 'innerLength': 1, + \ 'innerText': '' + \ }], + \ 'name': 'test.ts', + \ 'ruleName': 'no-consecutive-blank-lines', + \ 'ruleSeverity': 'ERROR', + \ 'startPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ } + \ }])]) + +Execute(The tslint handler should not report no-implicit-dependencies errors): + call ale#test#SetFilename('app/test.ts') + + AssertEqual + \ [ + \ ], + \ ale_linters#typescript#tslint#Handle(bufnr(''), [json_encode([{ + \ 'endPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ }, + \ 'failure': 'this is ignored', + \ 'name': 'test.ts', + \ 'ruleName': 'no-implicit-dependencies', + \ 'ruleSeverity': 'ERROR', + \ 'startPosition': { + \ 'character': 0, + \ 'line': 1, + \ 'position': 1 + \ }, + \ }])]) + +Execute(The tslint handler should set filename keys for temporary files): + " The temporary filename below is hacked into being a relative path so we can + " test that we resolve the temporary filename first. + let b:relative_to_root = substitute(expand('%:p'), '\v[^/\\]*([/\\])[^/\\]*', '../', 'g') + let b:tempname_suffix = substitute(tempname(), '^\v([A-Z]:)?[/\\]', '', '') + let b:relative_tempname = substitute(b:relative_to_root . b:tempname_suffix, '\\', '/', 'g') + + AssertEqual + \ [ + \ {'lnum': 47, 'col': 1, 'code': 'curly', 'end_lnum': 47, 'type': 'E', 'end_col': 26, 'text': 'if statements must be braced'}, + \ ], + \ ale_linters#typescript#tslint#Handle(bufnr(''), [json_encode([ + \ { + \ 'endPosition': { + \ 'character':25, + \ 'line':46, + \ 'position':1383, + \ }, + \ 'failure': 'if statements must be braced', + \ 'name': b:relative_tempname, + \ 'ruleName': 'curly', + \ 'ruleSeverity':'ERROR', + \ 'startPosition': { + \ 'character':0, + \ 'line':46, + \ 'position':1358, + \ } + \ }, + \ ])]) diff --git a/test/handler/test_typecheck_handler.vader b/test/handler/test_typecheck_handler.vader new file mode 100644 index 00000000..fda55d68 --- /dev/null +++ b/test/handler/test_typecheck_handler.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/typescript/typecheck.vim + +After: + call ale#linter#Reset() + +Execute(The typecheck handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 16, + \ 'col': 7, + \ 'text': "Type 'A' is not assignable to type 'B'", + \ }, + \ { + \ 'lnum': 7, + \ 'col': 41, + \ 'text': "Property 'a' does not exist on type 'A'", + \ }, + \ ], + \ ale_linters#typescript#typecheck#Handle(347, [ + \ "somets.ts[16, 7]: Type 'A' is not assignable to type 'B'", + \ "somets.ts[7, 41]: Property 'a' does not exist on type 'A'", + \ ]) diff --git a/test/handler/test_unimport_handler.vader b/test/handler/test_unimport_handler.vader new file mode 100644 index 00000000..5acdcbb9 --- /dev/null +++ b/test/handler/test_unimport_handler.vader @@ -0,0 +1,18 @@ +Before: + runtime ale_linters/python/unimport.vim + +After: + call ale#linter#Reset() + +Execute(The unimport handler should handle import warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 9, + \ 'type': 'W', + \ 'text': 'unused: urllib.parse', + \ }, + \ ], + \ ale_linters#python#unimport#Handle(1, [ + \ 'urllib.parse at path/to/file.py:9', + \ ]) diff --git a/test/handler/test_v_handler.vader b/test/handler/test_v_handler.vader new file mode 100644 index 00000000..4d6e3d9b --- /dev/null +++ b/test/handler/test_v_handler.vader @@ -0,0 +1,54 @@ +Before: + runtime ale_linters/v/v.vim + +After: + call ale#linter#Reset() + +Execute (The v handler should correctly parse error messages): + AssertEqual + \ [{ + \ 'lnum': 4, + \ 'col': 3, + \ 'filename': ale#path#GetAbsPath(expand('%:p:h'), 'const ants.v'), + \ 'type': 'W', + \ 'end_col': 14, + \ 'text': 'const names cannot contain uppercase letters, use snake_case instead' + \ }, + \ { + \ 'lnum': 4, + \ 'col': 8, + \ 'filename': ale#path#GetAbsPath(expand('%:p:h'), 'main.v'), + \ 'type': 'W', + \ 'end_col': 10, + \ 'text': 'module "os" is imported but never used' + \ }, + \ { + \ 'lnum': 20, + \ 'col': 10, + \ 'filename': ale#path#GetAbsPath(expand('%:p:h'), 'main.v'), + \ 'type': 'E', + \ 'end_col': 18, + \ 'text': 'undefined ident: `win_widt`' + \ }], + \ ale_linters#v#v#Handler('', [ + \ './const ants.v:4:3: warning: const names cannot contain uppercase letters, use snake_case instead', + \ ' 2 |', + \ ' 3 | const (', + \ ' 4 | BUTTON_TEXT = "OK"', + \ ' | ~~~~~~~~~~~', + \ ' 5 | )', + \ './main.v:4:8: warning: module "os" is imported but never used', + \ ' 2 |', + \ ' 3 | import ui', + \ ' 4 | import os', + \ ' | ~~', + \ ' 5 |', + \ ' 6 | const (', + \ './main.v:20:10: error: undefined ident: `win_widt`', + \ ' 18 | mut app := &App{}', + \ ' 19 | app.window = ui.window({', + \ ' 20 | width: win_widt', + \ ' | ~~~~~~~~', + \ ' 21 | height: win_height', + \ ' 22 | title: "Counter"', + \ ]) diff --git a/test/handler/test_vala_lint_handler.vader b/test/handler/test_vala_lint_handler.vader new file mode 100644 index 00000000..b8a4fbfa --- /dev/null +++ b/test/handler/test_vala_lint_handler.vader @@ -0,0 +1,54 @@ +Before: + runtime ale_linters/vala/vala_lint.vim + +After: + call ale#linter#Reset() + +Execute(The Vala-Lint handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 18, + \ 'col': 18, + \ 'text': 'Expected space before paren', + \ 'code': 'space-before-paren', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 64, + \ 'col': 37, + \ 'text': 'Expected space before paren', + \ 'code': 'space-before-paren', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 73, + \ 'col': 37, + \ 'text': 'Expected space before paren', + \ 'code': 'space-before-paren', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#vala#vala_lint#Handle(bufnr(''), [ + \ 'Application.vala', + \ ' 18.18 error Expected space before paren space-before-paren', + \ ' 64.37 warn Expected space before paren space-before-paren', + \ ' 73.37 error Expected space before paren space-before-paren', + \ ]) + +Execute(The Vala-Lint handler should ignore unknown error types): + AssertEqual + \ [ + \ { + \ 'lnum': 73, + \ 'col': 37, + \ 'text': 'Expected space before paren', + \ 'code': 'space-before-paren', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#vala#vala_lint#Handle(bufnr(''), [ + \ 'Application.vala', + \ ' 18.18 test Expected space before paren space-before-paren', + \ ' 73.37 error Expected space before paren space-before-paren', + \ ]) diff --git a/test/handler/test_vale_handler.vader b/test/handler/test_vale_handler.vader new file mode 100644 index 00000000..37badb47 --- /dev/null +++ b/test/handler/test_vale_handler.vader @@ -0,0 +1,88 @@ +Execute(The vale handler should handle broken JSON): + AssertEqual + \ [], + \ ale#handlers#vale#Handle(bufnr(''), ["{asdf"]) + +Execute(The vale handler should handle am empty string response): + AssertEqual + \ [], + \ ale#handlers#vale#Handle(bufnr(''), []) + +Execute(The vale handler should handle an empty result): + AssertEqual + \ [], + \ ale#handlers#vale#Handle(bufnr(''), ["{}"]) + +Execute(The vale handler should handle a normal example): + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 195, + \ 'end_col': 201, + \ 'type': 'W', + \ 'text': "Consider removing 'usually'", + \ 'code': 'vale.Hedging', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 1, + \ 'end_col': 27, + \ 'type': 'E', + \ 'text': "'Documentation' is repeated!", + \ 'code': 'vale.Repetition', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 1, + \ 'end_col': 27, + \ 'type': 'I', + \ 'text': "'Documentation' is repeated!", + \ 'code': 'vale.Repetition', + \ }, + \ ], + \ ale#handlers#vale#Handle(bufnr(''), [ + \ '{', + \ ' "/home/languitar/src/autosuspend/README.md": [', + \ ' {', + \ ' "Check": "vale.Hedging",', + \ ' "Description": "",', + \ ' "Line": 5,', + \ ' "Link": "",', + \ " \"Message\": \"Consider removing 'usually'\",", + \ ' "Severity": "warning",', + \ ' "Span": [', + \ ' 195,', + \ ' 201', + \ ' ],', + \ ' "Hide": false', + \ ' },', + \ ' {', + \ ' "Check": "vale.Repetition",', + \ ' "Description": "",', + \ ' "Line": 7,', + \ ' "Link": "",', + \ " \"Message\": \"'Documentation' is repeated!\",", + \ ' "Severity": "error",', + \ ' "Span": [', + \ ' 1,', + \ ' 27', + \ ' ],', + \ ' "Hide": false', + \ ' },', + \ ' {', + \ ' "Check": "vale.Repetition",', + \ ' "Description": "",', + \ ' "Line": 7,', + \ ' "Link": "",', + \ " \"Message\": \"'Documentation' is repeated!\",", + \ ' "Severity": "suggestion",', + \ ' "Span": [', + \ ' 1,', + \ ' 27', + \ ' ],', + \ ' "Hide": false', + \ ' }', + \ ' ]', + \ '}', + \ ]) 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_verilator_handler.vader b/test/handler/test_verilator_handler.vader new file mode 100644 index 00000000..efcf6619 --- /dev/null +++ b/test/handler/test_verilator_handler.vader @@ -0,0 +1,49 @@ +Before: + runtime ale_linters/verilog/verilator.vim + +After: + call ale#linter#Reset() + +Execute (The verilator handler should parse legacy messages with only line numbers): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected IDENTIFIER', + \ 'filename': 'foo.v' + \ }, + \ { + \ 'lnum': 10, + \ 'type': 'W', + \ 'text': 'Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=).', + \ 'filename': 'bar.v' + \ }, + \ ], + \ ale_linters#verilog#verilator#Handle(bufnr(''), [ + \ '%Error: foo.v:3: syntax error, unexpected IDENTIFIER', + \ '%Warning-BLKSEQ: bar.v:10: Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=).', + \ ]) + +Execute (The verilator handler should parse new format messages with line and column numbers): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col' : 1, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected endmodule, expecting ;', + \ 'filename': 'bar.v' + \ }, + \ { + \ 'lnum': 4, + \ 'col' : 6, + \ 'type': 'W', + \ 'text': 'Signal is not used: r', + \ 'filename': 'foo.v' + \ }, + \ ], + \ ale_linters#verilog#verilator#Handle(bufnr(''), [ + \ '%Error: bar.v:3:1: syntax error, unexpected endmodule, expecting ;', + \ '%Warning-UNUSED: foo.v:4:6: Signal is not used: r', + \ ]) diff --git a/test/handler/test_vint_handler.vader b/test/handler/test_vint_handler.vader new file mode 100644 index 00000000..c542b4ea --- /dev/null +++ b/test/handler/test_vint_handler.vader @@ -0,0 +1,65 @@ +Before: + runtime ale_linters/vim/vint.vim + +After: + call ale#linter#Reset() + +Execute(The vint handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'filename': 'gcc.vim', + \ 'text': 'Use scriptencoding when multibyte char exists (see :help :script encoding)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 17, + \ 'filename': 'gcc.vim', + \ 'end_col': 18, + \ 'text': 'Use robust operators ''==#'' or ''==?'' instead of ''=='' (see Google VimScript Style Guide (Matching))', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 8, + \ 'filename': 'gcc.vim', + \ 'end_col': 15, + \ 'text': 'Make the scope explicit like ''l:filename'' (see Anti-pattern of vimrc (Scope of identifier))', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 8, + \ 'filename': 'gcc.vim', + \ 'end_col': 15, + \ 'text': 'Undefined variable: filename (see :help E738)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 11, + \ 'filename': 'gcc.vim', + \ 'end_col': 16, + \ 'text': 'E128: Function name must start with a capital or contain a colon: foobar (see ynkdir/vim-vimlparser)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 9, + \ 'col': 12, + \ 'filename': 'gcc.vim', + \ 'end_col': 13, + \ 'text': 'Use robust operators ''=~#'' or ''=~?'' instead of ''=~'' (see Google VimScript Style Guide (Matching))', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#vim#vint#Handle(bufnr(''), [ + \ 'gcc.vim:1:1: warning: Use scriptencoding when multibyte char exists (see :help :script encoding)', + \ 'gcc.vim:3:17: warning: Use robust operators `==#` or `==?` instead of `==` (see Google VimScript Style Guide (Matching))', + \ 'gcc.vim:3:8: style_problem: Make the scope explicit like `l:filename` (see Anti-pattern of vimrc (Scope of identifier))', + \ 'gcc.vim:7:8: warning: Undefined variable: filename (see :help E738)', + \ 'gcc.vim:8:11: error: E128: Function name must start with a capital or contain a colon: foobar (see ynkdir/vim-vimlparser)', + \ 'gcc.vim:9:12: warning: Use robust operators `=~#` or `=~?` instead of `=~` (see Google VimScript Style Guide (Matching))', + \ ]) diff --git a/test/handler/test_vlog_handler.vader b/test/handler/test_vlog_handler.vader new file mode 100644 index 00000000..7262f63d --- /dev/null +++ b/test/handler/test_vlog_handler.vader @@ -0,0 +1,47 @@ +Before: + runtime ale_linters/verilog/vlog.vim + +After: + call ale#linter#Reset() + +Execute(The vlog handler should parse old-style lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'type': 'W', + \ 'text': '(vlog-2623) Undefined variable: C.', + \ 'filename': 'add.v' + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.', + \ 'filename': 'file.v' + \ }, + \ ], + \ 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.', + \ ]) + +Execute(The vlog handler should parse new-style lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'type': 'W', + \ 'text': '(vlog-2623) Undefined variable: C.', + \ 'filename': 'add.v' + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.', + \ 'filename': 'file.v' + \ }, + \ ], + \ ale_linters#verilog#vlog#Handle(bufnr(''), [ + \ '** Warning: (vlog-2623) add.v(7): Undefined variable: C.', + \ '** Error: (vlog-13294) file.v(1): 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 new file mode 100644 index 00000000..0756c3bb --- /dev/null +++ b/test/handler/test_vulture_handler.vader @@ -0,0 +1,90 @@ +Before: + runtime ale_linters/python/vulture.vim + + call ale#test#SetDirectory('/testplugin/test/handler') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Basic vulture check with relative path in result should be handled): + call ale#test#SetFilename('something_else.py') + AssertEqual + \ [ + \ { + \ 'lnum': 34, + \ 'text': 'unused variable ''foo'' (60% confidence)', + \ 'type': 'W', + \ 'filename': ale#path#Simplify(g:dir . '/something_else.py'), + \ }, + \ ], + \ ale_linters#python#vulture#Handle(bufnr(''), [ + \ './something_else.py:34: unused variable ''foo'' (60% confidence)', + \ ]) + +Execute(Basic vulture check with absolute path in result should be handled): + call ale#test#SetFilename('something_else.py') + AssertEqual + \ [ + \ { + \ 'lnum': 34, + \ 'text': 'unused variable ''foo'' (60% confidence)', + \ 'type': 'W', + \ 'filename': ale#path#Simplify(g:dir . '/something_else.py'), + \ }, + \ ], + \ ale_linters#python#vulture#Handle(bufnr(''), [ + \ ale#path#Simplify(g:dir . '/something_else.py') . ':34: unused variable ''foo'' (60% confidence)', + \ ]) + +Execute(Vulture check for two files should be handled): + call ale#test#SetFilename('something_else.py') + AssertEqual + \ [ + \ { + \ 'lnum': 34, + \ 'text': 'unused variable ''foo'' (60% confidence)', + \ 'type': 'W', + \ 'filename': ale#path#Simplify(g:dir . '/something_else.py'), + \ }, + \ { + \ 'lnum': 12, + \ 'text': 'unused variable ''bar'' (60% confidence)', + \ 'type': 'W', + \ 'filename': ale#path#Simplify(g:dir . '/second_one.py'), + \ }, + \ ], + \ ale_linters#python#vulture#Handle(bufnr(''), [ + \ './something_else.py:34: unused variable ''foo'' (60% confidence)', + \ './second_one.py:12: unused variable ''bar'' (60% confidence)', + \ ]) + + +Execute(Vulture exception should be handled): + call ale#test#SetFilename('something_else.py') + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ '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 ', + \ ' from .core import stuff', + \ 'BaddestException: Everything gone wrong', + \ ], "\n"), + \ } + \ ], + \ ale_linters#python#vulture#Handle(bufnr(''), [ + \ 'Traceback (most recent call last):', + \ ' File "/usr/lib/python3.6/site-packages/vulture/__init__.py", line 13, in ', + \ ' from .core import stuff', + \ 'BaddestException: Everything gone wrong', + \ ]) + +Execute(The vulture handler should handle empty output): + AssertEqual + \ [], + \ ale_linters#python#vulture#Handle(bufnr(''), []) diff --git a/test/handler/test_write_good_handler.vader b/test/handler/test_write_good_handler.vader new file mode 100644 index 00000000..8bf4b223 --- /dev/null +++ b/test/handler/test_write_good_handler.vader @@ -0,0 +1,37 @@ +Execute(The write-good handler should handle the example from the write-good README): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'end_col': 2, + \ 'type': 'W', + \ 'text': '"So" adds no meaning', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 12, + \ 'end_col': 21, + \ 'type': 'W', + \ 'text': '"was stolen" may be passive voice', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 2, + \ 'end_col': 2, + \ 'type': 'W', + \ 'text': '"foo bar" bla', + \ }, + \ ], + \ ale#handlers#writegood#Handle(bufnr(''), [ + \ 'In /tmp/vBYivbZ/6/test.md', + \ '=============', + \ 'So the cat was stolen.', + \ '^^', + \ '"So" adds no meaning on line 1 at column 0', + \ '-------------', + \ 'So the cat was stolen.', + \ ' ^^^^^^^^^^', + \ '"was stolen" may be passive voice on line 1 at column 11', + \ '"foo bar" bla on line 6 at column 1', + \ ]) diff --git a/test/handler/test_xmllint_handler.vader b/test/handler/test_xmllint_handler.vader new file mode 100644 index 00000000..a17d74a9 --- /dev/null +++ b/test/handler/test_xmllint_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/xml/xmllint.vim + +After: + call ale#linter#Reset() + +Execute(The xmllint handler should parse error messages correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 22, + \ 'type': 'W', + \ 'text': 'warning: Unsupported version ''dummy''' + \ }, + \ { + \ 'lnum': 34, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'parser error : Start tag expected, ''<'' not found' + \ } + \ ], + \ ale_linters#xml#xmllint#Handle(1, [ + \ 'path/to/file.xml:1: warning: Unsupported version ''dummy''', + \ '', + \ ' ^', + \ '-:34: parser error : Start tag expected, ''<'' not found', + \ 'blahblah>', + \ '^' + \ ]) 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/handler/test_yamllint_handler.vader b/test/handler/test_yamllint_handler.vader new file mode 100644 index 00000000..dd51119c --- /dev/null +++ b/test/handler/test_yamllint_handler.vader @@ -0,0 +1,59 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime! ale/handlers/yamllint.vim + +After: + Restore + + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() + +Execute(Problems should be parsed correctly for yamllint): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'missing document start "---"', + \ 'code': 'document-start', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'syntax error: expected the node content, but found ''''', + \ }, + \ ], + \ ale#handlers#yamllint#Handle(bufnr(''), [ + \ 'something.yaml:1:1: [warning] missing document start "---" (document-start)', + \ 'something.yml:2:1: [error] syntax error: expected the node content, but found ''''', + \ ]) + +Execute(The yamllint handler should respect ale_warn_about_trailing_whitespace): + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 18, + \ 'type': 'E', + \ 'text': 'trailing spaces', + \ 'code': 'trailing-spaces', + \ }, + \ ], + \ ale#handlers#yamllint#Handle(bufnr(''), [ + \ 'something.yml:5:18: [error] trailing spaces (trailing-spaces)', + \ ]) + + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ ], + \ ale#handlers#yamllint#Handle(bufnr(''), [ + \ 'something.yml:5:18: [error] trailing spaces (trailing-spaces)', + \ ]) diff --git a/test/handler/test_yosys_handler.vader b/test/handler/test_yosys_handler.vader new file mode 100644 index 00000000..a55d0b5b --- /dev/null +++ b/test/handler/test_yosys_handler.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/verilog/yosys.vim + +After: + call ale#linter#Reset() + +Execute(The yosys handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected TOK_ID', + \ 'filename': 'file.v' + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': 'internal error', + \ }, + \ ], + \ ale_linters#verilog#yosys#Handle(bufnr(''), [ + \ '1. Executing Verilog-2005 frontend: file.v', + \ 'ERROR: internal error', + \ 'file.v:3: ERROR: syntax error, unexpected TOK_ID', + \ ]) + diff --git a/test/handler/test_yq_handler.vader b/test/handler/test_yq_handler.vader new file mode 100644 index 00000000..861bf9e4 --- /dev/null +++ b/test/handler/test_yq_handler.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/yaml/yq.vim + +After: + call ale#linter#Reset() + +Execute (Should parse error correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'text': "did not find expected ',' or ']'", + \ } + \ ], + \ ale_linters#yaml#yq#Handle(bufnr(''), [ + \ "Error: bad file '-': yaml: line 2: did not find expected ',' or ']'" + \ ]) + + diff --git a/test/handler/test_zeek_handler.vader b/test/handler/test_zeek_handler.vader new file mode 100644 index 00000000..93b4687e --- /dev/null +++ b/test/handler/test_zeek_handler.vader @@ -0,0 +1,31 @@ +Before: + runtime ale_linters/zeek/zeek.vim + +After: + call ale#linter#Reset() + +Execute(The zeek handler should parse errors correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'text': 'unknown identifier bar, at or near "bar"', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#zeek#zeek#HandleErrors(bufnr(''), [ + \ 'error in /tmp/foo.zeek, line 2: unknown identifier bar, at or near "bar"' + \ ]) + +Execute(The zeek handler should parse warnings correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'text': 'expression value ignored (c$removal_hooks)', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#zeek#zeek#HandleErrors(bufnr(''), [ + \ 'warning in /tmp/bar.zeek, line 11: expression value ignored (c$removal_hooks)' + \ ]) diff --git a/test/handler/test_zlint_handler.vader b/test/handler/test_zlint_handler.vader new file mode 100644 index 00000000..c803e06a --- /dev/null +++ b/test/handler/test_zlint_handler.vader @@ -0,0 +1,44 @@ +Before: + runtime ale_linters/zig/zlint.vim + +After: + call ale#linter#Reset() + +Execute(The zlint handler should parse GitHub Actions format correctly): + " Create a temporary buffer + let buffer = bufnr('') + + " Define input lines + let input_lines = [ + \ '::warning file=test.zig,line=61,col=47,title=unsafe-undefined::`undefined` is missing a safety comment', + \ '', + \ '::error file=test2.zig,line=4,col=33,title=no-unresolved::Unresolved import to ''test3.zig''', + \ '', + \ ] + + " Define expected output + let expected_output = [ + \ { + \ 'filename': 'test.zig', + \ 'lnum': 61, + \ 'col': 47, + \ 'text': '`undefined` is missing a safety comment', + \ 'type': 'W', + \ 'code': 'unsafe-undefined' + \ }, + \ { + \ 'filename': 'test2.zig', + \ 'lnum': 4, + \ 'col': 33, + \ 'text': 'Unresolved import to ''test3.zig''', + \ 'type': 'E', + \ 'code': 'no-unresolved' + \ }, + \ ] + + " Get actual output + let actual_output = ale_linters#zig#zlint#Handle(buffer, input_lines) + + " Assert equality + AssertEqual expected_output, actual_output + diff --git a/test/jsonnet_files/testfile.jsonnet b/test/jsonnet_files/testfile.jsonnet new file mode 100644 index 00000000..fc8fb78a --- /dev/null +++ b/test/jsonnet_files/testfile.jsonnet @@ -0,0 +1 @@ +{ foo: bar } diff --git a/test/linter/test_ada_gcc.vader b/test/linter/test_ada_gcc.vader new file mode 100644 index 00000000..906b31a4 --- /dev/null +++ b/test/linter/test_ada_gcc.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('ada', 'gcc') + call ale#test#SetFilename('dummy.adb') + + function! GetOutputDir(command) abort + let l:split_command = split(a:command) + let l:index = index(l:split_command, '-o') + return l:split_command[l:index + 1] + endfunction + + let b:out_file = GetOutputDir(ale_linters#ada#gcc#GetCommand(bufnr(''))) + +After: + delfunction GetOutputDir + + unlet! b:out_file + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'gcc', + \ ale#Escape('gcc') . ' -x ada -c -gnatc' + \ . ' -o ' . b:out_file + \ . ' -I %s:h' + \ . ' -gnatwa -gnatq %t' + + let b:ale_ada_gcc_executable = 'foo' + + AssertLinter 'foo', + \ ale#Escape('foo') . ' -x ada -c -gnatc' + \ . ' -o ' . b:out_file + \ . ' -I %s:h' + \ . ' -gnatwa -gnatq %t' + +Execute(The options should be configurable): + let g:ale_ada_gcc_options = '--foo --bar' + + AssertLinter 'gcc', + \ ale#Escape('gcc') . ' -x ada -c -gnatc' + \ . ' -o ' . b:out_file + \ . ' -I %s:h' + \ . ' --foo --bar %t' diff --git a/test/linter/test_adals.vader b/test/linter/test_adals.vader new file mode 100644 index 00000000..5a04594e --- /dev/null +++ b/test/linter/test_adals.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('ada', 'adals') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Sets adals executable): + let g:ale_ada_adals_executable = '/path/to /Ada' + AssertLinter '/path/to /Ada', ale#Escape('/path/to /Ada') + +Execute(Sets adals encoding): + let b:ale_ada_adals_encoding = 'iso-8859-1' + AssertLSPConfig {'ada.defaultCharset': 'iso-8859-1', 'ada.projectFile': 'default.gpr'} + +Execute(Sets adals project): + let g:ale_ada_adals_project = 'myproject.gpr' + AssertLSPConfig {'ada.defaultCharset': 'utf-8', 'ada.projectFile': 'myproject.gpr'} diff --git a/test/linter/test_alex.vader b/test/linter/test_alex.vader new file mode 100644 index 00000000..08a0ee10 --- /dev/null +++ b/test/linter/test_alex.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') . ' --stdin --text', + +Execute(Should use the node_modules/.bin executable, if available): + call ale#test#SetFilename('../test-files/alex/node-modules/test_file.tex') + + AssertLinter ale#path#Simplify(g:dir . '/../test-files/alex/node-modules/node_modules/.bin/alex'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/alex/node-modules/node_modules/.bin/alex')) + \ . ' --stdin --text', + +Execute(Should use the node_modules/alex executable, if available): + call ale#test#SetFilename('../test-files/alex/node-modules-2/test_file.tex') + + AssertLinter ale#path#Simplify(g:dir . '/../test-files/alex/node-modules-2/node_modules/alex/cli.js'), + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/alex/node-modules-2/node_modules/alex/cli.js')) + \ . ' --stdin --text', + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('../test-files/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') . ' --stdin --text' diff --git a/test/linter/test_ameba.vader b/test/linter/test_ameba.vader new file mode 100644 index 00000000..7746b44f --- /dev/null +++ b/test/linter/test_ameba.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/linter/test_angular.vader b/test/linter/test_angular.vader new file mode 100644 index 00000000..2e407a00 --- /dev/null +++ b/test/linter/test_angular.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpLinterTest('html', 'angular') + let g:paths = {} + +After: + call ale#assert#TearDownLinterTest() + unlet g:paths + +Execute(The Angular LSP connection shouldn't be created outside of Angular projects): + AssertLSPLanguage 'html' + AssertLSPConfig {} + AssertLSPProject '' + AssertLinterNotExecuted + +Execute(The default command for Angular should be correct): + call ale#test#SetFilename('../test-files/angular/test.html') + let g:paths = { + \ 'ngserver': ale#test#GetFilename('../test-files/angular/node_modules/@angular/language-server/bin/ngserver'), + \ 'service': ale#test#GetFilename('../test-files/angular/node_modules/@angular/language-service'), + \ 'typescript': ale#test#GetFilename('../test-files/angular/node_modules/typescript'), + \} + + AssertLSPLanguage 'html' + AssertLSPProject ale#test#GetFilename('../test-files/angular') + AssertLinter 'node', ale#Escape('node') . ' ' . ale#Escape(g:paths.ngserver) + \ . ' --ngProbeLocations ' . ale#Escape(g:paths.service) + \ . ' --tsProbeLocations ' . ale#Escape(g:paths.typescript) + \ . ' --stdio' diff --git a/test/linter/test_ansible_language_server.vader b/test/linter/test_ansible_language_server.vader new file mode 100644 index 00000000..33634274 --- /dev/null +++ b/test/linter/test_ansible_language_server.vader @@ -0,0 +1,18 @@ +Before: + call ale#assert#SetUpLinterTest('ansible', 'language_server') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The ansible language server command callback should return default string): + AssertLinter 'ansible-language-server', ale#Escape('ansible-language-server') . ' --stdio' + +Execute(The ansible language server executable should be configurable): + let g:ale_ansible_language_server_executable = '~/.local/bin/als' + + AssertLinter '~/.local/bin/als' , ale#Escape('~/.local/bin/als') . ' --stdio' + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_ansible_language_server_config = {'ansible-language-server': {'ansible': {'completion': {'provideRedirectModules': v:false}}}} + AssertLSPConfig {'ansible-language-server': {'ansible': {'completion': {'provideRedirectModules': v:false}}}} diff --git a/test/linter/test_ansible_lint.vader b/test/linter/test_ansible_lint.vader new file mode 100644 index 00000000..3191fa7b --- /dev/null +++ b/test/linter/test_ansible_lint.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpLinterTest('ansible', 'ansible_lint') + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(The ansible_lint version <5.0.0 command callback should return default string): + GivenCommandOutput ['v4.1.2'] + AssertLinter 'ansible-lint', ale#Escape('ansible-lint') . ' --nocolor -p %t' + +Execute(The ansible_lint version >=5.0.0 command callback should return default string): + GivenCommandOutput ['v5.1.2'] + AssertLinter 'ansible-lint', ale#Escape('ansible-lint') . ' --nocolor --parseable-severity -x yaml %s' + +Execute(The ansible_lint version >=6.0.0 command callback should return default string): + GivenCommandOutput ['v6.0.2'] + AssertLinter 'ansible-lint', ale#Escape('ansible-lint') . ' --nocolor -f json -x yaml %s' + +Execute(The ansible_lint executable should be configurable): + let g:ale_ansible_ansible_lint_executable = '~/.local/bin/ansible-lint' + GivenCommandOutput ['v4.1.2'] + AssertLinter '~/.local/bin/ansible-lint', + \ ale#Escape('~/.local/bin/ansible-lint') . ' --nocolor -p %t' diff --git a/test/linter/test_asciidoc_textlint.vader b/test/linter/test_asciidoc_textlint.vader new file mode 100644 index 00000000..7e8b904f --- /dev/null +++ b/test/linter/test_asciidoc_textlint.vader @@ -0,0 +1,59 @@ +" 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: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 and options should be configurable): + let b:ale_textlint_executable = 'foobar' + let b:ale_textlint_options = '--something' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' --something -f json --stdin --stdin-filename %s' + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('../test-files/textlint/with_bin_path/foo.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_bin_path/node_modules/.bin/textlint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/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('../test-files/textlint/with_textlint_bin_path/foo.txt') + + if has('win32') + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape('node.exe') . ' ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + else + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + endif diff --git a/test/linter/test_asm_gcc.vader b/test/linter/test_asm_gcc.vader new file mode 100644 index 00000000..c7ca44cf --- /dev/null +++ b/test/linter/test_asm_gcc.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('asm', 'gcc') + call ale#test#SetFilename('test.cpp') + let b:command_tail = ' -x assembler' + \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') + \ . '-iquote %s:h' + \ . ' -Wall -' + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail + +Execute(The executable should be configurable): + let b:ale_asm_gcc_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . b:command_tail diff --git a/test/linter/test_avra_avra.vader b/test/linter/test_avra_avra.vader new file mode 100644 index 00000000..04722d68 --- /dev/null +++ b/test/linter/test_avra_avra.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('avra', 'avra') + + let b:command_tail = ' %t -o ' . g:ale#util#nul_file + let b:command_tail_opt = ' %t --max_errors 20 -o ' . g:ale#util#nul_file + +After: + unlet! b:command_tail + unlet! b:command_tail_opt + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'avra', ale#Escape('avra') . b:command_tail, + + let b:ale_avra_avra_executable = '~/avra' + + AssertLinter '~/avra', ale#Escape('~/avra') . b:command_tail + +Execute(The options should be configurable): + let b:ale_avra_avra_options = '--max_errors 20' + + AssertLinter 'avra', ale#Escape('avra') + \ . ' %t --max_errors 20 -o ' . g:ale#util#nul_file + +Execute(The options should be used in command): + let b:ale_avra_avra_options = '--max_errors 20' + + AssertLinter 'avra', ale#Escape('avra') . b:command_tail_opt diff --git a/test/linter/test_bandit.vader b/test/linter/test_bandit.vader new file mode 100644 index 00000000..803e3bf2 --- /dev/null +++ b/test/linter/test_bandit.vader @@ -0,0 +1,100 @@ +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('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' + +Execute(Setting executable to 'poetry' appends 'run bandit'): + let g:ale_python_bandit_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' + +Execute(Poetry is detected when python_bandit_auto_poetry is set): + let g:ale_python_bandit_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' + +Execute(uv is used when python_bandit_auto_uv is set): + let g:ale_python_bandit_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') + \ . ' run bandit' + \ . b:bandit_flags + \ . ' -' + +Execute(The bandit command callback should add .bandit by default): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/with_bandit/namespace/foo/bar.py') + + let b:config_path = ale#path#Simplify( + \ g:dir . '/../test-files/python/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 . '/../test-files/python/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/linter/test_bashate.vader b/test/linter/test_bashate.vader new file mode 100644 index 00000000..714cf690 --- /dev/null +++ b/test/linter/test_bashate.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('sh', 'bashate') + call ale#test#SetFilename('test.sh') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default bashate command should be correct): + AssertLinter 'bashate', ale#Escape('bashate') . ' %t' + +Execute(The bashate command should accept options): + let b:ale_sh_bashate_options = '-i E310 --max-line-length 100' + + AssertLinter 'bashate', + \ ale#Escape('bashate') . ' -i E310 --max-line-length 100 %t' diff --git a/test/linter/test_bib_bibclean.vader b/test/linter/test_bib_bibclean.vader new file mode 100644 index 00000000..fa6f7d33 --- /dev/null +++ b/test/linter/test_bib_bibclean.vader @@ -0,0 +1,24 @@ +Before: + call ale#assert#SetUpLinterTest('bib', 'bibclean') + + let g:ale_ruby_rubocop_executable = 'bibclean' + let g:ale_ruby_rubocop_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to bibclean): + AssertLinter 'bibclean', ale#Escape('bibclean') + \ . ' -file-position ' + +Execute(Should be able to set a custom executable): + let g:ale_bib_bibclean_executable = 'bin/bibclean' + + AssertLinter 'bin/bibclean' , ale#Escape('bin/bibclean') + \ . ' -file-position ' + +Execute(Should not include custom options): + let g:ale_bib_bibclean_options = '-no-prettryprint' + + AssertLinter 'bibclean' , ale#Escape('bibclean') + \ . ' -file-position ' diff --git a/test/linter/test_bicep_az_bicep.vader b/test/linter/test_bicep_az_bicep.vader new file mode 100644 index 00000000..b5dee48e --- /dev/null +++ b/test/linter/test_bicep_az_bicep.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('bicep', 'az_bicep') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + if has('win32') + AssertLinter 'az', ale#Escape('az') . ' bicep build --outfile NUL --file %s ' + else + AssertLinter 'az', ale#Escape('az') . ' bicep build --outfile /dev/null --file %s ' + endif + +Execute(The executable should be configurable): + let g:ale_bicep_az_bicep_executable = 'foobar' + + if has('win32') + AssertLinter 'foobar', ale#Escape('foobar') . ' bicep build --outfile NUL --file %s ' + else + AssertLinter 'foobar', ale#Escape('foobar') . ' bicep build --outfile /dev/null --file %s ' + endif diff --git a/test/linter/test_bicep_bicep.vader b/test/linter/test_bicep_bicep.vader new file mode 100644 index 00000000..a4057a72 --- /dev/null +++ b/test/linter/test_bicep_bicep.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('bicep', 'bicep') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + if has('win32') + AssertLinter 'bicep', ale#Escape('bicep') . ' build --outfile NUL %s' + else + AssertLinter 'bicep', ale#Escape('bicep') . ' build --outfile /dev/null %s' + endif + +Execute(The executable should be configurable): + let g:ale_bicep_bicep_executable = 'foobar' + + if has('win32') + AssertLinter 'foobar', ale#Escape('foobar') . ' build --outfile NUL %s' + else + AssertLinter 'foobar', ale#Escape('foobar') . ' build --outfile /dev/null %s' + endif diff --git a/test/linter/test_bingo.vader b/test/linter/test_bingo.vader new file mode 100644 index 00000000..9e719097 --- /dev/null +++ b/test/linter/test_bingo.vader @@ -0,0 +1,60 @@ +Before: + Save g:ale_go_go111module + + 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 + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The default bingo executable and options should be correct): + AssertLinter 'bingo', ale#Escape('bingo') . ' --mode stdio' + +Execute(The bingo executable and options should be configurable): + let b:ale_go_bingo_executable = 'boo' + let b:ale_go_bingo_options = '--mode stdio --trace' + + AssertLinter 'boo', ale#Escape('boo') . ' --mode stdio --trace' + +Execute(should support Go environment variables): + call ale#test#SetFilename('../test-files/go/go1/prj1/file.go') + let b:ale_go_go111module = 'on' + + AssertLinter 'bingo', + \ ale#Env('GO111MODULE', 'on') . ale#Escape('bingo') . ' --mode stdio' + +Execute(Should return directory for 'go.mod' if found in parent directory): + call ale#test#SetFilename('../test-files/go/test.go') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/go') + +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 + +Execute(Should ignore 'go.mod' and return '.git' dir if modules off): + call ale#test#SetFilename('../test-files/go/test.go') + + let b:ale_go_go111module = 'off' + let b:parent_dir = ale#path#Simplify(g:dir . '/..') + let b:git_dir = b:parent_dir . '/.git' + + if !isdirectory(b:git_dir) + call mkdir(b:git_dir) + endif + + AssertLSPProject b:parent_dir + + call delete(b:git_dir, 'd') + unlet! b:parent_dir + unlet! b:git_dir diff --git a/test/linter/test_biome.vader b/test/linter/test_biome.vader new file mode 100644 index 00000000..48736ec2 --- /dev/null +++ b/test/linter/test_biome.vader @@ -0,0 +1,49 @@ +Before: + Save g:ale_biome_options + Save g:ale_biome_lsp_project_root + + let g:ale_biome_options = '' + let g:ale_biome_lsp_project_root = '' + + call ale#assert#SetUpLinterTest('typescript', 'biome') + call ale#test#SetFilename('test.ts') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default biome command should be correct): + AssertLinter 'biome', ale#Escape('biome') . ' lsp-proxy' + +Execute(Uses the filetype as the language): + call ale#test#SetFilename('test.ts') + set filetype=typescript + AssertLSPLanguage 'typescript' + + call ale#test#SetFilename('test.tsx') + set filetype=typescriptreact + AssertLSPLanguage 'typescriptreact' + + call ale#test#SetFilename('test.js') + set filetype=javascript + AssertLSPLanguage 'javascript' + + call ale#test#SetFilename('test.jsx') + set filetype=javascriptreact + AssertLSPLanguage 'javascriptreact' + +Execute(Should find project root containing biome.json): + call ale#test#SetFilename('../test-files/biome/json/src/test.ts') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/biome/json') + +Execute(Should find project root containing biome.jsonc): + call ale#test#SetFilename('../test-files/biome/jsonc/src/test.ts') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/biome/jsonc') + +Execute(Should use user-specified project root): + let g:ale_biome_lsp_project_root = '/' + + call ale#test#SetFilename('../test-files/biome/jsonc/src/test.ts') + + AssertLSPProject '/' diff --git a/test/linter/test_bitbake.vader b/test/linter/test_bitbake.vader new file mode 100755 index 00000000..62d5fcea --- /dev/null +++ b/test/linter/test_bitbake.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('bitbake', 'oelint_adv') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'oelint-adv', ale#Escape('oelint-adv') . ' --quiet %s' + +Execute(The executable should be configurable): + let b:ale_bitbake_oelint_adv_executable = 'xyz' + + AssertLinter 'xyz', ale#Escape('xyz') . ' --quiet %s' diff --git a/test/linter/test_brakeman.vader b/test/linter/test_brakeman.vader new file mode 100644 index 00000000..d3bf1920 --- /dev/null +++ b/test/linter/test_brakeman.vader @@ -0,0 +1,37 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'brakeman') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The brakeman command callback should detect absence of a valid Rails app): + call ale#test#SetFilename('../test-files/ruby/not_a_rails_app/test.rb') + + AssertLinter 'brakeman', '' + +Execute(The brakeman command callback should find a valid Rails app root): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/db/test.rb') + + AssertLinter 'brakeman', ale#Escape('brakeman') + \ . ' -f json -q -p ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/ruby/valid_rails_app')) + +Execute(The brakeman command callback should include configured options): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/db/test.rb') + + let g:ale_ruby_brakeman_options = '--combobulate' + + AssertLinter 'brakeman', ale#Escape('brakeman') + \ . ' -f json -q --combobulate -p ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/ruby/valid_rails_app')) + +Execute(Setting bundle appends 'exec brakeman'): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/db/test.rb') + + let g:ale_ruby_brakeman_executable = 'bundle' + let g:ale_ruby_brakeman_options = '--combobulate' + + AssertLinter 'bundle', ale#Escape('bundle') + \ . ' exec brakeman' + \ . ' -f json -q --combobulate -p ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/ruby/valid_rails_app')) diff --git a/test/linter/test_buf_lint.vader b/test/linter/test_buf_lint.vader new file mode 100644 index 00000000..2f065adc --- /dev/null +++ b/test/linter/test_buf_lint.vader @@ -0,0 +1,32 @@ +Before: + call ale#assert#SetUpLinterTest('proto', 'buf_lint') + call ale#test#SetFilename('test.proto') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'buf', + \ ale#Escape('buf') + \ . ' lint' + \ . ' %s#include_package_files=true' + +Execute(The callback should include any additional config): + let b:ale_proto_buf_lint_executable = '/tmp/buf' + let b:ale_proto_buf_lint_config = '/tmp/buf.yaml' + + AssertLinter '/tmp/buf', + \ ale#Escape('/tmp/buf') + \ . ' lint' + \ . ' --config=' . ale#Escape('/tmp/buf.yaml') + \ . ' %s#include_package_files=true' + +Execute(The callback should include additional options): + let b:ale_proto_buf_lint_executable = '/tmp/buf' + let b:ale_proto_buf_lint_options = '--disable-symlinks' + + AssertLinter '/tmp/buf', + \ ale#Escape('/tmp/buf') + \ . ' lint' + \ . ' --disable-symlinks' + \ . ' %s#include_package_files=true' diff --git a/test/linter/test_bzl_buildifier.vader b/test/linter/test_bzl_buildifier.vader new file mode 100644 index 00000000..d2d39632 --- /dev/null +++ b/test/linter/test_bzl_buildifier.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('bzl', 'buildifier') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Should use default command when bazel_buildifier_options are not set): + call ale#test#SetDirectory('/testplugin/test/test-files/bzl/bazel-package') + call ale#test#SetFilename('BUILD.bazel') + + let g:ale_bazel_buildifier_executable = 'buildifier' + let g:ale_bazel_buildifier_options = '' + + AssertLinter 'buildifier', + \ ale#Escape('buildifier') . ' -mode check -lint warn -path %s' + + call ale#test#RestoreDirectory() + +Execute(Should use custom buildifier options when bazel_buildifier_options are set): + call ale#test#SetDirectory('/testplugin/test/test-files/bzl/bazel-package') + call ale#test#SetFilename('BUILD.bazel') + + let g:ale_bazel_buildifier_executable = 'buildifier' + let g:ale_bazel_buildifier_options = '-v' + + AssertLinter 'buildifier', + \ ale#Escape('buildifier') . ' -mode check -lint warn -path %s -v' + + call ale#test#RestoreDirectory() diff --git a/test/linter/test_c3_c3lsp.vader b/test/linter/test_c3_c3lsp.vader new file mode 100644 index 00000000..d942b123 --- /dev/null +++ b/test/linter/test_c3_c3lsp.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('c3', 'c3lsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default c3lsp settings should be correct): + AssertLinter 'c3lsp', ale#Escape('c3lsp') + AssertLSPConfig {} + +Execute(c3lsp should be configurable): + let b:ale_c3_c3lsp_executable = 'billy' + let b:ale_c3_c3lsp_init_options = {'x': 'y'} + + AssertLinter 'billy', ale#Escape('billy') + AssertLSPConfig {'x': 'y'} diff --git a/test/linter/test_c_cc.vader b/test/linter/test_c_cc.vader new file mode 100644 index 00000000..ce4bd162 --- /dev/null +++ b/test/linter/test_c_cc.vader @@ -0,0 +1,94 @@ +Before: + Save g:ale_c_parse_makefile + Save g:ale_history_enabled + + let g:ale_c_parse_makefile = 0 + let g:ale_history_enabled = 0 + + let g:get_cflags_return_value = '' + let g:executable_map = {} + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + function! ale#engine#IsExecutable(buffer, executable) abort + return has_key(g:executable_map, a:executable) + endfunction + + function! ale#c#GetCFlags(buffer, output) abort + return g:get_cflags_return_value + endfunction + + call ale#assert#SetUpLinterTest('c', 'cc') + + let b:command_tail = ' -S -x c' + \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -std=c11 -Wall -' + +After: + unlet! g:get_cflags_return_value + unlet! g:executable_map + unlet! b:command_tail + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + call ale#assert#TearDownLinterTest() + +Execute(clang should be used instead of gcc, if available): + let g:executable_map = {'clang': 1} + + AssertLinter 'clang', [ale#Escape('clang') . b:command_tail] + +Execute(The executable should be configurable): + AssertLinter 'gcc', [ale#Escape('gcc') . b:command_tail] + + let b:ale_c_cc_executable = 'foobar' + + AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] + +Execute(The -std flag should be replaced by parsed C flags): + let b:command_tail = substitute(b:command_tail, 'c11', 'c99 ', '') + let g:get_cflags_return_value = '-std=c99' + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail + +Execute(gcc should not use -x c-header with header files by default): + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/test.h') + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail + +Execute(clang should use -x c-header with header files by default): + let g:executable_map = {'clang': 1} + let b:command_tail = substitute(b:command_tail, '-x c', '-x c-header', '') + + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/test.h') + + AssertLinter 'clang', ale#Escape('clang') . b:command_tail + +Execute(gcc should use -x c-header with header files if configured to do so): + let b:ale_c_cc_use_header_lang_flag = 1 + let b:command_tail = substitute(b:command_tail, '-x c', '-x c-header', '') + + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/test.h') + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail + +Execute(clang should not use -x c-header with header files if configured to do so): + let g:executable_map = {'clang': 1} + let b:ale_c_cc_use_header_lang_flag = 0 + + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/test.h') + + AssertLinter 'clang', ale#Escape('clang') . b:command_tail + +Execute(The header file extensions should be configurable): + let g:executable_map = {'clang': 1} + let b:command_tail = substitute(b:command_tail, '-x c', '-x c-header', '') + + call ale#assert#SetUpLinterTest('c', 'cc') + let b:ale_c_cc_header_exts = ['json'] + call ale#test#SetFilename('../test-files/c/json_project/build/compile_commands.json') + + AssertLinter 'clang', ale#Escape('clang') . b:command_tail diff --git a/test/linter/test_c_ccls.vader b/test/linter/test_c_ccls.vader new file mode 100644 index 00000000..a4f575c6 --- /dev/null +++ b/test/linter/test_c_ccls.vader @@ -0,0 +1,69 @@ +" Author: Ye Jingchen , Ben Falconer +" Description: A language server for C + +Before: + call ale#assert#SetUpLinterTest('c', 'ccls') + + Save b:ale_c_build_dir_names + Save b:ale_c_ccls_executable + Save b:ale_c_ccls_init_options + +After: + call ale#assert#TearDownLinterTest() + +Execute(The project root should be detected correctly using compile_commands.json file): + call ale#test#SetFilename(tempname() . '/dummy.c') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_compile_commands_json/dummy.c') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_compile_commands_json') + +Execute(The project root should be detected correctly using .ccls file): + call ale#test#SetFilename(tempname() . '/dummy.c') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_ccls/dummy.c') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_ccls') + +Execute(The project root should be detected correctly using .ccls-root file): + call ale#test#SetFilename(tempname() . '/dummy.c') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_ccls-root/dummy.c') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_ccls-root') + +Execute(The executable should be configurable): + AssertLinter 'ccls', ale#Escape('ccls') + + let b:ale_c_ccls_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The initialization options should be configurable): + AssertLSPOptions {} + + let b:ale_c_ccls_init_options = { 'cacheDirectory': '/tmp/ccls' } + + AssertLSPOptions { 'cacheDirectory': '/tmp/ccls' } + +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('../test-files/ccls/with_ccls/dummy.c') + + AssertLSPOptions {} + + call ale#test#SetFilename('../test-files/ccls/with_compile_commands_json/dummy.c') + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/../test-files/ccls/with_compile_commands_json') } + + call ale#test#SetFilename('../test-files/ccls/with_build_dir/dummy.c') + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/../test-files/ccls/with_build_dir/unusual_build_dir_name') } diff --git a/test/linter/test_c_clang_tidy.vader b/test/linter/test_c_clang_tidy.vader new file mode 100644 index 00000000..2dfb3494 --- /dev/null +++ b/test/linter/test_c_clang_tidy.vader @@ -0,0 +1,71 @@ +Before: + Save g:ale_c_parse_makefile + let g:ale_c_parse_makefile = 0 + + call ale#assert#SetUpLinterTest('c', 'clangtidy') + call ale#test#SetFilename('test.c') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The clangtidy command default should be correct): + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') . ' %s' + +Execute(You should be able to set other checks for clang-tidy): + let b:ale_c_clangtidy_checks = ['-*', 'clang-analyzer-*'] + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('-*,clang-analyzer-*') . ' %s' + +Execute(You should be able to manually set compiler flags for clang-tidy): + let b:ale_c_clangtidy_checks = ['*'] + let b:ale_c_clangtidy_options = '-Wall' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') . ' -checks=' . ale#Escape('*') . ' %s -- -Wall' + +Execute(You should be able to manually set flags for clang-tidy): + let b:ale_c_clangtidy_extra_options = '-config=' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') . ' ' . ale#Escape('-config=') . ' %s' + +Execute(The build directory should be configurable): + let b:ale_c_clangtidy_checks = ['*'] + let b:ale_c_build_dir = '/foo/bar' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + +Execute(The build directory setting should override the options): + let b:ale_c_clangtidy_checks = ['*'] + let b:ale_c_build_dir = '/foo/bar' + let b:ale_c_clangtidy_options = '-Wall' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + +Execute(The build directory should be used for header files): + call ale#test#SetFilename('test.h') + + let b:ale_c_clangtidy_checks = ['*'] + let b:ale_c_build_dir = '/foo/bar' + let b:ale_c_clangtidy_options = '-Wall' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + +Execute(The executable should be configurable): + let b:ale_c_clangtidy_checks = ['*'] + let b:ale_c_clangtidy_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -checks=' . ale#Escape('*') . ' %s' diff --git a/test/linter/test_c_clangcheck.vader b/test/linter/test_c_clangcheck.vader new file mode 100644 index 00000000..60e1d80a --- /dev/null +++ b/test/linter/test_c_clangcheck.vader @@ -0,0 +1,37 @@ +# modified from test_cpp_cppcheck.vader + +Before: + call ale#assert#SetUpLinterTest('c', 'clangcheck') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'clang-check', + \ ale#Escape('clang-check') + \ . ' -analyze %s --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' + + let b:ale_c_clangcheck_executable = 'foobar' + + " The extra arguments in the command are used to prevent .plist files from + " being generated. + AssertLinter 'foobar', + \ ale#Escape('foobar') + \ . ' -analyze %s --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' + +Execute(The options should be configurable): + let b:ale_c_clangcheck_options = '--something' + + AssertLinter 'clang-check', + \ ale#Escape('clang-check') + \ . ' -analyze %s' + \ . ' --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' + \ . ' --something' + +Execute(The build directory should be used when set): + let b:ale_c_clangcheck_options = '--something' + let b:ale_c_build_dir = '/foo/bar' + + AssertLinter 'clang-check', + \ ale#Escape('clang-check') + \ . ' -analyze %s --something -p ' . ale#Escape('/foo/bar') diff --git a/test/linter/test_c_clangd.vader b/test/linter/test_c_clangd.vader new file mode 100644 index 00000000..b7a4e029 --- /dev/null +++ b/test/linter/test_c_clangd.vader @@ -0,0 +1,47 @@ +Before: + call ale#assert#SetUpLinterTest('c', 'clangd') + + Save b:ale_c_clangd_options + Save b:ale_c_build_dir + Save b:ale_c_build_dir_names + Save b:ale_c_parse_compile_commands + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'c' + +Execute(The default executable should be correct): + AssertLinter 'clangd', ale#Escape('clangd') + +Execute(The project root should be detected correctly): + call ale#test#SetFilename(tempname() . '/dummy.c') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/clangd/with_compile_commands/dummy.c') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/clangd/with_compile_commands') + +Execute(The executable should be configurable): + let g:ale_c_clangd_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The options should be configurable): + let b:ale_c_clangd_options = '-compile-commands-dir=foo' + + AssertLinter 'clangd', ale#Escape('clangd') . ' ' . b:ale_c_clangd_options + +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('../test-files/clangd/with_build_dir/dummy_src/dummy.c') + + let b:ale_c_clangd_options = '' + let b:ale_c_build_dir = '' + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + let b:ale_c_parse_compile_commands = 1 + + AssertLinter 'clangd', ale#Escape('clangd') + \ . ' -compile-commands-dir=' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/clangd/with_build_dir/unusual_build_dir_name')) diff --git a/test/linter/test_c_cppcheck.vader b/test/linter/test_c_cppcheck.vader new file mode 100644 index 00000000..0cc3a5d3 --- /dev/null +++ b/test/linter/test_c_cppcheck.vader @@ -0,0 +1,63 @@ +Before: + call ale#assert#SetUpLinterTest('c', 'cppcheck') + let b:command_tail = ' -q --language=c --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') . ' --enable=style -I' . ale#Escape(ale#path#Simplify(g:dir)) .' %t' + +After: + unlet! b:rel_file_path + unlet! b:command_tail + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'cppcheck', ale#Escape('cppcheck') . b:command_tail + + let b:ale_c_cppcheck_executable = 'foobar' + + AssertLinterCwd '' + AssertLinter 'foobar', ale#Escape('foobar') . b:command_tail + +Execute(cppcheck for C should detect compile_commands.json files): + let b:rel_file_path = '../test-files/cppcheck/one/foo.c' + call ale#test#SetFilename(b:rel_file_path) + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/cppcheck/one') + AssertLinter 'cppcheck', ale#Escape('cppcheck') + \ . ' -q --language=c' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' --project=' . ale#Escape('compile_commands.json') + \ . ' --file-filter=' . ale#Escape(ale#test#GetFilename(b:rel_file_path)) + \ . ' --enable=style %t' + +Execute(cppcheck for C should detect compile_commands.json files in build directories): + let b:rel_file_path = '../test-files/cppcheck/with_build_dir/foo.c' + call ale#test#SetFilename(b:rel_file_path) + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/cppcheck/with_build_dir') + AssertLinter 'cppcheck', ale#Escape('cppcheck') + \ . ' -q --language=c' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' --project=' . ale#Escape(ale#path#Simplify('build/compile_commands.json')) + \ . ' --file-filter=' . ale#Escape(ale#test#GetFilename(b:rel_file_path)) + \ . ' --enable=style %t' + +Execute(cppcheck for C should include file dir if compile_commands.json file is not found): + call ale#test#SetFilename('../test-files/cppcheck/foo.c') + + AssertLinter 'cppcheck', + \ ale#Escape('cppcheck') + \ . ' -q --language=c' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' --enable=style' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/cppcheck')) + \ . ' %t' + +Execute(cppcheck for C header should include file dir and not use compile_commands.json): + call ale#test#SetFilename('../test-files/cppcheck/one/foo.h') + + AssertLinter 'cppcheck', + \ ale#Escape('cppcheck') + \ . ' -q --language=c' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/cppcheck/one')) + \ . ' --suppress=unusedStructMember' + \ . ' --enable=style' + \ . ' %t' diff --git a/test/linter/test_c_cquery.vader b/test/linter/test_c_cquery.vader new file mode 100644 index 00000000..bca0dbbc --- /dev/null +++ b/test/linter/test_c_cquery.vader @@ -0,0 +1,37 @@ +Before: + call ale#assert#SetUpLinterTest('c', 'cquery') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The project root should be detected correctly using compile_commands.json file): + call ale#test#SetFilename(tempname() . '/dummy.c') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/cquery/dummy.c') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/cquery') + +Execute(The project root should be detected correctly using .cquery file): + call ale#test#SetFilename(tempname() . '/dummy.c') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/cquery/with_cquery/dummy.c') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/cquery/with_cquery') + +Execute(The executable should be configurable): + AssertLinter 'cquery', ale#Escape('cquery') + + let b:ale_c_cquery_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The cache directory should be configurable): + AssertLSPOptions {'cacheDirectory': expand('$HOME/.cache/cquery')} + + let b:ale_c_cquery_cache_directory = '/foo/bar' + + AssertLSPOptions {'cacheDirectory': '/foo/bar'} diff --git a/test/linter/test_c_flawfinder.vader b/test/linter/test_c_flawfinder.vader new file mode 100644 index 00000000..75626d7a --- /dev/null +++ b/test/linter/test_c_flawfinder.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('c', 'flawfinder') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default flawfinder command should be correct): + AssertLinter 'flawfinder', ale#Escape('flawfinder') . ' -CDQS --minlevel=1 %t' + +Execute(The minlevel of flawfinder should be configurable): + let b:ale_c_flawfinder_minlevel = 8 + + AssertLinter 'flawfinder', ale#Escape('flawfinder') . ' -CDQS --minlevel=8 %t' + +Execute(Additional flawfinder options should be configurable): + let b:ale_c_flawfinder_executable = 'foo' + let b:ale_c_flawfinder_options = '--foobar' + + AssertLinter 'foo', ale#Escape('foo') . ' -CDQS --foobar --minlevel=1 %t' diff --git a/test/linter/test_c_import_paths.vader b/test/linter/test_c_import_paths.vader new file mode 100644 index 00000000..19e39915 --- /dev/null +++ b/test/linter/test_c_import_paths.vader @@ -0,0 +1,162 @@ +Before: + " Make sure the c.vim file is loaded first. + call ale#c#FindProjectRoot(bufnr('')) + + Save g:ale_c_parse_compile_commands + Save g:ale_c_parse_makefile + Save g:__ale_c_project_filenames + + let g:original_project_filenames = g:__ale_c_project_filenames + let g:executable_map = {} + + " Remove the .git/HEAD dir for C import paths for these tests. + " The tests run inside of a git repo. + let g:__ale_c_project_filenames = filter( + \ copy(g:__ale_c_project_filenames), + \ 'v:val isnot# ''.git/HEAD''' + \) + + let g:ale_c_parse_compile_commands = 0 + let g:ale_c_parse_makefile = 0 + + runtime autoload/ale/engine.vim + + function! ale#engine#IsExecutable(buffer, executable) abort + return has_key(g:executable_map, a:executable) + endfunction + +After: + Restore + + unlet! g:original_project_filenames + unlet! g:executable_map + + runtime autoload/ale/engine.vim + + call ale#assert#TearDownLinterTest() + +Execute(The C cc linter should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('c', 'cc') + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/file.c') + let g:ale_c_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/makefile_project/include')) + \ . ' -' + +Execute(The C cc linter should include 'include' directories for projects with a configure file): + call ale#assert#SetUpLinterTest('c', 'cc') + call ale#test#SetFilename('../test-files/c/configure_project/subdir/file.c') + let g:ale_c_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/configure_project/include')) + \ . ' -' + +Execute(The C cc linter should include root directories for projects with .h files in them): + call ale#assert#SetUpLinterTest('c', 'cc') + call ale#test#SetFilename('../test-files/c/h_file_project/subdir/file.c') + let g:ale_c_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/h_file_project')) + \ . ' -' + +Execute(The C cc linter should include root directories for projects with .hpp files in them): + call ale#assert#SetUpLinterTest('c', 'cc') + call ale#test#SetFilename('../test-files/c/hpp_file_project/subdir/file.c') + let g:ale_c_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/hpp_file_project')) + \ . ' -' + +Execute(The C ClangTidy handler should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('c', 'clangtidy') + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/file.cpp') + let g:ale_c_clangtidy_options = '' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' %s ' + \ . '-- -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/makefile_project/include')) + +Execute(The C++ cc linter should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('cpp', 'cc') + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/file.cpp') + let g:ale_cpp_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/makefile_project/include')) + \ . ' -' + +Execute(The C++ cc linter should include 'include' directories for projects with a configure file): + call ale#assert#SetUpLinterTest('cpp', 'cc') + call ale#test#SetFilename('../test-files/c/configure_project/subdir/file.cpp') + let g:ale_cpp_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/configure_project/include')) + \ . ' -' + +Execute(The C++ cc linter should include root directories for projects with .h files in them): + call ale#assert#SetUpLinterTest('cpp', 'cc') + call ale#test#SetFilename('../test-files/c/h_file_project/subdir/file.cpp') + let g:ale_cpp_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/h_file_project')) + \ . ' -' + +Execute(The C++ cc linter should include root directories for projects with .hpp files in them): + call ale#assert#SetUpLinterTest('cpp', 'cc') + call ale#test#SetFilename('../test-files/c/hpp_file_project/subdir/file.cpp') + let g:ale_cpp_cc_options = '' + + AssertLinter 'gcc', + \ ale#Escape('gcc') + \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/hpp_file_project')) + \ . ' -' + +Execute(The C++ ClangTidy handler should include json folders for projects with suitable build directory in them): + call ale#assert#SetUpLinterTest('cpp', 'clangtidy') + call ale#test#SetFilename('../test-files/c/json_project/subdir/file.cpp') + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' %s ' + \ . '-p ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/json_project/build')) + +Execute(The C++ ClangTidy handler should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('cpp', 'clangtidy') + call ale#test#SetFilename('../test-files/c/makefile_project/subdir/file.cpp') + let g:ale_cpp_clangtidy_options = '' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' %s ' + \ . '-- -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/c/makefile_project/include')) + diff --git a/test/linter/test_cargo.vader b/test/linter/test_cargo.vader new file mode 100644 index 00000000..25dd0253 --- /dev/null +++ b/test/linter/test_cargo.vader @@ -0,0 +1,222 @@ +Before: + call ale#assert#SetUpLinterTest('rust', 'cargo') + call ale#test#SetFilename('../test-files/cargo/test.rs') + + let g:cd = 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/cargo')) . ' && ' + 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. + 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): + GivenCommandOutput [] + AssertLinter 'cargo', 'cargo build --frozen --message-format=json -q' + +Execute(`cargo check` should be used when the version is new enough): + 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 + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo check' . g:suffix] + +Execute(`cargo build` should be used when cargo is too old): + GivenCommandOutput ['cargo 0.16.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ '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 + + 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 + GivenCommandOutput [] + AssertLinter 'cargo', ['cargo build' . g:suffix] + +Execute(`cargo check` should be used when the version is new enough): + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo check' . g:suffix, + \] + + " We should cache the version check + 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', [ale#Escape('cargo') . ' --version', 'cargo check --all-targets' . g:suffix] + " We should cache the version check + 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', [ale#Escape('cargo') . ' --version', 'cargo check --tests' . g:suffix] + + " We should cache the version check + 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', [ale#Escape('cargo') . ' --version', 'cargo check --examples' . g:suffix] + + " We should cache the version check + 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', [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', [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', [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' + " When all features are enabled we should ignore extra features to add + " since it won't do anything + let b:ale_rust_cargo_include_features = 'foo bar' + + 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(Cargo should run from the crate directory when set to avoid the workspace): + let g:ale_rust_cargo_avoid_whole_workspace = 1 + call ale#test#SetFilename('../test-files/cargo/workspace_paths/subpath/test.rs') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/cargo/workspace_paths/subpath') + call ale#semver#ResetVersionCache() + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo check --frozen --message-format=json -q', + \] + +Execute(Cargo should not run from the crate directory when not set to avoid the workspace): + let g:ale_rust_cargo_avoid_whole_workspace = 0 + call ale#test#SetFilename('../test-files/cargo/workspace_paths/subpath/test.rs') + + AssertLinterCwd '' + call ale#semver#ResetVersionCache() + AssertLinter 'cargo', [ + \ 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', [ + \ 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', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo clippy --frozen --message-format=json -q -- -D warnings', + \] + +Execute(Clippy options work without prepending --): + let b:ale_rust_cargo_use_clippy = 1 + let b:ale_rust_cargo_clippy_options = '-D warnings' + + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo clippy --frozen --message-format=json -q -- -D warnings', + \] + +Execute(Build supports all cargo flags): + let g:ale_rust_cargo_use_check = 0 + let g:ale_rust_cargo_check_all_targets = 1 + let g:ale_rust_cargo_check_tests = 1 + let g:ale_rust_cargo_check_examples = 1 + let b:ale_rust_cargo_default_feature_behavior = 'all' + let b:ale_rust_cargo_target_dir = 'target/ale' + + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo build --all-targets --examples --tests --target-dir ' . ale#Escape('target/ale') . ' --frozen --message-format=json -q --all-features', + \] + +Execute(Clippy supports all cargo flags): + let b:ale_rust_cargo_use_clippy = 1 + let g:ale_rust_cargo_check_all_targets = 1 + let g:ale_rust_cargo_check_tests = 1 + let g:ale_rust_cargo_check_examples = 1 + let b:ale_rust_cargo_default_feature_behavior = 'all' + let b:ale_rust_cargo_clippy_options = '-D warnings' + let b:ale_rust_cargo_target_dir = 'target/ale' + + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo clippy --all-targets --examples --tests --target-dir ' . ale#Escape('target/ale') . ' --frozen --message-format=json -q --all-features -- -D warnings', + \] + +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', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo check --frozen --message-format=json -q', + \] + +Execute(`cargo --target-dir` should be used when the version is new enough and it is set): + let b:ale_rust_cargo_target_dir = 'target/ale' + + GivenCommandOutput ['cargo 0.17.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo check --target-dir ' . ale#Escape('target/ale') . g:suffix, + \] + +Execute(`cargo --target-dir` should not be used when the version is not new enough and it is set): + let b:ale_rust_cargo_target_dir = 'target/ale' + + GivenCommandOutput ['cargo 0.16.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo build' . g:suffix, + \] diff --git a/test/linter/test_checkmake.vader b/test/linter/test_checkmake.vader new file mode 100644 index 00000000..ec8a4b83 --- /dev/null +++ b/test/linter/test_checkmake.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_make_checkmake_config + + let g:ale_make_checkmake_config = '' + + call ale#assert#SetUpLinterTest('make', 'checkmake') + + " NOTE: the format string must be manually matched that set in the plugin + let b:format = '"{{.LineNumber}}:{{.Rule}}:{{.Violation}}{{\"\r\n\"}}"' + +After: + Restore + + unlet! b:command_tail + unlet! b:ale_make_checkmake_config + + call ale#assert#TearDownLinterTest() + +Execute(checkmake should run with default format option): + let b:command_tail = ' --format=' . b:format . ' %s' + + AssertLinter 'checkmake', 'checkmake' . b:command_tail + +Execute(checkmake command should take the config option if it is non-empty): + let g:ale_make_checkmake_config = '/path to/checkmake.ini' + let b:command_tail = ' --format=' . b:format + \ . ' --config="' . g:ale_make_checkmake_config . '"' + \ . ' %s' + + AssertLinter 'checkmake', 'checkmake' . b:command_tail + +Execute(the local buffer config option takes precedence over global option): + let g:ale_make_checkmake_config = '/path/to/checkmake.ini' + let b:ale_make_checkmake_config = '/another/checkmake.ini' + let b:command_tail = ' --format=' . b:format + \ . ' --config="' . b:ale_make_checkmake_config . '"' + \ . ' %s' + + AssertLinter 'checkmake', 'checkmake' . b:command_tail diff --git a/test/linter/test_checkov.vader b/test/linter/test_checkov.vader new file mode 100644 index 00000000..f93d34f3 --- /dev/null +++ b/test/linter/test_checkov.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('terraform', 'checkov') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be direct): + AssertLinter 'checkov', + \ ale#Escape('checkov') . ' -f %t -o json --quiet ' + +Execute(It should be possible to override the default command): + let b:ale_terraform_checkov_executable = '/bin/other/checkov' + AssertLinter '/bin/other/checkov', + \ ale#Escape('/bin/other/checkov') . ' -f %t -o json --quiet ' diff --git a/test/linter/test_checkstyle.vader b/test/linter/test_checkstyle.vader new file mode 100644 index 00000000..8197e6b5 --- /dev/null +++ b/test/linter/test_checkstyle.vader @@ -0,0 +1,72 @@ +Before: + call ale#assert#SetUpLinterTest('java', 'checkstyle') + call ale#test#SetFilename('dummy.java') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The checkstyle callback should return the correct default value): + AssertLinter 'checkstyle', + \ ale#Escape('checkstyle') + \ . ' -c ' . ale#Escape('/google_checks.xml') + \ . ' %s' + +Execute(The checkstyle executable should be configurable): + let b:ale_java_checkstyle_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') + \ . ' -c ' . ale#Escape('/google_checks.xml') + \ . ' %s' + +Execute(Custom options should be supported): + let b:ale_java_checkstyle_options = '--foobar -cp -classpath /path/to/checkstyle-8.7-all.jar' + + AssertLinter 'checkstyle', + \ ale#Escape('checkstyle') + \ . ' --foobar -cp -classpath /path/to/checkstyle-8.7-all.jar' + \ . ' -c ' . ale#Escape('/google_checks.xml') + \ . ' %s' + +Execute(configuration files set in _config should be supported): + let b:ale_java_checkstyle_config = ale#path#Simplify(g:dir . '/../test-files/checkstyle/other_config.xml') + + AssertLinter 'checkstyle', + \ ale#Escape('checkstyle') + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/checkstyle/other_config.xml')) + \ . ' %s' + +Execute(configuration files set in _options should be preferred over _config): + let b:ale_java_checkstyle_config = '/foo.xml' + let b:ale_java_checkstyle_options = '-c /bar.xml' + + AssertLinter 'checkstyle', ale#Escape('checkstyle') . ' -c /bar.xml %s' + + let b:ale_java_checkstyle_options = '-x -c /bar.xml' + + AssertLinter 'checkstyle', ale#Escape('checkstyle') . ' -x -c /bar.xml %s' + +Execute(google_checks.xml should be used by default): + call ale#test#SetFilename('../test-files/checkstyle/test.java') + + AssertLinter 'checkstyle', + \ ale#Escape('checkstyle') + \ . ' -c ' . ale#Escape('/google_checks.xml') + \ . ' %s' + +Execute(Other relative paths should be supported): + let b:ale_java_checkstyle_config = '../test-files/checkstyle/other_config.xml' + + AssertLinter 'checkstyle', + \ ale#Escape('checkstyle') + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/checkstyle/other_config.xml')) + \ . ' %s' + + call ale#test#SetFilename('../test-files/checkstyle/test.java') + + let b:ale_java_checkstyle_config = 'other_config.xml' + + AssertLinter 'checkstyle', + \ ale#Escape('checkstyle') + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/checkstyle/other_config.xml')) + \ . ' %s' diff --git a/test/linter/test_circleci.vader b/test/linter/test_circleci.vader new file mode 100644 index 00000000..000a77ec --- /dev/null +++ b/test/linter/test_circleci.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('yaml', 'circleci') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The linter should not run for every YAML file): + AssertLinterNotExecuted + +Execute(The linter should for YAML files in a .circleci directory): + call ale#test#SetFilename('../test-files/.circleci/config.yml') + + AssertLinter 'circleci', 'circleci --skip-update-check config validate - < %s' diff --git a/test/linter/test_clang_tidy.vader b/test/linter/test_clang_tidy.vader new file mode 100644 index 00000000..f7f0aa74 --- /dev/null +++ b/test/linter/test_clang_tidy.vader @@ -0,0 +1,78 @@ +Before: + Save g:ale_c_parse_makefile + let g:ale_c_parse_makefile = 0 + + call ale#assert#SetUpLinterTest('cpp', 'clangtidy') + call ale#test#SetFilename('test.cpp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The clangtidy command default should be correct): + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') . ' %s' + +Execute(You should be able to set other checks for clang-tidy): + let b:ale_cpp_clangtidy_checks = ['-*', 'clang-analyzer-*'] + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('-*,clang-analyzer-*') . ' %s' + +Execute(You should be able to manually set compiler flags for clang-tidy): + let b:ale_cpp_clangtidy_checks = ['*'] + let b:ale_cpp_clangtidy_options = '-Wall' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') . ' -checks=' . ale#Escape('*') . ' %s -- -Wall' + +Execute(You should be able to manually set flags for clang-tidy): + let b:ale_cpp_clangtidy_extra_options = '-config=' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') . ' ' . ale#Escape('-config=') . ' %s' + +Execute(The build directory should be configurable): + let b:ale_cpp_clangtidy_checks = ['*'] + let b:ale_c_build_dir = '/foo/bar' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + +Execute(The build directory setting should override the options): + let b:ale_cpp_clangtidy_checks = ['*'] + let b:ale_c_build_dir = '/foo/bar' + let b:ale_cpp_clangtidy_options = '-Wall' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + +Execute(The build directory should be used for header files): + call ale#test#SetFilename('test.h') + + let b:ale_cpp_clangtidy_checks = ['*'] + let b:ale_c_build_dir = '/foo/bar' + let b:ale_cpp_clangtidy_options = '-Wall' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + + call ale#test#SetFilename('test.hpp') + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + +Execute(The executable should be configurable): + let b:ale_cpp_clangtidy_checks = ['*'] + let b:ale_cpp_clangtidy_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -checks=' . ale#Escape('*') . ' %s' diff --git a/test/linter/test_clj_kondo.vader b/test/linter/test_clj_kondo.vader new file mode 100644 index 00000000..e62211c4 --- /dev/null +++ b/test/linter/test_clj_kondo.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('clojure', 'clj_kondo') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'clj-kondo', 'clj-kondo' + \ . ' --cache --lint - --filename %s' + +Execute(Extra options should be supported): + let g:ale_clojure_clj_kondo_options = '--config ./clj-kondo/config.edn' + + AssertLinter 'clj-kondo', 'clj-kondo' + \ . ' --config ./clj-kondo/config.edn --lint - --filename %s' diff --git a/test/linter/test_cmake_cmake_lint.vader b/test/linter/test_cmake_cmake_lint.vader new file mode 100644 index 00000000..9238f91a --- /dev/null +++ b/test/linter/test_cmake_cmake_lint.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('cmake', 'cmake_lint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'cmake-lint', ale#Escape('cmake-lint') . ' %s' + +Execute(The executable should be configurable): + let g:ale_cmake_cmake_lint_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' %s' diff --git a/test/linter/test_cookstyle.vader b/test/linter/test_cookstyle.vader new file mode 100644 index 00000000..ccd6b1a2 --- /dev/null +++ b/test/linter/test_cookstyle.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('chef', 'cookstyle') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default cookstyle command should be correct): + AssertLinter 'cookstyle', ale#Escape('cookstyle') . ' --force-exclusion --format json --stdin %s' + +Execute(The cookstyle executable and options should be configurable): + let b:ale_chef_cookstyle_executable = 'foobar' + let b:ale_chef_cookstyle_options = '--parallel' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --parallel --force-exclusion --format json --stdin %s' diff --git a/test/linter/test_cpp_cc.vader b/test/linter/test_cpp_cc.vader new file mode 100644 index 00000000..e6794c09 --- /dev/null +++ b/test/linter/test_cpp_cc.vader @@ -0,0 +1,94 @@ +Before: + Save g:ale_c_parse_makefile + Save g:ale_history_enabled + + let g:ale_c_parse_makefile = 0 + let g:ale_history_enabled = 0 + + let g:get_cflags_return_value = '' + let g:executable_map = {} + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + function! ale#engine#IsExecutable(buffer, executable) abort + return has_key(g:executable_map, a:executable) + endfunction + + function! ale#c#GetCFlags(buffer, output) abort + return g:get_cflags_return_value + endfunction + + call ale#assert#SetUpLinterTest('cpp', 'cc') + + let b:command_tail = ' -S -x c++' + \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -std=c++14 -Wall -' + +After: + unlet! g:get_cflags_return_value + unlet! g:executable_map + unlet! b:command_tail + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + call ale#assert#TearDownLinterTest() + +Execute(clang++ should be used instead of gcc, if available): + let g:executable_map = {'clang++': 1} + + AssertLinter 'clang++', [ale#Escape('clang++') . b:command_tail] + +Execute(The executable should be configurable): + AssertLinter 'gcc', [ale#Escape('gcc') . b:command_tail] + + let b:ale_cpp_cc_executable = 'foobar' + + AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] + +Execute(The -std flag should be replaced by parsed C flags): + let b:command_tail = substitute(b:command_tail, 'c++14', 'c++11 ', '') + let g:get_cflags_return_value = '-std=c++11' + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail + +Execute(gcc should not use -x c++-header with header files by default): + call ale#test#SetFilename('../test-files/c/hpp_file_project/test.hpp') + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail + +Execute(clang++ should use -x c++-header with header files by default): + let g:executable_map = {'clang++': 1} + let b:command_tail = substitute(b:command_tail, '-x c++', '-x c++-header', '') + + call ale#test#SetFilename('../test-files/c/hpp_file_project/test.hpp') + + AssertLinter 'clang++', ale#Escape('clang++') . b:command_tail + +Execute(gcc should use -x c-header with header files if configured to do so): + let b:ale_cpp_cc_use_header_lang_flag = 1 + let b:command_tail = substitute(b:command_tail, '-x c++', '-x c++-header', '') + + call ale#test#SetFilename('../test-files/c/hpp_file_project/test.hpp') + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail + +Execute(clang should not use -x c-header with header files if configured to do so): + let g:executable_map = {'clang++': 1} + let b:ale_cpp_cc_use_header_lang_flag = 0 + + call ale#test#SetFilename('../test-files/c/hpp_file_project/test.hpp') + + AssertLinter 'clang++', ale#Escape('clang++') . b:command_tail + +Execute(The header file extensions should be configurable): + let g:executable_map = {'clang++': 1} + let b:command_tail = substitute(b:command_tail, '-x c++', '-x c++-header', '') + + call ale#assert#SetUpLinterTest('cpp', 'cc') + let b:ale_cpp_cc_header_exts = ['json'] + call ale#test#SetFilename('../test-files/c/json_project/build/compile_commands.json') + + AssertLinter 'clang++', ale#Escape('clang++') . b:command_tail diff --git a/test/linter/test_cpp_ccls.vader b/test/linter/test_cpp_ccls.vader new file mode 100644 index 00000000..12aa30e3 --- /dev/null +++ b/test/linter/test_cpp_ccls.vader @@ -0,0 +1,69 @@ +" Author: Ye Jingchen , Ben Falconer +" Description: A language server for C++ + +Before: + call ale#assert#SetUpLinterTest('cpp', 'ccls') + + Save b:ale_c_build_dir_names + Save b:ale_cpp_ccls_executable + Save b:ale_cpp_ccls_init_options + +After: + call ale#assert#TearDownLinterTest() + +Execute(The project root should be detected correctly using compile_commands.json file): + call ale#test#SetFilename(tempname() . '/dummy.cpp') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_compile_commands_json/dummy.cpp') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_compile_commands_json') + +Execute(The project root should be detected correctly using .ccls file): + call ale#test#SetFilename(tempname() . '/dummy.cpp') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_ccls/dummy.cpp') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_ccls') + +Execute(The project root should be detected correctly using .ccls-root file): + call ale#test#SetFilename(tempname() . '/dummy.cpp') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_ccls-root/dummy.cpp') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_ccls-root') + +Execute(The executable should be configurable): + AssertLinter 'ccls', ale#Escape('ccls') + + let b:ale_cpp_ccls_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The initialization options should be configurable): + AssertLSPOptions {} + + let b:ale_cpp_ccls_init_options = { 'cacheDirectory': '/tmp/ccls' } + + AssertLSPOptions { 'cacheDirectory': '/tmp/ccls' } + +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('../test-files/ccls/with_ccls/dummy.c') + + AssertLSPOptions {} + + call ale#test#SetFilename('../test-files/ccls/with_compile_commands_json/dummy.c') + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/../test-files/ccls/with_compile_commands_json') } + + call ale#test#SetFilename('../test-files/ccls/with_build_dir/dummy.c') + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/../test-files/ccls/with_build_dir/unusual_build_dir_name') } diff --git a/test/linter/test_cpp_clangcheck.vader b/test/linter/test_cpp_clangcheck.vader new file mode 100644 index 00000000..188141d5 --- /dev/null +++ b/test/linter/test_cpp_clangcheck.vader @@ -0,0 +1,35 @@ +Before: + call ale#assert#SetUpLinterTest('cpp', 'clangcheck') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'clang-check', + \ ale#Escape('clang-check') + \ . ' -analyze %s --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' + + let b:ale_cpp_clangcheck_executable = 'foobar' + + " The extra arguments in the command are used to prevent .plist files from + " being generated. + AssertLinter 'foobar', + \ ale#Escape('foobar') + \ . ' -analyze %s --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' + +Execute(The options should be configurable): + let b:ale_cpp_clangcheck_options = '--something' + + AssertLinter 'clang-check', + \ ale#Escape('clang-check') + \ . ' -analyze %s' + \ . ' --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' + \ . ' --something' + +Execute(The build directory should be used when set): + let b:ale_cpp_clangcheck_options = '--something' + let b:ale_c_build_dir = '/foo/bar' + + AssertLinter 'clang-check', + \ ale#Escape('clang-check') + \ . ' -analyze %s --something -p ' . ale#Escape('/foo/bar') diff --git a/test/linter/test_cpp_clazy.vader b/test/linter/test_cpp_clazy.vader new file mode 100644 index 00000000..e5a81b8f --- /dev/null +++ b/test/linter/test_cpp_clazy.vader @@ -0,0 +1,56 @@ +Before: + call ale#assert#SetUpLinterTest('cpp', 'clazy') + call ale#test#SetFilename('test.cpp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The clazy command default should be correct): + AssertLinter 'clazy-standalone', + \ ale#Escape('clazy-standalone') . ' -checks=' . ale#Escape('level1') . ' %s' + +Execute(You should be able to remove the -checks option for clazy-standalone): + let b:ale_cpp_clazy_checks = [] + + AssertLinter 'clazy-standalone', ale#Escape('clazy-standalone') . ' %s' + +Execute(You should be able to set other checks for clazy-standalone): + let b:ale_cpp_clazy_checks = ['level2', 'level3'] + + AssertLinter 'clazy-standalone', + \ ale#Escape('clazy-standalone') + \ . ' -checks=' . ale#Escape('level2,level3') . ' %s' + +Execute(You should be able to manually set compiler flags for clazy-standalone): + let b:ale_cpp_clazy_options = '-qt4-compat' + + AssertLinter 'clazy-standalone', + \ ale#Escape('clazy-standalone') . ' -checks=' . ale#Escape('level1') . ' -qt4-compat' . ' %s' + \ +Execute(The build directory should be configurable): + let b:ale_c_build_dir = '/foo/bar' + + AssertLinter 'clazy-standalone', + \ ale#Escape('clazy-standalone') + \ . ' -checks=' . ale#Escape('level1') . ' -p ' . ale#Escape('/foo/bar') . ' %s' + +Execute(The build directory should be used for header files): + call ale#test#SetFilename('test.h') + + let b:ale_c_build_dir = '/foo/bar' + + AssertLinter 'clazy-standalone', + \ ale#Escape('clazy-standalone') + \ . ' -checks=' . ale#Escape('level1') . ' -p ' . ale#Escape('/foo/bar') . ' %s' + + call ale#test#SetFilename('test.hpp') + + AssertLinter 'clazy-standalone', + \ ale#Escape('clazy-standalone') + \ . ' -checks=' . ale#Escape('level1') . ' -p ' . ale#Escape('/foo/bar') . ' %s' + +Execute(The executable should be configurable): + let b:ale_cpp_clazy_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -checks=' . ale#Escape('level1') . ' %s' diff --git a/test/linter/test_cpp_cppcheck.vader b/test/linter/test_cpp_cppcheck.vader new file mode 100644 index 00000000..880bcb41 --- /dev/null +++ b/test/linter/test_cpp_cppcheck.vader @@ -0,0 +1,83 @@ +Before: + call ale#assert#SetUpLinterTest('cpp', 'cppcheck') + let b:command_tail = ' -q --language=c++ --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') . ' --enable=style -I' . ale#Escape(ale#path#Simplify(g:dir)) .' %t' + +After: + " Remove a test file we might open for some tests. + if &buftype != 'nofile' + set nomodified + set buftype=nofile + endif + + unlet! b:rel_file_path + unlet! b:command_tail + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'cppcheck', ale#Escape('cppcheck') . b:command_tail + + let b:ale_cpp_cppcheck_executable = 'foobar' + + AssertLinterCwd '' + AssertLinter 'foobar', ale#Escape('foobar') . b:command_tail + +Execute(cppcheck for C++ should detect compile_commands.json files): + let b:rel_file_path = '../test-files/cppcheck/one/foo.cpp' + call ale#test#SetFilename(b:rel_file_path) + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/cppcheck/one') + AssertLinter 'cppcheck', ale#Escape('cppcheck') + \ . ' -q --language=c++' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' --project=' . ale#Escape('compile_commands.json') + \ . ' --file-filter=' . ale#Escape(ale#test#GetFilename(b:rel_file_path)) + \ . ' --enable=style %t' + +Execute(cppcheck for C++ should detect compile_commands.json files in build directories): + let b:rel_file_path = '../test-files/cppcheck/with_build_dir/foo.cpp' + call ale#test#SetFilename(b:rel_file_path) + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/cppcheck/with_build_dir') + AssertLinter 'cppcheck', ale#Escape('cppcheck') + \ . ' -q --language=c++' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' --project=' . ale#Escape(ale#path#Simplify('build/compile_commands.json')) + \ . ' --file-filter=' . ale#Escape(ale#test#GetFilename(b:rel_file_path)) + \ . ' --enable=style %t' + +Execute(cppcheck for C++ should include file dir if compile_commands.json file is not found): + call ale#test#SetFilename('../test-files/cppcheck/foo.cpp') + + AssertLinter 'cppcheck', + \ ale#Escape('cppcheck') + \ . ' -q --language=c++' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' --enable=style' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/cppcheck')) + \ . ' %t' + +Execute(cppcheck for C++ header should include file dir and not use compile_commands.json): + call ale#test#SetFilename('../test-files/cppcheck/one/foo.hpp') + + AssertLinter 'cppcheck', + \ ale#Escape('cppcheck') + \ . ' -q --language=c++' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/cppcheck/one')) + \ . ' --suppress=unusedStructMember' + \ . ' --enable=style' + \ . ' %t' + +Execute(cppcheck for C++ should ignore compile_commands.json file if buffer is modified): + call ale#test#SetFilename('../test-files/cppcheck/one/foo.cpp') + + set buftype= + set modified + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/cppcheck/one') + AssertLinter 'cppcheck', ale#Escape('cppcheck') + \ . ' -q --language=c++' + \ . ' --template=' . ale#Escape('{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}') + \ . ' --enable=style' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/cppcheck/one')) + \ . ' %t' diff --git a/test/linter/test_cpp_cquery.vader b/test/linter/test_cpp_cquery.vader new file mode 100644 index 00000000..f638e401 --- /dev/null +++ b/test/linter/test_cpp_cquery.vader @@ -0,0 +1,40 @@ +" Author: Ben Falconer +" Description: A language server for C++ + +Before: + call ale#assert#SetUpLinterTest('cpp', 'cquery') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The project root should be detected correctly using compile_commands.json file): + call ale#test#SetFilename(tempname() . '/dummy.cpp') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/cquery/dummy.cpp') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/cquery') + +Execute(The project root should be detected correctly using .cquery file): + call ale#test#SetFilename(tempname() . '/dummy.cpp') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/cquery/with_cquery/dummy.cpp') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/cquery/with_cquery') + +Execute(The executable should be configurable): + AssertLinter 'cquery', ale#Escape('cquery') + + let b:ale_cpp_cquery_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The cache directory should be configurable): + AssertLSPOptions {'cacheDirectory': expand('$HOME/.cache/cquery')} + + let b:ale_cpp_cquery_cache_directory = '/foo/bar' + + AssertLSPOptions {'cacheDirectory': '/foo/bar'} diff --git a/test/linter/test_cpp_flawfinder.vader b/test/linter/test_cpp_flawfinder.vader new file mode 100644 index 00000000..69369536 --- /dev/null +++ b/test/linter/test_cpp_flawfinder.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpLinterTest('cpp', 'flawfinder') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The flawfinder command should be correct): + AssertLinter 'flawfinder', + \ ale#Escape('flawfinder') . ' -CDQS --minlevel=1 %t' + +Execute(The minlevel of flawfinder should be configurable): + let b:ale_cpp_flawfinder_minlevel = 8 + + AssertLinter 'flawfinder', + \ ale#Escape('flawfinder') . ' -CDQS --minlevel=8 %t' + +Execute(Additional flawfinder options should be configurable): + let b:ale_cpp_flawfinder_options = ' --foobar' + + AssertLinter 'flawfinder', + \ ale#Escape('flawfinder') . ' -CDQS --foobar --minlevel=1 %t' + +Execute(The flawfinder executable should be configurable): + let b:ale_cpp_flawfinder_executable = 'foo/bar' + + AssertLinter 'foo/bar', ale#Escape('foo/bar') . ' -CDQS --minlevel=1 %t' diff --git a/test/linter/test_cpplint.vader b/test/linter/test_cpplint.vader new file mode 100644 index 00000000..d5fd457b --- /dev/null +++ b/test/linter/test_cpplint.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('cpp', 'cpplint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'cpplint', ale#Escape('cpplint') . ' %s' + + let b:ale_cpp_cpplint_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' %s' + +Execute(The options should be configurable): + let b:ale_cpp_cpplint_options = '--something' + + AssertLinter 'cpplint', ale#Escape('cpplint') . ' --something %s' diff --git a/test/linter/test_cs_csc.vader b/test/linter/test_cs_csc.vader new file mode 100644 index 00000000..c01de7fd --- /dev/null +++ b/test/linter/test_cs_csc.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('cs', 'csc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The csc linter should return the correct default command): + AssertLinterCwd expand('%:p:h') + AssertLinter 'csc', 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs') + +Execute(The options should be be used in the command): + let g:ale_cs_csc_options = '' + + AssertLinter 'csc', 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs') + +Execute(The source path should be be used in the command): + let g:ale_cs_csc_source = '../foo/bar' + + AssertLinterCwd '../foo/bar' + AssertLinter 'csc', 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs') + +Execute(The list of search paths for assemblies should be be used in the command if not empty): + let g:ale_cs_csc_assembly_path = ['/usr/lib/mono', '../foo/bar'] + + AssertLinter 'csc', 'csc /unsafe' + \ . ' /lib:' . ale#Escape('/usr/lib/mono') . ',' . ale#Escape('../foo/bar') + \ . ' /out:TEMP /t:module /recurse:' . ale#Escape('*.cs') + + let g:ale_cs_csc_assembly_path = [] + + AssertLinter 'csc', 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs') + +Execute(The list of assemblies should be be used in the command if not empty): + let g:ale_cs_csc_assemblies = ['foo.dll', 'bar.dll'] + + AssertLinter 'csc', 'csc /unsafe' + \ . ' /r:' . ale#Escape('foo.dll') . ',' . ale#Escape('bar.dll') + \ . ' /out:TEMP /t:module /recurse:' . ale#Escape('*.cs') + + let g:ale_cs_csc_assemblies = [] + + AssertLinter 'csc', 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs') diff --git a/test/linter/test_cs_mcs.vader b/test/linter/test_cs_mcs.vader new file mode 100644 index 00000000..dbebd106 --- /dev/null +++ b/test/linter/test_cs_mcs.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('cs', 'mcs') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'mcs', 'mcs -unsafe --parse %t' + +Execute(The options should be be used in the command): + let b:ale_cs_mcs_options = '-pkg:dotnet' + + AssertLinter 'mcs', 'mcs -unsafe --parse -pkg:dotnet %t' diff --git a/test/linter/test_cs_mcsc.vader b/test/linter/test_cs_mcsc.vader new file mode 100644 index 00000000..8634a5f0 --- /dev/null +++ b/test/linter/test_cs_mcsc.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('cs', 'mcsc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The mcsc linter should return the correct default command): + AssertLinterCwd expand('%:p:h') + AssertLinter 'mcs', 'mcs -unsafe -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') + +Execute(The options should be be used in the command): + let g:ale_cs_mcsc_options = '-pkg:dotnet' + + AssertLinter 'mcs', 'mcs -unsafe -pkg:dotnet -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') + +Execute(The source path should be be used in the command): + let g:ale_cs_mcsc_source = '../foo/bar' + + AssertLinterCwd '../foo/bar' + AssertLinter 'mcs', 'mcs -unsafe -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') + +Execute(The list of search paths for assemblies should be be used in the command if not empty): + let g:ale_cs_mcsc_assembly_path = ['/usr/lib/mono', '../foo/bar'] + + AssertLinter 'mcs', 'mcs -unsafe' + \ . ' -lib:' . ale#Escape('/usr/lib/mono') . ',' . ale#Escape('../foo/bar') + \ . ' -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') + + let g:ale_cs_mcsc_assembly_path = [] + + AssertLinter 'mcs', 'mcs -unsafe -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') + +Execute(The list of assemblies should be be used in the command if not empty): + let g:ale_cs_mcsc_assemblies = ['foo.dll', 'bar.dll'] + + AssertLinter 'mcs', 'mcs -unsafe' + \ . ' -r:' . ale#Escape('foo.dll') . ',' . ale#Escape('bar.dll') + \ . ' -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') + + let g:ale_cs_mcsc_assemblies = [] + + AssertLinter 'mcs', 'mcs -unsafe -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') diff --git a/test/linter/test_cspell.vader b/test/linter/test_cspell.vader new file mode 100644 index 00000000..2987aa7a --- /dev/null +++ b/test/linter/test_cspell.vader @@ -0,0 +1,110 @@ +Before: + call ale#assert#SetUpLinterTest('tex', 'cspell') + + " We have to manually do our own variable reset because SetUpLinterTest calls + " ale#assert#ResetVariables, which specifically only resets variables that + " begin with ale__, per https://github.com/dense-analysis/ale/blob/76c2293e68a6cad3b192062743d25b8daa082205/autoload/ale/assert.vim#L256 + " + " Took a lot of debugging and reading both junegunn/vader.vim and most ALE + " files to find this behavior + + Save g:ale_cspell_executable + Save g:ale_cspell_use_global + Save g:ale_cspell_options + + unlet! g:ale_cspell_executable + unlet! g:ale_cspell_use_global + unlet! g:ale_cspell_options + + let g:ale_cspell_executable = 'cspell' + let g:ale_cspell_use_global = 0 + let g:ale_cspell_options = '' + + set filetype=tex + +After: + call ale#assert#TearDownLinterTest() + +Execute(The global executable should be used when the local one cannot be found): + AssertLinter + \ 'cspell', + \ ale#Escape('cspell') + \ . ' lint --no-color --no-progress --no-summary --language-id="latex" -- stdin' + +Execute(Should use the node_modules/.bin executable if available): + call ale#test#SetFilename('../test-files/cspell/node-modules/test.tex') + + AssertLinter + \ ale#path#Simplify(g:dir + \ . '/../test-files/cspell/node-modules/node_modules/.bin/cspell'), + \ ale#Escape(ale#path#Simplify(g:dir + \ . '/../test-files/cspell/node-modules/node_modules/.bin/cspell')) + \ . ' lint --no-color --no-progress --no-summary --language-id="latex" -- stdin' + +Execute(Should use the node_modules/cspell executable if available): + call ale#test#SetFilename('../test-files/cspell/node-modules-2/test.tex') + + AssertLinter + \ ale#path#Simplify(g:dir + \ . '/../test-files/cspell/node-modules-2/node_modules/cspell/bin.js'), + \ (has('win32') ? 'node.exe ': '') + \ . ale#Escape(ale#path#Simplify(g:dir + \ . '/../test-files/cspell/node-modules-2/node_modules/cspell/bin.js')) + \ . ' lint --no-color --no-progress --no-summary --language-id="latex" -- stdin' + +Execute(Should let users configure a global executable and override local paths): + let g:ale_cspell_executable = '/path/to/custom/cspell' + let g:ale_cspell_use_global = 1 + + AssertLinter + \ '/path/to/custom/cspell', + \ ale#Escape('/path/to/custom/cspell') + \ . ' lint --no-color --no-progress --no-summary --language-id="latex" -- stdin' + +Execute(Additional cspell options should be configurable): + call ale#test#SetFilename('../test-files/dummy') + + let g:ale_cspell_options = '--foobar' + + AssertLinter + \ 'cspell', + \ ale#Escape('cspell') + \ . ' lint --no-color --no-progress --no-summary --language-id="latex" --foobar -- stdin' + +Execute(The language id should be tex when filetype is plaintex): + set filetype=plaintex + + AssertLinter + \ 'cspell', + \ ale#Escape('cspell') + \ . ' lint --no-color --no-progress --no-summary --language-id="tex" -- stdin' + +Execute(The language id should be equal to filetype when not tex or plaintex): + set filetype=markdown + + AssertLinter + \ 'cspell', + \ ale#Escape('cspell') + \ . ' lint --no-color --no-progress --no-summary --language-id="markdown" -- stdin' + + set filetype=asciidoc + + AssertLinter + \ 'cspell', + \ ale#Escape('cspell') + \ . ' lint --no-color --no-progress --no-summary --language-id="asciidoc" -- stdin' + + set filetype=html + + AssertLinter + \ 'cspell', + \ ale#Escape('cspell') + \ . ' lint --no-color --no-progress --no-summary --language-id="html" -- stdin' + +Execute(The language id should not specified when filetype is empty): + set filetype= + + AssertLinter + \ 'cspell', + \ ale#Escape('cspell') + \ . ' lint --no-color --no-progress --no-summary -- stdin' diff --git a/test/linter/test_css_csslint.vader b/test/linter/test_css_csslint.vader new file mode 100644 index 00000000..7bba5bb1 --- /dev/null +++ b/test/linter/test_css_csslint.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('css', 'csslint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(--config should be set when the .csslintrc file is found): + call ale#test#SetFilename('../test-files/csslint/some-app/subdir/testfile.js') + + AssertLinter 'csslint', 'csslint --format=compact ' + \ . '--config=' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/csslint/some-app/.csslintrc')) + \ . ' %t' + +Execute(--config should not be used when no .csslintrc file exists): + call ale#test#SetFilename('../test-files/csslint/other-app/testfile.css') + + AssertLinter 'csslint', 'csslint --format=compact %t' diff --git a/test/linter/test_cucumber.vader b/test/linter/test_cucumber.vader new file mode 100644 index 00000000..6a7851ef --- /dev/null +++ b/test/linter/test_cucumber.vader @@ -0,0 +1,18 @@ +Before: + call ale#assert#SetUpLinterTest('cucumber', 'cucumber') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Should require the nearest features dir, if one is found): + call ale#test#SetFilename('../test-files/cucumber/features/cuke.feature') + + AssertLinter 'cucumber', + \ 'cucumber --dry-run --quiet --strict --format=json ' + \ . '-r ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/cucumber/features/')) . ' %t' + +Execute(Should require nothing if no features dir is found): + call ale#test#SetFilename('something/without/a/features/dir') + + AssertLinter 'cucumber', + \ 'cucumber --dry-run --quiet --strict --format=json %t' diff --git a/test/linter/test_cuda_nvcc.vader b/test/linter/test_cuda_nvcc.vader new file mode 100644 index 00000000..4578d052 --- /dev/null +++ b/test/linter/test_cuda_nvcc.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('cuda', 'nvcc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'nvcc', + \ ale#Escape('nvcc') . ' -cuda -std=c++11 %s -o ' . g:ale#util#nul_file + + let b:ale_cuda_nvcc_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -cuda -std=c++11 %s -o ' . g:ale#util#nul_file + +Execute(The options should be configurable): + let g:ale_cuda_nvcc_options = '--foobar' + + AssertLinter 'nvcc', + \ ale#Escape('nvcc') . ' -cuda --foobar %s -o ' . g:ale#util#nul_file diff --git a/test/linter/test_cypher_lint.vader b/test/linter/test_cypher_lint.vader new file mode 100644 index 00000000..6b64dc1f --- /dev/null +++ b/test/linter/test_cypher_lint.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/linter/test_d_dls.vader b/test/linter/test_d_dls.vader new file mode 100644 index 00000000..156ebf66 --- /dev/null +++ b/test/linter/test_d_dls.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('d', 'dls') + + Save &filetype + let &filetype = 'd' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'd' + +Execute(The default executable should be correct): + AssertLinter 'dls', 'dls' + +Execute(The executable should be configurable): + let g:ale_d_dls_executable = 'foobar' + + AssertLinter 'foobar', 'foobar' diff --git a/test/linter/test_dart_analysis_server.vader b/test/linter/test_dart_analysis_server.vader new file mode 100644 index 00000000..1b653dfd --- /dev/null +++ b/test/linter/test_dart_analysis_server.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('dart', 'analysis_server') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'dart', ale#Escape('dart') + \ . ' language-server --protocol=lsp' + +Execute(The executable should be configurable): + let g:ale_dart_analysis_server_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + \ . ' language-server --protocol=lsp' + +Execute(Should be able to disable new language-server command): + let g:ale_dart_analysis_server_enable_language_server = 0 + + AssertLinter 'dart', ale#Escape('dart') + \ . ' ./snapshots/analysis_server.dart.snapshot --lsp' diff --git a/test/linter/test_dart_language_server.vader b/test/linter/test_dart_language_server.vader new file mode 100644 index 00000000..5567f271 --- /dev/null +++ b/test/linter/test_dart_language_server.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('dart', 'language_server') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'dart_language_server', ale#Escape('dart_language_server') diff --git a/test/linter/test_desktop_file_validate.vader b/test/linter/test_desktop_file_validate.vader new file mode 100644 index 00000000..4a49057b --- /dev/null +++ b/test/linter/test_desktop_file_validate.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('desktop', 'desktop_file_validate') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'desktop-file-validate', + \ ale#Escape('desktop-file-validate') . ' %t' + +Execute(Extra options should work): + let b:ale_desktop_desktop_file_validate_options = '--warn-kde' + + AssertLinter 'desktop-file-validate', + \ ale#Escape('desktop-file-validate') . ' --warn-kde %t' diff --git a/test/linter/test_dialyxir.vader b/test/linter/test_dialyxir.vader new file mode 100644 index 00000000..250ffefd --- /dev/null +++ b/test/linter/test_dialyxir.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('elixir', 'dialyxir') + call ale#test#SetFilename('../test-files/elixir/mix_project/lib/app.ex') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Builds dialyxir command with a normal project): + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/mix_project') + AssertLinter 'mix', 'mix help dialyzer && mix dialyzer' + +Execute(Builds dialyxir command with an umbrella project): + call ale#test#SetFilename('../test-files/elixir/umbrella_project/apps/mix_project/lib/app.ex') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/umbrella_project') + AssertLinter 'mix', 'mix help dialyzer && mix dialyzer' diff --git a/test/linter/test_djlint.vader b/test/linter/test_djlint.vader new file mode 100644 index 00000000..fe87f644 --- /dev/null +++ b/test/linter/test_djlint.vader @@ -0,0 +1,69 @@ +Before: + call ale#assert#SetUpLinterTest('html', 'djlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default djlint command should be correct): + AssertLinter 'djlint', ale#Escape('djlint') . ' %s' + +Execute(The executable should be configurable): + let g:ale_html_djlint_executable = 'foo bar' + let g:ale_html_djlint_options = '--option' + + AssertLinter 'foo bar', ale#Escape('foo bar') . ' --option %s' + +Execute(The --profile option should not be overridden): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=htmldjango + let g:ale_html_djlint_options = '--profile jinja' + + AssertLinter 'djlint', ale#Escape(g:ale_html_djlint_executable) + \ . ' --profile jinja' + \ . ' %s', + +Execute(Should set --profile for htmlangular): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=htmlangular + + AssertLinter 'djlint', ale#Escape(g:ale_html_djlint_executable) + \ . ' --profile angular' + \ . ' %s', + +Execute(Should set --profile for jinja): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=jinja + + AssertLinter 'djlint', ale#Escape(g:ale_html_djlint_executable) + \ . ' --profile jinja' + \ . ' %s', + +Execute(Should set --profile for Handlebars): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=handlebars + + AssertLinter 'djlint', ale#Escape(g:ale_html_djlint_executable) + \ . ' --profile handlebars' + \ . ' %s', + +Execute(Should set --profile for nunjucks): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=nunjucks + + AssertLinter 'djlint', ale#Escape(g:ale_html_djlint_executable) + \ . ' --profile nunjucks' + \ . ' %s', + +Execute(Should set --profile for Go HTML Templates): + call ale#test#SetFilename('../test-files/djlint/testfile.html') + + set filetype=gohtmltmpl + + AssertLinter 'djlint', ale#Escape(g:ale_html_djlint_executable) + \ . ' --profile golang' + \ . ' %s', diff --git a/test/linter/test_dmd_commandline.vader b/test/linter/test_dmd_commandline.vader new file mode 100644 index 00000000..609fc073 --- /dev/null +++ b/test/linter/test_dmd_commandline.vader @@ -0,0 +1,96 @@ +Before: + runtime ale_linters/d/dmd.vim + +After: + call ale#linter#Reset() + +Execute(DMD command line should be correct with imports): + AssertEqual + \ 'dmd ' . + \ '-I' . ale#Escape('source') . ' ' . + \ '-I' . ale#Escape('/prefix/include/d') . ' ' . + \ '-I' . ale#Escape('/home/user/.dub/packages/pkg-0.0.1/pkg/src') . ' ' . + \ ' ' . + \ ' ' . + \ ' ' . + \ '-o- -wi -vcolumns -c %t', + \ ale_linters#d#dmd#DMDCommand(bufnr(''), [ + \ 'source', + \ '/prefix/include/d', + \ '/home/user/.dub/packages/pkg-0.0.1/pkg/src', + \ '', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], {}) + +Execute(DMD command line should be correct with imports and version): + AssertEqual + \ 'dmd ' . + \ '-I' . ale#Escape('source') . ' ' . + \ '-I' . ale#Escape('/prefix/include/d') . ' ' . + \ '-I' . ale#Escape('/home/user/.dub/packages/pkg-0.0.1/pkg/src') . ' ' . + \ ' ' . + \ '-version=' . ale#Escape('SOME_VERSION') . ' ' . + \ ' ' . + \ '-o- -wi -vcolumns -c %t', + \ ale_linters#d#dmd#DMDCommand(bufnr(''), [ + \ 'source', + \ '/prefix/include/d', + \ '/home/user/.dub/packages/pkg-0.0.1/pkg/src', + \ '', + \ '', + \ '', + \ 'SOME_VERSION', + \ '', + \ '', + \ ], {}) + +Execute(DMD command line should be correct): + AssertEqual + \ 'dmd ' . + \ '-I' . ale#Escape('source') . ' ' . + \ '-I' . ale#Escape('/prefix/include/d') . ' ' . + \ '-I' . ale#Escape('/home/user/.dub/packages/pkg-0.0.1/pkg/src') . ' ' . + \ '-J' . ale#Escape('views') . ' ' . + \ '-version=' . ale#Escape('SOME_VERSION') . ' ' . + \ '-version=' . ale#Escape('SOME_OTHER_VERSION') . ' ' . + \ '-debug=' . ale#Escape('SomeFeature') . ' ' . + \ '-o- -wi -vcolumns -c %t', + \ ale_linters#d#dmd#DMDCommand(bufnr(''), [ + \ 'source', + \ '/prefix/include/d', + \ '/home/user/.dub/packages/pkg-0.0.1/pkg/src', + \ '', + \ 'views', + \ '', + \ 'SOME_VERSION', + \ 'SOME_OTHER_VERSION', + \ '', + \ 'SomeFeature', + \ ], {}) + +Execute(DMD command line should be correct with CR): + " on windows, the function is called with carriage return + AssertEqual + \ 'dmd ' . + \ '-I' . ale#Escape('source') . ' ' . + \ '-I' . ale#Escape('C:\prefix\include\d') . ' ' . + \ '-I' . ale#Escape('C:\Users\user\AppData\Local\Dub\packages\pkg-0.0.1\pkg\src') . ' ' . + \ ' ' . + \ ' ' . + \ ' ' . + \ '-o- -wi -vcolumns -c %t', + \ ale_linters#d#dmd#DMDCommand(bufnr(''), [ + \ "source\r", + \ "C:\\prefix\\include\\d\r", + \ "C:\\Users\\user\\AppData\\Local\\Dub\\packages\\pkg-0.0.1\\pkg\\src\r", + \ "\r", + \ "\r", + \ "\r", + \ "\r", + \ "\r", + \ "\r", + \ ], {}) diff --git a/test/linter/test_dockerfile_lint.vader b/test/linter/test_dockerfile_lint.vader new file mode 100644 index 00000000..abc32e0d --- /dev/null +++ b/test/linter/test_dockerfile_lint.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('dockerfile', 'dockerfile_lint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'dockerfile_lint', ale#Escape('dockerfile_lint') . ' -p -j -f %t' + +Execute(The executable should be configurable): + let b:ale_dockerfile_dockerfile_lint_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -p -j -f %t' + +Execute(The options should be configurable): + let b:ale_dockerfile_dockerfile_lint_options = '-r additional.yaml' + + AssertLinter 'dockerfile_lint', ale#Escape('dockerfile_lint') . ' -r additional.yaml -p -j -f %t' + diff --git a/test/linter/test_dockerlinter.vader b/test/linter/test_dockerlinter.vader new file mode 100644 index 00000000..a7077221 --- /dev/null +++ b/test/linter/test_dockerlinter.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('dockerfile', 'dockerlinter') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'dockerlinter', ale#Escape('dockerlinter') . ' -j -f %t' + +Execute(The executable should be configurable): + let b:ale_dockerfile_dockerlinter_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -j -f %t' + +Execute(The options should be configurable): + let b:ale_dockerfile_dockerlinter_options = '-r additional.yaml' + + AssertLinter 'dockerlinter', ale#Escape('dockerlinter') . ' -r additional.yaml -j -f %t' + diff --git a/test/linter/test_dogma.vader b/test/linter/test_dogma.vader new file mode 100644 index 00000000..c8b599af --- /dev/null +++ b/test/linter/test_dogma.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('elixir', 'dogma') + call ale#test#SetFilename('../test-files/elixir/mix_project/lib/app.ex') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Builds dogma command with a normal project): + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/mix_project') + AssertLinter 'mix', 'mix help dogma && mix dogma %s --format=flycheck' + +Execute(Builds dogma command with an umbrella project): + call ale#test#SetFilename('../test-files/elixir/umbrella_project/apps/mix_project/lib/app.ex') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/umbrella_project') + AssertLinter 'mix', 'mix help dogma && mix dogma %s --format=flycheck' diff --git a/test/linter/test_eclipselsp.vader b/test/linter/test_eclipselsp.vader new file mode 100644 index 00000000..393621bd --- /dev/null +++ b/test/linter/test_eclipselsp.vader @@ -0,0 +1,111 @@ +Before: + call ale#assert#SetUpLinterTest('java', 'eclipselsp') + call ale#test#SetFilename('dummy.java') + + let b:ale_java_eclipselsp_path = '/home/user/eclipse.dst.ls' + + if has('win32') + let b:cfg = ale#path#Simplify(g:dir . '/../config_win') + elseif has('macunix') + let b:cfg = ale#path#Simplify(g:dir . '/../config_mac') + else + let b:cfg = ale#path#Simplify(g:dir . '/../config_linux') + endif + +After: + unlet! b:ale_java_eclipselsp_path + unlet! b:cfg + + 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', + \ ale#Escape(''), + \ '-configuration', + \ ale#Escape(b:cfg), + \ '-data', + \ ale#Escape(ale#path#Simplify('')) + \] + 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', + \ ale#Escape(''), + \ '-configuration', + \ ale#Escape(b:cfg), + \ '-data', + \ ale#Escape(ale#path#Simplify('')) + \] + AssertLinter '/bin/foobar', join(cmd, ' ') + +Execute(The eclipselsp callback should allow custom configuration path and javaagent): + let b:ale_java_eclipselsp_config_path = '/home/config' + let b:ale_java_eclipselsp_javaagent = '/home/lombok.jar /home/lombok2.jar' + let cmd = [ ale#Escape('java'), + \ ale#Escape('-javaagent:/home/lombok.jar'), + \ ale#Escape('-javaagent:/home/lombok2.jar'), + \ '-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#Escape(''), + \ '-configuration', + \ ale#Escape(ale#path#Simplify('/home/config')), + \ '-data', + \ ale#Escape(ale#path#Simplify('')) + \] + AssertLinter 'java', join(cmd, ' ') diff --git a/test/linter/test_elixir_credo.vader b/test/linter/test_elixir_credo.vader new file mode 100644 index 00000000..9c639c57 --- /dev/null +++ b/test/linter/test_elixir_credo.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('elixir', 'credo') + call ale#test#SetFilename('../test-files/elixir/mix_project/lib/app.ex') + + +After: + unlet! g:ale_elixir_credo_strict + + call ale#assert#TearDownLinterTest() + +Execute(Builds credo command with normal project): + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/mix_project') + AssertLinter 'mix', + \ 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' + +Execute(Builds credo command with umbrella project): + call ale#test#SetFilename('../test-files/elixir/umbrella_project/apps/mix_project/lib/app.ex') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/umbrella_project') + AssertLinter 'mix', + \ 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' + +Execute(Builds credo command with --strict mode when set to 1): + let g:ale_elixir_credo_strict = 1 + + AssertLinter 'mix', + \ 'mix help credo && mix credo --strict --format=flycheck --read-from-stdin %s' + +Execute(Builds credo command with suggest mode by default): + AssertLinter 'mix', + \ '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', + \ 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' + +Execute(Builds credo command with a custom config file): + let g:ale_elixir_credo_config_file = '/home/user/custom_credo.exs' + + AssertLinter 'mix', + \ 'mix help credo && mix credo suggest --config-file /home/user/custom_credo.exs --format=flycheck --read-from-stdin %s' diff --git a/test/linter/test_elixir_ls.vader b/test/linter/test_elixir_ls.vader new file mode 100644 index 00000000..84e805ba --- /dev/null +++ b/test/linter/test_elixir_ls.vader @@ -0,0 +1,34 @@ +Before: + call ale#assert#SetUpLinterTest('elixir', 'elixir_ls') + +After: + call ale#assert#TearDownLinterTest() + +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' + + 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('../test-files/elixir/umbrella_project/apps/app1/lib/app.ex') + + AssertLSPLanguage 'elixir' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/elixir/umbrella_project') + +Execute(should accept configuration settings): + AssertLSPConfig {} + let b:ale_elixir_elixir_ls_config = {'elixirLS': {'dialyzerEnabled': v:false}} + AssertLSPConfig {'elixirLS': {'dialyzerEnabled': v:false}} diff --git a/test/linter/test_elixir_mix.vader b/test/linter/test_elixir_mix.vader new file mode 100644 index 00000000..a04bee55 --- /dev/null +++ b/test/linter/test_elixir_mix.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('elixir', 'mix') + call ale#test#SetFilename('../test-files/elixir/mix_project/lib/app.ex') + let g:env_prefix = ale#Env('MIX_BUILD_PATH', 'TEMP_DIR') + +After: + unlet! g:env_prefix + + call ale#assert#TearDownLinterTest() + +Execute(The default mix command should be correct): + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/mix_project') + AssertLinter 'mix', g:env_prefix . 'mix compile %s' + +Execute(Build mix commands with an umbrella root): + call ale#test#SetFilename('../test-files/elixir/umbrella_project/apps/mix_project/lib/app.ex') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elixir/umbrella_project') + AssertLinter 'mix', g:env_prefix . 'mix compile %s' diff --git a/test/linter/test_elm_ls.vader b/test/linter/test_elm_ls.vader new file mode 100644 index 00000000..69ae2170 --- /dev/null +++ b/test/linter/test_elm_ls.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('elm', 'ls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + call ale#test#SetFilename('../test-files/elm/newapp/src/Main.elm') + + AssertLinter 'elm-language-server', ale#Escape('elm-language-server') . ' --stdio' + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/elm/newapp/src/Main.elm') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/elm/newapp') + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('../test-files/elm/newapp/src/Main.elm') + + let g:ale_elm_ls_executable = '/path/to/custom/elm-language-server' + let g:ale_elm_ls_use_global = 1 + + AssertLinter '/path/to/custom/elm-language-server', + \ ale#Escape('/path/to/custom/elm-language-server') . ' --stdio' + +Execute(The language should be correct): + AssertLSPLanguage 'elm' diff --git a/test/linter/test_elm_make.vader b/test/linter/test_elm_make.vader new file mode 100644 index 00000000..90e0c920 --- /dev/null +++ b/test/linter/test_elm_make.vader @@ -0,0 +1,63 @@ +Before: + call ale#assert#SetUpLinterTest('elm', 'make') + +After: + unlet! g:executable + + call ale#assert#TearDownLinterTest() + +Execute(should get valid executable with default params): + call ale#test#SetFilename('../test-files/elm/newapp/src/Main.elm') + + let g:executable = ale#path#Simplify(g:dir . '/../test-files/elm/newapp/node_modules/.bin/elm') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elm/newapp') + AssertLinter g:executable, + \ 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('../test-files/elm/newapp/tests/TestSuite.elm') + + let g:executable = ale#path#Simplify(g:dir . '/../test-files/elm/newapp/node_modules/.bin/elm-test') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elm/newapp') + AssertLinter g:executable, + \ ale#Escape(g:executable) . ' make --report=json --output=/dev/null --compiler ' + \ . ale#path#Simplify(g:dir . '/../test-files/elm/newapp/node_modules/.bin/elm') . ' %t' + +Execute(should fallback to elm executable with elm >= 0.19): + call ale#test#SetFilename('../test-files/elm/newapp-notests/tests/TestMain.elm') + + let g:executable = ale#path#Simplify(g:dir . '/../test-files/elm/newapp-notests/node_modules/.bin/elm') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elm/newapp-notests') + AssertLinter g:executable, + \ 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('../test-files/elm/oldapp/tests/TestSuite.elm') + + let g:executable = ale#path#Simplify(g:dir . '/../test-files/elm/oldapp/node_modules/.bin/elm') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elm/oldapp') + AssertLinter g:executable, + \ 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('../test-files/elm/newapp/src/Main.elm') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elm/newapp') + AssertLinter 'elm', + \ 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('../test-files/elm/newapp/src/Main.elm') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/elm/newapp') + AssertLinter 'other-elm', + \ ale#Escape('other-elm') . ' make --report=json --output=/dev/null %t' diff --git a/test/linter/test_embertemplatelint.vader b/test/linter/test_embertemplatelint.vader new file mode 100644 index 00000000..4efb0f0a --- /dev/null +++ b/test/linter/test_embertemplatelint.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('handlebars', 'embertemplatelint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Runs the right command for ember-template-lint >= 4.x): + GivenCommandOutput ['4.0.0'] + + AssertLinter 'ember-template-lint', + \ ale#Escape('ember-template-lint') . ' --format=json --filename %s' + +Execute(Runs the right command for ember-template-lint < 4.x): + GivenCommandOutput ['3.14.0'] + + AssertLinter 'ember-template-lint', + \ ale#Escape('ember-template-lint') . ' --json --filename %s' diff --git a/test/linter/test_erb.vader b/test/linter/test_erb.vader new file mode 100644 index 00000000..4adffcba --- /dev/null +++ b/test/linter/test_erb.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('eruby', 'erb') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should not contain any filter code by default): + call ale#test#SetFilename('../test-files/ruby/not_a_rails_app/file.rb') + + AssertLinter 'erb', 'erb -P -T - -x %t | ruby -c' + +Execute(Executable should filter invalid eRuby when inside a Rails project): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/app/views/my_great_view.html.erb') + + AssertLinter 'erb', + \ 'ruby -r erb -e ' . ale#Escape('puts ERB.new($stdin.read.gsub(%{<%=},%{<%}), trim_mode: %{-}).src') . '< %t | ruby -c' diff --git a/test/linter/test_erblint.vader b/test/linter/test_erblint.vader new file mode 100644 index 00000000..147b5965 --- /dev/null +++ b/test/linter/test_erblint.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpLinterTest('eruby', 'erblint') + call ale#test#SetFilename('dummy.html.erb') + + let g:ale_eruby_erblint_executable = 'erblint' + let g:ale_eruby_erblint_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to erblint): + AssertLinter 'erblint', ale#Escape('erblint') + \ . ' --format json --stdin %s' + +Execute(Should be able to set a custom executable): + let g:ale_eruby_erblint_executable = 'bin/erblint' + + AssertLinter 'bin/erblint' , ale#Escape('bin/erblint') + \ . ' --format json --stdin %s' + +Execute(Setting bundle appends 'exec erblint'): + let g:ale_eruby_erblint_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec erblint' + \ . ' --format json --stdin %s' diff --git a/test/linter/test_erlang_dialyzer.vader b/test/linter/test_erlang_dialyzer.vader new file mode 100644 index 00000000..5e818d7f --- /dev/null +++ b/test/linter/test_erlang_dialyzer.vader @@ -0,0 +1,45 @@ +Before: + call ale#assert#SetUpLinterTest('erlang', 'dialyzer') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct.): + AssertLinter 'dialyzer', + \ ale#Escape('dialyzer') + \ . ' -n --plt ' . ale#Escape(expand('$HOME/.dialyzer_plt')) + \ . ' -Wunmatched_returns' + \ . ' -Werror_handling' + \ . ' -Wrace_conditions' + \ . ' -Wunderspecs' + \ . ' %s' + +Execute(The command should accept configured executable.): + let b:ale_erlang_dialyzer_executable = '/usr/bin/dialyzer' + AssertLinter '/usr/bin/dialyzer', + \ ale#Escape('/usr/bin/dialyzer') + \ . ' -n --plt ' . ale#Escape(expand('$HOME/.dialyzer_plt')) + \ . ' -Wunmatched_returns' + \ . ' -Werror_handling' + \ . ' -Wrace_conditions' + \ . ' -Wunderspecs' + \ . ' %s' + +Execute(The command should accept configured options.): + let b:ale_erlang_dialyzer_options = '-r ' . expand('$HOME') + AssertLinter 'dialyzer', + \ ale#Escape('dialyzer') + \ . ' -n --plt ' . ale#Escape(expand('$HOME/.dialyzer_plt')) + \ . ' -r ' . expand('$HOME') + \ . ' %s' + +Execute(The command should accept configured PLT file.): + let b:ale_erlang_dialyzer_plt_file = 'custom-plt' + AssertLinter 'dialyzer', + \ ale#Escape('dialyzer') + \ . ' -n --plt ' . ale#Escape(expand('custom-plt')) + \ . ' -Wunmatched_returns' + \ . ' -Werror_handling' + \ . ' -Wrace_conditions' + \ . ' -Wunderspecs' + \ . ' %s' diff --git a/test/linter/test_erlang_elvis.vader b/test/linter/test_erlang_elvis.vader new file mode 100644 index 00000000..315bee15 --- /dev/null +++ b/test/linter/test_erlang_elvis.vader @@ -0,0 +1,52 @@ +Before: + call ale#assert#SetUpLinterTest('erlang', 'elvis') + +After: + unlet! b:root + + call ale#assert#TearDownLinterTest() + +Execute(Default command should be correct): + AssertLinter 'elvis', + \ ale#Escape('elvis') . ' rock --output-format=parsable ' + \ . ale#Escape(expand('%:.')) + +Execute(Executable should be configurable): + let b:ale_erlang_elvis_executable = '/path/to/elvis' + + AssertLinter '/path/to/elvis', + \ ale#Escape('/path/to/elvis') . ' rock --output-format=parsable ' + \ . ale#Escape(expand('%:.')) + +Execute(Project root should be detected using elvis.config): + let b:root = '../test-files/erlang/app_with_elvis_config' + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertLinter 'elvis', + \ ale#Escape('elvis') . ' rock --output-format=parsable ' + \ . ale#Escape(ale#path#Simplify('src/app.erl')) + AssertLinterCwd ale#test#GetFilename(b:root) + +Execute(Root of Rebar3 project should be detected): + let b:root = '../test-files/erlang/rebar3_app' + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertLinter 'elvis', + \ ale#Escape('elvis') . ' rock --output-format=parsable ' + \ . ale#Escape(ale#path#Simplify('src/app.erl')) + AssertLinterCwd ale#test#GetFilename(b:root) + + call ale#test#SetFilename(b:root . '/_checkouts/dep/src/dep.erl') + AssertLinter 'elvis', + \ ale#Escape('elvis') . ' rock --output-format=parsable ' + \ . ale#Escape(ale#path#Simplify('src/dep.erl')) + AssertLinterCwd ale#test#GetFilename(b:root . '/_checkouts/dep') + +Execute(Root of Erlang.mk project should be detected): + let b:root = '../test-files/erlang/erlang_mk_app' + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertLinter 'elvis', + \ ale#Escape('elvis') . ' rock --output-format=parsable ' + \ . ale#Escape(ale#path#Simplify('src/app.erl')) + AssertLinterCwd ale#test#GetFilename(b:root) diff --git a/test/linter/test_erlang_erlang_ls.vader b/test/linter/test_erlang_erlang_ls.vader new file mode 100644 index 00000000..2a3d7a70 --- /dev/null +++ b/test/linter/test_erlang_erlang_ls.vader @@ -0,0 +1,70 @@ +Before: + call ale#assert#SetUpLinterTest('erlang', 'erlang_ls') + +After: + unlet! b:root + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'erlang_ls', + \ ale#Escape('erlang_ls') . ' --log-level=' . ale#Escape('info') + +Execute(Executable should be configurable): + let b:ale_erlang_erlang_ls_executable = '/path/to/erlang_ls' + + AssertLinter '/path/to/erlang_ls', + \ ale#Escape('/path/to/erlang_ls') . ' --log-level=' . ale#Escape('info') + +Execute(Log level should be configurable): + let b:ale_erlang_erlang_ls_log_level = 'debug' + + AssertLinter 'erlang_ls', + \ ale#Escape('erlang_ls') . ' --log-level=' . ale#Escape('debug') + +Execute(Log directory should be configurable): + let b:ale_erlang_erlang_ls_log_dir = '/path/to/logs' + + AssertLinter 'erlang_ls', + \ ale#Escape('erlang_ls') + \ . ' --log-dir=' . ale#Escape('/path/to/logs') + \ . ' --log-level=' . ale#Escape('info') + +Execute(Project root should be detected using erlang_ls.config): + let b:root = '../test-files/erlang/app_with_erlang_ls_config' + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertLSPProject ale#test#GetFilename(b:root) + + call ale#test#SetFilename(b:root . '/_build/default/lib/dep/src/dep.erl') + AssertLSPProject ale#test#GetFilename(b:root) + + call ale#test#SetFilename(b:root . '/deps/dep/src/dep.erl') + AssertLSPProject ale#test#GetFilename(b:root) + +Execute(Root of Rebar3 project should be detected): + let b:root = '../test-files/erlang/rebar3_app' + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertLSPProject ale#test#GetFilename(b:root) + + call ale#test#SetFilename(b:root . '/_build/default/lib/dep/src/dep.erl') + AssertLSPProject ale#test#GetFilename(b:root) + + call ale#test#SetFilename(b:root . '/_checkouts/dep/src/dep.erl') + AssertLSPProject ale#test#GetFilename(b:root) + +Execute(Root of Erlang.mk project should be detected): + let b:root = '../test-files/erlang/erlang_mk_app' + + call ale#test#SetFilename(b:root . '/src/app.erl') + AssertLSPProject ale#test#GetFilename(b:root) + + call ale#test#SetFilename(b:root . '/deps/dep/src/dep.erl') + AssertLSPProject ale#test#GetFilename(b:root) + +Execute(Root of kerl managed Erlang/OTP installation should be detected): + let b:root = '../test-files/erlang/kerl_otp_root' + + call ale#test#SetFilename(b:root . '/lib/stdlib-4.1.1/array.erl') + AssertLSPProject ale#test#GetFilename(b:root) diff --git a/test/linter/test_erlang_erlc.vader b/test/linter/test_erlang_erlc.vader new file mode 100644 index 00000000..0651b512 --- /dev/null +++ b/test/linter/test_erlang_erlc.vader @@ -0,0 +1,62 @@ +Before: + call ale#assert#SetUpLinterTest('erlang', 'erlc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct.): + let g:cmd = ale_linters#erlang#erlc#GetCommand(bufnr('')) + let g:regex = 'erlc.\+-o.\+%t' + let g:matched = match(g:cmd, g:regex) + + " match returns -1 if not found + AssertNotEqual + \ g:matched, + \ -1, + \ 'Command error: expected [' . g:cmd . '] to match [' . g:regex . ']' + +Execute(The command should accept configured executable.): + let b:ale_erlang_erlc_executable = '/usr/bin/erlc' + let g:cmd = ale_linters#erlang#erlc#GetCommand(bufnr('')) + let g:regex = '/usr/bin/erlc.\+-o.\+%t' + let g:matched = match(g:cmd, g:regex) + + " match returns -1 if not found + AssertNotEqual + \ g:matched, + \ -1, + \ 'Command error: expected [' . g:cmd . '] to match [' . g:regex . ']' + +Execute(The command should accept configured options.): + let b:ale_erlang_erlc_options = '-I include' + let g:cmd = ale_linters#erlang#erlc#GetCommand(bufnr('')) + let g:regex = 'erlc.\+-o.\+-I include.\+%t' + let g:matched = match(g:cmd, g:regex) + + " match returns -1 if not found + AssertNotEqual + \ g:matched, + \ -1, + \ 'Command error: expected [' . g:cmd . '] to match [' . g:regex . ']' + +Execute(Linter should recognize OTP23 format.): + let g:lines = ["t.erl:6: only association operators '=>' are allowed in map construction"] + let g:output_text = ale_linters#erlang#erlc#Handle(bufnr(''), g:lines)[0].text + + let g:expected = "only association operators '=>' are allowed in map construction" + AssertEqual + \ g:output_text, + \ g:expected, + \ 'Command error: expected [' . g:output_text . '] to match [' . g:expected . ']' + +Execute(Linter should recognize OTP24 format.): + let g:lines = ["t.erl:6:16: only association operators '=>' are allowed in map construction", + \ "% 6| #{ a => A, b := B }.", + \ "% | ^"] + let g:output_text = ale_linters#erlang#erlc#Handle(bufnr(''), g:lines)[0].text + + let g:expected = "only association operators '=>' are allowed in map construction" + AssertEqual + \ g:output_text, + \ g:expected, + \ 'Command error: expected [' . g:output_text . '] to match [' . g:expected . ']' diff --git a/test/linter/test_erlang_syntaxerl.vader b/test/linter/test_erlang_syntaxerl.vader new file mode 100644 index 00000000..52218667 --- /dev/null +++ b/test/linter/test_erlang_syntaxerl.vader @@ -0,0 +1,45 @@ +Before: + call ale#assert#SetUpLinterTest('erlang', 'syntaxerl') + +After: + call ale#assert#TearDownLinterTest() + +Execute (The default commands should be correct): + AssertLinter 'syntaxerl', [ + \ ale#Escape('syntaxerl') . ' -h', + \ ale#Escape('syntaxerl') . ' %t', + \] + +Execute (The executable should be configurable): + let b:ale_erlang_syntaxerl_executable = '/path/to/syntaxerl' + + AssertLinter '/path/to/syntaxerl', [ + \ ale#Escape('/path/to/syntaxerl') . ' -h', + \ ale#Escape('/path/to/syntaxerl') . ' %t', + \] + +Execute (The -b option should be used when available): + GivenCommandOutput [ + \ 'Syntax checker for Erlang (0.14.0)', + \ 'Usage: syntaxerl [-d | --debug] ', + \ ' syntaxerl <-h | --help>', + \ ' -d, --debug Enable debug output', + \ ' -h, --help Show this message', + \] + AssertLinter 'syntaxerl', [ + \ ale#Escape('syntaxerl') . ' -h', + \ ale#Escape('syntaxerl') . ' %t', + \] + + GivenCommandOutput [ + \ 'Syntax checker for Erlang (0.14.0)', + \ 'Usage: syntaxerl [-b | --base ] [-d | --debug] ', + \ ' syntaxerl <-h | --help>', + \ ' -b, --base Set original filename', + \ ' -d, --debug Enable debug output', + \ ' -h, --help Show this message', + \] + AssertLinter 'syntaxerl', [ + \ ale#Escape('syntaxerl') . ' -h', + \ ale#Escape('syntaxerl') . ' -b %s %t', + \] diff --git a/test/linter/test_erubi.vader b/test/linter/test_erubi.vader new file mode 100644 index 00000000..cd4a0b68 --- /dev/null +++ b/test/linter/test_erubi.vader @@ -0,0 +1,32 @@ +Before: + call ale#assert#SetUpLinterTest('eruby', 'erubi') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should not contain any filter code by default): + call ale#test#SetFilename('../test-files/ruby/not_a_rails_app/file.rb') + + AssertLinter 'ruby', [ + \ 'ruby -r erubi/capture_end -e ' . ale#Escape('""'), + \ 'ruby -r erubi/capture_end -e ' . ale#Escape('puts Erubi::CaptureEndEngine.new($stdin.read).src') . '< %t | ruby -c', + \] + +Execute(Executable should filter invalid eRuby when inside a Rails project): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/app/views/my_great_view.html.erb') + + AssertLinter 'ruby', [ + \ 'ruby -r erubi/capture_end -e ' . ale#Escape('""'), + \ 'ruby -r erubi/capture_end -e ' . ale#Escape('puts Erubi::CaptureEndEngine.new($stdin.read.gsub(%{<%=},%{<%}), nil, %{-}).src') . '< %t | ruby -c', + \] + +Execute(Command should be blank if the first command in the chain returns output): + 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'", + \] + + AssertLinter 'ruby', [ + \ 'ruby -r erubi/capture_end -e ' . ale#Escape('""'), + \ '', + \] diff --git a/test/linter/test_erubis.vader b/test/linter/test_erubis.vader new file mode 100644 index 00000000..cfca54a2 --- /dev/null +++ b/test/linter/test_erubis.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('eruby', 'erubis') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should not contain any filter code by default): + call ale#test#SetFilename('../test-files/ruby/not_a_rails_app/file.rb') + + AssertLinter 'erubis', 'erubis -x %t | ruby -c' + +Execute(Executable should filter invalid eRuby when inside a Rails project): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/app/views/my_great_view.html.erb') + + AssertLinter 'erubis', + \ 'ruby -r erubis -e ' . ale#Escape('puts Erubis::Eruby.new($stdin.read.gsub(%{<%=},%{<%})).src') . '< %t | ruby -c' diff --git a/test/linter/test_eslint.vader b/test/linter/test_eslint.vader new file mode 100644 index 00000000..6c05fc6d --- /dev/null +++ b/test/linter/test_eslint.vader @@ -0,0 +1,87 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'eslint') + runtime autoload/ale/handlers/eslint.vim + + let b:args = ' -f json --stdin --stdin-filename %s' + +After: + unlet! b:args + unlet! b:executable + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinterCwd '' + AssertLinter 'eslint', ale#Escape('eslint') . b:args + +Execute(create-react-app directories should be detected correctly): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir/testfile.js') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js') + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint/react-app') + AssertLinter b:executable, + \ (has('win32') ? ale#Escape('node.exe') . ' ' : '') + \ . ale#Escape(b:executable) . b:args + +Execute(use-global should override create-react-app detection): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir/testfile.js') + + let g:ale_javascript_eslint_use_global = 1 + let g:ale_javascript_eslint_executable = 'eslint_d' + + let b:executable = 'eslint_d' + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint/react-app') + AssertLinter b:executable, ale#Escape(b:executable) . b:args + +Execute(other app directories should be detected correctly): + call ale#test#SetFilename('../test-files/eslint/other-app/subdir/testfile.js') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/eslint/node_modules/.bin/eslint') + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint') + AssertLinter b:executable, ale#Escape(b:executable) . b:args + +Execute(use-global should override other app directories): + call ale#test#SetFilename('../test-files/eslint/other-app/subdir/testfile.js') + + let g:ale_javascript_eslint_use_global = 1 + let g:ale_javascript_eslint_executable = 'eslint_d' + + let b:executable = 'eslint_d' + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint') + AssertLinter b:executable, ale#Escape(b:executable) . b:args + +Execute(eslint.js executables should be run with node on Windows): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir/testfile.js') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js') + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint/react-app') + AssertLinter b:executable, + \ (has('win32') ? ale#Escape('node.exe') . ' ' : '') + \ . ale#Escape(b:executable) . b:args + +Execute(eslint.js should be run from a containing project with node_modules): + call ale#test#SetFilename('../test-files/eslint/react-app/subdir-with-package-json/testfile.js') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/eslint/react-app/node_modules/eslint/bin/eslint.js') + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint/react-app') + AssertLinter b:executable, + \ (has('win32') ? ale#Escape('node.exe') . ' ' : '') + \ . ale#Escape(b:executable) . b:args + +Execute(eslint.js should be run from a containing project with .yarn/sdks): + call ale#test#SetFilename('../test-files/eslint/yarn2-app/subdir/testfile.js') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/eslint/yarn2-app/.yarn/sdks/eslint/bin/eslint.js') + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint/yarn2-app') + AssertLinter b:executable, + \ (has('win32') ? ale#Escape('node.exe') . ' ' : '') + \ . ale#Escape(b:executable) . b:args + +Execute(astro directories should be detected correctly): + call ale#test#SetFilename('../test-files/eslint/astro-app/src/pages/index.astro') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/eslint/astro-app/node_modules/eslint/bin/eslint.js') + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/eslint/astro-app') + AssertLinter b:executable, + \ (has('win32') ? ale#Escape('node.exe') . ' ' : '') + \ . ale#Escape(b:executable) . b:args diff --git a/test/linter/test_fecs.vader b/test/linter/test_fecs.vader new file mode 100644 index 00000000..4287d324 --- /dev/null +++ b/test/linter/test_fecs.vader @@ -0,0 +1,9 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'fecs') + runtime autoload/ale/handlers/fecs.vim + +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/linter/test_flake8.vader b/test/linter/test_flake8.vader new file mode 100644 index 00000000..d59c9e06 --- /dev/null +++ b/test/linter/test_flake8.vader @@ -0,0 +1,226 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'flake8') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + + GivenCommandOutput ['3.0.0'] + +After: + unlet! b:executable + unlet! b:bin_dir + call ale#assert#TearDownLinterTest() + +Execute(The flake8 callbacks should return the correct default values): + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + + " The version check should be cached. + GivenCommandOutput [] + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + + " Try with older versions. + call ale#semver#ResetVersionCache() + GivenCommandOutput ['2.9.9'] + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#Escape('flake8') . ' --format=default -', + \] + +Execute(The option for disabling changing directories should work): + let g:ale_python_flake8_change_directory = 'off' + + AssertLinterCwd ['', ''] + call ale#semver#ResetVersionCache() + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + + let g:ale_python_flake8_change_directory = 0 + + AssertLinterCwd [''] + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + + " Invalid options should be considered the same as turning the setting off. + let g:ale_python_flake8_change_directory = 'xxx' + + AssertLinterCwd [''] + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + +Execute(The option for changing directory to project root should work): + call ale#test#SetFilename('../test-files/python/namespace_package_tox/namespace/foo/bar.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + call ale#semver#ResetVersionCache() + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + +Execute(The option for changing directory to file dir should work): + let g:ale_python_flake8_change_directory = 'file' + call ale#test#SetFilename('../test-files/python/namespace_package_tox/namespace/foo/bar.py') + + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + + let g:ale_python_flake8_change_directory = 1 + + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + +Execute(The flake8 command callback should let you set options): + let g:ale_python_flake8_options = '--some-option' + + GivenCommandOutput ['3.0.4'] + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#Escape('flake8') . ' --some-option' + \ . ' --format=default --stdin-display-name %s -', + \] + + call ale#semver#ResetVersionCache() + GivenCommandOutput ['2.9.9'] + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#Escape('flake8') . ' --some-option --format=default -', + \] + +Execute(You should be able to set a custom executable and it should be escaped): + call ale#test#SetFilename('../test-files/dummy') + + let g:ale_python_flake8_executable = 'executable with spaces' + + AssertLinterCwd ['%s:h', '%s:h'] + call ale#semver#ResetVersionCache() + AssertLinter 'executable with spaces', [ + \ ale#Escape('executable with spaces') . ' --version', + \ ale#Escape('executable with spaces') + \ . ' --format=default' + \ . ' --stdin-display-name %s -', + \] + +Execute(The flake8 callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/flake8' + \) + + AssertLinter b:executable, [ + \ ale#Escape(b:executable) . ' --version', + \ ale#Escape(b:executable) + \ . ' --format=default' + \ . ' --stdin-display-name %s -', + \] + +Execute(The FindProjectRoot should detect the project root directory for namespace package via Manifest.in): + call ale#test#SetFilename('../test-files/python/namespace_package_manifest/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_manifest'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via setup.cf): + call ale#test#SetFilename('../test-files/python/namespace_package_setup/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_setup'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via pytest.ini): + call ale#test#SetFilename('../test-files/python/namespace_package_pytest/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_pytest'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via tox.ini): + call ale#test#SetFilename('../test-files/python/namespace_package_tox/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_tox'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for non-namespace package): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir'), + \ ale#python#FindProjectRoot(bufnr('')) + +" Some users currently run flake8 this way, so we should support it. +Execute(Using `python -m flake8` should be supported for running flake8): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_flake8_executable = 'python' + let g:ale_python_flake8_options = '-m flake8 --some-option' + + GivenCommandOutput ['2.9.9'] + AssertLinter 'python', [ + \ ale#Escape('python') . ' -m flake8 --version', + \ ale#Escape('python') + \ . ' -m flake8 --some-option --format=default -' + \] + + call ale#semver#ResetVersionCache() + + " Leading spaces shouldn't matter + let g:ale_python_flake8_options = ' -m flake8 --some-option' + + GivenCommandOutput ['2.9.9'] + AssertLinter 'python', [ + \ ale#Escape('python') . ' -m flake8 --version', + \ ale#Escape('python') + \ . ' -m flake8 --some-option --format=default -' + \] + +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. + GivenCommandOutput [] + AssertLinter 'path/to/pipenv', + \ 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('../test-files/python/pipenv/whatever.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run flake8 --format=default --stdin-display-name %s -' + +Execute(Setting executable to 'poetry' should append 'run flake8'): + let g:ale_python_flake8_executable = 'path/to/poetry' + + " FIXME: poetry should check the version with flake8. + GivenCommandOutput [] + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run flake8 --format=default -' + +Execute(poetry is detected when python_flake8_auto_poetry is set): + let g:ale_python_flake8_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run flake8 --format=default --stdin-display-name %s -' + +Execute(uv is detected when python_flake8_auto_uv is set): + let g:ale_python_flake8_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run flake8 --format=default --stdin-display-name %s -' diff --git a/test/linter/test_flakehell.vader b/test/linter/test_flakehell.vader new file mode 100644 index 00000000..8a159f0d --- /dev/null +++ b/test/linter/test_flakehell.vader @@ -0,0 +1,210 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'flakehell') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + + GivenCommandOutput ['0.8.0'] + +After: + unlet! b:executable + unlet! b:bin_dir + call ale#assert#TearDownLinterTest() + +Execute(The flakehell callbacks should return the correct default values): + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' --version', + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + + " The version check should be cached. + GivenCommandOutput [] + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + +Execute(The option for disabling changing directories should work): + let g:ale_python_flakehell_change_directory = 'off' + + AssertLinterCwd ['', ''] + call ale#semver#ResetVersionCache() + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' --version', + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + + let g:ale_python_flakehell_change_directory = 0 + + AssertLinterCwd [''] + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + + " Invalid options should be considered the same as turning the setting off. + let g:ale_python_flakehell_change_directory = 'xxx' + + AssertLinterCwd [''] + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + +Execute(The option for changing directory to project root should work): + call ale#test#SetFilename('../test-files/python/namespace_package_tox/namespace/foo/bar.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + call ale#semver#ResetVersionCache() + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' --version', + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + +Execute(The option for changing directory to file dir should work): + let g:ale_python_flakehell_change_directory = 'file' + call ale#test#SetFilename('../test-files/python/namespace_package_tox/namespace/foo/bar.py') + + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' --version', + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + + let g:ale_python_flakehell_change_directory = 1 + + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' lint --format=default --stdin-display-name %s -', + \] + +Execute(The flakehell command callback should let you set options): + let g:ale_python_flakehell_options = '--some-option' + + GivenCommandOutput ['0.8.0'] + AssertLinter 'flakehell', [ + \ ale#Escape('flakehell') . ' --version', + \ ale#Escape('flakehell') . ' lint --some-option' + \ . ' --format=default --stdin-display-name %s -', + \] + +Execute(You should be able to set a custom executable and it should be escaped): + let g:ale_python_flakehell_executable = 'executable with spaces' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinterCwd ['%s:h', '%s:h'] + call ale#semver#ResetVersionCache() + AssertLinter 'executable with spaces', [ + \ ale#Escape('executable with spaces') . ' --version', + \ ale#Escape('executable with spaces') + \ . ' lint' + \ . ' --format=default' + \ . ' --stdin-display-name %s -', + \] + +Execute(The flakehell callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/flakehell' + \) + + AssertLinter b:executable, [ + \ ale#Escape(b:executable) . ' --version', + \ ale#Escape(b:executable) + \ . ' lint' + \ . ' --format=default' + \ . ' --stdin-display-name %s -', + \] + +Execute(The FindProjectRoot should detect the project root directory for namespace package via Manifest.in): + call ale#test#SetFilename('../test-files/python/namespace_package_manifest/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_manifest'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via setup.cf): + call ale#test#SetFilename('../test-files/python/namespace_package_setup/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_setup'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via pytest.ini): + call ale#test#SetFilename('../test-files/python/namespace_package_pytest/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_pytest'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via tox.ini): + call ale#test#SetFilename('../test-files/python/namespace_package_tox/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_tox'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for non-namespace package): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir'), + \ ale#python#FindProjectRoot(bufnr('')) + +" Some users currently run flakehell this way, so we should support it. +Execute(Using `python -m flakehell` should be supported for running flakehell): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_flakehell_executable = 'python' + let g:ale_python_flakehell_options = '--some-option' + + AssertLinter 'python', [ + \ ale#Escape('python') . ' -m flakehell --version', + \ ale#Escape('python') + \ . ' -m flakehell lint --some-option --format=default --stdin-display-name %s -' + \] + + call ale#semver#ResetVersionCache() + + " Leading spaces shouldn't matter + let g:ale_python_flakehell_options = ' --some-option' + + AssertLinter 'python', [ + \ ale#Escape('python') . ' -m flakehell --version', + \ ale#Escape('python') + \ . ' -m flakehell lint --some-option --format=default --stdin-display-name %s -' + \] + +Execute(Setting executable to 'pipenv' should append 'run flakehell'): + let g:ale_python_flakehell_executable = 'path/to/pipenv' + + " FIXME: pipenv should check the version with flakehell. + GivenCommandOutput [] + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run flakehell lint --format=default -' + +Execute(Pipenv is detected when python_flakehell_auto_pipenv is set): + let g:ale_python_flakehell_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run flakehell lint --format=default --stdin-display-name %s -' + +Execute(Setting executable to 'poetry' should append 'run flakehell'): + let g:ale_python_flakehell_executable = 'path/to/poetry' + + " FIXME: poetry should check the version with flakehell. + GivenCommandOutput [] + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run flakehell lint --format=default -' + +Execute(poetry is detected when python_flakehell_auto_poetry is set): + let g:ale_python_flakehell_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run flakehell lint --format=default --stdin-display-name %s -' + +Execute(uv is detected when python_flakehell_auto_uv is set): + let g:ale_python_flakehell_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run flakehell lint --format=default --stdin-display-name %s -' diff --git a/test/linter/test_flow.vader b/test/linter/test_flow.vader new file mode 100644 index 00000000..8488a2e9 --- /dev/null +++ b/test/linter/test_flow.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'flow') + +After: + call ale#assert#TearDownLinterTest() + +Execute(flow should return a command to run if a .flowconfig file exists): + call ale#test#SetFilename('../test-files/flow/a/sub/dummy') + + AssertLinter 'flow', + \ ale#Escape('flow') + \ . ' 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('../test-files/flow/a/sub/dummy') + + let b:ale_javascript_flow_use_respect_pragma = 0 + + AssertLinter 'flow', + \ ale#Escape('flow') + \ . ' 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('../test-files/flow/a/sub/dummy') + + 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 < %t' + \ . (!has('win32') ? '; echo' : ''), + \] + +Execute(flow should not return a command to run if no .flowconfig file exists): + call ale#test#SetFilename('../test-files/flow/b/sub/dummy') + + AssertLinterNotExecuted diff --git a/test/linter/test_foodcritic.vader b/test/linter/test_foodcritic.vader new file mode 100644 index 00000000..b3b9b207 --- /dev/null +++ b/test/linter/test_foodcritic.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('chef', 'foodcritic') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default foodcritic command should be correct): + AssertLinter 'foodcritic', ale#Escape('foodcritic') . ' %s' + +Execute(The foodcritic executable and options should be configurable): + let b:ale_chef_foodcritic_executable = 'foobar' + " Tides should be escaped + let b:ale_chef_foodcritic_options = '-t ~F011' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -t \~F011 %s' diff --git a/test/linter/test_fortran_fortls.vader b/test/linter/test_fortran_fortls.vader new file mode 100644 index 00000000..581f94ba --- /dev/null +++ b/test/linter/test_fortran_fortls.vader @@ -0,0 +1,18 @@ +Before: + call ale#assert#SetUpLinterTest('fortran', 'language_server') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'fortls', ale#Escape('fortls') + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/fortls-project/test.F90') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/fortls-project') + +Execute(The language should be correct): + AssertLSPLanguage 'fortran' diff --git a/test/linter/test_fsc.vader b/test/linter/test_fsc.vader new file mode 100644 index 00000000..278e7c16 --- /dev/null +++ b/test/linter/test_fsc.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('scala', 'fsc') + +After: + call ale#assert#TearDownLinterTest() + +Given scala(An empty Scala file): +Execute(The default executable and command should be correct): + AssertLinter 'fsc', ale#Escape('fsc') . ' -Ystop-after:parser %t' + +Given scala.sbt(An empty SBT file): +Execute(fsc should not be run for sbt files): + AssertLinterNotExecuted diff --git a/test/linter/test_fusionlint.vader b/test/linter/test_fusionlint.vader new file mode 100644 index 00000000..1c63b811 --- /dev/null +++ b/test/linter/test_fusionlint.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('fuse', 'fusionlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The fuse fusionlint command callback should return the correct default string): + AssertLinter 'fusion-lint', ale#Escape('fusion-lint') . ' --filename %s -i' + +Execute(The fuse fusionlint command callback should let you set options): + let g:ale_fuse_fusionlint_options = '--example-option argument' + + AssertLinter 'fusion-lint', + \ ale#Escape('fusion-lint') . ' --example-option argument --filename %s -i' + +Execute(The fusionlint executable should be configurable): + let g:ale_fuse_fusionlint_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --filename %s -i' diff --git a/test/linter/test_gawk.vader b/test/linter/test_gawk.vader new file mode 100644 index 00000000..f4364291 --- /dev/null +++ b/test/linter/test_gawk.vader @@ -0,0 +1,25 @@ +Before: + call ale#assert#SetUpLinterTest('awk', 'gawk') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'gawk', + \ ale#Escape('gawk') . ' --source ' . ale#Escape('BEGIN { exit } END { exit 1 }') + \ . ' --lint -f %t /dev/null' + +Execute(The executable should be configurable): + let b:ale_awk_gawk_executable = '/other/gawk' + + AssertLinter '/other/gawk', + \ ale#Escape('/other/gawk') . ' --source ' . ale#Escape('BEGIN { exit } END { exit 1 }') + \ . ' --lint -f %t /dev/null' + +Execute(The options should be configurable): + let b:ale_awk_gawk_executable = 'gawk' + let b:ale_awk_gawk_options = '--lint=no-ext' + + AssertLinter 'gawk', + \ ale#Escape('gawk') . ' --source ' . ale#Escape('BEGIN { exit } END { exit 1 }') + \ . ' --lint --lint=no-ext -f %t /dev/null' diff --git a/test/linter/test_gfortran.vader b/test/linter/test_gfortran.vader new file mode 100644 index 00000000..4c135de8 --- /dev/null +++ b/test/linter/test_gfortran.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('fortran', 'gcc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default fortran gcc command should be correct): + AssertLinter 'gcc', ale#Escape('gcc') . ' -S -x f95 -fsyntax-only -ffree-form -Wall -' + +Execute(The fortran gcc executable and command should be configurable): + let g:ale_fortran_gcc_executable = 'gfortran' + let g:ale_fortran_gcc_options = '-Wotherthings' + + AssertLinter 'gfortran', ale#Escape('gfortran') + \ . ' -S -x f95 -fsyntax-only -ffree-form -Wotherthings -' + +Execute(The fortran gcc linter should allow you to use -ffixed-form): + let g:ale_fortran_gcc_use_free_form = 0 + + AssertLinter 'gcc', ale#Escape('gcc') . ' -S -x f95 -fsyntax-only -ffixed-form -Wall -' diff --git a/test/linter/test_ghdl.vader b/test/linter/test_ghdl.vader new file mode 100644 index 00000000..f254e11f --- /dev/null +++ b/test/linter/test_ghdl.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/linter/test_gitlint.vader b/test/linter/test_gitlint.vader new file mode 100644 index 00000000..4df675f3 --- /dev/null +++ b/test/linter/test_gitlint.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('gitcommit', 'gitlint') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + + call ale#assert#TearDownLinterTest() + +Execute(The gitlint callbacks should return the correct default values): + AssertLinter 'gitlint', ale#Escape('gitlint') . ' lint' + +Execute(The gitlint executable should be configurable, and escaped properly): + let g:ale_gitcommit_gitlint_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', + \ ale#Escape('executable with spaces') . ' lint' + +Execute(The gitlint command callback should let you set options): + let g:ale_gitcommit_gitlint_options = '--some-option' + + AssertLinter 'gitlint', ale#Escape('gitlint') . ' --some-option lint' + +Execute(The gitlint callbacks shouldn't detect virtualenv directories where they don't exist): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/COMMIT_EDITMSG') + + AssertLinter 'gitlint', ale#Escape('gitlint') . ' lint' + +Execute(The gitlint callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/COMMIT_EDITMSG') + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/gitlint' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' lint' + +Execute(You should able able to use the global gitlint instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/COMMIT_EDITMSG') + let g:ale_gitcommit_gitlint_use_global = 1 + + AssertLinter 'gitlint', ale#Escape('gitlint') . ' lint' diff --git a/test/linter/test_gleam_gleamlsp.vader b/test/linter/test_gleam_gleamlsp.vader new file mode 100644 index 00000000..a054db6b --- /dev/null +++ b/test/linter/test_gleam_gleamlsp.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('gleam', 'gleamlsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'gleam', ale#Escape('gleam') . ' lsp' + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/gleam/gleam.toml') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/gleam') diff --git a/test/linter/test_glslang.vader b/test/linter/test_glslang.vader new file mode 100644 index 00000000..980406af --- /dev/null +++ b/test/linter/test_glslang.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('glsl', 'glslang') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'glslangValidator', ale#Escape('glslangValidator') . ' -C %t' + +Execute(The executable should be configurable): + let b:ale_glsl_glslang_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -C %t' + +Execute(Options should work): + let g:ale_glsl_glslang_options = '--test' + + AssertLinter 'glslangValidator', + \ ale#Escape('glslangValidator') . ' --test -C %t' diff --git a/test/linter/test_glslls.vader b/test/linter/test_glslls.vader new file mode 100644 index 00000000..133c2a2f --- /dev/null +++ b/test/linter/test_glslls.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('glsl', 'glslls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'glslls', ale#Escape('glslls') . ' --stdin' + +Execute(Executable should be configurable): + let b:ale_glsl_glslls_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdin' + +Execute(Setting logfile should work): + let b:ale_glsl_glslls_logfile = '/tmp/test.log' + + AssertLinter 'glslls', + \ ale#Escape('glslls') . ' --verbose -l /tmp/test.log --stdin' diff --git a/test/linter/test_gobuild.vader b/test/linter/test_gobuild.vader new file mode 100644 index 00000000..cd2ca4ef --- /dev/null +++ b/test/linter/test_gobuild.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_go_go_executable + + call ale#assert#SetUpLinterTest('go', 'gobuild') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default go test command should be correct): + AssertLinterCwd '%s:h' + AssertLinter 'go', ale#Escape('go') . ' test -c -o /dev/null ./' + +Execute(Go environment variables should be supported): + let b:ale_go_go111module = 'on' + + AssertLinter 'go', ale#Env('GO111MODULE', 'on') + \ . ale#Escape('go') . ' test -c -o /dev/null ./' + + unlet! b:ale_go_go111module + +Execute(The go test executable and options should be configurable): + let g:ale_go_go_executable = 'foobar' + let g:ale_go_gobuild_options = '--foo-bar' + + AssertLinter 'foobar', ale#Escape('foobar') + \ . ' test --foo-bar -c -o /dev/null ./' diff --git a/test/linter/test_gofmt.vader b/test/linter/test_gofmt.vader new file mode 100644 index 00000000..b056a659 --- /dev/null +++ b/test/linter/test_gofmt.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_go_go111module + Save b:ale_go_go111module + + let b:ale_go_go111module = '' + + call ale#assert#SetUpLinterTest('go', 'gofmt') + call ale#test#SetFilename('../test-files/go/testfile2.go') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The default gofmt command should be correct): + AssertLinter 'gofmt', + \ ale#Escape('gofmt') . ' -e %t' + +Execute(The gofmt command should support Go environment variables): + let b:ale_go_go111module = 'on' + + AssertLinter 'gofmt', + \ ale#Env('GO111MODULE', 'on') + \ . ale#Escape('gofmt') . ' -e %t' diff --git a/test/linter/test_golangci_lint.vader b/test/linter/test_golangci_lint.vader new file mode 100644 index 00000000..1c5a730d --- /dev/null +++ b/test/linter/test_golangci_lint.vader @@ -0,0 +1,74 @@ +Before: + Save g:ale_go_go111module + + call ale#assert#SetUpLinterTest('go', 'golangci_lint') + call ale#test#SetFilename('test.go') + + " Test with version 1.64.8 by default + GivenCommandOutput ['golangci-lint has version 1.64.8 built with go1.23.0'] + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The golangci-lint defaults should be correct): + AssertLinterCwd '%s:h', + AssertLinter 'golangci-lint', + \ ale#Escape('golangci-lint') . ' run --out-format=json --show-stats=0' + +Execute(The golangci-lint defaults should be correct with no version info): + GivenCommandOutput [] + AssertLinterCwd '%s:h', + AssertLinter 'golangci-lint', + \ ale#Escape('golangci-lint') . ' run --out-format=json --show-stats=0' + +Execute(The golangci-lint defaults should be correct with version 2): + GivenCommandOutput ['golangci-lint has version 2.0.2 built with go1.24.0'] + AssertLinterCwd '%s:h', + AssertLinter 'golangci-lint', + \ ale#Escape('golangci-lint') . ' run --output.json.path stdout --output.text.path stderr --show-stats=0' + +Execute(The golangci-lint callback should use a configured executable): + let b:ale_go_golangci_lint_executable = 'something else' + + AssertLinter 'something else', + \ ale#Escape('something else') + \ . ' run --out-format=json --show-stats=0' + +Execute(The golangci-lint callback should use a configured version 2 executable): + GivenCommandOutput ['golangci-lint has version 2.0.0 built with go1.22.0'] + let b:ale_go_golangci_lint_executable = 'something else' + + AssertLinter 'something else', + \ ale#Escape('something else') + \ . ' run --output.json.path stdout --output.text.path stderr --show-stats=0' + +Execute(The golangci-lint callback should use configured options): + let b:ale_go_golangci_lint_options = '--foobar' + + AssertLinter 'golangci-lint', + \ ale#Escape('golangci-lint') + \ . ' run ' + \ . '--foobar ' + \ . '--out-format=json ' + \ . '--show-stats=0' + +Execute(The golangci-lint callback should support environment variables): + let b:ale_go_go111module = 'on' + + AssertLinter 'golangci-lint', + \ ale#Env('GO111MODULE', 'on') + \ . ale#Escape('golangci-lint') + \ . ' run ' + \ . '--out-format=json ' + \ . '--show-stats=0' + +Execute(The golangci-lint `lint_package` option should use the correct command): + let b:ale_go_golangci_lint_package = 0 + AssertLinter 'golangci-lint', + \ ale#Escape('golangci-lint') + \ . ' run ' . ale#Escape(expand('%' . ':t')) + \ . ' --out-format=json --show-stats=0' diff --git a/test/linter/test_golangserver.vader b/test/linter/test_golangserver.vader new file mode 100644 index 00000000..b31d8dc8 --- /dev/null +++ b/test/linter/test_golangserver.vader @@ -0,0 +1,76 @@ +Before: + Save $GOPATH + Save g:ale_completion_enabled + Save g:ale_go_go111module + + let g:ale_completion_enabled = 0 + let g:sep = has('win32') ? ';' : ':' + + call ale#assert#SetUpLinterTest('go', 'langserver') + let $GOPATH = ale#path#Simplify(g:dir . '/../test-files/go/go1') + \ . g:sep + \ . ale#path#Simplify(g:dir . '/../test-files/go/go2') + +After: + Restore + + unlet! b:ale_completion_enabled + unlet! b:ale_go_go111module + unlet! g:sep + + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'go-langserver', ale#Escape('go-langserver') + +Execute(should configure go-langserver callback executable): + let b:ale_go_langserver_executable = 'boo' + + AssertLinter 'boo', ale#Escape('boo') + +Execute(should set go-langserver options): + call ale#test#SetFilename('../test-files/go/go1/prj1/file.go') + let b:ale_completion_enabled = 1 + let b:ale_go_langserver_options = '' + + AssertLinter 'go-langserver', + \ ale#Escape('go-langserver') . ' -gocodecompletion' + + let b:ale_go_langserver_options = '-trace' + + AssertLinter 'go-langserver', + \ ale#Escape('go-langserver') . ' -gocodecompletion -trace' + +Execute(should ignore go-langserver -gocodecompletion option): + call ale#test#SetFilename('../test-files/go/go1/prj1/file.go') + + let b:ale_go_langserver_options = '-trace -gocodecompletion' + let b:ale_completion_enabled = 1 + + AssertLinter 'go-langserver', + \ ale#Escape('go-langserver') . ' -gocodecompletion -trace' + + let b:ale_completion_enabled = 0 + + AssertLinter 'go-langserver', ale#Escape('go-langserver') . ' -trace' + +Execute(should support Go environment variables): + let b:ale_go_go111module = 'on' + + AssertLinter 'go-langserver', + \ ale#Env('GO111MODULE', 'on') . ale#Escape('go-langserver') + +Execute(should set go-langserver for go app1): + call ale#test#SetFilename('../test-files/go/go1/prj1/file.go') + + AssertLSPLanguage 'go' + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/go/go1') + +Execute(should set go-langserver for go app2): + call ale#test#SetFilename('../test-files/go/go2/prj1/file.go') + + AssertLSPLanguage 'go' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/go/go2') diff --git a/test/linter/test_gopls.vader b/test/linter/test_gopls.vader new file mode 100644 index 00000000..1c91fa10 --- /dev/null +++ b/test/linter/test_gopls.vader @@ -0,0 +1,96 @@ +Before: + Save g:ale_go_go111module + Save $GOPATH + + let $GOPATH = '/non/existent/directory' + + call ale#assert#SetUpLinterTest('go', 'gopls') + +After: + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + unlet! b:ale_go_go111module + unlet! b:ale_go_go111module + unlet! b:ale_completion_enabled + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'gopls', ale#Escape('gopls') . ' --mode stdio' + +Execute(The executable should be configurable): + let b:ale_go_gopls_executable = 'boo' + + AssertLinter 'boo', ale#Escape('boo') . ' --mode stdio' + +Execute(gopls should be found in GOPATH): + " This is a directory with a fake executable + let $GOPATH = ale#test#GetFilename('../test-files/go/gopath') + + AssertLinter + \ ale#test#GetFilename('../test-files/go/gopath/bin/gopls'), + \ ale#Escape(ale#test#GetFilename('../test-files/go/gopath/bin/gopls')) + \ . ' --mode stdio' + +Execute(Global settings should be preferre for gopls if use_global = 1): + " This is a directory with a fake executable + let $GOPATH = ale#test#GetFilename('../test-files/go/gopath') + let b:ale_go_gopls_executable = 'boo' + let b:ale_go_gopls_use_global = 1 + + AssertLinter 'boo', ale#Escape('boo') . ' --mode stdio' + +Execute(Settings options should work): + call ale#test#SetFilename('../test-files/go/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' + + let b:ale_go_gopls_init_options = {'ui.diagnostic.analyses': {'composites': v:false}} + AssertLSPOptions {'ui.diagnostic.analyses': {'composites': v:false}} + +Execute(Go environment variables should be passed on): + let b:ale_go_go111module = 'off' + + AssertLinter 'gopls', + \ ale#Env('GO111MODULE', 'off') . ale#Escape('gopls') . ' --mode stdio' + +Execute(Project directories should be detected based on 'go.mod' being present): + call ale#test#SetFilename('../test-files/go/test.go') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/go') + +Execute(Project directories with .git should be detected): + call ale#test#SetFilename('test.go') + + if !isdirectory(g:dir . '/.git') + call mkdir(g:dir . '/.git') + endif + + AssertLSPProject g:dir + +Execute('go.mod' should be ignored if modules are off): + call ale#test#SetFilename('../test-files/go/test.go') + + let b:ale_go_go111module = 'off' + let b:parent_dir = ale#path#Simplify(g:dir . '/..') + let b:git_dir = b:parent_dir . '/.git' + + if !isdirectory(b:git_dir) + call mkdir(b:git_dir) + endif + + AssertLSPProject b:parent_dir + + call delete(b:git_dir, 'd') + unlet! b:parent_dir + unlet! b:git_dir diff --git a/test/linter/test_gosimple.vader b/test/linter/test_gosimple.vader new file mode 100644 index 00000000..960f8ee9 --- /dev/null +++ b/test/linter/test_gosimple.vader @@ -0,0 +1,19 @@ +Before: + Save g:ale_go_go111module + + call ale#assert#SetUpLinterTest('go', 'gosimple') + call ale#test#SetFilename('../test-files/go/testfile2.go') + +After: + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The default gosimple command should be correct): + AssertLinterCwd '%s:h' + AssertLinter 'gosimple', 'gosimple .' + +Execute(The gosimple command should support Go environment variables): + let b:ale_go_go111module = 'on' + + AssertLinter 'gosimple', ale#Env('GO111MODULE', 'on') . 'gosimple .' diff --git a/test/linter/test_gotype.vader b/test/linter/test_gotype.vader new file mode 100644 index 00000000..22829a17 --- /dev/null +++ b/test/linter/test_gotype.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_go_go111module + + call ale#assert#SetUpLinterTest('go', 'gotype') + call ale#test#SetFilename('../test-files/go/testfile2.go') + +After: + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The default gotype command should be correct): + AssertLinterCwd '%s:h' + AssertLinter 'gotype', 'gotype -e .' + +Execute(The gotype callback should ignore test files): + call ale#test#SetFilename('bla_test.go') + + AssertLinterNotExecuted + +Execute(The gotype callback should support Go environment variables): + let b:ale_go_go111module = 'on' + + AssertLinter 'gotype', ale#Env('GO111MODULE', 'on') . 'gotype -e .' diff --git a/test/linter/test_govet.vader b/test/linter/test_govet.vader new file mode 100644 index 00000000..0bd19c5b --- /dev/null +++ b/test/linter/test_govet.vader @@ -0,0 +1,28 @@ +Before: + Save g:ale_go_go_executable + Save g:ale_go_govet_options + Save g:ale_go_go111module + + call ale#assert#SetUpLinterTest('go', 'govet') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The default go vet command should be correct): + AssertLinter 'go', ale#Escape('go') . ' vet .' + AssertLinterCwd '%s:h' + +Execute(The go vet command and options should be configurable): + let g:ale_go_go_executable = 'foobar' + let g:ale_go_govet_options = '--foo-bar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' vet --foo-bar .' + +Execute(Go environment variables should be supported): + let b:ale_go_go111module = 'on' + + AssertLinter 'go', ale#Env('GO111MODULE', 'on') . ale#Escape('go') . ' vet .' diff --git a/test/linter/test_graphql_gqlint.vader b/test/linter/test_graphql_gqlint.vader new file mode 100644 index 00000000..22c05a6a --- /dev/null +++ b/test/linter/test_graphql_gqlint.vader @@ -0,0 +1,9 @@ +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): + AssertLinterCwd '%s:h' + AssertLinter 'gqlint', 'gqlint --reporter=simple %t' diff --git a/test/linter/test_hadolint.vader b/test/linter/test_hadolint.vader new file mode 100644 index 00000000..da335400 --- /dev/null +++ b/test/linter/test_hadolint.vader @@ -0,0 +1,25 @@ +Before: + call ale#assert#SetUpLinterTest('dockerfile', 'hadolint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(We should not use Docker by default): + AssertLinter 'hadolint', 'hadolint --no-color -' + +Execute(Options should be passed correctly when docker is disabled): + let b:ale_dockerfile_hadolint_options = '--ignore DL3006' + + AssertLinter 'hadolint', 'hadolint --ignore DL3006 --no-color -' + +Execute(The command should be correct when using Docker): + let b:ale_dockerfile_hadolint_use_docker = 'always' + + AssertLinter 'docker', 'docker run --rm -i hadolint/hadolint hadolint --no-color -' + +Execute(The command should be correct when using docker and supplying options): + let b:ale_dockerfile_hadolint_use_docker = 'always' + let b:ale_dockerfile_hadolint_options = '--ignore DL3006' + + AssertLinter 'docker', + \ 'docker run --rm -i hadolint/hadolint hadolint --ignore DL3006 --no-color -' diff --git a/test/linter/test_haml_hamllint.vader b/test/linter/test_haml_hamllint.vader new file mode 100644 index 00000000..1f5e2fa4 --- /dev/null +++ b/test/linter/test_haml_hamllint.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('haml', 'hamllint') + + let g:default_command = 'haml-lint %t' + +After: + unlet! b:conf + unlet! b:conf_hamllint + unlet! b:conf_rubocop + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'haml-lint', 'haml-lint %t' + +Execute(The command should have the .rubocop.yml prepended as an env var if one exists): + call ale#test#SetFilename('../test-files/hamllint/rubocop-yml/subdir/file.haml') + let b:conf = ale#path#Simplify(g:dir . '/../test-files/hamllint/rubocop-yml/.rubocop.yml') + + AssertLinter 'haml-lint', + \ ale#Env('HAML_LINT_RUBOCOP_CONF', b:conf) . 'haml-lint %t' + +Execute(The command should have the nearest .haml-lint.yml set as --config if it exists): + call ale#test#SetFilename('../test-files/hamllint/haml-lint-yml/subdir/file.haml') + let b:conf = ale#path#Simplify(g:dir . '/../test-files/hamllint/haml-lint-yml/.haml-lint.yml') + + AssertLinter 'haml-lint', + \ 'haml-lint --config ' . ale#Escape(b:conf) . ' %t', + +Execute(The command should include a .rubocop.yml and a .haml-lint if both are found): + call ale#test#SetFilename('../test-files/hamllint/haml-lint-and-rubocop/subdir/file.haml') + let b:conf_hamllint = ale#path#Simplify(g:dir . '/../test-files/hamllint/haml-lint-and-rubocop/.haml-lint.yml') + let b:conf_rubocop = ale#path#Simplify(g:dir . '/../test-files/hamllint/haml-lint-and-rubocop/.rubocop.yml') + + AssertLinter 'haml-lint', + \ ale#Env('HAML_LINT_RUBOCOP_CONF', b:conf_rubocop) + \ . 'haml-lint --config ' . ale#Escape(b:conf_hamllint) . ' %t' + +Execute(The executable can be overridden): + let b:ale_haml_hamllint_executable = 'bin/haml-lint' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'bin/haml-lint', 'bin/haml-lint %t' diff --git a/test/linter/test_haskell_cabal_ghc.vader b/test/linter/test_haskell_cabal_ghc.vader new file mode 100644 index 00000000..b4976b34 --- /dev/null +++ b/test/linter/test_haskell_cabal_ghc.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'cabal_ghc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The options should be used in the command): + AssertLinterCwd '%s:h' + AssertLinter 'cabal', 'cabal exec -- ghc -fno-code -v0 %t' + + let b:ale_haskell_cabal_ghc_options = 'foobar' + + AssertLinter 'cabal', 'cabal exec -- ghc foobar %t' diff --git a/test/linter/test_haskell_ghc.vader b/test/linter/test_haskell_ghc.vader new file mode 100644 index 00000000..746bc082 --- /dev/null +++ b/test/linter/test_haskell_ghc.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'ghc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default ghc command should be correct): + AssertLinter 'ghc', 'ghc -fno-code -v0 %t' + +Execute(The ghc options should be configurable): + let b:ale_haskell_ghc_options = 'foobar' + + AssertLinter 'ghc', 'ghc foobar %t' diff --git a/test/linter/test_haskell_ghc_mod.vader b/test/linter/test_haskell_ghc_mod.vader new file mode 100644 index 00000000..c1cc8597 --- /dev/null +++ b/test/linter/test_haskell_ghc_mod.vader @@ -0,0 +1,10 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'ghc_mod') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Default should use ghc-mod): + AssertLinter + \ 'ghc-mod', + \ ale#Escape('ghc-mod') . ' --map-file %s=%t check %s' diff --git a/test/linter/test_haskell_hdevtools.vader b/test/linter/test_haskell_hdevtools.vader new file mode 100644 index 00000000..0ef2f0e3 --- /dev/null +++ b/test/linter/test_haskell_hdevtools.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'hdevtools') + + let b:command_tail = ' check -g -Wall -p %s %t' + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'hdevtools', ale#Escape('hdevtools') . b:command_tail + + let b:ale_haskell_hdevtools_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . b:command_tail diff --git a/test/linter/test_haskell_hie.vader b/test/linter/test_haskell_hie.vader new file mode 100644 index 00000000..ab71b6b3 --- /dev/null +++ b/test/linter/test_haskell_hie.vader @@ -0,0 +1,23 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'hie') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'haskell' + +Execute(The default executable should be correct): + AssertLinter 'hie', ale#Escape('hie') . ' --lsp' + +Execute(The project root should be detected correctly): + AssertLSPProject g:dir + + call ale#test#SetFilename('../test-files/hie_paths/file.hs') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/hie_paths') + +Execute(The executable should be configurable): + let g:ale_haskell_hie_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --lsp' diff --git a/test/linter/test_haskell_hlint.vader b/test/linter/test_haskell_hlint.vader new file mode 100644 index 00000000..6d227c9d --- /dev/null +++ b/test/linter/test_haskell_hlint.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'hlint') + + let b:base_opts = '--color=never --json -' + +After: + unlet! b:base_opts + call ale#assert#TearDownLinterTest() + +Execute(executable should be configurable): + AssertLinter 'hlint', ale#Escape('hlint') . ' ' . b:base_opts + let b:ale_haskell_hlint_executable = 'myHlint' + AssertLinter 'myHlint', ale#Escape('myHlint') . ' ' . b:base_opts + +Execute(should accept options): + let b:ale_haskell_hlint_options= '-h myhlintfile.yaml' + AssertLinter 'hlint', ale#Escape('hlint') . ' -h myhlintfile.yaml ' . b:base_opts diff --git a/test/linter/test_haskell_hls.vader b/test/linter/test_haskell_hls.vader new file mode 100644 index 00000000..59a50078 --- /dev/null +++ b/test/linter/test_haskell_hls.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'hls') + + Save &filetype + let &filetype = 'haskell' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'haskell' + +Execute(The default executable should be correct): + AssertLinter 'haskell-language-server-wrapper', + \ ale#Escape('haskell-language-server-wrapper') . ' --lsp' + +Execute(The project root should be detected correctly): + AssertLSPProject g:dir + + call ale#test#SetFilename('hls_paths/file.hs') + + AssertLSPProject ale#path#Simplify(g:dir . '/hls_paths') + +Execute(The executable should be configurable): + let g:ale_haskell_hls_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --lsp' + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_haskell_hls_config = {'haskell': {'maxCompletions': 250}} + AssertLSPConfig {'haskell': {'maxCompletions': 250}} + +Execute(We should detect the root with cabal.project files, preferred over *.cabal files): + call ale#test#SetFilename('../test-files/haskell/haskell-packages-project/package-a/src/folder/dummy.hs') + + AssertLSPProject ale#test#GetFilename('../test-files/haskell/haskell-packages-project') + +Execute(We should a project root with *.cabal files): + call ale#test#SetFilename('../test-files/haskell/haskell-simple-package/package-a/src/folder/dummy.hs') + + AssertLSPProject ale#test#GetFilename('../test-files/haskell/haskell-simple-package/package-a') diff --git a/test/linter/test_haskell_stack_build.vader b/test/linter/test_haskell_stack_build.vader new file mode 100644 index 00000000..8b5b0971 --- /dev/null +++ b/test/linter/test_haskell_stack_build.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'stack_build') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The linter should not be executed when there's no stack.yaml file): + AssertLinterNotExecuted + +Execute(The linter should be executed when there is a stack.yaml file): + call ale#test#SetFilename('../test-files/stack/test.hs') + + AssertLinter 'stack', 'stack build --fast' diff --git a/test/linter/test_haskell_stack_ghc.vader b/test/linter/test_haskell_stack_ghc.vader new file mode 100644 index 00000000..04bd23f5 --- /dev/null +++ b/test/linter/test_haskell_stack_ghc.vader @@ -0,0 +1,18 @@ +Before: + call ale#assert#SetUpLinterTest('haskell', 'stack_ghc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The linter should not be executed when there's no stack.yaml file): + AssertLinterNotExecuted + +Execute(The linter should be executed when there is a stack.yaml file): + call ale#test#SetFilename('../test-files/stack/test.hs') + + AssertLinterCwd '%s:h' + AssertLinter 'stack', 'stack ghc -- -fno-code -v0 %t' + + let b:ale_haskell_stack_ghc_options = 'foobar' + + AssertLinter 'stack', 'stack ghc -- foobar %t' diff --git a/test/linter/test_hdl_checker_options.vader b/test/linter/test_hdl_checker_options.vader new file mode 100644 index 00000000..6e7eef46 --- /dev/null +++ b/test/linter/test_hdl_checker_options.vader @@ -0,0 +1,86 @@ +Before: + call ale#assert#SetUpLinterTest('vhdl', 'hdl_checker') + + Save g:ale_hdl_checker_executable + Save g:ale_hdl_checker_config_file + Save g:ale_hdl_checker_options + + let g:default_config_file = has('unix') ? '.hdl_checker.config' : '_hdl_checker.config' + + runtime autoload/ale/handlers/hdl_checker.vim + +After: + Restore + + call ale#assert#TearDownLinterTest() + + unlet! g:default_config_file + unlet! g:call_count + + runtime autoload/ale/handlers/hdl_checker.vim + +Execute(Get default initialization dict): + AssertEqual + \ {'project_file': g:default_config_file}, + \ ale#handlers#hdl_checker#GetInitOptions(bufnr('')) + +Execute(Get custom initialization dict): + let g:ale_hdl_checker_config_file = 'some_file_name' + + AssertEqual + \ {'project_file': 'some_file_name'}, + \ ale#handlers#hdl_checker#GetInitOptions(bufnr('')) + +Execute(Get the checker command without extra user parameters): + AssertEqual + \ ale#Escape('hdl_checker') . ' --lsp', + \ ale#handlers#hdl_checker#GetCommand(bufnr('')) + +Execute(Get the checker command with user configured parameters): + let g:ale_hdl_checker_options = '--log-level DEBUG' + + AssertEqual + \ ale#Escape('hdl_checker') . ' --lsp --log-level DEBUG', + \ ale#handlers#hdl_checker#GetCommand(bufnr('')) + +Execute(Customize executable): + let g:ale_hdl_checker_executable = '/some/other/path' + AssertEqual + \ ale#Escape('/some/other/path') . ' --lsp', + \ ale#handlers#hdl_checker#GetCommand(bufnr('')) + +Execute(Get project root based on .git): + call ale#test#SetFilename('../test-files/hdl_server/with_git/files/foo.vhd') + " Create .git file + silent! call mkdir(g:dir . '/../test-files/hdl_server/with_git/.git') + AssertNotEqual '', glob(g:dir . '/../test-files/hdl_server/with_git/.git') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/hdl_server/with_git'), + \ ale#handlers#hdl_checker#GetProjectRoot(bufnr('')) + +Execute(Get project root based on config file): + call ale#test#SetFilename('../test-files/hdl_server/with_config_file/foo.vhd') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/hdl_server/with_config_file'), + \ ale#handlers#hdl_checker#GetProjectRoot(bufnr('')) + +Execute(Return no project root if neither .git or config file are found): + let g:call_count = 0 + + " Mock this command to avoid the test to find ale's own .git folder + function! ale#handlers#hdl_checker#IsDotGit(path) abort + let g:call_count += 1 + return 0 + endfunction + + call ale#test#SetFilename('../test-files/hdl_server/foo.vhd') + + AssertEqual + \ '', + \ ale#handlers#hdl_checker#GetProjectRoot(bufnr('')) + + AssertEqual g:call_count, 1 + + unlet! g:call_count diff --git a/test/linter/test_html_stylelint.vader b/test/linter/test_html_stylelint.vader new file mode 100644 index 00000000..c5ac1b98 --- /dev/null +++ b/test/linter/test_html_stylelint.vader @@ -0,0 +1,60 @@ +Before: + Save g:ale_html_stylelint_executable + Save g:ale_html_stylelint_use_global + Save g:ale_html_stylelint_options + + unlet! b:executable + + unlet! g:ale_html_stylelint_executable + unlet! g:ale_html_stylelint_use_global + unlet! g:ale_html_stylelint_options + + call ale#test#SetDirectory('/testplugin/test/linter') + call ale#test#SetFilename('testfile.html') + + runtime ale_linters/html/stylelint.vim + +After: + Restore + + unlet! b:executable + unlet! b:ale_html_stylelint_executable + unlet! b:ale_html_stylelint_use_global + unlet! b:ale_html_stylelint_options + + call ale#test#SetFilename('test.txt') + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(node_modules directories should be discovered): + call ale#test#SetFilename('../test-files/stylelint/nested/testfile.html') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/stylelint/node_modules/.bin/stylelint' + \) + + AssertEqual b:executable, ale_linters#html#stylelint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape(b:executable) . ' --stdin-filename %s', + \ ale_linters#html#stylelint#GetCommand(bufnr('')) + +Execute(The global override should work): + let b:ale_html_stylelint_executable = 'foobar' + let b:ale_html_stylelint_use_global = 1 + + call ale#test#SetFilename('../test-files/stylelint/nested/testfile.html') + + AssertEqual 'foobar', ale_linters#html#stylelint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foobar') . ' --stdin-filename %s', + \ ale_linters#html#stylelint#GetCommand(bufnr('')) + +Execute(Extra options should be configurable): + let b:ale_html_stylelint_options = '--whatever' + + AssertEqual 'stylelint', ale_linters#html#stylelint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('stylelint') . ' --whatever --stdin-filename %s', + \ ale_linters#html#stylelint#GetCommand(bufnr('')) diff --git a/test/linter/test_htmlhint.vader b/test/linter/test_htmlhint.vader new file mode 100644 index 00000000..4475c1a7 --- /dev/null +++ b/test/linter/test_htmlhint.vader @@ -0,0 +1,51 @@ +Before: + call ale#assert#SetUpLinterTest('html', 'htmlhint') + call ale#test#SetFilename('../test-files/htmlhint/test.html') + + let g:node_executable = ale#path#Simplify( + \ g:dir . '/../test-files/htmlhint/node_modules/.bin/htmlhint' + \) + let g:config_path = ale#path#Simplify( + \ g:dir . '/../test-files/htmlhint/with_config/.htmlhintrc' + \) + +After: + unlet! g:node_executable + unlet! g:config_path + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter g:node_executable, + \ ale#Escape(g:node_executable) . ' --format=unix %t' + +Execute(The global executable should be used if the option is set): + let g:ale_html_htmlhint_executable = 'foo' + let g:ale_html_htmlhint_use_global = 1 + + AssertLinter 'foo', ale#Escape('foo') . ' --format=unix %t', + +" This is so old configurations which might include this still work. +Execute(--format=unix should be removed from the options if added): + let g:ale_html_htmlhint_options = '--format=unix' + + AssertLinter g:node_executable, + \ ale#Escape(g:node_executable) . ' --format=unix %t' + +Execute(The configuration file should be automatically detected): + call ale#test#SetFilename('../test-files/htmlhint/with_config/test.html') + + AssertLinter g:node_executable, + \ ale#Escape(g:node_executable) + \ . ' --config ' . ale#Escape(g:config_path) + \ . ' --format=unix %t' + +" This is so old configurations which might include the config will work. +Execute(The configuration file should be configurable through the options variable): + call ale#test#SetFilename('../test-files/htmlhint/with_config/test.html') + let g:ale_html_htmlhint_options = '--config=/foo/bar/.htmlhintrc' + + AssertLinter g:node_executable, + \ ale#Escape(g:node_executable) + \ . ' --config=/foo/bar/.htmlhintrc' + \ . ' --format=unix %t' diff --git a/test/linter/test_hurlfmt.vader b/test/linter/test_hurlfmt.vader new file mode 100644 index 00000000..22a62b3c --- /dev/null +++ b/test/linter/test_hurlfmt.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('hurl', 'hurlfmt') + call ale#test#SetFilename('dummy.hurl') + + let g:ale_ruby_hurlfmt_executable = 'hurlfmt' + let g:ale_ruby_hurlfmt_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to hurlfmt): + AssertLinter 'hurlfmt', ale#Escape('hurlfmt') + \ . ' --check --no-color ' + +Execute(Should be able to set a custom executable): + let g:ale_hurl_hurlfmt_executable = 'bin/hurlfmt' + + AssertLinter 'bin/hurlfmt' , ale#Escape('bin/hurlfmt') + \ . ' --check --no-color ' diff --git a/test/linter/test_ibm_openapi_validator.vader b/test/linter/test_ibm_openapi_validator.vader new file mode 100644 index 00000000..3484cc09 --- /dev/null +++ b/test/linter/test_ibm_openapi_validator.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('openapi', 'ibm_validator') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The yaml ibm-openapi-validator command callback should return the correct default string): + AssertLinter 'lint-openapi', ale#Escape('lint-openapi') . ' %t' + +Execute(The yaml ibm-openapi-validator command callback should be configurable): + let g:ale_openapi_ibm_validator_executable = '~/.local/bin/lint-openapi' + let g:ale_openapi_ibm_validator_options = '-c ~/.config' + + AssertLinter '~/.local/bin/lint-openapi', ale#Escape('~/.local/bin/lint-openapi') + \ . ' -c ~/.config %t' diff --git a/test/linter/test_idris.vader b/test/linter/test_idris.vader new file mode 100644 index 00000000..ce7cd270 --- /dev/null +++ b/test/linter/test_idris.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('idris', 'idris') + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The executable should be used in the command): + AssertLinter 'idris', + \ ale#Escape('idris') . ' --total --warnpartial --warnreach --warnipkg --check %s' + + let b:ale_idris_idris_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' --total --warnpartial --warnreach --warnipkg --check %s' + +Execute(The options should be configurable): + let b:ale_idris_idris_options = '--something' + + AssertLinter 'idris', ale#Escape('idris') . ' --something --check %s' diff --git a/test/linter/test_ink_ls.vader b/test/linter/test_ink_ls.vader new file mode 100644 index 00000000..a832a250 --- /dev/null +++ b/test/linter/test_ink_ls.vader @@ -0,0 +1,22 @@ +Before: + call ale#assert#SetUpLinterTest('ink', 'ls') + set ft=ink + +After: + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'ink-language-server', ale#Escape('ink-language-server') . ' --stdio' + +Execute(should set correct LSP values): + call ale#test#SetFilename('../test-files/ink/story/main.ink') + + AssertLSPLanguage 'ink' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ink/story') + +Execute(should accept configuration settings): + AssertLSPConfig {} + let b:ale_ink_ls_initialization_options = {'ink': {'runThroughMono': v:true}} + AssertLSPOptions {'ink': {'runThroughMono': v:true}} diff --git a/test/linter/test_inko_inko.vader b/test/linter/test_inko_inko.vader new file mode 100644 index 00000000..c08cbed4 --- /dev/null +++ b/test/linter/test_inko_inko.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('inko', 'inko') + call ale#test#SetFilename('../test-files/inko/test.inko') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'inko', ale#Escape('inko') . ' build --check --format=json %s' + +Execute(The inko callback should include tests/ for test paths): + call ale#engine#Cleanup(bufnr('')) + noautocmd e! ../test-files/inko/tests/test/test_foo.inko + call ale#engine#InitBufferInfo(bufnr('')) + + AssertLinter 'inko', + \ ale#Escape('inko') + \ . ' build --check --format=json --include ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/inko/tests/')) + \ . ' %s' diff --git a/test/linter/test_ispc_ispc.vader b/test/linter/test_ispc_ispc.vader new file mode 100644 index 00000000..9d91da8d --- /dev/null +++ b/test/linter/test_ispc_ispc.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('ispc', 'ispc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default ispc command should be configurable): + AssertLinter 'ispc', ale#Escape('ispc') . ' --nowrap %s' + +Execute(The ispc executable nad options should be configurable): + let b:ale_ispc_ispc_executable = 'foo' + let g:ale_ispc_ispc_options = '--foo' + + AssertLinter 'foo', ale#Escape('foo') . ' --nowrap --foo' . ' %s' diff --git a/test/linter/test_iverilog.vader b/test/linter/test_iverilog.vader new file mode 100644 index 00000000..5acf9b50 --- /dev/null +++ b/test/linter/test_iverilog.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('verilog', 'iverilog') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default iverilog command should be correct): + AssertLinter 'iverilog', 'iverilog -t null -Wall -y%s:h %t' + +Execute(iverilog options should be configurable): + " Additional args for the linter + let g:ale_verilog_iverilog_options = '-y.' + + AssertLinter 'iverilog', 'iverilog -t null -Wall -y%s:h -y. %t' diff --git a/test/linter/test_javac.vader b/test/linter/test_javac.vader new file mode 100644 index 00000000..85a76e6a --- /dev/null +++ b/test/linter/test_javac.vader @@ -0,0 +1,326 @@ +Before: + call ale#assert#SetUpLinterTest('java', 'javac') + call ale#test#SetFilename('dummy.java') + + let g:cp_sep = has('unix') ? ':' : ';' + let g:prefix = ale#Escape('javac') . ' -Xlint' + + function! GetCommand(previous_output) abort + let l:command = ale_linters#java#javac#GetCommand( + \ bufnr(''), + \ a:previous_output + \) + + let l:split_command = split(l:command) + let l:index = index(l:split_command, '-d') + + let l:split_command[l:index + 1] = 'TEMP' + + return join(l:split_command) + endfunction + +After: + unlet! g:cp_sep + unlet! g:prefix + + delfunction GetCommand + + call ale#assert#TearDownLinterTest() + +Execute(The javac callback should return the correct default value): + AssertLinterCwd '%s:h' + AssertLinter 'javac', g:prefix . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should use string type g:ale_java_javac_classpath correctly): + let g:ale_java_javac_classpath = 'foo.jar' + + AssertLinter 'javac', + \ g:prefix + \ . ' -cp ' . ale#Escape('foo.jar') + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should use list type g:ale_java_javac_classpath correctly): + let g:ale_java_javac_classpath = ['foo.jar'] + + AssertLinter 'javac', + \ g:prefix + \ . ' -cp ' . ale#Escape('foo.jar') + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The executable should be configurable): + let g:ale_java_javac_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' -Xlint' + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should include discovered classpaths): + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \], {}) + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join(['/foo/bar.jar', '/xyz/abc.jar'], g:cp_sep)) + \ . ' -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' + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \], {}) + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -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', + \], {}) + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ 'configured2.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_classpath = ['configured.jar'] + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \], {}) + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_classpath = ['configured.jar', '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', + \], {}) + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ 'configured2.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + +Execute(The javac callback should use string type g:ale_java_javac_sourcepath correctly): + let g:ale_java_javac_sourcepath = '../test-files/java/with_main/build/gen/main' + + AssertLinter 'javac', + \ g:prefix + \ . ' -sourcepath ' . ale#Escape( + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen/main/') + \ ) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should use list type g:ale_java_javac_sourcepath correctly): + let g:ale_java_javac_sourcepath = ['../test-files/java/with_main/build/gen/main'] + + AssertLinter 'javac', + \ g:prefix + \ . ' -sourcepath ' . ale#Escape( + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen/main/') + \ ) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback shouldn't add -sourcepath when g:ale_java_javac_sourcepath variable path doesn't exist): + let g:ale_java_javac_sourcepath = '../test-files/java/with_main/build/gen3/main' + + AssertLinter 'javac', + \ g:prefix + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should combine discovered sourcepath and manual ones): + call ale#engine#Cleanup(bufnr('')) + call ale#test#SetFilename('../test-files/java/with_main/src/main/java/com/something/dummy.java') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:ale_java_javac_sourcepath = '../test-files/java/with_main/build/gen/main' + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/src/main/java/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen/main/'), + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_sourcepath = '../test-files/java/with_main/build/gen/main' + \ . g:cp_sep . '../test-files/java/with_main/build/gen2/main' + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/src/main/java/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen/main/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen2/main/') + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_sourcepath = ['../test-files/java/with_main/build/gen/main'] + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/src/main/java/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen/main/') + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_sourcepath = [ + \ '../test-files/java/with_main/build/gen/main', + \ '../test-files/java/with_main/build/gen2/main' + \ ] + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/src/main/java/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen/main/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/build/gen2/main/') + \ ], g:cp_sep)) + \ . ' -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('')) + noautocmd e! ../test-files/java/with_main/src/main/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + AssertLinter 'javac', + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape( + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/src/main/java/') + \ ) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should combine detected source directories and classpaths): + call ale#engine#Cleanup(bufnr('')) + call ale#test#SetFilename('../test-files/java/with_main/src/main/java/com/something/dummy.java') + call ale#engine#InitBufferInfo(bufnr('')) + + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \], {}) + + AssertEqual + \ 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 . '/../test-files/java/with_main/src/main/java/') + \ ) + \ . ' -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' + + AssertLinter 'javac', + \ g:prefix . ' -d ' . ale#Escape('TEMP_DIR') . ' --anything --else %t' + +Execute(The javac callback should include src/test/java for test paths): + call ale#engine#Cleanup(bufnr('')) + " The test path is only included for test files. + " Regular Java files shouldn't import from tests. + noautocmd e! ../test-files/java/with_main/src/test/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + AssertLinter 'javac', + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/src/main/java/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/with_main/src/test/java/'), + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should include src/main/jaxb when available): + call ale#engine#Cleanup(bufnr('')) + noautocmd e! ../test-files/java/with_jaxb/src/main/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + AssertLinter 'javac', + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/../test-files/java/with_jaxb/src/main/java/'), + \ ale#path#Simplify(g:dir . '/../test-files/java/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('../test-files/java/no_main/src/test/java/com/something/dummy.java') + call ale#engine#InitBufferInfo(bufnr('')) + + AssertLinter 'javac', + \ ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/../test-files/java/no_main/src/test/java/'), + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' diff --git a/test/linter/test_javalsp.vader b/test/linter/test_javalsp.vader new file mode 100644 index 00000000..1435f9df --- /dev/null +++ b/test/linter/test_javalsp.vader @@ -0,0 +1,67 @@ +Before: + call ale#assert#SetUpLinterTest('java', 'javalsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The javalsp callback should return the correct default value): + AssertLinter '', ale#Escape('') + +Execute(The javalsp java executable should be configurable): + let b:ale_java_javalsp_executable = '/bin/foobar' + + AssertLinter '/bin/foobar', ale#Escape('/bin/foobar') + +Execute(The javalsp callback should return backward compatible value): + let b:ale_java_javalsp_executable = '/bin/java' + let cmd = [ + \ ale#Escape('/bin/java'), + \ '--add-exports jdk.compiler/com.sun.tools.javac.api=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.code=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.comp=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.main=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.tree=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.model=javacs', + \ '--add-exports jdk.compiler/com.sun.tools.javac.util=javacs', + \ '--add-opens jdk.compiler/com.sun.tools.javac.api=javacs', + \ '-m javacs/org.javacs.Main', + \] + AssertLinter '/bin/java', join(cmd, ' ') + +Execute(The javalsp should have default config): + AssertEqual + \ { + \ 'java': { + \ 'classPath': [], + \ 'externalDependencies': [] + \ } + \ }, + \ ale_linters#java#javalsp#Config(bufnr('')) + +Execute(The javalsp should have add missing config): + let b:ale_java_javalsp_config = { 'java': { 'classPath': ['aaa.jar'] } } + + AssertEqual + \ { + \ 'java': { + \ 'classPath': ['aaa.jar'], + \ 'externalDependencies': [] + \ } + \ }, + \ ale_linters#java#javalsp#Config(bufnr('')) + + let b:ale_java_javalsp_config = + \ { + \ 'java': { + \ 'externalDependencies': ['unit-test:2.0.0'] + \ } + \ } + + AssertEqual + \ { + \ 'java': { + \ 'classPath': [], + \ 'externalDependencies': ['unit-test:2.0.0'] + \ } + \ }, + \ ale_linters#java#javalsp#Config(bufnr('')) diff --git a/test/linter/test_javascript_deno_lsp.vader b/test/linter/test_javascript_deno_lsp.vader new file mode 100644 index 00000000..c48d52cc --- /dev/null +++ b/test/linter/test_javascript_deno_lsp.vader @@ -0,0 +1,84 @@ +Before: + Save g:ale_deno_import_map + Save g:ale_deno_unstable + Save g:ale_deno_executable + Save g:ale_deno_lsp_project_root + + let g:ale_deno_import_map = 'import_map.json' + let g:ale_deno_unstable = 0 + let g:ale_deno_executable = 'deno' + let g:ale_deno_lsp_project_root = '' + + runtime autoload/ale/handlers/deno.vim + call ale#assert#SetUpLinterTest('javascript', 'deno') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Should set deno lsp for JavaScript projects using stable Deno API): + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:false, + \ 'importMap': '' + \} + +Execute(Should set deno lsp using unstable Deno API if enabled by user): + let g:ale_deno_unstable = 1 + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:true, + \ 'importMap': '' + \} + +Execute(Should set the default importMap filepath): + call ale#test#SetFilename('../test-files/javascript_deno/main.js') + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:false, + \ 'importMap': ale#path#Simplify(g:dir . '/../test-files/javascript_deno/import_map.json') + \} + +Execute(Should set the importMap filepath from user defined importMap): + let g:ale_deno_import_map = 'custom_import_map.json' + call ale#test#SetFilename('../test-files/javascript_deno/main.js') + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:false, + \ 'importMap': ale#path#Simplify(g:dir . '/../test-files/javascript_deno/custom_import_map.json') + \} + +Execute(Should set the importMap filepath from user defined importMap with unstable API): + let g:ale_deno_import_map = 'custom_import_map.json' + let g:ale_deno_unstable = 1 + call ale#test#SetFilename('../test-files/javascript_deno/main.js') + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:true, + \ 'importMap': ale#path#Simplify(g:dir . '/../test-files/javascript_deno/custom_import_map.json') + \} + +Execute(Should find project root containing tsconfig.json): + call ale#test#SetFilename('../test-files/javascript_deno/main.js') + + AssertLSPLanguage 'javascript' + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/javascript_deno') + +Execute(Should use user-specified project root): + let g:ale_deno_lsp_project_root = '/' + + call ale#test#SetFilename('../test-files/javascript_deno/main.js') + + AssertLSPLanguage 'javascript' + AssertLSPProject '/' + +Execute(Check Deno LSP command): + AssertLinter 'deno', ale#Escape('deno') . ' lsp' diff --git a/test/linter/test_javascript_tsserver.vader b/test/linter/test_javascript_tsserver.vader new file mode 100644 index 00000000..1c29c8fd --- /dev/null +++ b/test/linter/test_javascript_tsserver.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'tsserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'tsserver', ale#Escape('tsserver') + +Execute(should resolve correct path when nested 1): + call ale#test#SetFilename('../test-files/tsserver/src/level-1/level-2/file3.ts') + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/tsserver/src/level-1') + +Execute(should resolve correct path when nested 2): + call ale#test#SetFilename('../test-files/tsserver/src/file1.ts') + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/tsserver') diff --git a/test/linter/test_jedils.vader b/test/linter/test_jedils.vader new file mode 100644 index 00000000..a86d80d6 --- /dev/null +++ b/test/linter/test_jedils.vader @@ -0,0 +1,63 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'jedils') + Save b:ale_python_auto_virtualenv + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:venv_bin + unlet! b:sep + unlet! b:executable + + call ale#test#SetFilename('..') + call ale#assert#TearDownLinterTest() + +Execute(The jedi-language-server command callback should return default string): + call ale#test#SetFilename('./foo.py') + + AssertLinter 'jedi-language-server', ale#Escape('jedi-language-server') + +Execute(The jedi-language-server executable should be configurable): + let g:ale_python_jedils_executable = '~/.local/bin/jedi-language-server' + + AssertLinter '~/.local/bin/jedi-language-server' , ale#Escape('~/.local/bin/jedi-language-server') + +Execute(virtualenv vars should be used when ale_python_auto_virtualenv = 1): + let b:ale_python_auto_virtualenv = 1 + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:venv_bin = ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir) + let b:sep = has('win32') ? ';' : ':' + let b:executable = ale#path#Simplify(b:venv_bin . '/jedi-language-server') + + AssertLinter b:executable, ale#python#AutoVirtualenvEnvString(bufnr('')) + \ . ale#Escape(b:executable) + Assert !empty(ale#python#AutoVirtualenvEnvString(bufnr(''))) + +Execute(You should be able to override the jedi-language-server virtualenv lookup): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_jedils_use_global = 1 + + AssertLinter 'jedi-language-server', ale#Escape('jedi-language-server') + +Execute(Setting executable to 'pipenv' appends 'run jedi-language-server'): + let g:ale_python_jedils_executable = 'path/to/pipenv' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'path/to/pipenv', ale#Escape('path/to/pipenv') . ' run jedi-language-server' + +Execute(poetry is detected when python_jedils_auto_poetry is set): + let g:ale_python_jedils_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run jedi-language-server' + +Execute(uv is detected when python_jedils_auto_uv is set): + let g:ale_python_jedils_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run jedi-language-server' diff --git a/test/linter/test_jq.vader b/test/linter/test_jq.vader new file mode 100644 index 00000000..20c3db5b --- /dev/null +++ b/test/linter/test_jq.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('json', 'jq') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'jq', ale#Escape('jq') diff --git a/test/linter/test_jscs.vader b/test/linter/test_jscs.vader new file mode 100644 index 00000000..7cdf5467 --- /dev/null +++ b/test/linter/test_jscs.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'jscs') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Should return the correct default values): + AssertLinter 'jscs', + \ ale#Escape('jscs') . ' --reporter inline --no-colors -' + +Execute(Should allow using a custom executable): + let g:ale_javascript_jscs_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' --reporter inline --no-colors -' diff --git a/test/linter/test_jshint.vader b/test/linter/test_jshint.vader new file mode 100644 index 00000000..517c957c --- /dev/null +++ b/test/linter/test_jshint.vader @@ -0,0 +1,17 @@ +Before: + Save g:ale_jshint_config_loc + + unlet! g:ale_jshint_config_loc + + call ale#assert#SetUpLinterTest('javascript', 'jshint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'jshint', ale#Escape('jshint') . ' --reporter unix --extract auto --filename %s -' + +Execute(Setting a config location should add the config parameter): + let g:ale_jshint_config_loc = '/some/file' + + AssertLinter 'jshint', ale#Escape('jshint') . ' --reporter unix --extract auto --config ' . ale#Escape('/some/file') . ' --filename %s -' diff --git a/test/linter/test_json_vscodejson.vader b/test/linter/test_json_vscodejson.vader new file mode 100644 index 00000000..f627bb3d --- /dev/null +++ b/test/linter/test_json_vscodejson.vader @@ -0,0 +1,33 @@ +Before: + let g:executable_map = {} + + call ale#assert#SetUpLinterTest('json', 'vscodejson') + + runtime autoload/ale/engine.vim + + " Stub out IsExecutable so we can emulate it. + function! ale#engine#IsExecutable(buffer, executable) abort + return get(g:executable_map, a:executable) + endfunction + +After: + unlet! g:executable_map + + call ale#assert#TearDownLinterTest() + + runtime autoload/ale/engine.vim + +Execute(The default executable name should be correct): + let g:executable_map = {'vscode-json-languageserver': 1} + + AssertLinter 'vscode-json-languageserver', [ale#Escape('vscode-json-languageserver') . ' --stdio'] + +Execute(We should fall back on the old executable name): + let g:executable_map = {'vscode-json-languageserver': 0} + + AssertLinter 'vscode-json-language-server', [ale#Escape('vscode-json-language-server') . ' --stdio'] + +Execute(Executable name should be configurable): + let b:ale_json_vscodejson_executable = 'foo' + + AssertLinter 'foo', [ale#Escape('foo') . ' --stdio'] diff --git a/test/linter/test_jsonlint.vader b/test/linter/test_jsonlint.vader new file mode 100644 index 00000000..52bbe248 --- /dev/null +++ b/test/linter/test_jsonlint.vader @@ -0,0 +1,31 @@ +Before: + call ale#assert#SetUpLinterTest('json', 'jsonlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(local executable should be detected correctly): + call ale#test#SetFilename('../test-files/jsonlint/app/src/app.json') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/jsonlint/app/node_modules/.bin/jsonlint'), + \ 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('../test-files/jsonlint/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('../test-files/jsonlint/app/src/app.json') + + AssertEqual + \ 'custom_jsonlint', + \ ale_linters#json#jsonlint#GetExecutable(bufnr('')) diff --git a/test/linter/test_jsonnet_lint.vader b/test/linter/test_jsonnet_lint.vader new file mode 100644 index 00000000..529ae008 --- /dev/null +++ b/test/linter/test_jsonnet_lint.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('jsonnet', 'jsonnet_lint') + call ale#test#SetFilename('../jsonnet_files/testfile.jsonnet') + +After: + Restore + + call ale#assert#TearDownLinterTest() + +Execute(The default jsonnet-lint command should be correct): + AssertLinter 'jsonnet-lint', + \ ale#Escape('jsonnet-lint') . ' %t' + +Execute(jsonnet-lint command and options should be customizable): + let g:ale_jsonnet_jsonnet_lint_executable = 'jsonnet' + let g:ale_jsonnet_jsonnet_lint_options = 'fmt' + + AssertLinter 'jsonnet', + \ ale#Escape('jsonnet') . ' fmt %t' diff --git a/test/linter/test_jsonnetfmt.vader b/test/linter/test_jsonnetfmt.vader new file mode 100644 index 00000000..d070cd9f --- /dev/null +++ b/test/linter/test_jsonnetfmt.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('jsonnet', 'jsonnetfmt') + call ale#test#SetFilename('../jsonnet_files/testfile.jsonnet') + +After: + Restore + + call ale#assert#TearDownLinterTest() + +Execute(The default jsonnetfmt command should be correct): + AssertLinter 'jsonnetfmt', + \ ale#Escape('jsonnetfmt') . ' %t' + +Execute(jsonnetfmt command and options should be customizable): + let g:ale_jsonnet_jsonnetfmt_executable = 'jsonnet' + let g:ale_jsonnet_jsonnetfmt_options = 'fmt' + + AssertLinter 'jsonnet', + \ ale#Escape('jsonnet') . ' fmt %t' diff --git a/test/linter/test_julia_languageserver.vader b/test/linter/test_julia_languageserver.vader new file mode 100644 index 00000000..d75665a0 --- /dev/null +++ b/test/linter/test_julia_languageserver.vader @@ -0,0 +1,30 @@ +Before: + Save g:ale_julia_executable + + call ale#assert#SetUpLinterTest('julia', 'languageserver') + +After: + Restore + + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'julia', + \ ale#Escape('julia') . + \' --project=@. --startup-file=no --history-file=no -e ' . + \ ale#Escape('using LanguageServer; using Pkg; import StaticLint; import SymbolServer; server = LanguageServer.LanguageServerInstance(isdefined(Base, :stdin) ? stdin : STDIN, isdefined(Base, :stdout) ? stdout : STDOUT, dirname(Pkg.Types.Context().env.project_file)); server.runlinter = true; run(server);') + +Execute(The executable should be configurable): + let g:ale_julia_executable = 'julia-new' + + AssertLinter 'julia-new', + \ ale#Escape('julia-new') . + \' --project=@. --startup-file=no --history-file=no -e ' . + \ ale#Escape('using LanguageServer; using Pkg; import StaticLint; import SymbolServer; server = LanguageServer.LanguageServerInstance(isdefined(Base, :stdin) ? stdin : STDIN, isdefined(Base, :stdout) ? stdout : STDOUT, dirname(Pkg.Types.Context().env.project_file)); server.runlinter = true; run(server);') + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/julia/test.jl') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/julia') diff --git a/test/linter/test_kotlin_languageserver.vader b/test/linter/test_kotlin_languageserver.vader new file mode 100644 index 00000000..97b867ab --- /dev/null +++ b/test/linter/test_kotlin_languageserver.vader @@ -0,0 +1,23 @@ +Before: + call ale#assert#SetUpLinterTest('kotlin', 'languageserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'kotlin-language-server', ale#Escape('kotlin-language-server') + +Execute(Gradle project roots with build.gradle should be detected correctly): + call ale#test#SetFilename('../test-files/gradle/build-gradle-project/src/main/kotlin/dummy.kt') + + AssertLSPProject ale#test#GetFilename('../test-files/gradle/build-gradle-project') + +Execute(Maven project roots with pom.xml should be detected correctly): + call ale#test#SetFilename('../test-files/maven/maven-kotlin-project/src/main/kotlin/dummy.kt') + + AssertLSPProject ale#test#GetFilename('../test-files/maven/maven-kotlin-project') + +Execute(No root should be detected if configuration files can't be found): + call ale#test#SetFilename('../test-files/gradle/non-gradle-project/src/main/kotlin/dummy.kt') + + AssertLSPProject '' diff --git a/test/linter/test_kotlinc.vader b/test/linter/test_kotlinc.vader new file mode 100644 index 00000000..fe94bffa --- /dev/null +++ b/test/linter/test_kotlinc.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/linter/test_languagetool.vader b/test/linter/test_languagetool.vader new file mode 100644 index 00000000..9893138d --- /dev/null +++ b/test/linter/test_languagetool.vader @@ -0,0 +1,22 @@ +Before: + Save g:ale_languagetool_executable + Save g:ale_languagetool_options + + let g:ale_languagetool_executable = 'languagetool' + let g:ale_languagetool_options = '--autoDetect' + + 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' + let g:ale_languagetool_options = '--language en' + + AssertLinter 'foobar' , ale#Escape('foobar') + \ . ' --language en %s' diff --git a/test/linter/test_less_stylelint.vader b/test/linter/test_less_stylelint.vader new file mode 100644 index 00000000..c27af79e --- /dev/null +++ b/test/linter/test_less_stylelint.vader @@ -0,0 +1,32 @@ +Before: + call ale#assert#SetUpLinterTest('less', 'stylelint') + unlet! b:executable + +After: + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(node_modules directories should be discovered): + call ale#test#SetFilename('../test-files/stylelint/nested/testfile.less') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/stylelint/node_modules/.bin/stylelint' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --stdin-filename %s' + +Execute(The global override should work): + let b:ale_less_stylelint_executable = 'foobar' + let b:ale_less_stylelint_use_global = 1 + + call ale#test#SetFilename('../test-files/stylelint/nested/testfile.less') + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdin-filename %s' + +Execute(Extra options should be configurable): + let b:ale_less_stylelint_options = '--whatever' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'stylelint', + \ ale#Escape('stylelint') . ' --whatever --stdin-filename %s' diff --git a/test/linter/test_lessc.vader b/test/linter/test_lessc.vader new file mode 100644 index 00000000..b7d664c6 --- /dev/null +++ b/test/linter/test_lessc.vader @@ -0,0 +1,46 @@ +Before: + call ale#assert#SetUpLinterTest('less', 'lessc') + call ale#test#SetFilename('testfile.less') + + unlet! b:executable + +After: + unlet! b:executable + + call ale#assert#TearDownLinterTest() + +Execute(node_modules directories should be discovered): + call ale#test#SetFilename('../test-files/lessc/nested/testfile.less') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/lessc/node_modules/.bin/lessc' + \) + + AssertLinter b:executable, ale#Escape(b:executable) + \ . ' --no-color --lint' + \ . ' --include-path=' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/lessc/nested')) + \ . ' -' + +Execute(The global override should work): + let b:ale_less_lessc_executable = 'foobar' + let b:ale_less_lessc_use_global = 1 + + call ale#test#SetFilename('../test-files/lessc/nested/testfile.less') + + AssertLinter 'foobar', ale#Escape('foobar') + \ . ' --no-color --lint' + \ . ' --include-path=' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/lessc/nested')) + \ . ' -' + +Execute(Extra options should be configurable): + let b:ale_less_lessc_options = '--whatever' + + AssertLinter 'lessc', ale#Escape('lessc') + \ . ' --no-color --lint' + \ . ' --include-path=' + \ . ale#Escape(ale#path#Simplify(g:dir)) + \ . ' --whatever' + \ . ' -' diff --git a/test/linter/test_lexical.vader b/test/linter/test_lexical.vader new file mode 100644 index 00000000..a5ca2f9e --- /dev/null +++ b/test/linter/test_lexical.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpLinterTest('elixir', 'lexical') + +After: + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + if has('win32') + AssertLinter 'lexical\start_lexical.bat', 'lexical\start_lexical.bat' + else + AssertLinter 'lexical/start_lexical.sh', 'lexical/start_lexical.sh' + endif + +Execute(should configure lexical release location): + let b:ale_elixir_lexical_release = 'boo' + + if has('win32') + AssertLinter 'boo\start_lexical.bat', 'boo\start_lexical.bat' + else + AssertLinter 'boo/start_lexical.sh', 'boo/start_lexical.sh' + endif + +Execute(should set correct LSP values): + call ale#test#SetFilename('../test-files/elixir/umbrella_project/apps/app1/lib/app.ex') + + AssertLSPLanguage 'elixir' + AssertLSPOptions {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/elixir/umbrella_project') diff --git a/test/linter/test_lintr.vader b/test/linter/test_lintr.vader new file mode 100644 index 00000000..8f6fb88f --- /dev/null +++ b/test/linter/test_lintr.vader @@ -0,0 +1,34 @@ +Before: + call ale#assert#SetUpLinterTest('r', 'lintr') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default lintr command should be correct): + AssertLinterCwd '%s:h' + AssertLinter 'Rscript', + \ 'Rscript --no-save --no-restore --no-site-file --no-init-file -e ' + \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' + \ . 'lint(cache = FALSE, commandArgs(TRUE), ' + \ . 'with_defaults())') + \ . ' %t' + +Execute(The lintr options should be configurable): + let b:ale_r_lintr_options = 'with_defaults(object_usage_linter = NULL)' + + AssertLinter 'Rscript', + \ 'Rscript --no-save --no-restore --no-site-file --no-init-file -e ' + \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' + \ . 'lint(cache = FALSE, commandArgs(TRUE), ' + \ . 'with_defaults(object_usage_linter = NULL))') + \ . ' %t' + +Execute(If the lint_package flag is set, lintr::lint_package should be called): + let b:ale_r_lintr_lint_package = 1 + + AssertLinter 'Rscript', + \ 'Rscript --no-save --no-restore --no-site-file --no-init-file -e ' + \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' + \ . 'lint_package(cache = FALSE, ' + \ . 'linters = with_defaults())') + \ . ' %t' diff --git a/test/linter/test_llc.vader b/test/linter/test_llc.vader new file mode 100644 index 00000000..a0caaa48 --- /dev/null +++ b/test/linter/test_llc.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('llvm', 'llc') + + function! AssertHasPrefix(str, prefix) abort + let msg = printf("'%s' is expected to be prefixed with '%s'", a:str, a:prefix) + AssertEqual stridx(a:str, a:prefix), 0, msg + endfunction + +After: + delfunction AssertHasPrefix + + call ale#assert#TearDownLinterTest() + +Execute(The llc command should be customizable): + AssertLinter 'llc', + \ ale#Escape('llc') . ' -filetype=null -o=' . g:ale#util#nul_file + + let g:ale_llvm_llc_executable = 'llc-5.0' + + AssertLinter 'llc-5.0', + \ ale#Escape('llc-5.0') . ' -filetype=null -o=' . g:ale#util#nul_file diff --git a/test/linter/test_llvm_mc.vader b/test/linter/test_llvm_mc.vader new file mode 100644 index 00000000..c77eaa0e --- /dev/null +++ b/test/linter/test_llvm_mc.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('asm', 'llvm_mc') + call ale#test#SetFilename('test.cpp') + let b:command_tail = ' --assemble' + \ . ' --filetype=asm' + \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' ' + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The default llvm-mc command should be correct): + AssertLinter 'llvm-mc', ale#Escape('llvm-mc') . b:command_tail + +Execute(The llvm-mc executable should be configurable): + let b:ale_asm_llvm_mc_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . b:command_tail diff --git a/test/linter/test_lua_language_server.vader b/test/linter/test_lua_language_server.vader new file mode 100644 index 00000000..f6abd364 --- /dev/null +++ b/test/linter/test_lua_language_server.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('lua', 'lua_language_server') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default lua-language-server settings should be correct): + AssertLinter 'lua-language-server', ale#Escape('lua-language-server') + AssertLSPConfig {} + +Execute(lua-language-server should be configurable): + let b:ale_lua_language_server_executable = 'billy' + let b:ale_lua_language_server_config = {'x': 'y'} + + AssertLinter 'billy', ale#Escape('billy') + AssertLSPConfig {'x': 'y'} + +Execute(lua-language-server should detect the project root using .luarc.json): + call ale#test#SetFilename('../lua/dummy.lua') + + AssertLSPProject ale#path#Simplify(g:dir . '/../lua') diff --git a/test/linter/test_lua_selene.vader b/test/linter/test_lua_selene.vader new file mode 100644 index 00000000..7387bace --- /dev/null +++ b/test/linter/test_lua_selene.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('lua', 'selene') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The lua selene command callback should return the correct default string): + AssertLinter 'selene', ale#Escape('selene') . ' --display-style=json -' + +Execute(The lua selene command callback should let you set options): + let g:ale_lua_selene_options = '--num-threads 2' + + AssertLinter 'selene', + \ ale#Escape('selene') . ' --num-threads 2 --display-style=json -' + +Execute(The selene executable should be configurable): + let g:ale_lua_selene_executable = 'selene.sh' + + AssertLinter 'selene.sh', ale#Escape('selene.sh') . ' --display-style=json -' diff --git a/test/linter/test_luac.vader b/test/linter/test_luac.vader new file mode 100644 index 00000000..55f39cba --- /dev/null +++ b/test/linter/test_luac.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('lua', 'luac') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'luac', ale#Escape('luac') . ' -p -' + +Execute(The luac executable should be configurable): + let g:ale_lua_luac_executable = 'luac.sh' + + AssertLinter 'luac.sh', ale#Escape('luac.sh') . ' -p -' diff --git a/test/linter/test_luacheck.vader b/test/linter/test_luacheck.vader new file mode 100644 index 00000000..1d7276b7 --- /dev/null +++ b/test/linter/test_luacheck.vader @@ -0,0 +1,39 @@ +Before: + call ale#assert#SetUpLinterTest('lua', 'luacheck') + " Default to testing linting Lua not in Vim directories. + call ale#test#SetFilename('/test.lua') + +After: + " Clear the variable for saving the result of the runtime check. + " We don't want to cache the result between tests. + unlet! b:ale_in_runtimepath + call ale#assert#TearDownLinterTest() + +Execute(The luacheck default command should be correct): + AssertLinter 'luacheck', + \ ale#Escape('luacheck') . ' --formatter plain --codes --filename %s -' + +Execute(You should be able to set luacheck options): + let g:ale_lua_luacheck_options = '--config filename' + + AssertLinter 'luacheck', + \ ale#Escape('luacheck') + \ . ' --config filename' + \ . ' --formatter plain --codes --filename %s -' + +Execute(The luacheck executable should be configurable): + let g:ale_lua_luacheck_executable = 'luacheck.sh' + + AssertLinter 'luacheck.sh', + \ ale#Escape('luacheck.sh') . ' --formatter plain --codes --filename %s -' + +Execute(The luacheck command should include vim as a global if in runtimepath): + call ale#test#SetFilename('test.lua') + AssertLinter 'luacheck', + \ ale#Escape('luacheck') . ' --globals vim --formatter plain --codes --filename %s -' + +Execute(The default Vim globals should not be set if globals are already set): + call ale#test#SetFilename('test.lua') + let g:ale_lua_luacheck_options = '--globals foo' + AssertLinter 'luacheck', + \ ale#Escape('luacheck') . ' --globals foo --formatter plain --codes --filename %s -' diff --git a/test/linter/test_markdown_markdownlint.vader b/test/linter/test_markdown_markdownlint.vader new file mode 100644 index 00000000..7ec626ee --- /dev/null +++ b/test/linter/test_markdown_markdownlint.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('markdown', 'markdownlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default markdownlint command should be correct): + AssertLinter 'markdownlint', ale#Escape('markdownlint') . ' %s' + +Execute(The executable should be configurable): + let g:ale_markdown_markdownlint_executable = 'foo bar' + let g:ale_markdown_markdownlint_options = '--option' + + AssertLinter 'foo bar', ale#Escape('foo bar') . ' --option %s' diff --git a/test/linter/test_markdown_marksman.vader b/test/linter/test_markdown_marksman.vader new file mode 100644 index 00000000..42f93683 --- /dev/null +++ b/test/linter/test_markdown_marksman.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('markdown', 'marksman') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'marksman', ale#Escape('marksman') . ' server' diff --git a/test/linter/test_markdown_mdl.vader b/test/linter/test_markdown_mdl.vader new file mode 100644 index 00000000..1ce4db1a --- /dev/null +++ b/test/linter/test_markdown_mdl.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('markdown', 'mdl') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'mdl', ale#Escape('mdl') . ' -j' + +Execute(The executable and options should be configurable): + let g:ale_markdown_mdl_executable = 'foo bar' + let g:ale_markdown_mdl_options = '--wat' + + AssertLinter 'foo bar', ale#Escape('foo bar') . ' -j --wat' + +Execute(Setting bundle appends 'exec mdl'): + let g:ale_markdown_mdl_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') . ' exec mdl -j' diff --git a/test/linter/test_markdown_vale.vader b/test/linter/test_markdown_vale.vader new file mode 100644 index 00000000..5300805b --- /dev/null +++ b/test/linter/test_markdown_vale.vader @@ -0,0 +1,32 @@ +Before: + call ale#assert#SetUpLinterTest('markdown', 'vale') + call ale#test#SetFilename('dummy.md') + + let g:ale_markdown_vale_executable = 'vale' + let g:ale_markdown_vale_input_file = '%t' + let g:ale_markdown_vale_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to vale): + AssertLinter 'vale', ale#Escape('vale') + \ . ' --output=JSON %t' + +Execute(Should be able to set a custom executable): + let g:ale_markdown_vale_executable = 'bin/vale' + + AssertLinter 'bin/vale' , ale#Escape('bin/vale') + \ . ' --output=JSON %t' + +Execute(Should be able to set custom options): + let g:ale_markdown_vale_options = '--foo --bar' + + AssertLinter 'vale', ale#Escape('vale') + \ . ' --output=JSON --foo --bar %t' + +Execute(Should be able to set a custom input file): + let g:ale_markdown_vale_input_file = '%s' + + AssertLinter 'vale', ale#Escape('vale') + \ . ' --output=JSON %s' diff --git a/test/linter/test_mercury_mmc.vader b/test/linter/test_mercury_mmc.vader new file mode 100644 index 00000000..5ab5e74f --- /dev/null +++ b/test/linter/test_mercury_mmc.vader @@ -0,0 +1,22 @@ +Before: + call ale#assert#SetUpLinterTest('mercury', 'mmc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinterCwd '%s:h' + AssertLinter 'mmc', + \ ale#Escape('mmc') . ' --errorcheck-only --make --output-compile-error-lines 100 %s:t:r' + +Execute(The executable should be configurable): + let b:ale_mercury_mmc_executable = 'foo' + + AssertLinter 'foo', + \ ale#Escape('foo') . ' --errorcheck-only --make --output-compile-error-lines 100 %s:t:r' + +Execute(The options should be configurable): + let b:ale_mercury_mmc_options = '--bar' + + AssertLinter 'mmc', + \ ale#Escape('mmc') . ' --errorcheck-only --bar %s:t:r' diff --git a/test/linter/test_mypy.vader b/test/linter/test_mypy.vader new file mode 100644 index 00000000..3cad6c3e --- /dev/null +++ b/test/linter/test_mypy.vader @@ -0,0 +1,114 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'mypy') + 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 mypy callbacks should return the correct default values): + AssertLinterCwd g:dir + AssertLinter 'mypy', + \ ale#Escape('mypy') + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' + +Execute(The mypy executable should be configurable, and escaped properly): + let g:ale_python_mypy_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', + \ ale#Escape('executable with spaces') + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' + +Execute(The mypy command callback should let you set options): + let g:ale_python_mypy_options = '--some-option' + + AssertLinter 'mypy', + \ ale#Escape('mypy') + \ . ' --some-option' + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' + +Execute(The mypy command should switch directories to the detected project root): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'mypy', + \ ale#Escape('mypy') + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' + +Execute(The mypy callbacks should detect virtualenv directories and switch to the project root): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/mypy') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter b:executable, + \ ale#Escape(b:executable) + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' + +Execute(The mypy callbacks should cd to directory containing mypy.ini if found): + call ale#test#SetFilename('../test-files/python/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_mypy_ini_and_pytest_ini') + AssertLinter 'mypy', + \ ale#Escape('mypy') + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' + +Execute(You should able able to use the global mypy instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_mypy_use_global = 1 + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter 'mypy', + \ ale#Escape('mypy') + \ . ' --show-column-numbers' + \ . ' --shadow-file %s %t %s' + +Execute(Setting executable to 'pipenv' appends 'run mypy'): + let g:ale_python_mypy_executable = 'path/to/pipenv' + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run mypy' + \ . ' --show-column-numbers --shadow-file %s %t %s' + +Execute(Pipenv is detected when python_mypy_auto_pipenv is set): + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + let g:ale_python_mypy_auto_pipenv = 1 + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run mypy --show-column-numbers --shadow-file %s %t %s' + +Execute(Setting executable to 'poetry' appends 'run mypy'): + let g:ale_python_mypy_executable = 'path/to/poetry' + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run mypy' + \ . ' --show-column-numbers --shadow-file %s %t %s' + +Execute(Poetry is detected when python_mypy_auto_poetry is set): + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + let g:ale_python_mypy_auto_poetry = 1 + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run mypy --show-column-numbers --shadow-file %s %t %s' + +Execute(uv is detected when python_mypy_auto_uv is set): + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + let g:ale_python_mypy_auto_uv = 1 + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'uv', + \ ale#Escape('uv') . ' run mypy --show-column-numbers --shadow-file %s %t %s' diff --git a/test/linter/test_naga.vader b/test/linter/test_naga.vader new file mode 100644 index 00000000..bf91604b --- /dev/null +++ b/test/linter/test_naga.vader @@ -0,0 +1,10 @@ +Before: + call ale#assert#SetUpLinterTest('wgsl', 'naga') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The naga command should be customizable): + let g:ale_wgsl_naga_executable = '/path/to/naga' + AssertLinter '/path/to/naga', + \ ale#Escape('/path/to/naga') . ' --stdin-file-path %s' diff --git a/test/linter/test_nagelfar.vader b/test/linter/test_nagelfar.vader new file mode 100644 index 00000000..c7afdacc --- /dev/null +++ b/test/linter/test_nagelfar.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('tcl', 'nagelfar') + +After: + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The default nagelfar command should be correct): + AssertLinter 'nagelfar.tcl', ale#Escape('nagelfar.tcl') . ' %s' + +Execute(The negalfar executable and options should be configurable): + let b:ale_tcl_nagelfar_executable = 'foobar' + let b:ale_tcl_nagelfar_options = '--something' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --something %s' diff --git a/test/linter/test_nasm_nasm.vader b/test/linter/test_nasm_nasm.vader new file mode 100644 index 00000000..ef56aa28 --- /dev/null +++ b/test/linter/test_nasm_nasm.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('nasm', 'nasm') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default nasm command should be correct): + AssertLinter 'nasm', ale#Escape('nasm') + \ . ' -X gnu -I %s:h' . (has('win32') ? '\' : '/') + \ . ' %s -o ' . (has('win32') ? 'NUL' : '/dev/null') + +Execute(The nasm executable and options should be configurable): + let b:ale_nasm_nasm_executable = '~/nasm' + let b:ale_nasm_nasm_options = '-w-macro-params' + + AssertLinter '~/nasm', ale#Escape('~/nasm') + \ . ' -X gnu -I %s:h' . (has('win32') ? '\' : '/') + \ . ' -w-macro-params' + \ . ' %s -o ' . (has('win32') ? 'NUL' : '/dev/null') diff --git a/test/linter/test_nimlsp.vader b/test/linter/test_nimlsp.vader new file mode 100644 index 00000000..c109deef --- /dev/null +++ b/test/linter/test_nimlsp.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpLinterTest('nim', 'nimlsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(It does not set nim sources by default): + AssertLinter 'nimlsp', ale#Escape('nimlsp') + +Execute(Sets nimlsp and escapes sources from g:ale_nim_nimlsp_nim_sources): + let g:ale_nim_nimlsp_nim_sources = '/path/to /Nim' + AssertLinter 'nimlsp', ale#Escape('nimlsp') . ' ' . ale#Escape('/path/to /Nim') diff --git a/test/linter/test_nix_deadnix.vader b/test/linter/test_nix_deadnix.vader new file mode 100644 index 00000000..90416302 --- /dev/null +++ b/test/linter/test_nix_deadnix.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('nix', 'deadnix') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The deadnix command should be correct): + AssertLinter 'deadnix', ale#Escape('deadnix') . ' -o json -- %t' + +Execute(Additional deadnix options should be configurable): + let g:ale_nix_deadnix_options = '--foobar' + + AssertLinter 'deadnix', + \ ale#Escape('deadnix') . ' -o json --foobar -- %t' + +Execute(The deadnix command should be configurable): + let g:ale_nix_deadnix_executable = 'foo/bar' + + AssertLinter 'foo/bar', ale#Escape('foo/bar') . ' -o json -- %t' diff --git a/test/linter/test_nix_statix.vader b/test/linter/test_nix_statix.vader new file mode 100644 index 00000000..a6a389d5 --- /dev/null +++ b/test/linter/test_nix_statix.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('nix', 'statix') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default statix command should be correct): + AssertLinter 'statix', ale#Escape('statix') . ' check -o errfmt --stdin' + +Execute(The statix executable and options should be configurable): + let g:ale_nix_statix_check_executable = 'foo' + let g:ale_nix_statix_check_options = '--foobar' + + AssertLinter 'foo', ale#Escape('foo') . ' check -o errfmt --stdin --foobar' diff --git a/test/linter/test_npmgroovylint.vader b/test/linter/test_npmgroovylint.vader new file mode 100644 index 00000000..e87b209b --- /dev/null +++ b/test/linter/test_npmgroovylint.vader @@ -0,0 +1,18 @@ +Before: + Save b:ale_groovy_npmgroovylint_options + + call ale#assert#SetUpLinterTest('groovy', 'npmgroovylint') + call ale#test#SetFilename('test.groovy') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default npm-groovy-lint command should be correct): + AssertLinter 'npm-groovy-lint', + \ ale#Escape('npm-groovy-lint') . ' --failon none --output json --loglevel warning %t' + +Execute(Default options should be configurable): + let b:ale_groovy_npmgroovylint_options = '--loglevel info' + + AssertLinter 'npm-groovy-lint', + \ ale#Escape('npm-groovy-lint') . ' --failon none --output json --loglevel info %t' diff --git a/test/linter/test_objc_ccls.vader b/test/linter/test_objc_ccls.vader new file mode 100644 index 00000000..58d824c5 --- /dev/null +++ b/test/linter/test_objc_ccls.vader @@ -0,0 +1,66 @@ +Before: + call ale#assert#SetUpLinterTest('objc', 'ccls') + + Save b:ale_c_build_dir_names + Save b:ale_objc_ccls_executable + Save b:ale_objc_ccls_init_options + +After: + call ale#assert#TearDownLinterTest() + +Execute(The project root should be detected correctly using compile_commands.json file): + call ale#test#SetFilename(tempname() . '/dummy.m') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_compile_commands_json/dummy.m') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_compile_commands_json') + +Execute(The project root should be detected correctly using .ccls file): + call ale#test#SetFilename(tempname() . '/dummy.m') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_ccls/dummy.m') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_ccls') + +Execute(The project root should be detected correctly using .ccls-root file): + call ale#test#SetFilename(tempname() . '/dummy.m') + + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ccls/with_ccls-root/dummy.m') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ccls/with_ccls-root') + +Execute(The executable should be configurable): + AssertLinter 'ccls', ale#Escape('ccls') + + let b:ale_objc_ccls_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The initialization options should be configurable): + AssertLSPOptions {} + + let b:ale_objc_ccls_init_options = { 'cacheDirectory': '/tmp/ccls' } + + AssertLSPOptions { 'cacheDirectory': '/tmp/ccls' } + +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('../test-files/ccls/with_ccls/dummy.c') + + AssertLSPOptions {} + + call ale#test#SetFilename('../test-files/ccls/with_compile_commands_json/dummy.c') + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/../test-files/ccls/with_compile_commands_json') } + + call ale#test#SetFilename('../test-files/ccls/with_build_dir/dummy.c') + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/../test-files/ccls/with_build_dir/unusual_build_dir_name') } diff --git a/test/linter/test_ocaml_ocamllsp.vader b/test/linter/test_ocaml_ocamllsp.vader new file mode 100644 index 00000000..4f33af18 --- /dev/null +++ b/test/linter/test_ocaml_ocamllsp.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('ocaml', 'ocamllsp') + + Save &filetype + let &filetype = 'ocaml' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'ocaml' + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ocamllsp/file.ml') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ocamllsp') + +Execute(The executable should be run using opam exec by default): + call ale#test#SetFilename('../test-files/ocamllsp/file.ml') + + AssertLinter 'ocamllsp', 'opam config exec -- ocamllsp' + +Execute(The executable should be run directly if use_opam flag is disabled): + let g:ale_ocaml_ocamllsp_use_opam = 0 + call ale#test#SetFilename('../test-files/ocamllsp/file.ml') + + AssertLinter 'ocamllsp', 'ocamllsp' diff --git a/test/linter/test_ocaml_ols.vader b/test/linter/test_ocaml_ols.vader new file mode 100644 index 00000000..ddab9958 --- /dev/null +++ b/test/linter/test_ocaml_ols.vader @@ -0,0 +1,41 @@ +Before: + call ale#assert#SetUpLinterTest('ocaml', 'ols') + + Save &filetype + let &filetype = 'ocaml' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'ocaml' + +Execute(The default executable should be correct): + AssertLinter 'ocaml-language-server', + \ ale#Escape('ocaml-language-server') . ' --stdio' + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ols/file.ml') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ols') + +Execute(The local executable should be used when available): + call ale#test#SetFilename('../test-files/ols/file.ml') + + AssertLinter ale#path#Simplify(g:dir . '/../test-files/ols/node_modules/.bin/ocaml-language-server'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/ols/node_modules/.bin/ocaml-language-server')) . ' --stdio' + +Execute(The global executable should always be used when use_global is set): + let g:ale_ocaml_ols_use_global = 1 + call ale#test#SetFilename('../test-files/ols/file.ml') + + AssertLinter 'ocaml-language-server', + \ ale#Escape('ocaml-language-server') . ' --stdio' + +Execute(The executable should be configurable): + let g:ale_ocaml_ols_executable = 'foobar' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdio' diff --git a/test/linter/test_ocamlinterface_ocamllsp.vader b/test/linter/test_ocamlinterface_ocamllsp.vader new file mode 100644 index 00000000..aa0b2100 --- /dev/null +++ b/test/linter/test_ocamlinterface_ocamllsp.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('ocamlinterface', 'ocamllsp') + + Save &filetype + let &filetype = 'ocamlinterface' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'ocaml.interface' + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ocamllsp/file.ml') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ocamllsp') + +Execute(The executable should be run using opam exec by default): + call ale#test#SetFilename('../test-files/ocamllsp/file.ml') + + AssertLinter 'ocamllsp', 'opam config exec -- ocamllsp' + +Execute(The executable should be run directly if use_opam flag is disabled): + let g:ale_ocaml_ocamllsp_use_opam = 0 + call ale#test#SetFilename('../test-files/ocamllsp/file.ml') + + AssertLinter 'ocamllsp', 'ocamllsp' diff --git a/test/linter/test_odin_ols.vader b/test/linter/test_odin_ols.vader new file mode 100644 index 00000000..ac7d2797 --- /dev/null +++ b/test/linter/test_odin_ols.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('odin', 'ols') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'ols', ale#Escape('ols') + +Execute(The LSP values should be set correctly): + call ale#test#SetFilename('../test-files/odin/main.odin') + + AssertLSPLanguage 'odin' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject '.' diff --git a/test/linter/test_openscad_sca2d.vader b/test/linter/test_openscad_sca2d.vader new file mode 100644 index 00000000..c2409f55 --- /dev/null +++ b/test/linter/test_openscad_sca2d.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpLinterTest('openscad', 'sca2d') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The options should be used in the command): + AssertLinter 'sca2d', ale#Escape('sca2d') . ' %s' + + let b:ale_openscad_sca2d_options = '--foobar' + + AssertLinter 'sca2d', ale#Escape('sca2d') . ' --foobar %s' diff --git a/test/linter/test_packwerk.vader b/test/linter/test_packwerk.vader new file mode 100644 index 00000000..9a91fd9e --- /dev/null +++ b/test/linter/test_packwerk.vader @@ -0,0 +1,38 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'packwerk') + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/db/test.rb') + + let g:ale_ruby_packwerk_executable = 'packwerk' + let g:ale_ruby_packwerk_options = '' + + let b:sep = has('win32') ? '\' : '/' + +After: + unlet! b:sep + + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to packwerk): + AssertLinter 'packwerk', ale#Escape('packwerk') + \ . ' check ' + \ . ale#Escape('db' . b:sep . 'test.rb') + +Execute(Should be able to set a custom executable): + let g:ale_ruby_packwerk_executable = 'bin/packwerk' + + AssertLinter 'bin/packwerk', ale#Escape('bin/packwerk') + \ . ' check ' + \ . ale#Escape('db' . b:sep . 'test.rb') + +Execute(Setting bundle appends 'exec packwerk'): + let g:ale_ruby_packwerk_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec packwerk' + \ . ' check ' + \ . ale#Escape('db' . b:sep . 'test.rb') + +Execute(Command callback should be empty when not in a valid Rails app): + call ale#test#SetFilename('../test-files/ruby/not_a_rails_app/test.rb') + + AssertLinter 'packwerk', '' diff --git a/test/linter/test_perl.vader b/test/linter/test_perl.vader new file mode 100644 index 00000000..3c4b661c --- /dev/null +++ b/test/linter/test_perl.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('perl', 'perl') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default Perl command callback should be correct): + AssertLinter 'perl', ale#Escape('perl') . ' -c -Mwarnings -Ilib %t' + +Execute(Overriding the executable and command should work): + let b:ale_perl_perl_executable = 'foobar' + let b:ale_perl_perl_options = '-w' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -w %t' diff --git a/test/linter/test_perl6.vader b/test/linter/test_perl6.vader new file mode 100644 index 00000000..d3ec6e17 --- /dev/null +++ b/test/linter/test_perl6.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('perl6', 'perl6') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default Perl6 command callback should be correct): + AssertLinter 'perl6', 'perl6' . ' -c -Ilib %t' + +Execute(Overriding the executable and command should work): + let b:ale_perl6_perl6_executable = 'foobar' + let b:ale_perl6_perl6_options = '-w' + + AssertLinter 'foobar', 'foobar' . ' -w %t' diff --git a/test/linter/test_perlcritic.vader b/test/linter/test_perlcritic.vader new file mode 100644 index 00000000..0f1e2856 --- /dev/null +++ b/test/linter/test_perlcritic.vader @@ -0,0 +1,36 @@ +Before: + call ale#assert#SetUpLinterTest('perl', 'perlcritic') + call ale#test#SetFilename('test.pl') + let g:ale_perl_perlcritic_profile = '' + +After: + unlet! b:readme_path + call ale#assert#TearDownLinterTest() + +Execute(The command should be correct with g:ale_perl_perlcritic_showrules off): + let b:ale_perl_perlcritic_showrules = 0 + + AssertLinter 'perlcritic', ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m\n') . ' --nocolor' + +Execute(The command should be correct with g:ale_perl_perlcritic_showrules on): + let b:ale_perl_perlcritic_showrules = 1 + + AssertLinter 'perlcritic', ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m [%p]\n') . ' --nocolor' + +Execute(The command search for the profile file when set): + let b:ale_perl_perlcritic_profile = 'README.md' + + let b:readme_path = ale#path#Simplify(expand('%:p:h:h:h') . '/README.md') + + AssertLinter 'perlcritic', ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m\n') . ' --nocolor' + \ . ' --profile ' . ale#Escape(b:readme_path) + +Execute(Extra options should be set appropriately): + let b:ale_perl_perlcritic_options = 'beep boop' + + AssertLinter 'perlcritic', ale#Escape('perlcritic') + \ . ' --verbose ' . ale#Escape('%l:%c %m\n') . ' --nocolor' + \ . ' beep boop' diff --git a/test/linter/test_php.vader b/test/linter/test_php.vader new file mode 100644 index 00000000..670d7196 --- /dev/null +++ b/test/linter/test_php.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'php') + let b:command_tail = ' -l -d error_reporting=E_ALL -d display_errors=1' + \ . ' -d log_errors=0 --' + +After: + unlet! b:command_tail + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'php', ale#Escape('php') . b:command_tail + + let b:ale_php_php_executable = '/path/to/php' + + AssertLinter '/path/to/php', ale#Escape('/path/to/php') . b:command_tail diff --git a/test/linter/test_php_intelephense.vader b/test/linter/test_php_intelephense.vader new file mode 100644 index 00000000..d6e2469d --- /dev/null +++ b/test/linter/test_php_intelephense.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'intelephense') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'intelephense', + \ ale#Escape('intelephense') . ' --stdio' + +Execute(The project path should be correct for .git directories): + call ale#test#SetFilename('../test-files/php/with-git/test.php') + silent! call mkdir('../test-files/php/with-git/.git', 'p') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-git') + +Execute(The project path should be correct for composer.json file): + call ale#test#SetFilename('../test-files/php/with-composer/test.php') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-composer') + +Execute(The project cache should be saved in a temp dir): + call ale#test#SetFilename('../test-files/php/with-composer/test.php') + let g:ale_php_intelephense_config = { 'storagePath': '/tmp/intelephense' } + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-composer') diff --git a/test/linter/test_php_langserver.vader b/test/linter/test_php_langserver.vader new file mode 100644 index 00000000..28741713 --- /dev/null +++ b/test/linter/test_php_langserver.vader @@ -0,0 +1,32 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'langserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'php-language-server.php', + \ 'php ' . ale#Escape('php-language-server.php') + +Execute(Vendor executables should be detected): + call ale#test#SetFilename('../test-files/php/test.php') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/php/vendor/bin/php-language-server.php'), + \ 'php ' . ale#Escape(ale#path#Simplify( + \ g:dir + \ . '/../test-files/php/vendor/bin/php-language-server.php' + \ )) + +Execute(The project path should be correct for .git directories): + call ale#test#SetFilename('../test-files/php/with-git/test.php') + silent! call mkdir('../test-files/php/with-git/.git') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-git') + +Execute(The project path should be correct for composer.json file): + call ale#test#SetFilename('../test-files/php/with-composer/test.php') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-composer') diff --git a/test/linter/test_phpactor.vader b/test/linter/test_phpactor.vader new file mode 100644 index 00000000..8968bba1 --- /dev/null +++ b/test/linter/test_phpactor.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'phpactor') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'phpactor', + \ ale#Escape('phpactor') . ' language-server' + +Execute(The project path should be correct for .git directories): + call ale#test#SetFilename('../test-files/php/with-git/test.php') + silent! call mkdir('../test-files/php/with-git/.git') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-git') + +Execute(The project path should be correct for composer.json file): + call ale#test#SetFilename('../test-files/php/with-composer/test.php') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-composer') diff --git a/test/linter/test_phpcs.vader b/test/linter/test_phpcs.vader new file mode 100644 index 00000000..afb88e32 --- /dev/null +++ b/test/linter/test_phpcs.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'phpcs') + +After: + unlet! g:executable + + call ale#assert#TearDownLinterTest() + +Execute(The local phpcs executable should be used): + call ale#test#SetFilename('../test-files/phpcs/project-with-phpcs/foo/test.php') + + let g:executable = ale#path#Simplify(g:dir . '/../test-files/phpcs/project-with-phpcs/vendor/bin/phpcs') + + AssertLinterCwd '%s:h' + AssertLinter g:executable, ale#Escape(g:executable) + \ . ' -s --report=emacs --stdin-path=%s' + +Execute(use_global should override local executable detection): + let g:ale_php_phpcs_use_global = 1 + + call ale#test#SetFilename('../test-files/phpcs/project-with-phpcs/foo/test.php') + + AssertLinter 'phpcs', ale#Escape('phpcs') + \ . ' -s --report=emacs --stdin-path=%s' + +Execute(Projects without local executables should use the global one): + call ale#test#SetFilename('../test-files/phpcs/project-without-phpcs/foo/test.php') + + AssertLinter 'phpcs', ale#Escape('phpcs') + \ . ' -s --report=emacs --stdin-path=%s' + +Execute(User provided options should be used): + let g:ale_php_phpcs_options = '--my-user-provided-option my-value' + + AssertLinter 'phpcs', ale#Escape('phpcs') + \ . ' -s --report=emacs --stdin-path=%s --my-user-provided-option my-value' + +Execute(The _standard option should be used): + let g:ale_php_phpcs_standard = 'foobar' + + AssertLinter 'phpcs', ale#Escape('phpcs') + \ . ' -s --report=emacs --stdin-path=%s --standard=' . ale#Escape('foobar') diff --git a/test/linter/test_phpmd.vader b/test/linter/test_phpmd.vader new file mode 100644 index 00000000..29bf857e --- /dev/null +++ b/test/linter/test_phpmd.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'phpmd') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Custom executables should be used for the executable and command): + let g:ale_php_phpmd_executable = 'phpmd_test' + + AssertLinter 'phpmd_test', + \ ale#Escape('phpmd_test') + \ . ' %t text cleancode,codesize,controversial,design,naming,unusedcode --ignore-violations-on-exit' diff --git a/test/linter/test_phpstan.vader b/test/linter/test_phpstan.vader new file mode 100644 index 00000000..908a186d --- /dev/null +++ b/test/linter/test_phpstan.vader @@ -0,0 +1,135 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'phpstan') + + let g:old_dir = g:dir + + " Create a temporary directory and work within it, otherwise these tests + " cannot be run in parallel. + let g:parent_dir = tempname() + let g:dir = ale#path#Simplify(g:parent_dir . '/src') + + call mkdir(g:parent_dir, '', 0750) + call mkdir(g:dir, '', 0750) + + silent! execute 'cd ' . fnameescape(g:dir) + silent! noautocmd execute 'file ' . fnameescape(ale#path#Simplify(g:dir . '/test.php')) + + call delete('./phpstan.neon') + + GivenCommandOutput ['0.10.2'] + +After: + silent! execute 'cd ' . fnameescape(g:old_dir) + call delete(g:dir, 'rf') + let g:dir = g:old_dir + unlet! g:old_dir + call ale#assert#TearDownLinterTest() + +Execute(The local phpstan executable should be used): + call mkdir('vendor/bin', 'p', 0750) + call writefile([''], 'vendor/bin/phpstan') + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + + let g:executable = ale#path#Simplify(g:dir . '/vendor/bin/phpstan') + + AssertLinter g:executable, + \ ale#Escape(g:executable) . ' analyze --no-progress --errorFormat json -l ' . ale#Escape('4') . ' %s' + AssertLinterCwd v:null + +Execute(use_global should override local executable detection): + let g:ale_php_phpstan_use_global = 1 + + call mkdir('vendor/bin', 'p', 0750) + call writefile([''], 'vendor/bin/phpstan') + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + + AssertLinter 'phpstan', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat json -l ' . ale#Escape('4') . ' %s' + +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 --no-progress --errorFormat json -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 --no-progress --errorFormat json -l ' . ale#Escape('3') . ' %s' + +Execute(Custom phpstan configuration file): + let g:ale_php_phpstan_configuration = 'phpstan_config' + + AssertLinter 'phpstan', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat json -c ' . ale#Escape('phpstan_config') . ' -l ' . ale#Escape('4') . ' %s' + +Execute(Choose the right format for error format param): + GivenCommandOutput ['0.10.3'] + + AssertLinter 'phpstan', [ + \ ale#Escape('phpstan') . ' --version', + \ ale#Escape('phpstan') . ' analyze --no-progress --error-format json -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 json %s' + \ ] + AssertLinterCwd g:dir + +Execute(Configuration dist file exists in current directory): + call writefile(['parameters:', ' level: 7'], './phpstan.neon.dist') + 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 json %s' + \ ] + AssertLinterCwd g:dir + +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 json -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 json -c ' . ale#Escape('phpstan.custom.neon') . ' %s' + \ ] + +Execute(Autoload parameter is added to the command): + let g:ale_php_phpstan_autoload = 'autoload.php' + + AssertLinter 'phpstan', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat json -a ' . ale#Escape('autoload.php') . ' -l ' . ale#Escape('4') . ' %s' + +Execute(Memory limit parameter is added to the command): + let g:ale_php_phpstan_memory_limit = '500M' + + AssertLinter 'phpstan', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat json -l ' . ale#Escape('4') . ' --memory-limit=' . ale#Escape('500M') . ' %s' + +Execute(Directory is changed to that of the configuration file): + call writefile([], '../phpstan.neon') + + AssertLinterCwd g:parent_dir + AssertLinter 'phpstan', + \ ale#Escape('phpstan') . ' analyze --no-progress --errorFormat json %s' diff --git a/test/linter/test_pony_ponyc.vader b/test/linter/test_pony_ponyc.vader new file mode 100644 index 00000000..18952db7 --- /dev/null +++ b/test/linter/test_pony_ponyc.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('pony', 'ponyc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default ponyc command should be correct): + AssertLinter 'ponyc', ale#Escape('ponyc') . ' --pass paint' + +Execute(The pony executable and options should be configurable): + let b:ale_pony_ponyc_executable = 'foobar' + let b:ale_pony_ponyc_options = '--some-option' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --some-option' diff --git a/test/linter/test_prospector.vader b/test/linter/test_prospector.vader new file mode 100644 index 00000000..934849a7 --- /dev/null +++ b/test/linter/test_prospector.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'prospector') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Setting executable to 'pipenv' appends 'run prospector'): + let g:ale_python_prospector_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run prospector' + \ . ' --messages-only --absolute-paths --zero-exit --output-format json %s' + +Execute(Pipenv is detected when python_prospector_auto_pipenv is set): + let g:ale_python_prospector_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run prospector' + \ . ' --messages-only --absolute-paths --zero-exit --output-format json %s' + +Execute(Setting executable to 'poetry' appends 'run prospector'): + let g:ale_python_prospector_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run prospector' + \ . ' --messages-only --absolute-paths --zero-exit --output-format json %s' + +Execute(Poetry is detected when python_prospector_auto_poetry is set): + let g:ale_python_prospector_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run prospector' + \ . ' --messages-only --absolute-paths --zero-exit --output-format json %s' + +Execute(uv is detected when python_prospector_auto_uv is set): + let g:ale_python_prospector_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run prospector' + \ . ' --messages-only --absolute-paths --zero-exit --output-format json %s' diff --git a/test/linter/test_proto.vader b/test/linter/test_proto.vader new file mode 100644 index 00000000..726588c0 --- /dev/null +++ b/test/linter/test_proto.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('proto', 'protoc_gen_lint') + call ale#test#SetFilename('test.proto') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'protoc', + \ 'protoc' . ' -I ' . ale#Escape(getcwd()) . ' --lint_out=. ' . '%s' + +Execute(The callback should include any additional options): + let b:ale_proto_protoc_gen_lint_options = '--some-option' + + AssertLinter 'protoc', + \ 'protoc' . ' -I ' . ale#Escape(getcwd()) . ' --some-option --lint_out=. ' . '%s' diff --git a/test/linter/test_protolint.vader b/test/linter/test_protolint.vader new file mode 100644 index 00000000..4463b629 --- /dev/null +++ b/test/linter/test_protolint.vader @@ -0,0 +1,24 @@ +Before: + call ale#assert#SetUpLinterTest('proto', 'protolint') + call ale#test#SetFilename('test.proto') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'protolint', + \ ale#Escape('protolint') + \ . ' lint' + \ . ' -reporter=unix' + \ . ' %s' + +Execute(The callback should include any additional options): + let b:ale_proto_protolint_executable = '/tmp/protolint' + let b:ale_proto_protolint_config = '/tmp/protolint.yaml' + + AssertLinter '/tmp/protolint', + \ ale#Escape('/tmp/protolint') + \ . ' lint' + \ . ' -config_path=' . ale#Escape('/tmp/protolint.yaml') + \ . ' -reporter=unix' + \ . ' %s' diff --git a/test/linter/test_psalm.vader b/test/linter/test_psalm.vader new file mode 100644 index 00000000..94e718d4 --- /dev/null +++ b/test/linter/test_psalm.vader @@ -0,0 +1,44 @@ +Before: + call ale#assert#SetUpLinterTest('php', 'psalm') + +After: + unlet! g:i + unlet! g:matched + + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'psalm', + \ ale#Escape('psalm') . ' --language-server' + +Execute(Vendor executables should be detected): + call ale#test#SetFilename('../test-files/psalm/test.php') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/psalm/vendor/bin/psalm'), + \ ale#Escape(ale#path#Simplify( + \ g:dir + \ . '/../test-files/psalm/vendor/bin/psalm' + \ )) . ' --language-server' + + let g:ale_php_psalm_use_global = 1 + + AssertLinter 'psalm', + \ ale#Escape('psalm') . ' --language-server' + +Execute(User provided options should be used): + let g:ale_php_psalm_options = '--my-user-provided-option my-value' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'psalm', + \ ale#Escape('psalm') + \ . ' --language-server --my-user-provided-option my-value' + +Execute(The project path should be correct for composer.json file): + call ale#test#SetFilename('../test-files/php/with-composer/test.php') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/php/with-composer') diff --git a/test/linter/test_puglint.vader b/test/linter/test_puglint.vader new file mode 100644 index 00000000..8a445408 --- /dev/null +++ b/test/linter/test_puglint.vader @@ -0,0 +1,48 @@ +Before: + call ale#assert#SetUpLinterTest('pug', 'puglint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(puglint should detect local executables and package.json): + call ale#test#SetFilename('../test-files/puglint/test.pug') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/package.json')) + \ . ' -r inline %t' + +Execute(puglint should use global executables if configured): + let g:ale_pug_puglint_use_global = 1 + + call ale#test#SetFilename('../test-files/puglint/test.pug') + + AssertLinter 'pug-lint', + \ ale#Escape('pug-lint') + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/package.json')) + \ . ' -r inline %t' + +Execute(puglint should detect .pug-lintrc): + call ale#test#SetFilename('../test-files/puglint/puglint_rc_dir/subdir/test.pug') + + AssertLinter ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/puglint_rc_dir/.pug-lintrc')) + \ . ' -r inline %t' + +Execute(puglint should detect .pug-lintrc.js): + call ale#test#SetFilename('../test-files/puglint/puglint_rc_js_dir/subdir/test.pug') + + AssertLinter ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/puglint_rc_js_dir/.pug-lintrc.js')) + \ . ' -r inline %t' + +Execute(puglint should detect .pug-lintrc.json): + call ale#test#SetFilename('../test-files/puglint/puglint_rc_json_dir/subdir/test.pug') + + AssertLinter ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/puglint/puglint_rc_json_dir/.pug-lintrc.json')) + \ . ' -r inline %t' diff --git a/test/linter/test_puppet_languageserver.vader b/test/linter/test_puppet_languageserver.vader new file mode 100644 index 00000000..3fb458ab --- /dev/null +++ b/test/linter/test_puppet_languageserver.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('puppet', 'languageserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(old-style module should find its root correctly): + call ale#test#SetFilename('../test-files/puppet/old-style-module/manifests/init.pp') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/puppet/old-style-module'), + \ ale_linters#puppet#languageserver#GetProjectRoot(bufnr('')) + \ +Execute(new-style module should find its root correctly): + call ale#test#SetFilename('../test-files/puppet/new-style-module/lib/puppet/types/exampletype.rb') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/puppet/new-style-module'), + \ ale_linters#puppet#languageserver#GetProjectRoot(bufnr('')) diff --git a/test/linter/test_purescript_ls.vader b/test/linter/test_purescript_ls.vader new file mode 100644 index 00000000..3ef9707a --- /dev/null +++ b/test/linter/test_purescript_ls.vader @@ -0,0 +1,31 @@ +Before: + call ale#assert#SetUpLinterTest('purescript', 'ls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'purescript-language-server', ale#Escape('purescript-language-server') . ' --stdio' + +Execute(should set correct LSP values): + call ale#test#SetFilename('../test-files/purescript/spago/Foo.purs') + + AssertLSPLanguage 'purescript' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/purescript/spago') + +Execute(should set correct project for bower): + call ale#test#SetFilename('../test-files/purescript/bower/Foo.purs') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/purescript/bower') + +Execute(should set correct project for psc-package): + call ale#test#SetFilename('../test-files/purescript/psc-package/Foo.purs') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/purescript/psc-package') + +Execute(should accept configuration settings): + AssertLSPConfig {} + let b:ale_purescript_ls_config = {'purescript': {'addSpagoSources': v:true}} + AssertLSPConfig {'purescript': {'addSpagoSources': v:true}} diff --git a/test/linter/test_pycln.vader b/test/linter/test_pycln.vader new file mode 100644 index 00000000..3d48ee7b --- /dev/null +++ b/test/linter/test_pycln.vader @@ -0,0 +1,124 @@ +Before: + Save g:ale_python_auto_pipenv + + let g:ale_python_auto_pipenv = 0 + + call ale#assert#SetUpLinterTest('python', 'pycln') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + let b:cmd_tail = ' --check -' + + GivenCommandOutput ['pycln, version 1.3.0'] + +After: + unlet! b:bin_dir + unlet! b:executable + unlet! b:cmd_tail + + call ale#assert#TearDownLinterTest() + +Execute(The pycln callbacks should return the correct default values): + AssertLinterCwd expand('%:p:h') + AssertLinter 'pycln', ale#Escape('pycln') . b:cmd_tail + +Execute(pycln should run with the file path of buffer in old versions): + " version `1.3.0` supports liniting input from stdin + GivenCommandOutput ['pycln, version 1.2.99'] + + AssertLinterCwd expand('%:p:h') + AssertLinter 'pycln', ale#Escape('pycln') . b:cmd_tail[:-3] . ' %s' + +Execute(pycln should run with the stdin in new enough versions): + GivenCommandOutput ['pycln, version 1.3.0'] + + AssertLinterCwd expand('%:p:h') + AssertLinter 'pycln', ale#Escape('pycln') . b:cmd_tail[:-3] . ' -' + +Execute(The option for disabling changing directories should work): + let g:ale_python_pycln_change_directory = 0 + + AssertLinterCwd '' + AssertLinter 'pycln', ale#Escape('pycln') . b:cmd_tail + +Execute(The pycln executable and options should be configurable): + let g:ale_python_pycln_executable = 'foo' + let g:ale_python_pycln_options = '--some-flag' + + AssertLinter 'foo', ale#Escape('foo') . ' --some-flag' . b:cmd_tail + +Execute(The pycln callbacks shouldn't detect virtualenv directories where they don't exist): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'pycln', ale#Escape('pycln') . b:cmd_tail + +Execute(The pycln callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pycln' + \) + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter b:executable, ale#Escape(b:executable) . b:cmd_tail + +Execute(You should able able to use the global pycln instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_pycln_use_global = 1 + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter 'pycln', ale#Escape('pycln') . b:cmd_tail + +Execute(Setting executable to 'pipenv' appends 'run pycln'): + let g:ale_python_pycln_executable = 'path/to/pipenv' + let g:ale_python_pycln_use_global = 1 + + AssertLinter 'path/to/pipenv', ale#Escape('path/to/pipenv') . ' run pycln' + \ . b:cmd_tail + +Execute(Pipenv is detected when python_pycln_auto_pipenv is set): + let g:ale_python_pycln_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'pipenv', ale#Escape('pipenv') . ' run pycln' + \ . b:cmd_tail + +Execute(Setting executable to 'poetry' appends 'run pycln'): + let g:ale_python_pycln_executable = 'path/to/poetry' + let g:ale_python_pycln_use_global = 1 + + AssertLinter 'path/to/poetry', ale#Escape('path/to/poetry') . ' run pycln' + \ . b:cmd_tail + +Execute(poetry is detected when python_pycln_auto_poetry is set): + let g:ale_python_pycln_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'poetry', ale#Escape('poetry') . ' run pycln' + \ . b:cmd_tail + +Execute(uv is detected when python_pycln_auto_uv is set): + let g:ale_python_pycln_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'uv', ale#Escape('uv') . ' run pycln' + \ . b:cmd_tail + +Execute(configuration files set in _config should be supported): + let g:ale_python_pycln_config_file = ale#path#Simplify(g:dir . '/../test-files/pycln/other_config.xml') + + AssertLinter 'pycln', + \ ale#Escape('pycln') + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/pycln/other_config.xml')) + \ . b:cmd_tail + +Execute(configuration file set in _options overrides _config): + let g:ale_python_pycln_config_file = '/foo.xml' + let g:ale_python_pycln_options = '--config /bar.xml' + + AssertLinter 'pycln', ale#Escape('pycln') . ' --config /bar.xml' . b:cmd_tail + + let b:ale_python_pycln_options = '-x --config /bar.xml' + + AssertLinter 'pycln', ale#Escape('pycln') . ' -x --config /bar.xml' . b:cmd_tail diff --git a/test/linter/test_pycodestyle.vader b/test/linter/test_pycodestyle.vader new file mode 100644 index 00000000..85983122 --- /dev/null +++ b/test/linter/test_pycodestyle.vader @@ -0,0 +1,53 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pycodestyle') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The pycodestyle command callback should return default string): + AssertLinter 'pycodestyle', ale#Escape('pycodestyle') . ' -' + +Execute(The pycodestyle command callback should allow options): + let g:ale_python_pycodestyle_options = '--exclude=test*.py' + + AssertLinter 'pycodestyle', + \ ale#Escape('pycodestyle') . ' --exclude=test*.py -' + +Execute(The pycodestyle executable should be configurable): + let g:ale_python_pycodestyle_executable = '~/.local/bin/pycodestyle' + + AssertLinter '~/.local/bin/pycodestyle', + \ ale#Escape('~/.local/bin/pycodestyle') . ' -' + +Execute(Setting executable to 'pipenv' appends 'run pycodestyle'): + let g:ale_python_pycodestyle_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run pycodestyle -' + +Execute(Pipenv is detected when python_pycodestyle_auto_pipenv is set): + let g:ale_python_pycodestyle_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run pycodestyle -' + +Execute(Setting executable to 'poetry' appends 'run pycodestyle'): + let g:ale_python_pycodestyle_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run pycodestyle -' + +Execute(Poetry is detected when python_pycodestyle_auto_poetry is set): + let g:ale_python_pycodestyle_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run pycodestyle -' + +Execute(uv is detected when python_pycodestyle_auto_uv is set): + let g:ale_python_pycodestyle_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run pycodestyle -' diff --git a/test/linter/test_pydocstyle.vader b/test/linter/test_pydocstyle.vader new file mode 100644 index 00000000..cdc36885 --- /dev/null +++ b/test/linter/test_pydocstyle.vader @@ -0,0 +1,51 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pydocstyle') + call ale#test#SetFilename('example/test.py') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The pydocstyle command callback should return default string): + AssertLinterCwd '%s:h' + AssertLinter 'pydocstyle', ale#Escape('pydocstyle') . ' %s' + +Execute(The pydocstyle command callback should allow options): + let g:ale_python_pydocstyle_options = '--verbose' + + AssertLinter 'pydocstyle', ale#Escape('pydocstyle') . ' --verbose %s' + +Execute(The pydocstyle executable should be configurable): + let g:ale_python_pydocstyle_executable = '~/.local/bin/pydocstyle' + + AssertLinter '~/.local/bin/pydocstyle', + \ ale#Escape('~/.local/bin/pydocstyle') . ' %s' + +Execute(Setting executable to 'pipenv' appends 'run pydocstyle'): + let g:ale_python_pydocstyle_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run pydocstyle %s' + +Execute(Pipenv is detected when python_pydocstyle_auto_pipenv is set): + let g:ale_python_pydocstyle_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', ale#Escape('pipenv') . ' run pydocstyle %s' + +Execute(Setting executable to 'poetry' appends 'run pydocstyle'): + let g:ale_python_pydocstyle_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run pydocstyle %s' + +Execute(Poetry is detected when python_pydocstyle_auto_poetry is set): + let g:ale_python_pydocstyle_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', ale#Escape('poetry') . ' run pydocstyle %s' + +Execute(uv is detected when python_pydocstyle_auto_uv is set): + let g:ale_python_pydocstyle_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', ale#Escape('uv') . ' run pydocstyle %s' diff --git a/test/linter/test_pyflakes.vader b/test/linter/test_pyflakes.vader new file mode 100644 index 00000000..09272620 --- /dev/null +++ b/test/linter/test_pyflakes.vader @@ -0,0 +1,67 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pyflakes') + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(The pyflakes command callback should return default string): + AssertLinter 'pyflakes', ale#Escape('pyflakes') . ' %t' + +Execute(The pyflakes executable should be configurable): + let g:ale_python_pyflakes_executable = '~/.local/bin/pyflakes' + + AssertLinter '~/.local/bin/pyflakes', + \ ale#Escape('~/.local/bin/pyflakes') . ' %t' + +Execute(The pyflakes executable should be run from the virtualenv path): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pyflakes' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' %t' + +Execute(You should be able to override the pyflakes virtualenv lookup): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_pyflakes_use_global = 1 + + AssertLinter 'pyflakes', ale#Escape('pyflakes') . ' %t' + +Execute(Setting executable to 'pipenv' appends 'run pyflakes'): + let g:ale_python_pyflakes_executable = 'path/to/pipenv' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run pyflakes %t', + +Execute(Pipenv is detected when python_pyflakes_auto_pipenv is set): + let g:ale_python_pyflakes_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run pyflakes %t' + +Execute(Setting executable to 'poetry' appends 'run pyflakes'): + let g:ale_python_pyflakes_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run pyflakes %t', + +Execute(Poetry is detected when python_pyflakes_auto_poetry is set): + let g:ale_python_pyflakes_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run pyflakes %t' + +Execute(uv is detected when python_pyflakes_auto_uv is set): + let g:ale_python_pyflakes_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run pyflakes %t' diff --git a/test/linter/test_pylama.vader b/test/linter/test_pylama.vader new file mode 100644 index 00000000..6e0aa293 --- /dev/null +++ b/test/linter/test_pylama.vader @@ -0,0 +1,94 @@ +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 default pylama command should be correct): + AssertLinterCwd ale#path#Simplify(g:dir) + AssertLinter 'pylama', ale#Escape('pylama') . b:command_tail + +Execute(The option for disabling changing directories should work): + let g:ale_python_pylama_change_directory = 0 + + AssertLinterCwd '' + 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' + + AssertLinterCwd ale#path#Simplify(g:dir) + AssertLinter 'executable with spaces', + \ 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' + + AssertLinterCwd ale#path#Simplify(g:dir) + AssertLinter 'pylama', 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 . '/../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'pylama', ale#Escape('pylama') . b:command_tail + +Execute(The pylama command callback shouldn't detect virtualenv directories where they don't exist): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'pylama', ale#Escape('pylama') . b:command_tail + +Execute(The pylama command callback should detect virtualenv directories and switch to the project root): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pylama' + \) + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter b:executable, ale#Escape(b:executable) . b:command_tail + +Execute(You should able able to use the global pylama instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_pylama_use_global = 1 + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter 'pylama', 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#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('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', ale#Escape('pipenv') . ' run pylama' . b:command_tail + +Execute(Setting executable to 'poetry' appends 'run pylama'): + let g:ale_python_pylama_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run pylama' . b:command_tail + +Execute(poetry is detected when python_pylama_auto_poetry is set): + let g:ale_python_pylama_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', ale#Escape('poetry') . ' run pylama' . b:command_tail + +Execute(uv is detected when python_pylama_auto_uv is set): + let g:ale_python_pylama_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', ale#Escape('uv') . ' run pylama' . b:command_tail diff --git a/test/linter/test_pylint.vader b/test/linter/test_pylint.vader new file mode 100644 index 00000000..73e792fb --- /dev/null +++ b/test/linter/test_pylint.vader @@ -0,0 +1,104 @@ +Before: + Save g:ale_python_auto_pipenv + + let g:ale_python_auto_pipenv = 0 + + call ale#assert#SetUpLinterTest('python', 'pylint') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + let b:command_tail = ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + + GivenCommandOutput ['pylint 2.3.0'] + +After: + unlet! b:bin_dir + unlet! b:executable + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The pylint callbacks should return the correct default values): + AssertLinterCwd expand('%:p:h') + AssertLinter 'pylint', ale#Escape('pylint') . b:command_tail + +Execute(Pylint should run with the --from-stdin in new enough versions): + GivenCommandOutput ['pylint 2.4.0'] + + AssertLinterCwd expand('%:p:h') + AssertLinter 'pylint', ale#Escape('pylint') . b:command_tail[:-3] . '--from-stdin %s' + +Execute(The option for disabling changing directories should work): + let g:ale_python_pylint_change_directory = 0 + + AssertLinterCwd '' + AssertLinter 'pylint', ale#Escape('pylint') . b:command_tail + +Execute(The pylint executable should be configurable, and escaped properly): + let g:ale_python_pylint_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', ale#Escape('executable with spaces') . b:command_tail + +Execute(The pylint command callback should let you set options): + let g:ale_python_pylint_options = '--some-option' + + AssertLinter 'pylint', ale#Escape('pylint') . ' --some-option' . b:command_tail + +Execute(The pylint callbacks shouldn't detect virtualenv directories where they don't exist): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'pylint', ale#Escape('pylint') . b:command_tail + +Execute(The pylint callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pylint' + \) + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter b:executable, ale#Escape(b:executable) . b:command_tail + +Execute(You should able able to use the global pylint instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_pylint_use_global = 1 + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter 'pylint', ale#Escape('pylint') . b:command_tail + +Execute(Setting executable to 'pipenv' appends 'run pylint'): + let g:ale_python_pylint_executable = 'path/to/pipenv' + let g:ale_python_pylint_use_global = 1 + + AssertLinter 'path/to/pipenv', ale#Escape('path/to/pipenv') . ' run pylint' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + +Execute(Pipenv is detected when python_pylint_auto_pipenv is set): + let g:ale_python_pylint_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'pipenv', ale#Escape('pipenv') . ' run pylint' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + +Execute(Setting executable to 'poetry' appends 'run pylint'): + let g:ale_python_pylint_executable = 'path/to/poetry' + let g:ale_python_pylint_use_global = 1 + + AssertLinter 'path/to/poetry', ale#Escape('path/to/poetry') . ' run pylint' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + +Execute(poetry is detected when python_pylint_auto_poetry is set): + let g:ale_python_pylint_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'poetry', ale#Escape('poetry') . ' run pylint' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + +Execute(uv is detected when python_pylint_auto_uv is set): + let g:ale_python_pylint_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'uv', ale#Escape('uv') . ' run pylint' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' diff --git a/test/linter/test_pylsp.vader b/test/linter/test_pylsp.vader new file mode 100644 index 00000000..dd92d70a --- /dev/null +++ b/test/linter/test_pylsp.vader @@ -0,0 +1,98 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pylsp') + Save b:ale_python_auto_virtualenv + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:venv_bin + unlet! b:sep + unlet! b:executable + + call ale#test#SetFilename('..') + call ale#assert#TearDownLinterTest() + +Execute(The default pylsp command should be correct): + call ale#test#SetFilename('./foo.py') + + AssertLinter 'pylsp', ale#Escape('pylsp') + +Execute(The pylsp command and executable should be configurable): + let g:ale_python_pylsp_executable = '~/.local/bin/pylsp' + let g:ale_python_pylsp_options = '--some-option' + + AssertLinter '~/.local/bin/pylsp' , ale#Escape('~/.local/bin/pylsp') + \ . ' --some-option' + +Execute(The cwd and project root should be detected correctly): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#test#GetFilename('../test-files/python/with_virtualenv/subdir') + AssertLSPProject ale#test#GetFilename('../test-files/python/with_virtualenv/subdir') + +Execute(The pylsp executable should be run from the virtualenv path): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pylsp' + \) + + AssertEqual ale#Escape(b:executable), + \ ale_linters#python#pylsp#GetCommand(bufnr('')) + +Execute(virtualenv vars should be used when ale_python_auto_virtualenv = 1): + let b:ale_python_auto_virtualenv = 1 + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:venv_bin = ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir) + let b:sep = has('win32') ? ';' : ':' + let b:executable = ale#path#Simplify(b:venv_bin . '/pylsp') + + AssertLinter b:executable, ale#python#AutoVirtualenvEnvString(bufnr('')) + \ . ale#Escape(b:executable) + Assert !empty(ale#python#AutoVirtualenvEnvString(bufnr(''))) + +Execute(You should be able to override the pylsp virtualenv lookup): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_pylsp_use_global = 1 + + AssertLinter 'pylsp', ale#Escape('pylsp') + +Execute(Setting executable to 'pipenv' appends 'run pylsp'): + let g:ale_python_pylsp_executable = 'path/to/pipenv' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'path/to/pipenv', ale#Escape('path/to/pipenv') . ' run pylsp' + +Execute(Pipenv is detected when python_pylsp_auto_pipenv is set): + let g:ale_python_pylsp_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run pylsp' + +Execute(Setting executable to 'poetry' appends 'run pylsp'): + let g:ale_python_pylsp_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', ale#Escape('path/to/poetry') . ' run pylsp' + +Execute(poetry is detected when python_pylsp_auto_poetry is set): + let g:ale_python_pylsp_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run pylsp' + +Execute(uv is detected when python_pylsp_auto_uv is set): + let g:ale_python_pylsp_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run pylsp' + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_python_pylsp_config = {'pylsp': {'plugins': {'preload': {'enabled': v:false}}}} + AssertLSPConfig {'pylsp': {'plugins': {'preload': {'enabled': v:false}}}} diff --git a/test/linter/test_pymarkdown.vader b/test/linter/test_pymarkdown.vader new file mode 100644 index 00000000..2bfb2387 --- /dev/null +++ b/test/linter/test_pymarkdown.vader @@ -0,0 +1,50 @@ +Before: + call ale#assert#SetUpLinterTest('markdown', 'pymarkdown') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The pymarkdown command callback should return default string): + AssertLinter 'pymarkdown', ale#Escape('pymarkdown') . ' scan-stdin' + +Execute(The pycodestyle command callback should allow options): + let g:markdown_pymarkdown_options = '--exclude=test*.py' + +Execute(The pymarkdown executable should be configurable): + let g:ale_markdown_pymarkdown_executable = '~/.local/bin/pymarkdown' + + AssertLinter '~/.local/bin/pymarkdown', + \ ale#Escape('~/.local/bin/pymarkdown') . ' scan-stdin' + +Execute(Setting executable to 'pipenv' appends 'run pymarkdown'): + let g:ale_markdown_pymarkdown_executable = 'path/to/pipenv' + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run pymarkdown scan-stdin' + +Execute(Pipenv is detected when markdown_pymarkdown_auto_pipenv is set): + let g:ale_markdown_pymarkdown_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run pymarkdown scan-stdin' + +Execute(Setting executable to 'poetry' appends 'run pymarkdown'): + let g:ale_markdown_pymarkdown_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run pymarkdown scan-stdin' + +Execute(Poetry is detected when markdown_pymarkdown_auto_poetry is set): + let g:ale_markdown_pymarkdown_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run pymarkdown scan-stdin' + +Execute(uv is detected when markdown_pymarkdown_auto_uv is set): + let g:ale_markdown_pymarkdown_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run pymarkdown scan-stdin' diff --git a/test/linter/test_pymarkdown_handler.vader b/test/linter/test_pymarkdown_handler.vader new file mode 100644 index 00000000..13acdd7d --- /dev/null +++ b/test/linter/test_pymarkdown_handler.vader @@ -0,0 +1,52 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/markdown/pymarkdown.vim + +After: + Restore + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() + +Execute (Should parse error correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'M', + \ 'text': 'Headings should be surrounded by blank lines', + \ 'code': 'MD022', + \ } + \ ], + \ ale_linters#markdown#pymarkdown#Handle(bufnr(''), [ + \ 'foo.md:1:1: MD022: Headings should be surrounded by blank lines', + \ ]) + +Execute(Warnings about trailing whitespace should be reported by default): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'M', + \ 'text': 'who cares', + \ 'code': 'MD009', + \ }, + \ ], + \ ale_linters#markdown#pymarkdown#Handle(bufnr(''), [ + \ 'foo.md:1:1: MD009: who cares', + \ ]) + +Execute(Disabling trailing whitespace warnings should work): + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#markdown#pymarkdown#Handle(bufnr(''), [ + \ 'foo.md:1:1: MD009: who cares', + \ ]) \ No newline at end of file diff --git a/test/linter/test_pyre.vader b/test/linter/test_pyre.vader new file mode 100644 index 00000000..c46bc034 --- /dev/null +++ b/test/linter/test_pyre.vader @@ -0,0 +1,76 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pyre') + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(The pyre command callback should return default string): + call ale#test#SetFilename('./foo.py') + + AssertLinter 'pyre', ale#Escape('pyre') . ' persistent' + +Execute(The pyre executable should be configurable): + let g:ale_python_pyre_executable = '~/.local/bin/pyre' + + AssertLinter '~/.local/bin/pyre', + \ ale#Escape('~/.local/bin/pyre') . ' persistent' + +Execute(The pyre executable should be run from the virtualenv path): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pyre' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' persistent' + +Execute(You should be able to override the pyre virtualenv lookup): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_pyre_use_global = 1 + + AssertLinter 'pyre', ale#Escape('pyre') . ' persistent' + +Execute(Setting executable to 'pipenv' appends 'run pyre'): + let g:ale_python_pyre_executable = 'path/to/pipenv' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run pyre persistent' + +Execute(Pipenv is detected when python_pyre_auto_pipenv is set): + let g:ale_python_pyre_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run pyre persistent' + +Execute(Setting executable to 'poetry' appends 'run pyre'): + let g:ale_python_pyre_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run pyre persistent' + +Execute(Poetry is detected when python_pyre_auto_poetry is set): + let g:ale_python_pyre_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run pyre persistent' + +Execute(uv is detected when python_pyre_auto_uv is set): + let g:ale_python_pyre_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run pyre persistent' + +Execute(The FindProjectRoot should detect the project root directory for namespace package via .pyre_configuration.local): + silent execute 'file ' . fnameescape(g:dir . '/../test-files/python/pyre_configuration_dir/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/pyre_configuration_dir'), + \ ale#python#FindProjectRoot(bufnr('')) diff --git a/test/linter/test_pyrex_cython.vader b/test/linter/test_pyrex_cython.vader new file mode 100644 index 00000000..af86366a --- /dev/null +++ b/test/linter/test_pyrex_cython.vader @@ -0,0 +1,30 @@ +Before: + call ale#assert#SetUpLinterTest('pyrex', 'cython') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default cython command should be correct): + AssertLinter 'cython', ale#Escape('cython') + \ . ' --working %s:h' + \ . ' --include-dir %s:h' + \ . ' --warning-extra' + \ . ' --output-file ' . g:ale#util#nul_file . ' %t' + +Execute(The cython executable should be configurable): + let b:ale_pyrex_cython_executable = 'cython_foobar' + + AssertLinter 'cython_foobar', ale#Escape('cython_foobar') + \ . ' --working %s:h' + \ . ' --include-dir %s:h' + \ . ' --warning-extra' + \ . ' --output-file ' . g:ale#util#nul_file . ' %t' + +Execute(Additional cython options should be configurable): + let b:ale_pyrex_cython_options = '--foobar' + + AssertLinter 'cython', ale#Escape('cython') + \ . ' --working %s:h' + \ . ' --include-dir %s:h' + \ . ' --foobar' + \ . ' --output-file ' . g:ale#util#nul_file . ' %t' diff --git a/test/linter/test_pyright.vader b/test/linter/test_pyright.vader new file mode 100644 index 00000000..91a715e2 --- /dev/null +++ b/test/linter/test_pyright.vader @@ -0,0 +1,188 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pyright') + Save b:ale_python_auto_virtualenv + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:venv_bin + unlet! b:sep + unlet! b:executable + + call ale#test#SetFilename('..') + call ale#assert#TearDownLinterTest() + +Execute(The command callback should return the correct default string): + call ale#test#SetFilename('./foo.py') + + AssertLinter + \ 'pyright-langserver', + \ ale#Escape('pyright-langserver') . ' --stdio' + +Execute(The executable should be configurable): + let g:ale_python_pyright_executable = '/bin/foo-bar' + + AssertLinter + \ '/bin/foo-bar', + \ ale#Escape('/bin/foo-bar') . ' --stdio' + +Execute(The default configuration should be mostly empty): + " The default configuration needs to have at least one key in it, + " or the server won't start up properly. + AssertLSPConfig {'python': {}} + + let b:ale_python_pyright_config = {} + + AssertLSPConfig {'python': {}} + +Execute(The cwd and project root should be detected correctly): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#test#GetFilename('../test-files/python/with_virtualenv/subdir') + AssertLSPProject ale#test#GetFilename('../test-files/python/with_virtualenv/subdir') + +Execute(virtualenv paths should be set in configuration by default): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/python'), + \ 'venvPath': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env'), + \ }, + \} + +Execute(The pythonPath should be set based on whatever the override for the venvPath is set to): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + " This overrides the default detection of the path. + let b:ale_python_pyright_config = { + \ 'python': { + \ 'venvPath': '/foo/bar', + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': ale#path#Simplify('/foo/bar/' . b:bin_dir . '/python'), + \ 'venvPath': '/foo/bar', + \ }, + \} + +Execute(You should be able to override pythonPath when venvPath is detected): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + " This overrides the default detection of the path. + let b:ale_python_pyright_config = { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env'), + \ }, + \} + +Execute(You should be able to override both pythonPath and venvPath): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + " This overrides the default detection of the path. + let b:ale_python_pyright_config = { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': '/other/dir', + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': '/other/dir', + \ }, + \} + +Execute(You should be able to define other settings): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:ale_python_pyright_config = { + \ 'python': { + \ 'analysis': {'logLevel': 'warning'}, + \ }, + \ 'pyright': { + \ 'disableLanguageServices': v:true, + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'analysis': {'logLevel': 'warning'}, + \ 'pythonPath': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/python'), + \ 'venvPath': ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env'), + \ }, + \ 'pyright': { + \ 'disableLanguageServices': v:true, + \ }, + \} + +Execute(The pyright callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/pyright-langserver' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --stdio' + +Execute(virtualenv vars should be used when ale_python_auto_virtualenv = 1): + let b:ale_python_auto_virtualenv = 1 + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:venv_bin = ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir) + let b:sep = has('win32') ? ';' : ':' + let b:executable = ale#path#Simplify(b:venv_bin . '/pyright-langserver') + + AssertLinter b:executable, ale#python#AutoVirtualenvEnvString(bufnr('')) + \ . ale#Escape(b:executable) . ' --stdio' + Assert !empty(ale#python#AutoVirtualenvEnvString(bufnr(''))) + +Execute(Setting executable to 'pipenv' should append 'run pyright-langserver'): + call ale#test#SetFilename('../test-files') + + let g:ale_python_pyright_executable = 'path/to/pipenv' + + GivenCommandOutput [] + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run pyright-langserver --stdio' + +Execute(Pipenv is detected when python_pyright_auto_pipenv is set): + let g:ale_python_pyright_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run pyright-langserver --stdio' + +Execute(Setting executable to 'poetry' should append 'run pyright-langserver'): + let g:ale_python_pyright_executable = 'path/to/poetry' + + GivenCommandOutput [] + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run pyright-langserver --stdio' + +Execute(poetry is detected when python_pyright_auto_poetry is set): + let g:ale_python_pyright_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinterCwd ale#python#FindProjectRootIni(bufnr('')) + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run pyright-langserver --stdio' + +Execute(uv is detected when python_pyright_auto_uv is set): + let g:ale_python_pyright_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run pyright-langserver --stdio' diff --git a/test/linter/test_qmlfmt.vader b/test/linter/test_qmlfmt.vader new file mode 100644 index 00000000..53502f4d --- /dev/null +++ b/test/linter/test_qmlfmt.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('qml', 'qmlfmt') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The qml qmlfmt command callback should return the correct default string): + AssertLinter 'qmlfmt', ale#Escape('qmlfmt') . ' -e', + +Execute(The qmlfmt executable should be configurable): + let g:ale_qml_qmlfmt_executable = '~/.local/bin/qmlfmt' + + AssertLinter '~/.local/bin/qmlfmt', ale#Escape('~/.local/bin/qmlfmt') . ' -e' diff --git a/test/linter/test_r_languageserver.vader b/test/linter/test_r_languageserver.vader new file mode 100644 index 00000000..1a6fe851 --- /dev/null +++ b/test/linter/test_r_languageserver.vader @@ -0,0 +1,22 @@ +Before: + call ale#assert#SetUpLinterTest('r', 'languageserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'Rscript', 'Rscript --no-save --no-restore --no-site-file --no-init-file -e ' . ale#Escape('languageserver::run()') + +Execute(The project root should be detected correctly): + AssertLSPProject '.' + + call ale#test#SetFilename('../test-files/r/dummy/test.R') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/r') + +Execute(Should accept configuration settings): + AssertLSPConfig {} + + let b:ale_r_languageserver_config = {'r': {'lsp': {'debug': 'true', 'diagnostics': 'true'}}} + + AssertLSPConfig {'r': {'lsp': {'debug': 'true', 'diagnostics': 'true'}}} diff --git a/test/linter/test_racket_langserver.vader b/test/linter/test_racket_langserver.vader new file mode 100644 index 00000000..021eb565 --- /dev/null +++ b/test/linter/test_racket_langserver.vader @@ -0,0 +1,45 @@ +" Author: D. Ben Knoble +" Description: Tests for racket-langserver lsp linter. +Before: + call ale#assert#SetUpLinterTest('racket', 'langserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(command callback should return default string): + AssertLinter 'racket', ale#Escape('racket').' -l racket-langserver' + +Execute(should set racket-langserver for deep module 3): + call ale#test#SetFilename('../test-files/racket/many-inits/a/b/c/foo.rkt') + AssertLSPLanguage 'racket' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#test#GetFilename('../test-files/racket/many-inits') + +Execute(should set racket-langserver for deep module 2): + call ale#test#SetFilename('../test-files/racket/many-inits/a/b/foo.rkt') + AssertLSPLanguage 'racket' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#test#GetFilename('../test-files/racket/many-inits') + +Execute(should set racket-langserver for deep module 1): + call ale#test#SetFilename('../test-files/racket/many-inits/a/foo.rkt') + AssertLSPLanguage 'racket' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#test#GetFilename('../test-files/racket/many-inits') + +Execute(should set racket-langserver for top-level module): + call ale#test#SetFilename('../test-files/racket/many-inits/foo.rkt') + AssertLSPLanguage 'racket' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#test#GetFilename('../test-files/racket/many-inits') + +Execute(should set racket-langserver for non-package module or script): + call ale#test#SetFilename('../test-files/racket/simple-script/foo.rkt') + AssertLSPLanguage 'racket' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#test#GetFilename('../test-files/racket/simple-script') diff --git a/test/linter/test_racket_raco.vader b/test/linter/test_racket_raco.vader new file mode 100644 index 00000000..fb83ffa1 --- /dev/null +++ b/test/linter/test_racket_raco.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/linter/test_rails_best_practices.vader b/test/linter/test_rails_best_practices.vader new file mode 100644 index 00000000..6a6f7a53 --- /dev/null +++ b/test/linter/test_rails_best_practices.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'rails_best_practices') + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/db/test.rb') + + let b:args = '--silent -f json' + \ . ' --output-file ' . (has('win32') ? '%t' : '/dev/stdout') + let b:app_path = ale#path#Simplify(g:dir . '/../test-files/ruby/valid_rails_app') + let b:suffix = has('win32') ? '; type %t' : '' + +After: + unlet! b:args + unlet! b:app_path + unlet! b:suffix + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to rails_best_practices): + AssertLinter 'rails_best_practices', ale#Escape('rails_best_practices') + \ . ' ' . b:args + \ . ' ' . ale#Escape(b:app_path) + \ . b:suffix + +Execute(Should be able to set a custom executable): + let g:ale_ruby_rails_best_practices_executable = 'bin/rails_best_practices' + + AssertLinter 'bin/rails_best_practices', ale#Escape('bin/rails_best_practices') + \ . ' ' . b:args + \ . ' ' . ale#Escape(b:app_path) + \ . b:suffix + +Execute(Setting bundle appends 'exec rails_best_practices'): + let g:ale_ruby_rails_best_practices_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec rails_best_practices' + \ . ' ' . b:args + \ . ' ' . ale#Escape(b:app_path) + \ . b:suffix + +Execute(Command callback should be empty when not in a valid Rails app): + call ale#test#SetFilename('../test-files/ruby/not_a_rails_app/test.rb') + + AssertLinter 'rails_best_practices', '' diff --git a/test/linter/test_reason_ls.vader b/test/linter/test_reason_ls.vader new file mode 100644 index 00000000..57ea7302 --- /dev/null +++ b/test/linter/test_reason_ls.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('reason', 'ls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The linter should not be run by default): + AssertLinterNotExecuted + +Execute(The executable should be configurable): + let b:ale_reason_ls_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(There should be no default project root): + AssertLSPProject '' + +Execute(The project root should be detected using bsconfig.json): + call ale#test#SetFilename('../test-files/reasonml/test.ml') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/reasonml') diff --git a/test/linter/test_reason_ols.vader b/test/linter/test_reason_ols.vader new file mode 100644 index 00000000..aab1285f --- /dev/null +++ b/test/linter/test_reason_ols.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('reason', 'ols') + + Save &filetype + let &filetype = 'reason' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'reason' + +Execute(The default executable should be correct): + AssertLinter 'ocaml-language-server', + \ ale#Escape('ocaml-language-server') . ' --stdio' + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/ols/file.re') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/ols') + +Execute(The local executable should be used when available): + call ale#test#SetFilename('../test-files/ols/file.re') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/ols/node_modules/.bin/ocaml-language-server'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/ols/node_modules/.bin/ocaml-language-server')) . ' --stdio' + +Execute(The global executable should always be used when use_global is set): + let g:ale_reason_ols_use_global = 1 + call ale#test#SetFilename('../test-files/ols/file.re') + + AssertLinter 'ocaml-language-server', + \ ale#Escape('ocaml-language-server') . ' --stdio' + +Execute(The executable should be configurable): + let g:ale_reason_ols_executable = 'foobar' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdio' diff --git a/test/linter/test_reek.vader b/test/linter/test_reek.vader new file mode 100644 index 00000000..798c3314 --- /dev/null +++ b/test/linter/test_reek.vader @@ -0,0 +1,49 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'reek') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The reek callbacks should return the correct default values): + 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', + \] + + " Try with older versions. + call ale#semver#ResetVersionCache() + + GivenCommandOutput ['reek 4.8.2'] + AssertLinter 'reek', [ + \ ale#Escape('reek') . ' --version', + \ ale#Escape('reek') . ' -f json --no-progress --no-color --force-exclusion', + \] + +Execute(Setting bundle appends 'exec reek'): + let g:ale_ruby_reek_executable = 'bundle' + + GivenCommandOutput ['reek 5.0.0'] + AssertLinter 'bundle', ale#Escape('bundle') + \ . ' exec reek' + \ . ' -f json --no-progress --no-color --force-exclusion --stdin-filename %s', + + " Try with older versions. + call ale#semver#ResetVersionCache() + + 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): + 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', + \] + + GivenCommandOutput [] + AssertLinter 'reek', [ + \ ale#Escape('reek') . ' -f json --no-progress --no-color --force-exclusion --stdin-filename %s', + \] diff --git a/test/linter/test_refurb.vader b/test/linter/test_refurb.vader new file mode 100644 index 00000000..c36fe49c --- /dev/null +++ b/test/linter/test_refurb.vader @@ -0,0 +1,92 @@ +Before: + Save g:ale_python_auto_pipenv + Save g:ale_python_auto_poetry + + let g:ale_python_auto_pipenv = 0 + let g:ale_python_auto_poetry = 0 + + call ale#assert#SetUpLinterTest('python', 'refurb') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + + call ale#assert#TearDownLinterTest() + +Execute(The refurb callbacks should return the correct default values): + AssertLinterCwd expand('%:p:h') + AssertLinter 'refurb', ale#Escape('refurb') . ' %s' + +Execute(The option for disabling changing directories should work): + let g:ale_python_refurb_change_directory = 0 + + AssertLinterCwd '' + AssertLinter 'refurb', ale#Escape('refurb') . ' %s' + +Execute(The refurb executable should be configurable, and escaped properly): + let g:ale_python_refurb_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', ale#Escape('executable with spaces') . ' %s' + +Execute(The refurb command callback should let you set options): + let g:ale_python_refurb_options = '--some-flag' + AssertLinter 'refurb', ale#Escape('refurb') . ' --some-flag %s' + + let g:ale_python_refurb_options = '--some-option value' + AssertLinter 'refurb', ale#Escape('refurb') . ' --some-option value %s' + +Execute(The refurb callbacks shouldn't detect virtualenv directories where they don't exist): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'refurb', ale#Escape('refurb') . ' %s' + +Execute(The refurb callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/refurb' + \) + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter b:executable, ale#Escape(b:executable) . ' %s' + +Execute(You should able able to use the global refurb instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_refurb_use_global = 1 + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter 'refurb', ale#Escape('refurb') . ' %s' + +Execute(Setting executable to 'pipenv' appends 'run refurb'): + let g:ale_python_refurb_executable = 'path/to/pipenv' + let g:ale_python_refurb_use_global = 1 + + AssertLinter 'path/to/pipenv', ale#Escape('path/to/pipenv') . ' run refurb %s' + +Execute(Pipenv is detected when python_refurb_auto_pipenv is set): + let g:ale_python_refurb_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'pipenv', ale#Escape('pipenv') . ' run refurb %s' + +Execute(Setting executable to 'poetry' appends 'run refurb'): + let g:ale_python_refurb_executable = 'path/to/poetry' + let g:ale_python_refurb_use_global = 1 + + AssertLinter 'path/to/poetry', ale#Escape('path/to/poetry') . ' run refurb %s' + +Execute(poetry is detected when python_refurb_auto_poetry is set): + let g:ale_python_refurb_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'poetry', ale#Escape('poetry') . ' run refurb %s' + +Execute(uv is detected when python_refurb_auto_uv is set): + let g:ale_python_refurb_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'uv', ale#Escape('uv') . ' run refurb %s' diff --git a/test/linter/test_rego_opacheck.vader b/test/linter/test_rego_opacheck.vader new file mode 100644 index 00000000..d0fa3117 --- /dev/null +++ b/test/linter/test_rego_opacheck.vader @@ -0,0 +1,16 @@ + +" Based upon :help ale-development +Before: + call ale#assert#SetUpLinterTest('rego', 'opacheck') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'opa', + \ ale#Escape('opa') . ' check %s:h --format json ' + +Execute(The default command should be overridden): + let b:ale_rego_opacheck_executable = '/bin/other/opa' + AssertLinter '/bin/other/opa', + \ ale#Escape('/bin/other/opa') . ' check %s:h --format json ' diff --git a/test/linter/test_remark_lint.vader b/test/linter/test_remark_lint.vader new file mode 100644 index 00000000..7f69008c --- /dev/null +++ b/test/linter/test_remark_lint.vader @@ -0,0 +1,37 @@ +Before: + " This is just one language for the linter. + call ale#assert#SetUpLinterTest('markdown', 'remark_lint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'remark', + \ ale#Escape('remark') . ' --no-stdout --no-color' + +Execute(The executable should be configurable): + let b:ale_markdown_remark_lint_executable = 'foobar' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' --no-stdout --no-color' + +Execute(The options should be configurable): + let b:ale_markdown_remark_lint_options = '--something' + + AssertLinter 'remark', + \ ale#Escape('remark') . ' --something --no-stdout --no-color' + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('../test-files/remark_lint/with_bin_path/foo.md') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/remark_lint/with_bin_path/node_modules/.bin/remark'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/remark_lint/with_bin_path/node_modules/.bin/remark')) + \ . ' --no-stdout --no-color' + +Execute(The global executable should be used if the option is set): + let b:ale_markdown_remark_lint_use_global = 1 + call ale#test#SetFilename('../test-files/remark_lint/with_bin_path/foo.md') + + AssertLinter 'remark', ale#Escape('remark') + \ . ' --no-stdout --no-color' diff --git a/test/linter/test_revive.vader b/test/linter/test_revive.vader new file mode 100644 index 00000000..172294f3 --- /dev/null +++ b/test/linter/test_revive.vader @@ -0,0 +1,30 @@ +Before: + Save g:ale_go_go111module + + call ale#assert#SetUpLinterTest('go', 'revive') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The default revive command should be correct): + AssertLinter 'revive', ale#Escape('revive') . ' %t' + +Execute(The revive executable should be configurable): + let b:ale_go_revive_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' %t' + +Execute(The revive options should be configurable): + let b:ale_go_revive_options = '--foo' + + AssertLinter 'revive', ale#Escape('revive') . ' --foo %t' + +Execute(The revive command should support Go environment variables): + let b:ale_go_go111module = 'on' + + AssertLinter 'revive', + \ ale#Env('GO111MODULE', 'on') . ale#Escape('revive') . ' %t' diff --git a/test/linter/test_rflint.vader b/test/linter/test_rflint.vader new file mode 100644 index 00000000..0ee97b30 --- /dev/null +++ b/test/linter/test_rflint.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('robot', 'rflint') + let b:rflint_format = ' --format' + \ . ' "{filename}:{severity}:{linenumber}:{char}:{rulename}:{message}" %s' + +After: + call ale#assert#TearDownLinterTest() + unlet! b:rflint_format + +Execute(The rflint command callback should return default string): + AssertLinter 'rflint', + \ 'rflint' + \ . b:rflint_format + +Execute(The rflint executable should be configurable): + let g:ale_robot_rflint_executable = '~/.local/bin/rflint' + + AssertLinter '~/.local/bin/rflint', + \ '~/.local/bin/rflint' + \ . b:rflint_format diff --git a/test/linter/test_rnix.vader b/test/linter/test_rnix.vader new file mode 100644 index 00000000..8970ee99 --- /dev/null +++ b/test/linter/test_rnix.vader @@ -0,0 +1,12 @@ +" Author: jD91mZM2 +" Description: Tests for rnix-lsp language client +Before: + call ale#assert#SetUpLinterTest('nix', 'rnix_lsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(should start rnix-lsp): + AssertLSPLanguage 'nix' + AssertLSPOptions {} + AssertLSPProject ale#path#Simplify('.') diff --git a/test/linter/test_rst_textlint.vader b/test/linter/test_rst_textlint.vader new file mode 100644 index 00000000..66ab30e9 --- /dev/null +++ b/test/linter/test_rst_textlint.vader @@ -0,0 +1,58 @@ +" 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:ale_textlint_executable + unlet! b:ale_textlint_use_global + unlet! b:ale_textlint_options + + call ale#assert#TearDownLinterTest() + +Execute(The default textlint command should be correct): + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' -f json --stdin --stdin-filename %s' + +Execute(The text executable and command should be configurable): + let b:ale_textlint_executable = 'foobar' + let b:ale_textlint_options = '--something' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' --something -f json --stdin --stdin-filename %s' + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('../test-files/textlint/with_bin_path/foo.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_bin_path/node_modules/.bin/textlint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/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('../test-files/textlint/with_textlint_bin_path/foo.txt') + + if has('win32') + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape('node.exe') . ' ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + else + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + endif diff --git a/test/linter/test_rubocop.vader b/test/linter/test_rubocop.vader new file mode 100644 index 00000000..e7cc32e8 --- /dev/null +++ b/test/linter/test_rubocop.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'rubocop') + call ale#test#SetFilename('dummy.rb') + + let g:ale_ruby_rubocop_executable = 'rubocop' + let g:ale_ruby_rubocop_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to rubocop): + AssertLinter 'rubocop', ale#Escape('rubocop') + \ . ' --format json --force-exclusion --stdin %s' + +Execute(Should be able to set a custom executable): + let g:ale_ruby_rubocop_executable = 'bin/rubocop' + + AssertLinter 'bin/rubocop' , ale#Escape('bin/rubocop') + \ . ' --format json --force-exclusion --stdin %s' + +Execute(Setting bundle appends 'exec rubocop'): + let g:ale_ruby_rubocop_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec rubocop' + \ . ' --format json --force-exclusion --stdin %s' diff --git a/test/linter/test_ruby.vader b/test/linter/test_ruby.vader new file mode 100644 index 00000000..d957079d --- /dev/null +++ b/test/linter/test_ruby.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'ruby') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'ruby', ale#Escape('ruby') . ' -w -c %t' + +Execute(The executable should be configurable): + let g:ale_ruby_ruby_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' -w -c %t' diff --git a/test/linter/test_ruby_debride.vader b/test/linter/test_ruby_debride.vader new file mode 100644 index 00000000..f7628432 --- /dev/null +++ b/test/linter/test_ruby_debride.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'debride') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'debride', ale#Escape('debride') . ' %s' diff --git a/test/linter/test_ruby_solargraph.vader b/test/linter/test_ruby_solargraph.vader new file mode 100644 index 00000000..1ae67f50 --- /dev/null +++ b/test/linter/test_ruby_solargraph.vader @@ -0,0 +1,43 @@ +" Author: Horacio Sanson +" Description: Tests for solargraph lsp linter. +Before: + call ale#assert#SetUpLinterTest('ruby', 'solargraph') + +After: + call ale#assert#TearDownLinterTest() + +Execute(command callback should return default string): + AssertLinter 'solargraph', ale#Escape('solargraph') . ' stdio' + +Execute(command callback executable can be overridden): + let g:ale_ruby_solargraph_executable = 'foobar' + AssertLinter 'foobar', ale#Escape('foobar') . ' stdio' + +Execute(should set solargraph for rails app): + call ale#test#SetFilename('../test-files/ruby/valid_rails_app/app/models/thing.rb') + AssertLSPLanguage 'ruby' + AssertLSPOptions {} + AssertLSPProject ale#test#GetFilename('../test-files/ruby/valid_rails_app') + +Execute(should set solargraph for ruby app1): + call ale#test#SetFilename('../test-files/ruby/valid_ruby_app1/lib/file.rb') + AssertLSPLanguage 'ruby' + AssertLSPOptions {} + AssertLSPProject ale#test#GetFilename('../test-files/ruby/valid_ruby_app1') + +Execute(should set solargraph for ruby app2): + call ale#test#SetFilename('../test-files/ruby/valid_ruby_app2/lib/file.rb') + AssertLSPLanguage 'ruby' + AssertLSPOptions {} + AssertLSPProject ale#test#GetFilename('../test-files/ruby/valid_ruby_app2') + +Execute(should set solargraph for ruby app3): + call ale#test#SetFilename('../test-files/ruby/valid_ruby_app3/lib/file.rb') + AssertLSPLanguage 'ruby' + AssertLSPOptions {} + AssertLSPProject ale#test#GetFilename('../test-files/ruby/valid_ruby_app3') + +Execute(should accept initialization options): + AssertLSPOptions {} + let b:ale_ruby_solargraph_options = { 'diagnostics': 'true' } + AssertLSPOptions { 'diagnostics': 'true' } diff --git a/test/linter/test_ruby_steep.vader b/test/linter/test_ruby_steep.vader new file mode 100644 index 00000000..59ab1e73 --- /dev/null +++ b/test/linter/test_ruby_steep.vader @@ -0,0 +1,69 @@ +" Author: Loic Nageleisen +" Description: Tests for steep linter. +Before: + call ale#assert#SetUpLinterTest('ruby', 'steep') + + let g:ale_ruby_steep_executable = 'steep' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to steep): + call ale#test#SetFilename('../test-files/ruby/nested/foo/dummy.rb') + AssertLinter 'steep', ale#Escape('steep') + \ . ' check ' + \ . ' dummy.rb' + +Execute(Should be able to set a custom executable): + let g:ale_ruby_steep_executable = 'bin/steep' + + call ale#test#SetFilename('../test-files/ruby/nested/foo/dummy.rb') + AssertLinter 'bin/steep' , ale#Escape('bin/steep') + \ . ' check ' + \ . ' dummy.rb' + +Execute(Setting bundle appends 'exec steep'): + let g:ale_ruby_steep_executable = 'path to/bundle' + + call ale#test#SetFilename('../test-files/ruby/nested/foo/dummy.rb') + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec steep' + \ . ' check ' + \ . ' dummy.rb' + +Execute(should accept options): + let g:ale_ruby_steep_options = '--severity-level=hint' + + call ale#test#SetFilename('../test-files/ruby/nested/foo/dummy.rb') + AssertLinter 'steep', ale#Escape('steep') + \ . ' check' + \ . ' --severity-level=hint' + \ . ' dummy.rb' + +Execute(Should not lint files out of steep root): + call ale#test#SetFilename('../test-files/ruby/nested/dummy.rb') + AssertLinter 'steep', '' + +Execute(Should lint files at top steep root): + call ale#test#SetFilename('../test-files/ruby/nested/foo/dummy.rb') + AssertLinter 'steep', ale#Escape('steep') + \ . ' check ' + \ . ' dummy.rb' + +Execute(Should lint files below top steep root): + call ale#test#SetFilename('../test-files/ruby/nested/foo/one/dummy.rb') + AssertLinter 'steep', ale#Escape('steep') + \ . ' check ' + \ . ' one' . (has('win32') ? '\' : '/') . 'dummy.rb' + +Execute(Should lint files at nested steep root): + call ale#test#SetFilename('../test-files/ruby/nested/foo/two/dummy.rb') + AssertLinter 'steep', ale#Escape('steep') + \ . ' check ' + \ . ' dummy.rb' + +Execute(Should lint files below nested steep root): + call ale#test#SetFilename('../test-files/ruby/nested/foo/two/three/dummy.rb') + AssertLinter 'steep', ale#Escape('steep') + \ . ' check ' + \ . ' three' . (has('win32') ? '\' : '/') . 'dummy.rb' diff --git a/test/linter/test_ruff.vader b/test/linter/test_ruff.vader new file mode 100644 index 00000000..07f3b1c4 --- /dev/null +++ b/test/linter/test_ruff.vader @@ -0,0 +1,127 @@ +Before: + Save g:ale_python_auto_pipenv + + let g:ale_python_auto_pipenv = 0 + + call ale#assert#SetUpLinterTest('python', 'ruff') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + let b:command_head = ale#Escape('ruff') . ' -q --no-fix' + let b:command_tail = ' --format json-lines --stdin-filename %s -' + + GivenCommandOutput ['ruff 0.0.83'] + +After: + unlet! b:bin_dir + unlet! b:executable + unlet! b:command_tail + + call ale#assert#TearDownLinterTest() + +Execute(The ruff callbacks should return the correct default values): + AssertLinterCwd expand('%:p:h') + AssertLinter 'ruff', b:command_head . b:command_tail + +Execute(ruff should run with the file path of buffer in old versions): + " version `0.0.69` supports liniting input from stdin + GivenCommandOutput ['ruff 0.0.68'] + + AssertLinterCwd expand('%:p:h') + AssertLinter 'ruff', b:command_head . b:command_tail[:-23] . ' %s' + +Execute(ruff should run with the --output-format flag in new versions): + GivenCommandOutput ['ruff 0.1.0'] + + AssertLinterCwd expand('%:p:h') + AssertLinter 'ruff', b:command_head . ' --output-format json-lines --stdin-filename %s -' + +Execute(ruff should run with the stdin in new enough versions): + GivenCommandOutput ['ruff 0.0.83'] + + AssertLinterCwd expand('%:p:h') + AssertLinter 'ruff', b:command_head . b:command_tail[:-3] . ' -' + " AssertLinter 'ruff', b:command_head . b:command_tail[:-3] . '--format json-lines -' + +Execute(ruff should run with the check subcmd in versions >= 0.3.0): + GivenCommandOutput ['ruff 0.3.0'] + + AssertLinterCwd expand('%:p:h') + let b:cmd_head = ale#Escape('ruff') . ' check -q --no-fix' + AssertLinter 'ruff', b:cmd_head . ' --output-format json-lines --stdin-filename %s -' + +Execute(The option for disabling changing directories should work): + let g:ale_python_ruff_change_directory = 0 + + AssertLinterCwd '' + AssertLinter 'ruff', b:command_head . b:command_tail + +Execute(The ruff executable should be configurable, and escaped properly): + let g:ale_python_ruff_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', ale#Escape('executable with spaces') . ' -q --no-fix' . b:command_tail + +Execute(The ruff command callback should let you set options): + let g:ale_python_ruff_options = '--some-flag' + AssertLinter 'ruff', b:command_head . ' --some-flag' . b:command_tail + + let g:ale_python_ruff_options = '--some-option value' + AssertLinter 'ruff', b:command_head . ' --some-option value' . b:command_tail + +Execute(The ruff callbacks shouldn't detect virtualenv directories where they don't exist): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'ruff', b:command_head . b:command_tail + +Execute(The ruff callbacks should detect virtualenv directories): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ruff' + \) + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter b:executable, ale#Escape(b:executable) . ' -q --no-fix' . b:command_tail + +Execute(You should able able to use the global ruff instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_ruff_use_global = 1 + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter 'ruff', b:command_head . b:command_tail + +Execute(Setting executable to 'pipenv' appends 'run ruff'): + let g:ale_python_ruff_executable = 'path/to/pipenv' + let g:ale_python_ruff_use_global = 1 + + AssertLinter 'path/to/pipenv', ale#Escape('path/to/pipenv') . ' run ruff -q --no-fix' + \ . b:command_tail + +Execute(Pipenv is detected when python_ruff_auto_pipenv is set): + let g:ale_python_ruff_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'pipenv', ale#Escape('pipenv') . ' run ruff -q --no-fix' + \ . b:command_tail + +Execute(Setting executable to 'poetry' appends 'run ruff'): + let g:ale_python_ruff_executable = 'path/to/poetry' + let g:ale_python_ruff_use_global = 1 + + AssertLinter 'path/to/poetry', ale#Escape('path/to/poetry') . ' run ruff -q --no-fix' + \ . b:command_tail + +Execute(poetry is detected when python_ruff_auto_poetry is set): + let g:ale_python_ruff_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'poetry', ale#Escape('poetry') . ' run ruff -q --no-fix' + \ . b:command_tail + +Execute(uv is detected when python_ruff_auto_uv is set): + let g:ale_python_ruff_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinterCwd expand('%:p:h') + AssertLinter 'uv', ale#Escape('uv') . ' run ruff -q --no-fix' + \ . b:command_tail diff --git a/test/linter/test_rust_analyzer.vader b/test/linter/test_rust_analyzer.vader new file mode 100644 index 00000000..1dd2c780 --- /dev/null +++ b/test/linter/test_rust_analyzer.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpLinterTest('rust', 'analyzer') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'rust-analyzer', ale#Escape('rust-analyzer') + +Execute(The project root should be detected correctly in cargo projects): + call ale#test#SetFilename('../test-files/rust/cargo/testfile.rs') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/rust/cargo') + +Execute(The project root should be detected correctly in non-cargo projects): + call ale#test#SetFilename('../test-files/rust/rust-project/testfile.rs') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/rust/rust-project') + +Execute(The project root should be empty when no project files can be detected): + call ale#test#SetFilename('../test-files/dummy') + + AssertLSPProject '' + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_rust_analyzer_config = {'diagnostics': {'disabled': ['unresolved-import']}} + AssertLSPOptions {'diagnostics': {'disabled': ['unresolved-import']}} diff --git a/test/linter/test_rust_rls.vader b/test/linter/test_rust_rls.vader new file mode 100644 index 00000000..d481e858 --- /dev/null +++ b/test/linter/test_rust_rls.vader @@ -0,0 +1,33 @@ +Before: + call ale#assert#SetUpLinterTest('rust', 'rls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'rls', ale#Escape('rls') + +Execute(The toolchain should be configurable): + let g:ale_rust_rls_toolchain = 'stable' + + AssertLinter 'rls', ale#Escape('rls') . ' +' . ale#Escape('stable') + +Execute(The toolchain should be omitted if not given): + let g:ale_rust_rls_toolchain = '' + + AssertLinter 'rls', ale#Escape('rls') + +Execute(The project root should be detected correctly for cargo projects): + call ale#test#SetFilename('../test-files/rust/cargo/testfile.rs') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/rust/cargo') + +Execute(The project root should be empty when no project files can be detected): + call ale#test#SetFilename('../test-files/dummy') + + AssertLSPProject '' + +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/linter/test_rustc.vader b/test/linter/test_rustc.vader new file mode 100644 index 00000000..92d9fa14 --- /dev/null +++ b/test/linter/test_rustc.vader @@ -0,0 +1,25 @@ +Before: + call ale#assert#SetUpLinterTest('rust', 'rustc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'rustc', 'rustc --error-format=json --emit=mir -o /dev/null -' + +Execute(The options should be configurable): + call ale#test#SetFilename('../test-files/dummy') + + let b:ale_rust_rustc_options = '--foo' + + AssertLinter 'rustc', 'rustc --error-format=json --foo -' + +Execute(Some default paths should be included when the project is a Cargo project): + call ale#test#SetFilename('../test-files/rust/cargo/testfile.rs') + + AssertLinter 'rustc', 'rustc --error-format=json --emit=mir -o /dev/null' + \ . ' -L ' . ale#Escape(ale#path#GetAbsPath(g:dir, '../test-files/rust/cargo/target/debug/deps')) + \ . ' -L ' . ale#Escape(ale#path#GetAbsPath(g:dir, '../test-files/rust/cargo/target/release/deps')) + \ . ' -' diff --git a/test/linter/test_ruumba.vader b/test/linter/test_ruumba.vader new file mode 100644 index 00000000..9fa48903 --- /dev/null +++ b/test/linter/test_ruumba.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpLinterTest('eruby', 'ruumba') + call ale#test#SetFilename('dummy.html.erb') + + let g:ale_eruby_ruumba_executable = 'ruumba' + let g:ale_eruby_ruumba_options = '' + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to ruumba): + AssertLinter 'ruumba', ale#Escape('ruumba') + \ . ' --format json --force-exclusion --stdin %s' + +Execute(Should be able to set a custom executable): + let g:ale_eruby_ruumba_executable = 'bin/ruumba' + + AssertLinter 'bin/ruumba' , ale#Escape('bin/ruumba') + \ . ' --format json --force-exclusion --stdin %s' + +Execute(Setting bundle appends 'exec ruumba'): + let g:ale_eruby_ruumba_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec ruumba' + \ . ' --format json --force-exclusion --stdin %s' diff --git a/test/linter/test_sass_sasslint.vader b/test/linter/test_sass_sasslint.vader new file mode 100644 index 00000000..87f0c8ad --- /dev/null +++ b/test/linter/test_sass_sasslint.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('sass', 'sasslint') + call ale#test#SetFilename('test.sass') + unlet! b:executable + +After: + call ale#assert#TearDownLinterTest() + +Execute(should default to source, bin/sass-lint.js): + call ale#test#SetFilename('../test-files/sasslint/with-source/test.sass') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/sasslint/with-source/node_modules/sass-lint/bin/sass-lint.js' + \) + + AssertLinter b:executable, + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(b:executable) + \ . ' -v -q -f compact %t' + +Execute(should fallback to bin, .bin/sass-lint): + call ale#test#SetFilename('../test-files/sasslint/with-bin/test.sass') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/sasslint/with-bin/node_modules/.bin/sass-lint' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' -v -q -f compact %t' + +Execute(should fallback to global bin): + AssertLinter 'sass-lint', ale#Escape('sass-lint') . ' -v -q -f compact %t' + +Execute(The global executable should be configurable): + let b:ale_sass_sasslint_executable = 'foo' + + AssertLinter 'foo', ale#Escape('foo') . ' -v -q -f compact %t' + +Execute(The options should be configurable): + let b:ale_sass_sasslint_options = '--bar' + + AssertLinter 'sass-lint', ale#Escape('sass-lint') . ' --bar -v -q -f compact %t' diff --git a/test/linter/test_scala_metals.vader b/test/linter/test_scala_metals.vader new file mode 100644 index 00000000..b14e3e02 --- /dev/null +++ b/test/linter/test_scala_metals.vader @@ -0,0 +1,21 @@ +" Author: Jeffrey Lau https://github.com/zoonfafer +" Description: Tests for the Scala Metals linter +Before: + call ale#assert#SetUpLinterTest('scala', 'metals') + +After: + call ale#assert#TearDownLinterTest() + +Execute(should set metals for sbt project with build.sbt): + call ale#test#SetFilename('../test-files/scala/valid_sbt_project/Main.scala') + AssertLSPLanguage 'scala' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#test#GetFilename('../test-files/scala/valid_sbt_project') + +Execute(should not set metals for sbt project without build.sbt): + call ale#test#SetFilename('../test-files/scala/invalid_sbt_project/Main.scala') + AssertLSPLanguage 'scala' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject '' diff --git a/test/linter/test_scala_sbtserver.vader b/test/linter/test_scala_sbtserver.vader new file mode 100644 index 00000000..118e090f --- /dev/null +++ b/test/linter/test_scala_sbtserver.vader @@ -0,0 +1,23 @@ +" Author: ophirr33 +" Description: Tests for the sbt Server lsp linter +Before: + call ale#assert#SetUpLinterTest('scala', 'sbtserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(should set sbtserver for sbt project with build.sbt): + call ale#test#SetFilename('../test-files/scala/valid_sbt_project/Main.scala') + AssertLSPLanguage 'scala' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#test#GetFilename('../test-files/scala/valid_sbt_project') + AssertLSPAddress '127.0.0.1:4273' + +Execute(should not set sbtserver for sbt project without build.sbt): + call ale#test#SetFilename('../test-files/scala/invalid_sbt_project/Main.scala') + AssertLSPLanguage 'scala' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject '' + AssertLSPAddress '127.0.0.1:4273' diff --git a/test/linter/test_scalac.vader b/test/linter/test_scalac.vader new file mode 100644 index 00000000..ea5ae109 --- /dev/null +++ b/test/linter/test_scalac.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('scala', 'scalac') + +After: + call ale#assert#TearDownLinterTest() + +Given scala(An empty Scala file): +Execute(The default executable and command should be correct): + AssertLinter 'scalac', ale#Escape('scalac') . ' -Ystop-after:parser %t' + +Given scala.sbt(An empty SBT file): +Execute(scalac should not be run for sbt files): + AssertLinterNotExecuted diff --git a/test/linter/test_scalastyle.vader b/test/linter/test_scalastyle.vader new file mode 100644 index 00000000..3c28f7a3 --- /dev/null +++ b/test/linter/test_scalastyle.vader @@ -0,0 +1,34 @@ +Before: + call ale#assert#SetUpLinterTest('scala', 'scalastyle') + +After: + unlet! g:ale_scalastyle_config_loc + call ale#linter#Reset() + +Execute(Should return the correct default command): + AssertLinter 'scalastyle', 'scalastyle %t' + +Execute(Should allow using a custom config file): + let b:ale_scala_scalastyle_config = '/dooper/config.xml' + + AssertLinter 'scalastyle', 'scalastyle' + \ . ' --config ' . ale#Escape('/dooper/config.xml') + \ . ' %t' + +Execute(Should support a legacy option for the scalastyle config): + unlet! g:ale_scala_scalastyle_config + let g:ale_scalastyle_config_loc = '/dooper/config.xml' + + call ale#linter#Reset() + runtime ale_linters/scala/scalastyle.vim + + AssertLinter 'scalastyle', 'scalastyle' + \ . ' --config ' . ale#Escape('/dooper/config.xml') + \ . ' %t' + +Execute(Should allow using custom options): + let b:ale_scala_scalastyle_options = '--warnings false --quiet true' + + AssertLinter 'scalastyle', 'scalastyle' + \ . ' --warnings false --quiet true' + \ . ' %t' diff --git a/test/linter/test_scss_sasslint.vader b/test/linter/test_scss_sasslint.vader new file mode 100644 index 00000000..839761c2 --- /dev/null +++ b/test/linter/test_scss_sasslint.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('scss', 'sasslint') + call ale#test#SetFilename('test.scss') + unlet! b:executable + +After: + call ale#assert#TearDownLinterTest() + +Execute(should default to source, bin/sass-lint.js): + call ale#test#SetFilename('../test-files/sasslint/with-source/test.scss') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/sasslint/with-source/node_modules/sass-lint/bin/sass-lint.js' + \) + + AssertLinter b:executable, + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(b:executable) + \ . ' -v -q -f compact %t' + +Execute(should fallback to bin, .bin/sass-lint): + call ale#test#SetFilename('../test-files/sasslint/with-bin/test.scss') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/sasslint/with-bin/node_modules/.bin/sass-lint' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' -v -q -f compact %t' + +Execute(should fallback to global bin): + AssertLinter 'sass-lint', ale#Escape('sass-lint') . ' -v -q -f compact %t' + +Execute(The global executable should be configurable): + let b:ale_scss_sasslint_executable = 'foo' + + AssertLinter 'foo', ale#Escape('foo') . ' -v -q -f compact %t' + +Execute(The options should be configurable): + let b:ale_scss_sasslint_options = '--bar' + + AssertLinter 'sass-lint', ale#Escape('sass-lint') . ' --bar -v -q -f compact %t' diff --git a/test/linter/test_scss_stylelint.vader b/test/linter/test_scss_stylelint.vader new file mode 100644 index 00000000..bf45ccd0 --- /dev/null +++ b/test/linter/test_scss_stylelint.vader @@ -0,0 +1,33 @@ +Before: + call ale#assert#SetUpLinterTest('scss', 'stylelint') + unlet! b:executable + +After: + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(node_modules directories should be discovered): + call ale#test#SetFilename('../test-files/stylelint/nested/testfile.scss') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/stylelint/node_modules/.bin/stylelint' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --stdin-filename %s' + +Execute(The global override should work): + let b:ale_scss_stylelint_executable = 'foobar' + let b:ale_scss_stylelint_use_global = 1 + + call ale#test#SetFilename('../test-files/stylelint/nested/testfile.scss') + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdin-filename %s' + +Execute(Extra options should be configurable): + call ale#test#SetFilename('../test-files/dummy') + + let b:ale_scss_stylelint_options = '--configFile ''/absolute/path/to/file''' + + AssertLinter 'stylelint', + \ ale#Escape('stylelint') . ' --configFile ''/absolute/path/to/file'' --stdin-filename %s' diff --git a/test/linter/test_shellcheck.vader b/test/linter/test_shellcheck.vader new file mode 100644 index 00000000..40995755 --- /dev/null +++ b/test/linter/test_shellcheck.vader @@ -0,0 +1,106 @@ +Before: + call ale#assert#SetUpLinterTest('sh', 'shellcheck') + call ale#test#SetFilename('test.sh') + + let b:suffix = ' -f gcc -' + +After: + unlet! b:is_bash + unlet! b:suffix + call ale#assert#TearDownLinterTest() + +Execute(The default shellcheck command should be correct): + AssertLinterCwd '%s:h' + AssertLinter 'shellcheck', ale#Escape('shellcheck') . b:suffix + +Execute(The option disabling changing directories should work): + let g:ale_sh_shellcheck_change_directory = 0 + + AssertLinterCwd '' + AssertLinter 'shellcheck', ale#Escape('shellcheck') . b:suffix + +Execute(The shellcheck command should accept options): + let b:ale_sh_shellcheck_options = '--foobar' + + AssertLinter 'shellcheck', ale#Escape('shellcheck') . ' --foobar' . b:suffix + +Execute(The shellcheck command should accept options and exclusions): + let b:ale_sh_shellcheck_options = '--foobar' + let b:ale_sh_shellcheck_exclusions = 'foo,bar' + + AssertLinter 'shellcheck', + \ ale#Escape('shellcheck') . ' --foobar -e foo,bar' . b:suffix + +Execute(The shellcheck command should include the dialect): + let b:is_bash = 1 + + AssertLinter 'shellcheck', 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', ale#Escape('shellcheck') . ' -s ksh93' . b:suffix + +Execute(The shellcheck command should allow unspecified dialect): + let b:ale_sh_shellcheck_dialect = '' + + AssertLinter 'shellcheck', 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' + let b:ale_sh_shellcheck_exclusions = 'foo,bar' + + AssertLinter 'shellcheck', ale#Escape('shellcheck') + \ . ' -s bash --foobar -e foo,bar' + \ . b:suffix + +Execute(The -x option should be added when the version is new enough): + AssertLinter 'shellcheck', [ + \ ale#Escape('shellcheck') . ' --version', + \ ale#Escape('shellcheck') . b:suffix, + \] + + GivenCommandOutput [ + \ 'ShellCheck - shell script analysis tool', + \ 'version: 0.4.4', + \ 'license: GNU General Public License, version 3', + \ 'website: http://www.shellcheck.net', + \] + AssertLinter 'shellcheck', [ + \ ale#Escape('shellcheck') . ' --version', + \ ale#Escape('shellcheck') . ' -x' . b:suffix, + \] + + " We should cache the version check + GivenCommandOutput [] + AssertLinter 'shellcheck', [ + \ ale#Escape('shellcheck') . ' -x' . b:suffix, + \] + +Execute(The -x option should not be added when the version is too old): + GivenCommandOutput [ + \ 'ShellCheck - shell script analysis tool', + \ 'version: 0.3.9', + \ 'license: GNU General Public License, version 3', + \ 'website: http://www.shellcheck.net', + \] + AssertLinter 'shellcheck', [ + \ ale#Escape('shellcheck') . ' --version', + \ ale#Escape('shellcheck') . b:suffix, + \] + +Execute(The version check shouldn't be run again for old versions): + GivenCommandOutput [ + \ 'ShellCheck - shell script analysis tool', + \ 'version: 0.3.9', + \ 'license: GNU General Public License, version 3', + \ 'website: http://www.shellcheck.net', + \] + AssertLinter 'shellcheck', [ + \ ale#Escape('shellcheck') . ' --version', + \ ale#Escape('shellcheck') . b:suffix, + \] + AssertLinter 'shellcheck', [ + \ ale#Escape('shellcheck') . b:suffix, + \] diff --git a/test/linter/test_slang.vader b/test/linter/test_slang.vader new file mode 100644 index 00000000..deeb0663 --- /dev/null +++ b/test/linter/test_slang.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('verilog', 'slang') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default slang command should be correct): + AssertLinter 'slang', 'slang -Weverything -I%s:h %t' + +Execute(slang options should be configurable): + " Additional args for the linter + let g:ale_verilog_slang_options = '--define-macro DWIDTH=12' + + AssertLinter 'slang', 'slang -Weverything -I%s:h --define-macro DWIDTH=12 %t' diff --git a/test/linter/test_slimlint.vader b/test/linter/test_slimlint.vader new file mode 100644 index 00000000..33df9ac0 --- /dev/null +++ b/test/linter/test_slimlint.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpLinterTest('slim', 'slimlint') + let g:default_command = 'slim-lint %t' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'slim-lint', 'slim-lint %t' + +Execute(The command should have the .rubocop.yml prepended as an env var if one exists): + call ale#test#SetFilename('../test-files/slimlint/subdir/file.slim') + + AssertLinter 'slim-lint', + \ ale#Env( + \ 'SLIM_LINT_RUBOCOP_CONF', + \ ale#path#Simplify(g:dir . '/../test-files/slimlint/.rubocop.yml') + \ ) + \ . 'slim-lint %t' diff --git a/test/linter/test_solc.vader b/test/linter/test_solc.vader new file mode 100644 index 00000000..23521f6a --- /dev/null +++ b/test/linter/test_solc.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('solidity', 'solc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'solc', 'solc %s' + +Execute(The options should be configurable): + let g:ale_solidity_solc_options = '--foobar' + + AssertLinter 'solc', 'solc --foobar %s' diff --git a/test/linter/test_solc_commit.vader b/test/linter/test_solc_commit.vader new file mode 100644 index 00000000..e25c47e7 --- /dev/null +++ b/test/linter/test_solc_commit.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('solidity', 'solc') + let g:ale_solidity_solc_executable = 'solc-v0.8.4+commit.c7e474f2' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The executable command should be configurable): + AssertLinter 'solc-v0.8.4+commit.c7e474f2', 'solc-v0.8.4+commit.c7e474f2 %s' + +Execute(The options should be configurable): + let g:ale_solidity_solc_options = '--foobar' + + AssertLinter 'solc-v0.8.4+commit.c7e474f2', 'solc-v0.8.4+commit.c7e474f2 --foobar %s' diff --git a/test/linter/test_solhint.vader b/test/linter/test_solhint.vader new file mode 100644 index 00000000..f4b07656 --- /dev/null +++ b/test/linter/test_solhint.vader @@ -0,0 +1,26 @@ +Before: + call ale#assert#SetUpLinterTest('solidity', 'solhint') + + let b:args = ' --formatter unix %s' + +After: + unlet! b:args + unlet! b:executable + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinterCwd '' + AssertLinter 'solhint', ale#Escape('solhint') . b:args + +Execute(The options should be configurable): + let g:ale_solidity_solhint_options = '--foobar' + + AssertLinter 'solhint', ale#Escape('solhint') . ' --foobar' . b:args + +Execute(solhint should be run from a containing project with solhint executable): + call ale#test#SetFilename('../test-files/solhint/Contract.sol') + + let b:executable = ale#path#Simplify(g:dir . '/../test-files/solhint/node_modules/.bin/solhint') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/solhint') + AssertLinter b:executable, ale#Escape(b:executable) . b:args diff --git a/test/linter/test_sorbet.vader b/test/linter/test_sorbet.vader new file mode 100644 index 00000000..fe758635 --- /dev/null +++ b/test/linter/test_sorbet.vader @@ -0,0 +1,34 @@ + +Before: + call ale#assert#SetUpLinterTest('ruby', 'sorbet') + call ale#test#SetFilename('dummy.rb') + + let g:ale_ruby_sorbet_executable = 'srb' + let g:ale_ruby_sorbet_options = '' + let g:ale_ruby_sorbet_enable_watchman = 0 + +After: + call ale#assert#TearDownLinterTest() + +Execute(Executable should default to srb): + AssertLinter 'srb', ale#Escape('srb') + \ . ' tc --lsp --disable-watchman' + +Execute(Able to enable watchman): + let g:ale_ruby_sorbet_enable_watchman = 1 + + AssertLinter 'srb', ale#Escape('srb') + \ . ' tc --lsp' + +Execute(Should be able to set a custom executable): + let g:ale_ruby_sorbet_executable = 'bin/srb' + + AssertLinter 'bin/srb' , ale#Escape('bin/srb') + \ . ' tc --lsp --disable-watchman' + +Execute(Setting bundle appends 'exec srb tc'): + let g:ale_ruby_sorbet_executable = 'path to/bundle' + + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') + \ . ' exec srb' + \ . ' tc --lsp --disable-watchman' diff --git a/test/linter/test_spectral.vader b/test/linter/test_spectral.vader new file mode 100644 index 00000000..cfcf0987 --- /dev/null +++ b/test/linter/test_spectral.vader @@ -0,0 +1,31 @@ +Before: + call ale#assert#SetUpLinterTest('yaml', 'spectral') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The yaml spectral command callback should return the correct default string): + AssertLinter 'spectral', ale#Escape('spectral') . ' lint --ignore-unknown-format -q -f text %t' + +Execute(The yaml spectral command callback should be configurable): + let g:ale_yaml_spectral_executable = '~/.local/bin/spectral' + + AssertLinter '~/.local/bin/spectral', + \ ale#Escape('~/.local/bin/spectral') + \ . ' lint --ignore-unknown-format -q -f text %t' + +Execute(The yaml spectral command callback should allow a global installation to be used): + let g:ale_yaml_spectral_executable = '/usr/local/bin/spectral' + let g:ale_yaml_spectral_use_global = 1 + + AssertLinter '/usr/local/bin/spectral', + \ ale#Escape('/usr/local/bin/spectral') + \ . ' lint --ignore-unknown-format -q -f text %t' + +Execute(The yaml spectral command callback should allow a local installation to be used): + call ale#test#SetFilename('../test-files/spectral/openapi.yaml') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/spectral/node_modules/.bin/spectral'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/spectral/node_modules/.bin/spectral')) + \ . ' lint --ignore-unknown-format -q -f text %t' diff --git a/test/linter/test_sql_sqlfluff.vader b/test/linter/test_sql_sqlfluff.vader new file mode 100644 index 00000000..8fde882d --- /dev/null +++ b/test/linter/test_sql_sqlfluff.vader @@ -0,0 +1,18 @@ +Before: + call ale#assert#SetUpLinterTest('sql', 'sqlfluff') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default sqlfluff command should be correct): + AssertLinter 'sqlfluff', + \ ale#Escape('sqlfluff') + \ . ' lint --dialect ansi --format json %t' + +Execute(The sqlfluff executable and command should be configurable): + let g:ale_sql_sqlfluff_executable = 'foobar' + let g:ale_sql_sqlfluff_options = '--whatever' + + AssertLinter 'foobar', + \ ale#Escape('foobar') + \ . ' lint --dialect ansi --format json --whatever %t' diff --git a/test/linter/test_sqllint.vader b/test/linter/test_sqllint.vader new file mode 100644 index 00000000..eea9b4e0 --- /dev/null +++ b/test/linter/test_sqllint.vader @@ -0,0 +1,12 @@ +Before: + " Load the linter and set up a series of commands, reset linter variables, + " clear caches, etc. + " + " Vader's 'Save' command will be called here for linter variables. + call ale#assert#SetUpLinterTest('sql', 'sqllint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'sql-lint', ['sql-lint'] diff --git a/test/linter/test_standard.vader b/test/linter/test_standard.vader new file mode 100644 index 00000000..4722cd4a --- /dev/null +++ b/test/linter/test_standard.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'standard') + call ale#test#SetFilename('testfile.js') + unlet! b:executable + +After: + call ale#assert#TearDownLinterTest() + +Execute(bin/cmd.js paths should be preferred): + call ale#test#SetFilename('../test-files/standard/with-cmd/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/standard/with-cmd/node_modules/standard/bin/cmd.js' + \) + + AssertLinter b:executable, + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(b:executable) + \ . ' --stdin %s' + +Execute(.bin directories should be used too): + call ale#test#SetFilename('../test-files/standard/with-bin/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/standard/with-bin/node_modules/.bin/standard' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --stdin %s' + +Execute(The global executable should be used otherwise): + AssertLinter 'standard', ale#Escape('standard') . ' --stdin %s' + +Execute(The global executable should be configurable): + let b:ale_javascript_standard_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdin %s' + +Execute(The options should be configurable): + let b:ale_javascript_standard_options = '--wat' + + AssertLinter 'standard', ale#Escape('standard') . ' --wat --stdin %s' diff --git a/test/linter/test_standardrb.vader b/test/linter/test_standardrb.vader new file mode 100644 index 00000000..108dd870 --- /dev/null +++ b/test/linter/test_standardrb.vader @@ -0,0 +1,26 @@ +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 %s' + +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 %s' + +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 %s' diff --git a/test/linter/test_standardts.vader b/test/linter/test_standardts.vader new file mode 100644 index 00000000..33ca8b25 --- /dev/null +++ b/test/linter/test_standardts.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('typescript', 'standard') + call ale#test#SetFilename('testfile.js') + unlet! b:executable + +After: + call ale#assert#TearDownLinterTest() + +Execute(bin/cmd.js paths should be preferred): + call ale#test#SetFilename('../test-files/standard/with-cmd/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/standard/with-cmd/node_modules/standard/bin/cmd.js' + \) + + AssertLinter b:executable, + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(b:executable) + \ . ' --stdin %s' + +Execute(.bin directories should be used too): + call ale#test#SetFilename('../test-files/standard/with-bin/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/standard/with-bin/node_modules/.bin/standard' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --stdin %s' + +Execute(The global executable should be used otherwise): + AssertLinter 'standard', ale#Escape('standard') . ' --stdin %s' + +Execute(The global executable should be configurable): + let b:ale_typescript_standard_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdin %s' + +Execute(The options should be configurable): + let b:ale_typescript_standard_options = '--wat' + + AssertLinter 'standard', ale#Escape('standard') . ' --wat --stdin %s' diff --git a/test/linter/test_starknet.vader b/test/linter/test_starknet.vader new file mode 100644 index 00000000..368ab702 --- /dev/null +++ b/test/linter/test_starknet.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('cairo', 'starknet') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'starknet-compile', 'starknet-compile %s' + +Execute(Extra options should be supported): + let g:ale_cairo_starknet_options = '--config' + + AssertLinter 'starknet-compile', 'starknet-compile --config %s' diff --git a/test/linter/test_staticcheck.vader b/test/linter/test_staticcheck.vader new file mode 100644 index 00000000..94f24a54 --- /dev/null +++ b/test/linter/test_staticcheck.vader @@ -0,0 +1,49 @@ +Before: + Save g:ale_go_go111module + Save $GOPATH + + let $GOPATH = '/non/existent/directory' + + call ale#assert#SetUpLinterTest('go', 'staticcheck') + call ale#test#SetFilename('test.go') + +After: + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The staticcheck callback should return the right defaults): + AssertLinterCwd '%s:h' + AssertLinter 'staticcheck', ale#Escape('staticcheck') . ' .' + +Execute(staticcheck should be found in GOPATH): + " This is a directory with a fake executable + let $GOPATH = ale#test#GetFilename('../test-files/go/gopath') + + AssertLinter + \ ale#test#GetFilename('../test-files/go/gopath/bin/staticcheck'), + \ ale#Escape(ale#test#GetFilename('../test-files/go/gopath/bin/staticcheck')) + \ . ' .' + +Execute(The staticcheck callback should use configured options): + let b:ale_go_staticcheck_options = '-test' + + AssertLinter 'staticcheck', ale#Escape('staticcheck') . ' -test .' + +Execute(Unset the staticcheck `lint_package` option should use the correct command): + let b:ale_go_staticcheck_lint_package = 0 + + AssertLinterCwd '%s:h' + AssertLinter 'staticcheck', ale#Escape('staticcheck') . ' %s:t' + +Execute(The staticcheck callback should use the `GO111MODULE` option if set): + let b:ale_go_go111module = 'off' + + AssertLinter 'staticcheck', + \ ale#Env('GO111MODULE', 'off') . ale#Escape('staticcheck') . ' .' + + " Test with lint_package option set + let b:ale_go_staticcheck_lint_package = 0 + + AssertLinter 'staticcheck', + \ ale#Env('GO111MODULE', 'off') . ale#Escape('staticcheck') . ' %s:t' diff --git a/test/linter/test_sugarss_stylelint.vader b/test/linter/test_sugarss_stylelint.vader new file mode 100644 index 00000000..c6000a9a --- /dev/null +++ b/test/linter/test_sugarss_stylelint.vader @@ -0,0 +1,33 @@ +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('../test-files/stylelint/nested/testfile.sss') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/../test-files/stylelint/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('../test-files/stylelint/nested/testfile.sss') + + AssertLinter 'foobar', ale#Escape('foobar') . ' --syntax=sugarss --stdin-filename %s' + +Execute(Extra options should be configurable): + call ale#test#SetFilename('../test-files/dummy') + + 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/linter/test_svelteserver.vader b/test/linter/test_svelteserver.vader new file mode 100644 index 00000000..c09f1682 --- /dev/null +++ b/test/linter/test_svelteserver.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('svelte', 'svelteserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'svelteserver', ale#Escape('svelteserver') . ' --stdio' diff --git a/test/linter/test_swaglint.vader b/test/linter/test_swaglint.vader new file mode 100644 index 00000000..98f0c594 --- /dev/null +++ b/test/linter/test_swaglint.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('yaml', 'swaglint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The yaml swaglint command callback should return the correct default string): + AssertLinter 'swaglint', ale#Escape('swaglint') . ' -r compact --stdin' + +Execute(The yaml swaglint command callback should be configurable): + let g:ale_yaml_swaglint_executable = '~/.local/bin/swaglint' + + AssertLinter '~/.local/bin/swaglint', + \ ale#Escape('~/.local/bin/swaglint') . ' -r compact --stdin' + +Execute(The yaml swaglint command callback should allow a global installation to be used): + let g:ale_yaml_swaglint_executable = '/usr/local/bin/swaglint' + let g:ale_yaml_swaglint_use_global = 1 + + AssertLinter '/usr/local/bin/swaglint', + \ ale#Escape('/usr/local/bin/swaglint') . ' -r compact --stdin' + +Execute(The yaml swaglint command callback should allow a local installation to be used): + call ale#test#SetFilename('../test-files/swaglint/docs/swagger.yaml') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/swaglint/node_modules/.bin/swaglint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/swaglint/node_modules/.bin/swaglint')) + \ . ' -r compact --stdin' diff --git a/test/linter/test_swift_appleswiftformat.vader b/test/linter/test_swift_appleswiftformat.vader new file mode 100644 index 00000000..3dbae8ff --- /dev/null +++ b/test/linter/test_swift_appleswiftformat.vader @@ -0,0 +1,42 @@ +Before: + call ale#assert#SetUpLinterTest('swift', 'appleswiftformat') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Should use default command when use_swiftpm is not set): + call ale#test#SetFilename('../test-files/swift/non-swift-package-project/src/folder/dummy.swift') + + let g:ale_swift_appleswiftformat_executable = 'swift-format' + let g:ale_swift_appleswiftformat_use_swiftpm = 0 + + AssertLinter 'swift-format', ale#Escape('swift-format') . ' lint %t' + +Execute(Should use default command and available configuration when use_swiftpm is not set): + call ale#test#SetDirectory('/testplugin/test/test-files/swift/swift-package-project-with-config') + call ale#test#SetFilename('src/folder/dummy.swift') + + let g:ale_swift_appleswiftformat_executable = 'swift-format' + let g:ale_swift_appleswiftformat_use_swiftpm = 0 + + AssertLinter 'swift-format', + \ ale#Escape('swift-format') . ' lint %t ' . '--configuration ' + \ . glob(g:dir . '/.swift-format') + + call ale#test#RestoreDirectory() + +Execute(Should use swift run when use_swiftpm is set to 1): + call ale#test#SetFilename('../test-files/swift/swift-package-project/src/folder/dummy.swift') + + let g:ale_swift_appleswiftformat_use_swiftpm = 1 + + AssertLinter 'swift', ale#Escape('swift') . ' run swift-format lint %t' + +Execute(Should use the provided global executable): + call ale#test#SetFilename('../test-files/swift/swift-package-project/src/folder/dummy.swift') + + let g:ale_swift_appleswiftformat_executable = '/path/to/custom/swift-format' + let g:ale_swift_appleswiftformat_use_swiftpm = 0 + + AssertLinter '/path/to/custom/swift-format', + \ ale#Escape('/path/to/custom/swift-format') . ' lint %t' diff --git a/test/linter/test_swift_sourcekitlsp.vader b/test/linter/test_swift_sourcekitlsp.vader new file mode 100644 index 00000000..1040d590 --- /dev/null +++ b/test/linter/test_swift_sourcekitlsp.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('../test-files/swift/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('../test-files/swift/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/linter/test_swiftlint.vader b/test/linter/test_swiftlint.vader new file mode 100644 index 00000000..d2442b0a --- /dev/null +++ b/test/linter/test_swiftlint.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('swift', 'swiftlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Global installation should be the default executable): + call ale#test#SetFilename('../test-files/swiftlint/global/testfile.swift') + + AssertEqual + \ 'swiftlint', + \ ale_linters#swift#swiftlint#GetExecutable(bufnr('')) + +Execute(React Native apps using CocoaPods should take precedence over the default executable): + call ale#test#SetFilename('../test-files/swiftlint/react-native/testfile.swift') + + AssertEqual + \ tolower(ale#test#GetFilename('../test-files/swiftlint/react-native/ios/Pods/SwiftLint/swiftlint')), + \ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr(''))) + +Execute(CocoaPods installation should take precedence over the default executable): + call ale#test#SetFilename('../test-files/swiftlint/cocoapods/testfile.swift') + + AssertEqual + \ tolower(ale#test#GetFilename('../test-files/swiftlint/cocoapods/Pods/SwiftLint/swiftlint')), + \ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr(''))) + +Execute(Top level CocoaPods installation should take precedence over React Native installation): + call ale#test#SetFilename('../test-files/swiftlint/cocoapods-and-react-native/testfile.swift') + + AssertEqual + \ tolower(ale#test#GetFilename('../test-files/swiftlint/cocoapods-and-react-native/Pods/SwiftLint/swiftlint')), + \ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr(''))) + +Execute(use-global should override other versions): + let g:ale_swift_swiftlint_use_global = 1 + let g:ale_swift_swiftlint_executable = 'swiftlint_d' + + call ale#test#SetFilename('../test-files/swiftlint/cocoapods-and-react-native/testfile.swift') + + AssertEqual + \ 'swiftlint_d', + \ ale_linters#swift#swiftlint#GetExecutable(bufnr('')) diff --git a/test/linter/test_systemd_analyze.vader b/test/linter/test_systemd_analyze.vader new file mode 100644 index 00000000..d97c87be --- /dev/null +++ b/test/linter/test_systemd_analyze.vader @@ -0,0 +1,9 @@ +Before: + call ale#assert#SetUpLinterTest('systemd', 'systemd_analyze') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'systemd-analyze', + \ 'SYSTEMD_LOG_COLOR=0 ' . ale#Escape('systemd-analyze') . ' --user verify %s' diff --git a/test/linter/test_terraform_ls.vader b/test/linter/test_terraform_ls.vader new file mode 100644 index 00000000..df0186c7 --- /dev/null +++ b/test/linter/test_terraform_ls.vader @@ -0,0 +1,37 @@ +Before: + call ale#assert#SetUpLinterTest('terraform', 'terraform_ls') + +After: + unlet! b:ale_terraform_terraform_executable + unlet! b:ale_terraform_ls_executable + unlet! b:ale_terraform_ls_options + + call ale#assert#TearDownLinterTest() + +Execute(The terraform-ls language should be correct): + AssertLSPLanguage 'terraform' + +Execute(The default terraform-ls command should be correct): + AssertLinter 'terraform-ls', ale#Escape('terraform-ls') . ' serve' + +Execute(The terrarform-ls executable and options should be configurable): + let b:ale_terraform_ls_executable = 'foo' + let b:ale_terraform_ls_options = '--bar' + + AssertLinter 'foo', ale#Escape('foo') . ' serve --bar' + +Execute(Should ignore non-absolute path custom terraform executables): + let b:ale_terraform_terraform_executable = 'terraform' + + AssertLinter 'terraform-ls', ale#Escape('terraform-ls') . ' serve' + +Execute(Should set absolute custom terraform executable): + let b:ale_terraform_terraform_executable = '/bin/terraform' + + AssertLinter 'terraform-ls', + \ ale#Escape('terraform-ls') . ' serve -tf-exec /bin/terraform' + +Execute(Should return nearest directory with .terraform if found in parent directory): + call ale#test#SetFilename('../test-files/terraform/main.tf') + + AssertLSPProject ale#test#GetFilename('../test-files/terraform') diff --git a/test/linter/test_terraform_lsp.vader b/test/linter/test_terraform_lsp.vader new file mode 100644 index 00000000..27f27ffb --- /dev/null +++ b/test/linter/test_terraform_lsp.vader @@ -0,0 +1,29 @@ +Before: + call ale#assert#SetUpLinterTest('terraform', 'terraform_lsp') + +After: + unlet! b:ale_terraform_langserver_executable + unlet! b:ale_terraform_langserver_options + + call ale#assert#TearDownLinterTest() + +Execute(Should send correct LSP language): + AssertLSPLanguage 'terraform' + +Execute(Should load default executable): + AssertLinter 'terraform-lsp', ale#Escape('terraform-lsp') + +Execute(Should configure custom executable): + let b:ale_terraform_langserver_executable = 'foo' + AssertLinter 'foo', ale#Escape('foo') + +Execute(Should set custom options): + let b:ale_terraform_langserver_options = '--bar' + + AssertLinter 'terraform-lsp', + \ ale#Escape('terraform-lsp') . ' --bar' + +Execute(Should return nearest directory with .terraform if found in parent directory): + call ale#test#SetFilename('../test-files/terraform/main.tf') + + AssertLSPProject ale#test#GetFilename('../test-files/terraform') diff --git a/test/linter/test_terraform_terraform.vader b/test/linter/test_terraform_terraform.vader new file mode 100644 index 00000000..697ffcda --- /dev/null +++ b/test/linter/test_terraform_terraform.vader @@ -0,0 +1,15 @@ +" Based upon :help ale-development +Before: + call ale#assert#SetUpLinterTest('terraform', 'terraform') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'terraform', + \ ale#Escape('terraform') . ' validate -no-color -json ' + +Execute(The default command should be overridden): + let b:ale_terraform_terraform_executable = '/bin/other/terraform' + AssertLinter '/bin/other/terraform', + \ ale#Escape('/bin/other/terraform') . ' validate -no-color -json ' diff --git a/test/linter/test_terraform_tflint.vader b/test/linter/test_terraform_tflint.vader new file mode 100644 index 00000000..c608bc8a --- /dev/null +++ b/test/linter/test_terraform_tflint.vader @@ -0,0 +1,23 @@ +Before: + call ale#assert#SetUpLinterTest('terraform', 'tflint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default tflint command should be correct): + AssertLinter 'tflint', ale#Escape('tflint') . ' -f json' + +Execute(Test tflint executable and command should be configurable): + let g:ale_terraform_tflint_executable = 'fnord' + let g:ale_terraform_tflint_options = '--whatever' + + AssertLinter 'fnord', ale#Escape('fnord') . ' --whatever -f json' + +Execute(Configuration files should be found): + call ale#test#SetFilename('../test-files/tflint/foo/bar.tf') + + AssertLinter 'tflint', + \ ale#Escape('tflint') + \ . ' --config ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/tflint/foo/.tflint.hcl')) + \ . ' -f json' diff --git a/test/linter/test_terraform_tfsec.vader b/test/linter/test_terraform_tfsec.vader new file mode 100644 index 00000000..c3a7eae2 --- /dev/null +++ b/test/linter/test_terraform_tfsec.vader @@ -0,0 +1,38 @@ +Before: + call ale#assert#SetUpLinterTest('terraform', 'tfsec') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'tfsec', ale#Escape('tfsec') . ' --format json' + +Execute(The default executable should be configurable): + let b:ale_terraform_tfsec_executable = '/usr/bin/tfsec' + + AssertLinter '/usr/bin/tfsec', ale#Escape('/usr/bin/tfsec') . ' --format json' + +Execute(Overriding options should work): + let g:ale_terraform_tfsec_executable = '/usr/local/bin/tfsec' + let g:ale_terraform_tfsec_options = '--minimum-severity MEDIUM' + + AssertLinter '/usr/local/bin/tfsec', + \ ale#Escape('/usr/local/bin/tfsec') . ' --minimum-severity MEDIUM --format json' + +Execute(Configuration yml file should be found): + call ale#test#SetFilename('../test-files/tfsec/yml/main.tf') + + AssertLinter 'tfsec', + \ ale#Escape('tfsec') + \ . ' --config-file ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/tfsec/yml/.tfsec/config.yml')) + \ . ' --format json' + +Execute(Configuration json file should be found): + call ale#test#SetFilename('../test-files/tfsec/json/main.tf') + + AssertLinter 'tfsec', + \ ale#Escape('tfsec') + \ . ' --config-file ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/tfsec/json/.tfsec/config.json')) + \ . ' --format json' diff --git a/test/linter/test_tex_chktex.vader b/test/linter/test_tex_chktex.vader new file mode 100644 index 00000000..038bd10c --- /dev/null +++ b/test/linter/test_tex_chktex.vader @@ -0,0 +1,50 @@ +Before: + call ale#assert#SetUpLinterTest('tex', 'chktex') + + GivenCommandOutput ['ChkTeX v1.7.6 - Copyright 1995-96 Jens T. Berger Thielemann'] + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'chktex', [ + \ ale#Escape('chktex') . ' --version', + \ ale#Escape('chktex') + \ . ' -v0 -p stdin -q' + \ . ' -I', + \] + + " The version check should be cached. + GivenCommandOutput [] + AssertLinter 'chktex', [ + \ ale#Escape('chktex') + \ . ' -v0 -p stdin -q' + \ . ' -I', + \] + + " Try newer version + call ale#semver#ResetVersionCache() + GivenCommandOutput ['ChkTeX v1.7.8 - Copyright 1995-96 Jens T. Berger Thielemann'] + AssertLinter 'chktex', [ + \ ale#Escape('chktex') . ' --version', + \ ale#Escape('chktex') + \ . ' -v0 -p stdin -q' + \ . ' -S TabSize=1' + \ . ' -I', + \] + +Execute(The executable should be configurable): + let g:ale_tex_chktex_executable = 'bin/foo' + + AssertLinter 'bin/foo', + \ ale#Escape('bin/foo') + \ . ' -v0 -p stdin -q' + \ . ' -I' + +Execute(The options should be configurable): + let b:ale_tex_chktex_options = '--something' + + AssertLinter 'chktex', + \ ale#Escape('chktex') + \ . ' -v0 -p stdin -q' + \ . ' --something' diff --git a/test/linter/test_tex_lacheck.vader b/test/linter/test_tex_lacheck.vader new file mode 100644 index 00000000..b404cc78 --- /dev/null +++ b/test/linter/test_tex_lacheck.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/linter/test_tex_textlint.vader b/test/linter/test_tex_textlint.vader new file mode 100644 index 00000000..f99e0fd0 --- /dev/null +++ b/test/linter/test_tex_textlint.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('../test-files/textlint/with_bin_path/foo.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_bin_path/node_modules/.bin/textlint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/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('../test-files/textlint/with_textlint_bin_path/foo.txt') + + if has('win32') + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape('node.exe') . ' ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + else + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + endif diff --git a/test/linter/test_texlab.vader b/test/linter/test_texlab.vader new file mode 100644 index 00000000..b60b2083 --- /dev/null +++ b/test/linter/test_texlab.vader @@ -0,0 +1,35 @@ +Before: + call ale#assert#SetUpLinterTest('tex', 'texlab') + + Save &filetype + let &filetype = 'tex' + +After: + call ale#assert#TearDownLinterTest() + +Execute(The language string should be correct): + AssertLSPLanguage 'tex' + +Execute(The default executable path should be correct): + AssertLinter 'texlab', ale#Escape('texlab') + +Execute(The project root should be detected correctly): + call ale#test#SetFilename('../test-files/tex/sample1.tex') + silent! call mkdir('../test-files/tex/.git') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/tex') + +Execute(The executable should be configurable): + let b:ale_tex_texlab_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') + +Execute(The options should be configurable): + let b:ale_tex_texlab_options = '-v' + + AssertLinter 'texlab', ale#Escape('texlab') . ' ' . b:ale_tex_texlab_options + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_tex_texlab_config = {'build':{'onSave':v:true}} + AssertLSPConfig {'build':{'onSave':v:true}} diff --git a/test/linter/test_textlint.vader b/test/linter/test_textlint.vader new file mode 100644 index 00000000..4b1629d9 --- /dev/null +++ b/test/linter/test_textlint.vader @@ -0,0 +1,60 @@ +" Author: januswel, w0rp + +Before: + " This is just one language for the linter. + call ale#assert#SetUpLinterTest('markdown', '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 textlint command should be correct): + AssertLinter 'textlint', + \ ale#Escape('textlint') . ' -f json --stdin --stdin-filename %s' + +Execute(The textlint executable and options should be configurable): + let b:ale_textlint_executable = 'foobar' + let b:ale_textlint_options = '--something' + + AssertLinter 'foobar', + \ ale#Escape('foobar') . ' --something -f json --stdin --stdin-filename %s' + +Execute(The local executable from .bin should be used if available): + call ale#test#SetFilename('../test-files/textlint/with_bin_path/foo.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_bin_path/node_modules/.bin/textlint'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/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('../test-files/textlint/with_textlint_bin_path/foo.txt') + + if has('win32') + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape('node.exe') . ' ' . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + else + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js')) + \ . ' -f json --stdin --stdin-filename %s' + endif diff --git a/test/linter/test_thrift.vader b/test/linter/test_thrift.vader new file mode 100644 index 00000000..cbada818 --- /dev/null +++ b/test/linter/test_thrift.vader @@ -0,0 +1,53 @@ +Before: + call ale#assert#SetUpLinterTest('thrift', 'thrift') + let b:suffix = ' -out ' . ale#Escape('TEMP_DIR') . ' %t' + + function! GetCommand(buffer) abort + call ale#engine#InitBufferInfo(a:buffer) + let l:command = ale_linters#thrift#thrift#GetCommand(a:buffer) + call ale#engine#Cleanup(a:buffer) + + let l:split_command = split(l:command) + let l:index = index(l:split_command, '-out') + + if l:index >= 0 + let l:split_command[l:index + 1] = 'TEMP' + endif + + return join(l:split_command) + endfunction + +After: + unlet! b:suffix + delfunction GetCommand + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'thrift', ale#Escape('thrift') . ' --gen cpp -I . -strict' . b:suffix + +Execute(The executable should be configurable): + let b:ale_thrift_thrift_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --gen cpp -I . -strict' . b:suffix + +Execute(The list of generators should be configurable): + let b:ale_thrift_thrift_generators = ['java', 'py:dynamic'] + + AssertLinter 'thrift', ale#Escape('thrift') + \ . ' --gen java --gen py:dynamic -I . -strict' . b:suffix + + let b:ale_thrift_thrift_generators = [] + + AssertLinter 'thrift', ale#Escape('thrift') . ' --gen cpp -I . -strict' . b:suffix + +Execute(The list of include paths should be configurable): + let b:ale_thrift_thrift_includes = ['included/path'] + + AssertLinter 'thrift', ale#Escape('thrift') + \ . ' --gen cpp -I included/path -strict' . b:suffix + +Execute(The string of compiler options should be configurable): + let b:ale_thrift_thrift_options = '-strict --allow-64bit-consts' + + AssertLinter 'thrift', ale#Escape('thrift') + \ . ' --gen cpp -I . -strict --allow-64bit-consts' . b:suffix diff --git a/test/linter/test_thriftcheck.vader b/test/linter/test_thriftcheck.vader new file mode 100644 index 00000000..bf2bbab4 --- /dev/null +++ b/test/linter/test_thriftcheck.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpLinterTest('thrift', 'thriftcheck') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'thriftcheck', ale#Escape('thriftcheck') + \ . ' --stdin-filename %s %t' + +Execute(The executable and options should be configurable): + let b:ale_thrift_thriftcheck_executable = 'foobar' + let b:ale_thrift_thriftcheck_options = '--errors-only' + + AssertLinter 'foobar', ale#Escape('foobar') + \ . ' --errors-only --stdin-filename %s %t' diff --git a/test/linter/test_tslint.vader b/test/linter/test_tslint.vader new file mode 100644 index 00000000..1b291d9f --- /dev/null +++ b/test/linter/test_tslint.vader @@ -0,0 +1,23 @@ +Before: + call ale#assert#SetUpLinterTest('typescript', 'tslint') + call ale#test#SetFilename('test.ts') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default tslint command should be correct): + AssertLinterCwd '%s:h' + AssertLinter 'tslint', ale#Escape('tslint') . ' --format json %t' + +Execute(The rules directory option should be included if set): + let b:ale_typescript_tslint_rules_dir = '/foo/bar' + + AssertLinter 'tslint', + \ ale#Escape('tslint') . ' --format json' + \ . ' -r ' . ale#Escape('/foo/bar') + \ . ' %t' + +Execute(The executable should be configurable and escaped): + let b:ale_typescript_tslint_executable = 'foo bar' + + AssertLinter 'foo bar', ale#Escape('foo bar') . ' --format json %t' diff --git a/test/linter/test_typescript_deno_lsp.vader b/test/linter/test_typescript_deno_lsp.vader new file mode 100644 index 00000000..c77b2a10 --- /dev/null +++ b/test/linter/test_typescript_deno_lsp.vader @@ -0,0 +1,86 @@ +Before: + Save g:ale_deno_import_map + Save g:ale_deno_importMap + Save g:ale_deno_unstable + Save g:ale_deno_executable + Save g:ale_deno_lsp_project_root + + let g:ale_deno_import_map = 'import_map.json' + let g:ale_deno_importMap = '' + let g:ale_deno_unstable = 0 + let g:ale_deno_executable = 'deno' + let g:ale_deno_lsp_project_root = '' + + runtime autoload/ale/handlers/deno.vim + call ale#assert#SetUpLinterTest('typescript', 'deno') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Should set deno lsp for TypeScript projects using stable Deno API): + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:false, + \ 'importMap': '' + \} + +Execute(Should set deno lsp using unstable Deno API if enabled by user): + let g:ale_deno_unstable = 1 + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:true, + \ 'importMap': '' + \} + +Execute(Should set the default importMap filepath): + call ale#test#SetFilename('../test-files/typescript/test.ts') + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:false, + \ 'importMap': ale#path#Simplify(g:dir . '/../test-files/typescript/import_map.json') + \} + +Execute(Should set the importMap filepath from user defined importMap): + let g:ale_deno_importMap = 'custom_import_map.json' + call ale#test#SetFilename('../test-files/typescript/test.ts') + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:false, + \ 'importMap': ale#path#Simplify(g:dir . '/../test-files/typescript/custom_import_map.json') + \} + +Execute(Should set the importMap filepath from user defined importMap with unstable API): + let g:ale_deno_import_map = 'custom_import_map.json' + let g:ale_deno_unstable = 1 + call ale#test#SetFilename('../test-files/typescript/test.ts') + + AssertLSPOptions { + \ 'enable': v:true, + \ 'lint': v:true, + \ 'unstable': v:true, + \ 'importMap': ale#path#Simplify(g:dir . '/../test-files/typescript/custom_import_map.json') + \} + +Execute(Should find project root containing tsconfig.json): + call ale#test#SetFilename('../test-files/typescript/test.ts') + + AssertLSPLanguage 'typescript' + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/typescript') + +Execute(Should use user-specified project root): + let g:ale_deno_lsp_project_root = '/' + + call ale#test#SetFilename('../test-files/typescript/test.ts') + + AssertLSPLanguage 'typescript' + AssertLSPProject '/' + +Execute(Check Deno LSP command): + AssertLinter 'deno', ale#Escape('deno') . ' lsp' diff --git a/test/linter/test_typescript_tsserver.vader b/test/linter/test_typescript_tsserver.vader new file mode 100644 index 00000000..719ac184 --- /dev/null +++ b/test/linter/test_typescript_tsserver.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('typescript', 'tsserver') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'tsserver', ale#Escape('tsserver') diff --git a/test/linter/test_unimport.vader b/test/linter/test_unimport.vader new file mode 100644 index 00000000..9e8e9112 --- /dev/null +++ b/test/linter/test_unimport.vader @@ -0,0 +1,78 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'unimport') + call ale#test#SetFilename('test.py') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:executable + unlet! b:bin_dir + + call ale#assert#TearDownLinterTest() + +Execute(The unimport callbacks should return the correct default values): + AssertLinter 'unimport', ale#Escape('unimport') . ' --check %t' + +Execute(The unimport executable should be configurable, and escaped properly): + let b:ale_python_unimport_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --check %t' + +Execute(The unimport command callback should let you set options): + let b:ale_python_unimport_options = '--gitignore' + + AssertLinter 'unimport', ale#Escape('unimport') . ' --gitignore --check %t' + +Execute(The unimport command should switch directories to the detected project root): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'unimport', ale#Escape('unimport') . ' --check %t' + +Execute(The unimport callbacks should detect virtualenv directories and switch to the project root): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/unimport' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --check %t' + +Execute(You should able able to use the global unimport instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_unimport_use_global = 1 + + AssertLinter 'unimport', ale#Escape('unimport') . ' --check %t' + +Execute(Setting executable to 'pipenv' appends 'run unimport'): + let g:ale_python_unimport_executable = 'path/to/pipenv' + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'path/to/pipenv', ale#Escape('path/to/pipenv') . ' run unimport --check %t' + +Execute(Pipenv is detected when python_unimport_auto_pipenv is set): + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + let g:ale_python_unimport_auto_pipenv = 1 + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'pipenv', ale#Escape('pipenv') . ' run unimport --check %t' + +Execute(Setting executable to 'poetry' appends 'run unimport'): + let g:ale_python_unimport_executable = 'path/to/poetry' + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'path/to/poetry', ale#Escape('path/to/poetry') . ' run unimport --check %t' + +Execute(Poetry is detected when python_unimport_auto_poetry is set): + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + let g:ale_python_unimport_auto_poetry = 1 + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'poetry', ale#Escape('poetry') . ' run unimport --check %t' + +Execute(uv is detected when python_unimport_auto_uv is set): + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + let g:ale_python_unimport_auto_uv = 1 + + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'uv', ale#Escape('uv') . ' run unimport --check %t' diff --git a/test/linter/test_v_command_callback.vader b/test/linter/test_v_command_callback.vader new file mode 100644 index 00000000..7bffa958 --- /dev/null +++ b/test/linter/test_v_command_callback.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('v', 'v') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default v command should be correct): + AssertLinter 'v', ale#Escape('v') . ' . -o /tmp/vim-ale-v' + +Execute(The v executable and options should be configurable): + let g:ale_v_v_executable = 'foobar' + let g:ale_v_v_options = '--foo-bar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --foo-bar . -o /tmp/vim-ale-v' diff --git a/test/linter/test_vcom.vader b/test/linter/test_vcom.vader new file mode 100644 index 00000000..77218f74 --- /dev/null +++ b/test/linter/test_vcom.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/linter/test_verilator.vader b/test/linter/test_verilator.vader new file mode 100644 index 00000000..b65f3459 --- /dev/null +++ b/test/linter/test_verilator.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('verilog', 'verilator') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default verilator command should be correct): + AssertLinter 'verilator', 'verilator --lint-only -Wall -Wno-DECLFILENAME -I%s:h %t' + +Execute(verilator options should be configurable): + " Additional args for the linter + let g:ale_verilog_verilator_options = '-sv --default-language "1800-2012"' + + AssertLinter 'verilator', 'verilator --lint-only -Wall -Wno-DECLFILENAME -I%s:h -sv --default-language "1800-2012" %t' diff --git a/test/linter/test_vim_vimls.vader b/test/linter/test_vim_vimls.vader new file mode 100644 index 00000000..eb9239a2 --- /dev/null +++ b/test/linter/test_vim_vimls.vader @@ -0,0 +1,77 @@ +" Author: Jeffrey Lau https://github.com/zoonfafer +" Description: Tests for the Vim vimls linter + +Before: + call ale#assert#SetUpLinterTest('vim', 'vimls') + +After: + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'vim-language-server', ale#Escape('vim-language-server') . ' --stdio' + +Execute(should set correct LSP values): + call ale#test#SetFilename('../test-files/vim/path_with_autoload/test.vim') + AssertLSPLanguage 'vim' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/vim/path_with_autoload') + +Execute(should set correct project for .git/): + let b:parent_dir = ale#path#Simplify(g:dir . '/..') + let b:git_dir = b:parent_dir . '/.git' + + call ale#test#SetFilename('../test-files/vim/test.vim') + + if !isdirectory(b:git_dir) + call mkdir(b:git_dir) + endif + + AssertLSPProject ale#path#Simplify(b:parent_dir) + + call delete(b:git_dir, 'd') + unlet! b:git_dir + +Execute(should set correct project for plugin/): + call ale#test#SetFilename('../test-files/vim/path_with_plugin/test.vim') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/vim/path_with_plugin') + +Execute(should accept configuration settings): + AssertLSPConfig {} + + let b:ale_vim_vimls_config = {'vim': {'foobar': v:true}} + AssertLSPConfig {'vim': {'foobar': v:true}} + +Execute(should set correct project for .vimrc): + call ale#test#SetFilename('../test-files/vim/path_with_vimrc/.vimrc') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/vim/path_with_vimrc') + +Execute(should set correct project for init.vim): + call ale#test#SetFilename('../test-files/vim/path_with_initvim/init.vim') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/vim/path_with_initvim') + +Execute(should use the local executable when available): + call ale#test#SetFilename('../test-files/vim/file.vim') + + AssertLinter ale#path#Simplify(g:dir . '/../test-files/vim/node_modules/.bin/vim-language-server'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/vim/node_modules/.bin/vim-language-server')) . ' --stdio' + +Execute(should let the global executable to be used): + let g:ale_vim_vimls_use_global = 1 + call ale#test#SetFilename('../test-files/vim/file.vim') + + AssertLinter 'vim-language-server', + \ ale#Escape('vim-language-server') . ' --stdio' + +Execute(should allow the executable to be configured): + let g:ale_vim_vimls_executable = 'foobar' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdio' diff --git a/test/linter/test_vint.vader b/test/linter/test_vint.vader new file mode 100644 index 00000000..4a224d01 --- /dev/null +++ b/test/linter/test_vint.vader @@ -0,0 +1,34 @@ +Before: + call ale#assert#SetUpLinterTest('vim', 'vint') + let b:common_flags = (has('nvim') ? ' --enable-neovim' : '') + \ . ' -f "{file_path}:{line_number}:{column_number}: {severity}: {policy_name} - {description} (see {reference})"' + +After: + unlet! b:common_flags + + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'vint', [ + \ ale#Escape('vint') .' --version', + \ ale#Escape('vint') .' -s --no-color' . b:common_flags . ' %t', + \] + +Execute(The executable should be configurable): + let g:ale_vim_vint_executable = 'foobar' + + AssertLinter 'foobar', [ + \ ale#Escape('foobar') .' --version', + \ ale#Escape('foobar') .' -s --no-color' . b:common_flags . ' %t', + \] + +Execute(The --no-color flag should not be used for older Vint versions): + GivenCommandOutput ['v0.3.5'] + + AssertLinter 'vint', ale#Escape('vint') .' -s' . b:common_flags . ' %t' + +Execute(--stdin-display-name should be used in newer versions): + GivenCommandOutput ['v0.4.0'] + + AssertLinter 'vint', ale#Escape('vint') .' -s --no-color' . b:common_flags + \ . ' --stdin-display-name %s -' diff --git a/test/linter/test_vlog.vader b/test/linter/test_vlog.vader new file mode 100644 index 00000000..a07944f7 --- /dev/null +++ b/test/linter/test_vlog.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/linter/test_volar.vader b/test/linter/test_volar.vader new file mode 100644 index 00000000..d6dc8737 --- /dev/null +++ b/test/linter/test_volar.vader @@ -0,0 +1,27 @@ +Before: + call ale#assert#SetUpLinterTest('vue', 'volar') + + let g:tsserver_path = '' + let g:actual_path = '' + let g:init_opts = {} + +After: + call ale#assert#TearDownLinterTest() + + unlet g:tsserver_path + unlet g:actual_path + unlet g:init_opts + +Execute(Assert Volar LSP for Vue Project): + call ale#test#SetFilename('../test-files/volar/src/App.vue') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/volar') + +Execute(Assert proper tsserverlibrary for Volar LSP): + call ale#test#SetFilename('../test-files/volar/src/App.vue') + + let g:init_opts = ale_linters#vue#volar#GetInitializationOptions(bufnr('')) + let g:tsserver_path = init_opts.typescript.tsdk + let g:actual_path = ale#path#Simplify(g:dir . '/../test-files/volar/node_modules/typescript/lib/') + + AssertEqual g:tsserver_path, g:actual_path diff --git a/test/linter/test_vulture.vader b/test/linter/test_vulture.vader new file mode 100644 index 00000000..abc8514b --- /dev/null +++ b/test/linter/test_vulture.vader @@ -0,0 +1,85 @@ +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): + AssertLinterCwd expand('#' . bufnr('') . ':p:h') + AssertLinter 'vulture', ale#Escape('vulture') . ' .' + +Execute(The vulture command callback should lint project root, when present): + call ale#test#SetFilename('../test-files/python/no_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir') + AssertLinter 'vulture', ale#Escape('vulture') . ' .' + +Execute(The option for disabling change directory works and only lints file): + let g:ale_python_vulture_change_directory = 0 + + AssertLinterCwd '' + 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#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#Escape('vulture') . ' --some-option .' + +Execute(The vulture command callback should detect virtualenv directories and switch to the project root): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/vulture' + \) + + AssertLinterCwd ale#path#Simplify(g:dir . '/../test-files/python/with_virtualenv/subdir') + AssertLinter b:executable, ale#Escape(b:executable) . ' .' + +Execute(You should able able to use the global vulture instead): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_vulture_use_global = 1 + + AssertLinter 'vulture', 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#Escape('path/to/pipenv') . ' run vulture' . ' .' + +Execute(Setting executable to 'poetry' appends 'run vulture'): + let g:ale_python_vulture_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', ale#Escape('path/to/poetry') . ' run vulture' . ' .' + +Execute(pipenv is detected when python_vulture_auto_pipenv is set): + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + let g:ale_python_vulture_auto_pipenv = 1 + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run vulture' . ' .' + + +Execute(poetry is detected when python_vulture_auto_poetry is set): + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + let g:ale_python_vulture_auto_poetry = 1 + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run vulture' . ' .' + +Execute(uv is detected when python_vulture_auto_uv is set): + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + let g:ale_python_vulture_auto_uv = 1 + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run vulture' . ' .' diff --git a/test/linter/test_write_good.vader b/test/linter/test_write_good.vader new file mode 100644 index 00000000..8958dd6a --- /dev/null +++ b/test/linter/test_write_good.vader @@ -0,0 +1,55 @@ +Before: + " This is just one example of a language using the linter. + call ale#assert#SetUpLinterTest('markdown', 'writegood') + + " The options are shared between many languages. + Save g:ale_writegood_options + Save g:ale_writegood_executable + Save g:ale_writegood_use_global + + unlet! g:ale_writegood_options + unlet! g:ale_writegood_executable + unlet! g:ale_writegood_use_global + + call ale#test#SetFilename('testfile.txt') + call ale#handlers#writegood#ResetOptions() + +After: + call ale#assert#TearDownLinterTest() + +Execute(The global executable should be used when the local one cannot be found): + AssertLinter + \ 'write-good', + \ ale#Escape('write-good') . ' %t', + +Execute(The options should be used in the command): + let g:ale_writegood_options = '--foo --bar' + + AssertLinter + \ 'write-good', + \ ale#Escape('write-good') . ' --foo --bar %t', + +Execute(Should use the node_modules/.bin executable, if available): + call ale#test#SetFilename('../test-files/write-good/node-modules/test.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/write-good/node-modules/node_modules/.bin/write-good'), + \ ale#Escape(ale#path#Simplify(g:dir . '/../test-files/write-good/node-modules/node_modules/.bin/write-good')) + \ . ' %t', + +Execute(Should use the node_modules/write-good executable, if available): + call ale#test#SetFilename('../test-files/write-good/node-modules-2/test.txt') + + AssertLinter + \ ale#path#Simplify(g:dir . '/../test-files/write-good/node-modules-2/node_modules/write-good/bin/write-good.js'), + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../test-files/write-good/node-modules-2/node_modules/write-good/bin/write-good.js')) + \ . ' %t', + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('../test-files/write-good/node-modules-2/test.txt') + + let g:ale_writegood_executable = 'foo-bar' + let g:ale_writegood_use_global = 1 + + AssertLinter 'foo-bar', ale#Escape('foo-bar') . ' %t' diff --git a/test/linter/test_xmllint.vader b/test/linter/test_xmllint.vader new file mode 100644 index 00000000..5a2377c2 --- /dev/null +++ b/test/linter/test_xmllint.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('xml', 'xmllint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The xml xmllint command callback should return the correct default string): + AssertLinter 'xmllint', ale#Escape('xmllint') . ' --noout -' + +Execute(The xml xmllint command callback should let you set options): + let g:ale_xml_xmllint_options = '--xinclude --postvalid' + + AssertLinter 'xmllint', + \ ale#Escape('xmllint') . ' --xinclude --postvalid --noout -' + +Execute(The xmllint executable should be configurable): + let g:ale_xml_xmllint_executable = '~/.local/bin/xmllint' + + AssertLinter '~/.local/bin/xmllint', + \ ale#Escape('~/.local/bin/xmllint') . ' --noout -' diff --git a/test/linter/test_xo.vader b/test/linter/test_xo.vader new file mode 100644 index 00000000..a1e28836 --- /dev/null +++ b/test/linter/test_xo.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('javascript', 'xo') + call ale#test#SetFilename('testfile.jsx') + unlet! b:executable + + set filetype=javascriptreact + runtime autoload/ale/handlers/xo.vim + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default xo command should be correct): + AssertLinter 'xo', ale#Escape('xo') . ' --reporter json --stdin --stdin-filename %s' + +Execute(The xo executable and command should be configurable): + let b:ale_javascript_xo_executable = 'foobar' + let b:ale_javascript_xo_options = '--wat' + + AssertLinter 'foobar', ale#Escape('foobar') + \ . ' --wat --reporter json --stdin --stdin-filename %s' diff --git a/test/linter/test_xots.vader b/test/linter/test_xots.vader new file mode 100644 index 00000000..cc38ff02 --- /dev/null +++ b/test/linter/test_xots.vader @@ -0,0 +1,23 @@ +Before: + call ale#assert#SetUpLinterTest('typescript', 'xo') + call ale#test#SetFilename('testfile.tsx') + unlet! b:executable + + set filetype=typescriptreact + runtime autoload/ale/handlers/xo.vim + +After: + call ale#assert#TearDownLinterTest() + +Execute(The XO executable should be called): + AssertLinter 'xo', ale#Escape('xo') . ' --reporter json --stdin --stdin-filename %s' + +Execute(The XO executable should be configurable): + let b:ale_typescript_xo_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --reporter json --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 json --stdin --stdin-filename %s' diff --git a/test/linter/test_xvhdl.vader b/test/linter/test_xvhdl.vader new file mode 100644 index 00000000..384e7e10 --- /dev/null +++ b/test/linter/test_xvhdl.vader @@ -0,0 +1,14 @@ +Before: + call ale#assert#SetUpLinterTest('vhdl', 'xvhdl') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default xvhdl command should be correct): + AssertLinter 'xvhdl', ale#Escape('xvhdl') . ' --2008 %t' + +Execute(The xvhdl executable and options should be configurable): + let b:ale_vhdl_xvhdl_executable = 'foobar' + let b:ale_vhdl_xvhdl_options = '--something' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --something %t' diff --git a/test/linter/test_xvlog.vader b/test/linter/test_xvlog.vader new file mode 100644 index 00000000..56b5c4c2 --- /dev/null +++ b/test/linter/test_xvlog.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('verilog', 'xvlog') + +After: + 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/linter/test_yaml_actionlint.vader b/test/linter/test_yaml_actionlint.vader new file mode 100644 index 00000000..db34007f --- /dev/null +++ b/test/linter/test_yaml_actionlint.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('yaml', 'actionlint') + call ale#test#SetFilename('/.github/file.yml') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Command should always have -no-color, -oneline and - options): + let g:ale_yaml_actionlint_options = '' + + AssertLinter 'actionlint', ale#Escape('actionlint') . ' -no-color -oneline - ' + +Execute(Options should be added to command): + let g:ale_yaml_actionlint_options = '-shellcheck= -pyflakes=' + + AssertLinter 'actionlint', + \ ale#Escape('actionlint') . ' -shellcheck= -pyflakes= -no-color -oneline - ' + +Execute(actionlint not run on files outside of /.github/ paths): + call ale#test#SetFilename('/something-else/file.yml') diff --git a/test/linter/test_yaml_ls.vader b/test/linter/test_yaml_ls.vader new file mode 100644 index 00000000..449ce8c3 --- /dev/null +++ b/test/linter/test_yaml_ls.vader @@ -0,0 +1,21 @@ +Before: + call ale#assert#SetUpLinterTest('yaml', 'ls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'yaml-language-server', ale#Escape('yaml-language-server') . ' --stdio' + +Execute(should set correct LSP values): + call ale#test#SetFilename('../test-files/yaml/test.yaml') + + AssertLSPLanguage 'yaml' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/yaml') + +Execute(should accept configuration settings): + AssertLSPConfig {} + let b:ale_yaml_ls_config = {'yaml': {'hover': v:false, 'completion': v:true}} + AssertLSPConfig {'yaml': {'hover': v:false, 'completion': v:true}} diff --git a/test/linter/test_yang_lsp.vader b/test/linter/test_yang_lsp.vader new file mode 100644 index 00000000..5be7501f --- /dev/null +++ b/test/linter/test_yang_lsp.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpLinterTest('yang', 'yang_lsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The executable should be configurable): + AssertLinter 'yang-language-server', ale#Escape('yang-language-server') + + let b:ale_yang_lsp_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') diff --git a/test/linter/test_yara_yls.vader b/test/linter/test_yara_yls.vader new file mode 100644 index 00000000..e3e3976d --- /dev/null +++ b/test/linter/test_yara_yls.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('yara', 'yls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'yls', ale#Escape('yls') . ' -v' diff --git a/test/linter/test_yq.vader b/test/linter/test_yq.vader new file mode 100644 index 00000000..e2e7dcd1 --- /dev/null +++ b/test/linter/test_yq.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('yaml', 'yq') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'yq', ale#Escape('yq') diff --git a/test/linter/test_zeek.vader b/test/linter/test_zeek.vader new file mode 100644 index 00000000..af58a414 --- /dev/null +++ b/test/linter/test_zeek.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('zeek', 'zeek') + + let b:command_tail = ' --parse-only %s' + +After: + call ale#assert#TearDownLinterTest() + + unlet! b:command_tail + +Execute(The default command should be correct): + AssertLinter 'zeek', ale#Escape('zeek') . b:command_tail + +Execute(The zeek executable should be configurable, and escaped properly): + let g:ale_zeek_zeek_executable = 'executable with spaces' + + AssertLinter 'executable with spaces', ale#Escape('executable with spaces') . b:command_tail diff --git a/test/linter/test_zig_zlint.vader b/test/linter/test_zig_zlint.vader new file mode 100644 index 00000000..d36e9af2 --- /dev/null +++ b/test/linter/test_zig_zlint.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('zig', 'zlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The zlint executable and command should be configured correctly): + AssertLinter 'zlint', ale#Escape('zlint') . ' %s -f gh' + + " Set a custom executable path + let g:ale_zig_zlint_executable = '/custom/path/to/zlint' + + AssertLinter + \ '/custom/path/to/zlint', + \ ale#Escape('/custom/path/to/zlint') . ' %s -f gh' diff --git a/test/linter/test_zig_zls.vader b/test/linter/test_zig_zls.vader new file mode 100644 index 00000000..6d814be4 --- /dev/null +++ b/test/linter/test_zig_zls.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('zig', 'zls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'zls', ale#Escape('zls') + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('../test-files/zig/main.zig') + + AssertLSPProject ale#path#Simplify(g:dir . '/../test-files/zig') diff --git a/test/lsp/test_closing_documents.vader b/test/lsp/test_closing_documents.vader new file mode 100644 index 00000000..29377768 --- /dev/null +++ b/test/lsp/test_closing_documents.vader @@ -0,0 +1,176 @@ +Before: + runtime autoload/ale/lsp.vim + + let g:message_list = [] + + function! MarkAllConnectionsInitialized() abort + for l:conn in values(ale#lsp#GetConnections()) + let l:conn.initialized = 1 + endfor + endfunction + + function! MarkDocumentOpened() abort + for l:conn in values(ale#lsp#GetConnections()) + let l:conn.open_documents[bufnr('')] = 1 + endfor + endfunction + + function! ale#lsp#Send(conn_id, message) abort + let l:connections = ale#lsp#GetConnections() + + if !l:connections[a:conn_id].initialized + throw 'LSP server not initialized yet!' + endif + + call add(g:message_list, [a:conn_id] + a:message) + endfunction + + call ale#lsp#ResetConnections() + +After: + unlet! g:message_list + delfunction MarkAllConnectionsInitialized + delfunction MarkDocumentOpened + + call ale#lsp#ResetConnections() + + runtime autoload/ale/lsp.vim + +Execute(No errors should be thrown if the connection is not initialized): + call ale#lsp#Register('command', '/foo', '', {}) + call MarkDocumentOpened() + + call ale#engine#Cleanup(bufnr('')) + AssertEqual [], g:message_list + +Execute(No messages should be sent if the document wasn't opened): + call ale#lsp#Register('command', '/foo', '', {}) + call MarkAllConnectionsInitialized() + + call ale#engine#Cleanup(bufnr('')) + AssertEqual [], g:message_list + +Execute(A message should be sent if the document was opened): + call ale#lsp#Register('command', '/foo', 'lang', {}) + call MarkAllConnectionsInitialized() + + call ale#lsp#OpenDocument('command:/foo', bufnr('')) + call ale#engine#Cleanup(bufnr('')) + " We should only send the message once. + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ }, + \ }], + \ ], + \ g:message_list + +Execute(A message should be sent if the document was opened for tsserver): + call ale#lsp#Register('command', '/foo', 'lang', {}) + call ale#lsp#MarkConnectionAsTsserver('command:/foo') + + call ale#lsp#OpenDocument('command:/foo', bufnr('')) + call ale#engine#Cleanup(bufnr('')) + " We should only send the message once. + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'ts@open', {'file': expand('%:p')}], + \ ['command:/foo', 1, 'ts@close', {'file': expand('%:p')}], + \ ], + \ g:message_list + +Execute(Re-opening and closing the documents should work): + call ale#lsp#Register('command', '/foo', 'lang', {}) + call MarkAllConnectionsInitialized() + + call ale#lsp#OpenDocument('command:/foo', bufnr('')) + call ale#engine#Cleanup(bufnr('')) + call ale#lsp#OpenDocument('command:/foo', bufnr('')) + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 2, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ }, + \ }], + \ ], + \ g:message_list + +Execute(Messages for closing documents should be sent to each server): + call ale#lsp#Register('command', '/foo', 'lang', {}) + call ale#lsp#Register('command', '/bar', 'lang', {}) + call MarkAllConnectionsInitialized() + + call ale#lsp#OpenDocument('command:/foo', bufnr('')) + call ale#lsp#OpenDocument('command:/bar', bufnr('')) + call ale#engine#Cleanup(bufnr('')) + " We should only send the message once. + call ale#engine#Cleanup(bufnr('')) + + AssertEqual + \ [ + \ ['command:/foo', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 2, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/bar', 1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ 'languageId': 'lang', + \ 'text': "\n", + \ }, + \ }], + \ ['command:/bar', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ }, + \ }], + \ ['command:/foo', 1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ }, + \ }], + \ ], + \ g:message_list diff --git a/test/lsp/test_did_save_event.vader b/test/lsp/test_did_save_event.vader new file mode 100644 index 00000000..13963451 --- /dev/null +++ b/test/lsp/test_did_save_event.vader @@ -0,0 +1,147 @@ +Before: + Save g:ale_lint_on_save + Save g:ale_enabled + Save g:ale_linters + Save g:ale_run_synchronously + Save g:ale_disable_lsp + + call ale#test#SetDirectory('/testplugin/test/completion') + call ale#test#SetFilename('dummy.txt') + + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + + let g:ale_disable_lsp = 0 + unlet! b:ale_disable_lsp + let g:ale_lint_on_save = 1 + let b:ale_enabled = 1 + let g:ale_lsp_next_message_id = 1 + let g:ale_run_synchronously = 1 + let g:conn_id = v:null + let g:message_list = [] + + function! LanguageCallback() abort + return 'foobar' + endfunction + + function! ProjectRootCallback() abort + return expand('.') + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'dummy_linter', + \ 'lsp': 'stdio', + \ 'command': 'cat - > /dev/null', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'language': function('LanguageCallback'), + \ 'project_root': function('ProjectRootCallback'), + \ }) + let g:ale_linters = {'foobar': ['dummy_linter']} + + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let g:conn_id = ale#lsp#Register('executable', '/foo/bar', 'foobar', {}) + call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) + let l:details = { + \ 'command': 'foobar', + \ 'buffer': a:buffer, + \ 'connection_id': g:conn_id, + \ 'project_root': '/foo/bar', + \} + + call a:Callback(a:linter, l:details) + + return 1 + endfunction + + " Replace the Send function for LSP, so we can monitor calls to it. + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + endfunction + +After: + Restore + + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + unlet! b:ale_enabled + unlet! b:ale_linters + unlet! g:message_list + unlet! b:ale_save_event_fired + + delfunction LanguageCallback + delfunction ProjectRootCallback + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + " Stop any timers we left behind. + " This stops the tests from failing randomly. + call ale#completion#StopTimer() + + runtime autoload/ale/completion.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + +Given foobar (Some imaginary filetype): + + +Execute(Server should be notified on save): + call ale#events#SaveEvent(bufnr('')) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}], + \ }], + \ ], + \ g:message_list + +Execute(Server should be notified on save with didSave is supported by server): + + " Replace has capability function to simulate didSave server capability + function! ale#lsp#HasCapability(conn_id, capability) abort + if a:capability == 'did_save' + return 1 + endif + return 0 + endfunction + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}], + \ }], + \ [1, 'textDocument/didSave', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ }, + \ }], + \ ], + \ g:message_list + +Execute(Server should be notified on change): + call ale#events#FileChangedEvent(bufnr('')) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}], + \ }], + \ ], + \ g:message_list diff --git a/test/lsp/test_engine_lsp_response_handling.vader b/test/lsp/test_engine_lsp_response_handling.vader new file mode 100644 index 00000000..7b18befc --- /dev/null +++ b/test/lsp/test_engine_lsp_response_handling.vader @@ -0,0 +1,552 @@ +Before: + Save g:ale_set_lists_synchronously + Save g:ale_buffer_info + Save g:ale_lsp_error_messages + Save g:ale_set_loclist + Save g:ale_set_signs + Save g:ale_set_quickfix + Save g:ale_set_highlights + Save g:ale_echo_cursor + Save g:ale_disable_lsp + Save g:ale_history_enabled + Save g:ale_history_log_output + + let g:ale_disable_lsp = 0 + let g:ale_set_lists_synchronously = 1 + let g:ale_buffer_info = {} + let g:ale_set_loclist = 1 + " Disable features we don't need for these tests. + let g:ale_set_signs = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + let g:ale_history_enabled = 1 + let g:ale_history_log_output = 1 + + unlet! g:ale_lsp_error_messages + unlet! b:ale_linters + unlet! b:ale_disable_lsp + + call ale#linter#Reset() + call ale#test#SetDirectory('/testplugin/test') + call setloclist(0, []) + +After: + Restore + + unlet! b:ale_linters + + call setloclist(0, []) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + call ale#lsp_linter#ClearLSPData() + +Given foobar(An empty file): +Execute(tsserver syntax error responses should be handled correctly): + runtime ale_linters/typescript/tsserver.vim + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.ts') + endif + + call ale#engine#InitBufferInfo(bufnr('')) + + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t') + endif + + " When we get syntax errors and no semantic errors, we should keep the + " syntax errors. + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': expand('%:p'), + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': ''','' expected.', + \ "code":1005 + \ }, + \ ], + \ }, + \}) + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': expand('%:p'), + \ 'diagnostics':[ + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 14, + \ 'vcol': 0, + \ 'nr': 1005, + \ 'type': 'E', + \ 'text': '1005: '','' expected.', + \ 'valid': 1, + \ 'pattern': '', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + + " After we get empty syntax errors, we should clear them. + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': expand('%:p'), + \ 'diagnostics':[ + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + + " Syntax errors on the project root should not populate the LocList. + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir, + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': ''','' expected.', + \ "code":1005 + \ }, + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +Execute(tsserver semantic error responses should be handled correctly): + runtime ale_linters/typescript/tsserver.vim + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.ts') + endif + + call ale#engine#InitBufferInfo(bufnr('')) + + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t') + endif + + " When we get syntax errors and no semantic errors, we should keep the + " syntax errors. + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': expand('%:p'), + \ 'diagnostics':[ + \ ], + \ }, + \}) + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': expand('%:p'), + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': 'Some semantic error', + \ "code":1005 + \ }, + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 14, + \ 'vcol': 0, + \ 'nr': 1005, + \ 'type': 'E', + \ 'text': '1005: Some semantic error', + \ 'valid': 1, + \ 'pattern': '', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + + " After we get empty syntax errors, we should clear them. + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': expand('%:p'), + \ 'diagnostics':[ + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + + " Semantic errors on the project root should not populate the LocList. + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': g:dir, + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': 'Some semantic error', + \ "code":1005 + \ }, + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +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 diagnostics responses should be handled correctly): + let b:ale_linters = ['eclipselsp'] + runtime ale_linters/java/eclipselsp.vim + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.java') + endif + + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}}) + + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t') + endif + + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'jsonrpc':'2.0', + \ 'method':'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'diagnostics': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 0, + \ 'character':0 + \ }, + \ 'end': { + \ 'line': 0, + \ 'character':0 + \ } + \ }, + \ 'severity': 2, + \ 'code': "", + \ 'source': 'Java', + \ 'message': 'Missing JRE 1-8' + \ } + \ ] + \ } + \}) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'pattern': '', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'Missing JRE 1-8' + \ } + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +Execute(LSP diagnostics responses on project root should not populate loclist): + let b:ale_linters = ['eclipselsp'] + runtime ale_linters/java/eclipselsp.vim + call ale#test#SetFilename('filename.java') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}}) + + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'jsonrpc':'2.0', + \ 'method':'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri':'file://' . g:dir, + \ 'diagnostics': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 0, + \ 'character':0 + \ }, + \ 'end': { + \ 'line': 0, + \ 'character':0 + \ } + \ }, + \ 'severity': 2, + \ 'code': "", + \ 'source': 'Java', + \ 'message': 'Missing JRE 1-8' + \ } + \ ] + \ } + \}) + + AssertEqual + \ [ + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +Execute(LSP errors should mark linters no longer active): + let b:ale_linters = ['pylsp'] + runtime ale_linters/python/pylsp.vim + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'pylsp', 'aliases': [], 'lsp': 'stdio'}}) + + 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#ToFileURI(g:dir . '/filename.py'), + \ 'diagnostics': [], + \ }, + \}) + + AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list + +Execute(LSP pull model diagnostic responses should be handled): + let b:ale_linters = ['eclipselsp'] + runtime ale_linters/java/eclipselsp.vim + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.java') + endif + + call ale#engine#InitBufferInfo(bufnr('')) + let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('eclipselsp') + call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}}) + call ale#lsp_linter#SetDiagnosticURIMap({'347': ale#util#ToURI(expand('%:p'))}) + + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t') + endif + + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'jsonrpc':'2.0', + \ 'id': 347, + \ 'result': { + \ 'kind': 'full', + \ 'items': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 0, + \ 'character':0 + \ }, + \ 'end': { + \ 'line': 0, + \ 'character':0 + \ } + \ }, + \ 'severity': 2, + \ 'code': "", + \ 'source': 'Java', + \ 'message': 'Missing JRE 1-8' + \ } + \ ] + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'pattern': '', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'Missing JRE 1-8' + \ } + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list + +Execute(LSP pull model diagnostic responses that are 'unchanged' should be handled): + let b:ale_linters = ['eclipselsp'] + runtime ale_linters/java/eclipselsp.vim + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.java') + endif + + call ale#engine#InitBufferInfo(bufnr('')) + let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('eclipselsp') + let g:ale_buffer_info[bufnr('')].loclist = [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'pattern': '', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'Missing JRE 1-8' + \ }, + \] + + call ale#lsp_linter#SetLSPLinterMap({'1': {'name': 'eclipselsp', 'aliases': [], 'lsp': 'stdio'}}) + call ale#lsp_linter#SetDiagnosticURIMap({'347': ale#util#ToURI(expand('%:p'))}) + + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t') + endif + + call ale#lsp_linter#HandleLSPResponse(1, { + \ 'jsonrpc':'2.0', + \ 'id': 347, + \ 'result': { + \ 'kind': 'unchanged', + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'pattern': '', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'Missing JRE 1-8' + \ } + \ ], + \ g:ale_buffer_info[bufnr('')].loclist + AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list + +Execute(LSP errors should be logged in the history): + call ale#lsp_linter#SetLSPLinterMap({'347': {'name': 'foobar', 'aliases': [], 'lsp': 'stdio'}}) + call ale#lsp_linter#HandleLSPResponse(347, { + \ 'jsonrpc': '2.0', + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': ['123', '456'], + \ }, + \ }, + \}) + + AssertEqual + \ {'foobar': ["xyz\n123\n456"]}, + \ get(g:, 'ale_lsp_error_messages', {}) diff --git a/test/lsp/test_handling_window_requests.vader b/test/lsp/test_handling_window_requests.vader new file mode 100644 index 00000000..5639dbc2 --- /dev/null +++ b/test/lsp/test_handling_window_requests.vader @@ -0,0 +1,91 @@ +Before: + let g:expr_list = [] + let g:linter_name = 'some_linter' + let g:format = '%severity%:%linter%: %s' + " Get the default value to restore it + let g:default_severity = g:ale_lsp_show_message_severity + let g:ale_lsp_show_message_severity = 'information' + + function! ale#util#ShowMessage(expr) abort + call add(g:expr_list, a:expr) + endfunction + +After: + unlet! g:expr_list + unlet! g:linter_name + unlet! g:format + let g:ale_lsp_show_message_severity = g:default_severity + unlet! g:default_severity + +Execute(ale#lsp_window#HandleShowMessage() should only show errors when severity is set to "error"): + let g:ale_lsp_show_message_severity = 'error' + " We should escape the quotes from this message. + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an ''error'''}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual ['Error:some_linter: an ''error'''], g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should only show errors and warnings when severity is set to "warning"): + let g:ale_lsp_show_message_severity = 'warning' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual ['Error:some_linter: an error', 'Warning:some_linter: a warning'], g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should only show errors, warnings and infos when severity is set to "information"): + let g:ale_lsp_show_message_severity = 'information' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning', + \ 'Info:some_linter: an info'], + \ g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should only show errors, warnings and infos when severity is set to "info"): + let g:ale_lsp_show_message_severity = 'info' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning', + \ 'Info:some_linter: an info'], + \ g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should show all messages is severity is set to "log"): + let g:ale_lsp_show_message_severity = 'log' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning', + \ 'Info:some_linter: an info', + \ 'Log:some_linter: a log'], + \ g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should not show anything if severity is configured as disabled): + let g:ale_lsp_show_message_severity = 'disabled' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [], g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should use "warning" when severity is set to an invalid value): + let g:ale_lsp_show_message_severity = 'foo' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning'], + \ g:expr_list diff --git a/test/lsp/test_lsp_address_split.vader b/test/lsp/test_lsp_address_split.vader new file mode 100644 index 00000000..adc1735a --- /dev/null +++ b/test/lsp/test_lsp_address_split.vader @@ -0,0 +1,9 @@ +Execute(Address splitting should function as intended): + AssertEqual ['foo', v:null], ale#lsp#SplitAddress('foo') + AssertEqual ['foo:', v:null], ale#lsp#SplitAddress('foo:') + AssertEqual ['foo', v:null], ale#lsp#SplitAddress('foo:0') + AssertEqual ['foo', 123], ale#lsp#SplitAddress('foo:123') + AssertEqual ['protocol:/foo:', v:null], ale#lsp#SplitAddress('protocol:/foo:') + AssertEqual ['protocol:/foo', v:null], ale#lsp#SplitAddress('protocol:/foo:0') + AssertEqual ['protocol:/foo', 123], ale#lsp#SplitAddress('protocol:/foo:123') + AssertEqual ['protocol:foo', v:null], ale#lsp#SplitAddress('protocol:foo') diff --git a/test/lsp/test_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader new file mode 100644 index 00000000..8a71dd05 --- /dev/null +++ b/test/lsp/test_lsp_client_messages.vader @@ -0,0 +1,405 @@ +Before: + let g:ale_lsp_next_version_id = 1 + + call ale#test#SetDirectory('/testplugin/test/lsp') + call ale#test#SetFilename('foo/bar.ts') + +After: + call ale#test#RestoreDirectory() + +Execute(ale#lsp#message#Initialize() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'initialize', + \ { + \ 'processId': getpid(), + \ 'rootPath': '/foo/bar', + \ 'capabilities': {}, + \ 'initializationOptions': {'foo': 'bar'}, + \ 'rootUri': 'file:///foo/bar', + \ } + \ ], + \ ale#lsp#message#Initialize('/foo/bar', {'foo': 'bar'}, {}) + +Execute(ale#lsp#message#Initialized() should return correct messages): + AssertEqual [1, 'initialized', {}], ale#lsp#message#Initialized() + +Execute(ale#lsp#message#Shutdown() should return correct messages): + AssertEqual [0, 'shutdown'], ale#lsp#message#Shutdown() + +Execute(ale#lsp#message#Exit() should return correct messages): + AssertEqual [1, 'exit'], ale#lsp#message#Exit(), + +Given typescript(A TypeScript file with 3 lines): + Foo() + Bar() + Baz() + +Execute(ale#util#GetBufferContents() should return correctly formatted newlines): + AssertEqual "Foo()\nBar()\nBaz()\n", ale#util#GetBufferContents(bufnr('')) + +Execute(ale#lsp#message#DidOpen() should return correct messages): + let g:ale_lsp_next_version_id = 12 + AssertEqual + \ [ + \ 1, + \ 'textDocument/didOpen', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ 'languageId': 'typescript', + \ 'version': 12, + \ 'text': "Foo()\nBar()\nBaz()\n", + \ }, + \ } + \ ], + \ ale#lsp#message#DidOpen(bufnr(''), 'typescript') + +Execute(ale#lsp#message#DidChange() should return correct messages): + let g:ale_lsp_next_version_id = 34 + + AssertEqual + \ [ + \ 1, + \ 'textDocument/didChange', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ 'version': 34, + \ }, + \ 'contentChanges': [{'text': "Foo()\nBar()\nBaz()\n"}], + \ } + \ ], + \ ale#lsp#message#DidChange(bufnr('')) + " The version numbers should increment. + AssertEqual + \ 35, + \ ale#lsp#message#DidChange(bufnr(''))[2].textDocument.version + AssertEqual + \ 36, + \ ale#lsp#message#DidChange(bufnr(''))[2].textDocument.version + +Execute(ale#lsp#message#DidSave() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'textDocument/didSave', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ } + \ ], + \ ale#lsp#message#DidSave(bufnr(''), v:false) + +Execute(ale#lsp#message#DidSave() should return correct message with includeText capability): + AssertEqual + \ [ + \ 1, + \ 'textDocument/didSave', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ 'version': 1, + \ }, + \ 'text': ale#util#GetBufferContents(bufnr('')), + \ } + \ ], + \ ale#lsp#message#DidSave(bufnr(''), v:true) + +Execute(ale#lsp#message#DidClose() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'textDocument/didClose', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ } + \ ], + \ ale#lsp#message#DidClose(bufnr('')) + +Execute(ale#lsp#message#Completion() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/completion', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ } + \ ], + \ ale#lsp#message#Completion(bufnr(''), 12, 34, '') + +Execute(ale#lsp#message#Completion() should return correct messages with a trigger charaacter): + AssertEqual + \ [ + \ 0, + \ 'textDocument/completion', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ 'context': {'triggerKind': 2, 'triggerCharacter': '.'}, + \ } + \ ], + \ ale#lsp#message#Completion(bufnr(''), 12, 34, '.') + \ +Execute(ale#lsp#message#Definition() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/definition', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ '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#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ } + \ ], + \ ale#lsp#message#TypeDefinition(bufnr(''), 12, 34) + +Execute(ale#lsp#message#Implementation() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/implementation', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ } + \ ], + \ ale#lsp#message#Implementation(bufnr(''), 12, 34) + +Execute(ale#lsp#message#References() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/references', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ 'context': {'includeDeclaration': v:false}, + \ } + \ ], + \ ale#lsp#message#References(bufnr(''), 12, 34) + +Execute(ale#lsp#message#Symbol() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'workspace/symbol', + \ { + \ 'query': 'foobar', + \ } + \ ], + \ ale#lsp#message#Symbol('foobar') + +Execute(ale#lsp#message#Hover() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/hover', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 33}, + \ } + \ ], + \ ale#lsp#message#Hover(bufnr(''), 12, 34) + +Execute(ale#lsp#message#DidChangeConfiguration() should return correct messages): + let g:ale_lsp_configuration = { + \ 'foo': 'bar' + \ } + AssertEqual + \ [ + \ 1, + \ 'workspace/didChangeConfiguration', + \ { + \ 'settings': { + \ 'foo': 'bar', + \ } + \ } + \ ], + \ ale#lsp#message#DidChangeConfiguration(bufnr(''), g:ale_lsp_configuration) + +Execute(ale#lsp#message#Diagnostic() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'textDocument/diagnostic', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'), + \ }, + \ } + \ ], + \ ale#lsp#message#Diagnostic(bufnr('')) + +Execute(ale#lsp#tsserver_message#Open() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'ts@open', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ } + \ ], + \ ale#lsp#tsserver_message#Open(bufnr('')) + +Execute(ale#lsp#tsserver_message#Close() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'ts@close', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ } + \ ], + \ ale#lsp#tsserver_message#Close(bufnr('')) + +Execute(ale#lsp#tsserver_message#Change() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'ts@change', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 1, + \ 'offset': 1, + \ 'endLine': 1073741824, + \ 'endOffset': 1, + \ 'insertString': "Foo()\nBar()\nBaz()\n", + \ } + \ ], + \ ale#lsp#tsserver_message#Change(bufnr('')) + +Execute(ale#lsp#tsserver_message#Geterr() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'ts@geterr', + \ { + \ 'files': [ale#path#Simplify(g:dir . '/foo/bar.ts')], + \ } + \ ], + \ ale#lsp#tsserver_message#Geterr(bufnr('')) + +Execute(ale#lsp#tsserver_message#Completions() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@completions', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ 'prefix': 'abc', + \ 'includeExternalModuleExports': 1, + \ } + \ ], + \ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12, 'abc', 1) + +Execute(ale#lsp#tsserver_message#CompletionEntryDetails() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@completionEntryDetails', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ 'entryNames': ['foo', 'bar'], + \ } + \ ], + \ ale#lsp#tsserver_message#CompletionEntryDetails(bufnr(''), 347, 12, ['foo', 'bar']) + +Execute(ale#lsp#tsserver_message#Definition() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@definition', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ } + \ ], + \ ale#lsp#tsserver_message#Definition(bufnr(''), 347, 12) + +Execute(ale#lsp#tsserver_message#TypeDefinition() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@typeDefinition', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ } + \ ], + \ ale#lsp#tsserver_message#TypeDefinition(bufnr(''), 347, 12) + +Execute(ale#lsp#tsserver_message#Implementation() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@implementation', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ } + \ ], + \ ale#lsp#tsserver_message#Implementation(bufnr(''), 347, 12) + +Execute(ale#lsp#tsserver_message#References() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@references', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ } + \ ], + \ ale#lsp#tsserver_message#References(bufnr(''), 347, 12) + +Execute(ale#lsp#tsserver_message#Quickinfo() should return correct messages): + AssertEqual + \ [ + \ 0, + \ 'ts@quickinfo', + \ { + \ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'), + \ 'line': 347, + \ 'offset': 12, + \ } + \ ], + \ ale#lsp#tsserver_message#Quickinfo(bufnr(''), 347, 12) diff --git a/test/lsp/test_lsp_command_formatting.vader b/test/lsp/test_lsp_command_formatting.vader new file mode 100644 index 00000000..e99e1dad --- /dev/null +++ b/test/lsp/test_lsp_command_formatting.vader @@ -0,0 +1,44 @@ +Before: + Save g:ale_command_wrapper + + runtime autoload/ale/lsp.vim + + let g:ale_command_wrapper = '' + + let g:args = [] + + " Mock the StartProgram function so we can just capture the arguments. + function! ale#lsp#StartProgram(...) abort + let g:args = a:000[1:] + endfunction + +After: + Restore + + unlet! g:args + + runtime autoload/ale/lsp.vim + +Execute(Command formatting should be applied correctly for LSP linters): + call ale#lsp_linter#StartLSP( + \ bufnr(''), + \ { + \ 'name': 'linter', + \ 'language': {-> 'x'}, + \ 'project_root': {-> '/foo/bar'}, + \ 'lsp': 'stdio', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': '%e --foo', + \ }, + \ {-> 0} + \) + + if has('win32') + AssertEqual + \ ['cmd', 'cmd /s/c "cmd --foo"'], + \ g:args + else + AssertEqual + \ ['true', [&shell, '-c', '''true'' --foo']], + \ g:args + endif diff --git a/test/lsp/test_lsp_connections.vader b/test/lsp/test_lsp_connections.vader new file mode 100644 index 00000000..1c2fceab --- /dev/null +++ b/test/lsp/test_lsp_connections.vader @@ -0,0 +1,227 @@ +Before: + let g:ale_lsp_next_message_id = 1 + +After: + if exists('b:conn') && has_key(b:conn, 'id') + call ale#lsp#RemoveConnectionWithID(b:conn.id) + endif + + unlet! b:data + unlet! b:conn + +Execute(GetNextMessageID() should increment appropriately): + " We should get the initial ID, and increment a bit. + AssertEqual 1, ale#lsp#GetNextMessageID() + AssertEqual 2, ale#lsp#GetNextMessageID() + AssertEqual 3, ale#lsp#GetNextMessageID() + + " Set the maximum ID. + let g:ale_lsp_next_message_id = 9223372036854775807 + + " When we hit the maximum ID, the next ID afterwards should be 1. + AssertEqual 9223372036854775807, ale#lsp#GetNextMessageID() + AssertEqual 1, ale#lsp#GetNextMessageID() + +Execute(ale#lsp#CreateMessageData() should create an appropriate message): + " NeoVim outputs JSON with spaces, so the output is a little different. + if has('nvim') + " 79 is the size in bytes for UTF-8, not the number of characters. + AssertEqual + \ [ + \ 1, + \ "Content-Length: 79\r\n\r\n" + \ . '{"method": "someMethod", "jsonrpc": "2.0", "id": 1, "params": {"foo": "barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + " Check again to ensure that we use the next ID. + AssertEqual + \ [ + \ 2, + \ "Content-Length: 79\r\n\r\n" + \ . '{"method": "someMethod", "jsonrpc": "2.0", "id": 2, "params": {"foo": "barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + else + AssertEqual + \ [ + \ 1, + \ "Content-Length: 71\r\n\r\n" + \ . '{"method":"someMethod","jsonrpc":"2.0","id":1,"params":{"foo":"barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + AssertEqual + \ [ + \ 2, + \ "Content-Length: 71\r\n\r\n" + \ . '{"method":"someMethod","jsonrpc":"2.0","id":2,"params":{"foo":"barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + endif + +Execute(ale#lsp#CreateMessageData() should create messages without params): + if has('nvim') + AssertEqual + \ [ + \ 1, + \ "Content-Length: 56\r\n\r\n" + \ . '{"method": "someOtherMethod", "jsonrpc": "2.0", "id": 1}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someOtherMethod']) + else + AssertEqual + \ [ + \ 1, + \ "Content-Length: 51\r\n\r\n" + \ . '{"method":"someOtherMethod","jsonrpc":"2.0","id":1}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someOtherMethod']) + endif + +Execute(ale#lsp#CreateMessageData() should create notifications): + if has('nvim') + AssertEqual + \ [ + \ 0, + \ "Content-Length: 48\r\n\r\n" + \ . '{"method": "someNotification", "jsonrpc": "2.0"}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification']) + AssertEqual + \ [ + \ 0, + \ "Content-Length: 74\r\n\r\n" + \ . '{"method": "someNotification", "jsonrpc": "2.0", "params": {"foo": "bar"}}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}]) + else + AssertEqual + \ [ + \ 0, + \ "Content-Length: 45\r\n\r\n" + \ . '{"method":"someNotification","jsonrpc":"2.0"}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification']) + AssertEqual + \ [ + \ 0, + \ "Content-Length: 68\r\n\r\n" + \ . '{"method":"someNotification","jsonrpc":"2.0","params":{"foo":"bar"}}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}]) + endif + +Execute(ale#lsp#CreateMessageData() should create tsserver notification messages): + if has('nvim') + AssertEqual + \ [ + \ 0, + \ '{"seq": null, "type": "request", "command": "someNotification"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([1, 'ts@someNotification']) + AssertEqual + \ [ + \ 0, + \ '{"seq": null, "arguments": {"foo": "bar"}, "type": "request", "command": "someNotification"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([1, 'ts@someNotification', {'foo': 'bar'}]) + else + AssertEqual + \ [ + \ 0, + \ '{"seq":null,"type":"request","command":"someNotification"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([1, 'ts@someNotification']) + AssertEqual + \ [ + \ 0, + \ '{"seq":null,"arguments":{"foo":"bar"},"type":"request","command":"someNotification"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([1, 'ts@someNotification', {'foo': 'bar'}]) + endif + +Execute(ale#lsp#CreateMessageData() should create tsserver messages expecting responses): + if has('nvim') + AssertEqual + \ [ + \ 1, + \ '{"seq": 1, "type": "request", "command": "someMessage"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([0, 'ts@someMessage']) + AssertEqual + \ [ + \ 2, + \ '{"seq": 2, "arguments": {"foo": "bar"}, "type": "request", "command": "someMessage"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([0, 'ts@someMessage', {'foo': 'bar'}]) + else + AssertEqual + \ [ + \ 1, + \ '{"seq":1,"type":"request","command":"someMessage"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([0, 'ts@someMessage']) + AssertEqual + \ [ + \ 2, + \ '{"seq":2,"arguments":{"foo":"bar"},"type":"request","command":"someMessage"}' + \ . "\n", + \ ], + \ ale#lsp#CreateMessageData([0, 'ts@someMessage', {'foo': 'bar'}]) + endif + +Execute(ale#lsp#ReadMessageData() should read single whole messages): + AssertEqual + \ ['', [{'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}}]], + \ ale#lsp#ReadMessageData( + \ "Content-Length: 49\r\n\r\n" + \ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}' + \ ) + +Execute(ale#lsp#ReadMessageData() should ignore other headers): + AssertEqual + \ ['', [{'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}}]], + \ ale#lsp#ReadMessageData( + \ "First-Header: 49\r\n" + \ . "Content-Length: 49\r\n" + \ . "Other-Header: 49\r\n" + \ . "\r\n" + \ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}' + \ ) + +Execute(ale#lsp#ReadMessageData() should handle partial messages): + let b:data = "Content-Length: 49\r\n\r\n" . '{"id":2,"jsonrpc":"2.0","result":' + + AssertEqual [b:data, []], ale#lsp#ReadMessageData(b:data) + +Execute(ale#lsp#ReadMessageData() should handle multiple messages): + AssertEqual + \ ['', [ + \ {'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}}, + \ {'id': 2, 'jsonrpc': '2.0', 'result': {'foo123': 'barÜ'}}, + \ ]], + \ ale#lsp#ReadMessageData( + \ "Content-Length: 49\r\n\r\n" + \ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}' + \ . "Content-Length: 52\r\n\r\n" + \ . '{"id":2,"jsonrpc":"2.0","result":{"foo123":"barÜ"}}' + \ ) + +Execute(ale#lsp#ReadMessageData() should handle a message with part of a second message): + let b:data = "Content-Length: 52\r\n\r\n" . '{"id":2,"jsonrpc":"2.' + + AssertEqual + \ [b:data, [ + \ {'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}}, + \ ]], + \ ale#lsp#ReadMessageData( + \ "Content-Length: 49\r\n\r\n" + \ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}' + \ . b:data + \ ) diff --git a/test/lsp/test_lsp_custom_request.vader b/test/lsp/test_lsp_custom_request.vader new file mode 100644 index 00000000..6a864cef --- /dev/null +++ b/test/lsp/test_lsp_custom_request.vader @@ -0,0 +1,158 @@ +Before: + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + + let g:address = 'ccls_address' + let g:conn_id = -1 + let g:executable = 'ccls' + let g:executable_or_address = '' + let g:linter_name = 'ccls' + let g:magic_number = 42 + let g:no_result = 0 + let g:message_list = [] + let g:message_id = 1 + let g:method = '$ccls/call' + let g:parameters = {} + let g:project_root = '/project/root' + let g:response = '' + let g:return_value = -1 + + let g:linter_list = [{ + \ 'output_stream': 'stdout', + \ 'lint_file': 0, + \ 'language': 'cpp', + \ 'name': g:linter_name, + \ 'project_root': {b -> g:project_root}, + \ 'aliases': [], + \ 'read_buffer': 1, + \ 'command': '%e' + \ }] + + let g:callback_result = g:no_result + + " Encode dictionary to jsonrpc + function! Encode(obj) abort + let l:body = json_encode(a:obj) + return 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body + endfunction + + " Replace the StartLSP function to mock an LSP linter + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let g:conn_id = ale#lsp#Register(g:executable_or_address, g:project_root, '', {}) + call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) + call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'})) + + let l:details = { + \ 'command': g:executable, + \ 'buffer': a:buffer, + \ 'connection_id': g:conn_id, + \ 'project_root': g:project_root, + \} + + call ale#lsp_linter#OnInit(a:linter, l:details, a:Callback) + endfunction + + " Dummy callback + function! Callback(response) abort + let g:callback_result = a:response.result.value + endfunction + + " Replace the GetAll function to mock an LSP linter + function! ale#linter#GetAll(filetype) abort + return g:linter_list + endfunction + + " Replace the Send function to mock an LSP linter + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + return g:message_id + endfunction + + " Code for a test case + function! TestCase(is_notification) abort + " Test sending a custom request + let g:return_value = ale#lsp_linter#SendRequest( + \ bufnr('%'), + \ g:linter_name, + \ [a:is_notification, g:method, g:parameters], + \ function('Callback')) + + Assert index(g:message_list, [a:is_notification, g:method, g:parameters]) >= 0 + + " Mock an incoming response to the request + let g:response = Encode({ + \ 'id': g:message_id, + \ 'jsonrpc': '2.0', + \ 'result': {'value': g:magic_number} + \ }) + call ale#lsp#HandleMessage(g:conn_id, g:response) + + AssertEqual + \ a:is_notification ? g:no_result : g:magic_number, + \ g:callback_result + endfunction + +After: + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + unlet! g:callback_result + unlet! g:conn_id + unlet! g:executable + unlet! g:is_notification + unlet! g:linter_name + unlet! g:magic_number + unlet! g:message_list + unlet! g:message_id + unlet! g:method + unlet! g:no_result + unlet! g:parameters + unlet! g:project_root + unlet! g:response + unlet! g:return_value + + delfunction Encode + delfunction Callback + delfunction TestCase + + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + +Given cpp(Empty cpp file): +Execute(Test custom request to server identified by executable): + let g:executable_or_address = g:executable + let g:linter_list[0].executable = {b -> g:executable} + let g:linter_list[0].lsp = 'stdio' + let g:is_notification = 0 + + call TestCase(g:is_notification) + +Given cpp(Empty cpp file): +Execute(Test custom notification to server identified by executable): + let g:executable_or_address = g:executable + let g:linter_list[0].executable = {b -> g:executable} + let g:linter_list[0].lsp = 'stdio' + let g:is_notification = 1 + + call TestCase(g:is_notification) + +Given cpp(Empty cpp file): +Execute(Test custom request to server identified by address): + let g:executable_or_address = g:address + let g:linter_list[0].address = {b -> g:address} + let g:linter_list[0].lsp = 'socket' + let g:is_notification = 0 + + call TestCase(g:is_notification) + +Given cpp(Empty cpp file): +Execute(Test custom notification to server identified by address): + let g:executable_or_address = g:address + let g:linter_list[0].address = {b -> g:address} + let g:linter_list[0].lsp = 'socket' + let g:is_notification = 1 + + call TestCase(g:is_notification) diff --git a/test/lsp/test_lsp_error_parsing.vader b/test/lsp/test_lsp_error_parsing.vader new file mode 100644 index 00000000..44169c80 --- /dev/null +++ b/test/lsp/test_lsp_error_parsing.vader @@ -0,0 +1,74 @@ +Execute(Invalid responses should be handled): + AssertEqual '', ale#lsp#response#GetErrorMessage({}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': 0}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {}}) + AssertEqual '', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': 0, + \ 'message': 'x', + \ }, + \}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {'code': -32602}}) + AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {'code': -32603}}) + +Execute(Messages without tracebacks should be handled): + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ }, + \}) + AssertEqual 'abc', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32603, + \ 'message': 'abc', + \ }, + \}) + +Execute(Invalid traceback data should be tolerated): + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ }, + \ }, + \}) + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': 0, + \ }, + \ }, + \}) + AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': [], + \ }, + \ }, + \}) + +Execute(Messages with tracebacks should be handled): + AssertEqual "xyz\n123\n456", ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': { + \ 'traceback': ['123', '456'], + \ }, + \ }, + \}) + +Execute(Messages with string data should be handled): + AssertEqual "xyz\nUncaught Exception", ale#lsp#response#GetErrorMessage({ + \ 'error': { + \ 'code': -32602, + \ 'message': 'xyz', + \ 'data': 'Uncaught Exception', + \ }, + \}) diff --git a/test/lsp/test_lsp_root_detection.vader b/test/lsp/test_lsp_root_detection.vader new file mode 100644 index 00000000..2aead6ba --- /dev/null +++ b/test/lsp/test_lsp_root_detection.vader @@ -0,0 +1,67 @@ +Before: + Save g:ale_root + Save b:ale_root + + let g:ale_root = {} + + call ale#assert#SetUpLinterTest('c', 'clangd') + + function! Hook1(buffer) + return 'abc123' + endfunction + +After: + Restore + + delfunction Hook1 + + call ale#assert#TearDownLinterTest() + +Execute(The buffer-specific variable can be a string): + let b:ale_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_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_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_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_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_root = {'clangd': '/some/path', 'golangserver': '/other/path'} + let g:ale_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_root = {'golangserver': '/other/path'} + let g:ale_root = {'clangd': '/some/path', 'golangserver': '/elsewhere'} + call ale#test#SetFilename('other-file.c') + + AssertLSPProject '/some/path' + +Execute(No path should be returned by default): + call ale#test#SetFilename(tempname() . '/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..ef36028e --- /dev/null +++ b/test/lsp/test_lsp_startup.vader @@ -0,0 +1,496 @@ +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:lsp_started = 0 + + 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(buffer) abort + let l:linter = values(ale#linter#GetLintersLoaded())[0][0] + + return ale#lsp_linter#StartLSP( + \ a:buffer, + \ l:linter, + \ {linter, details -> add(g:calls, [linter.name, details])}, + \) + endfunction + + function! AssertInitSuccess(linter_name, conn_prefix, language, root, command, buffer) abort + let l:messages = PopMessages() + + if a:linter_name is# 'tsserver' + AssertEqual + \ [ + \ { + \ 'seq': v:null, + \ 'arguments': { + \ 'file': expand('#' . a:buffer . ':p'), + \ }, + \ 'type': 'request', + \ 'command': 'open', + \ }, + \ ], + \ l:messages + else + AssertEqual + \ [ + \ { + \ 'method': 'initialize', + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'params': { + \ 'initializationOptions': {}, + \ 'rootUri': ale#path#ToFileURI(a:root), + \ 'rootPath': a:root, + \ 'processId': getpid(), + \ 'capabilities': { + \ 'workspace': { + \ 'applyEdit': v:false, + \ 'didChangeConfiguration': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'symbol': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'workspaceFolders': v:false, + \ 'configuration': v:false, + \ }, + \ 'textDocument': { + \ 'synchronization': { + \ 'dynamicRegistration': v:false, + \ 'willSave': v:false, + \ 'willSaveWaitUntil': v:false, + \ 'didSave': v:true, + \ }, + \ 'completion': { + \ 'dynamicRegistration': v:false, + \ 'completionItem': { + \ 'snippetSupport': v:false, + \ 'commitCharactersSupport': v:false, + \ 'documentationFormat': ['plaintext', 'markdown'], + \ 'deprecatedSupport': v:false, + \ 'preselectSupport': v:false, + \ }, + \ 'contextSupport': v:false, + \ }, + \ 'hover': { + \ 'dynamicRegistration': v:false, + \ 'contentFormat': ['plaintext', 'markdown'], + \ }, + \ 'references': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'documentSymbol': { + \ 'dynamicRegistration': v:false, + \ 'hierarchicalDocumentSymbolSupport': v:false, + \ }, + \ 'definition': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport': v:false, + \ }, + \ 'typeDefinition': { + \ 'dynamicRegistration': v:false, + \ }, + \ 'implementation': { + \ 'dynamicRegistration': v:false, + \ 'linkSupport': v:false, + \ }, + \ 'diagnostic': { + \ 'dynamicRegistration': v:true, + \ 'relatedDocumentSupport': v:true, + \ }, + \ 'publishDiagnostics': { + \ 'relatedInformation': v:true, + \ }, + \ 'codeAction': { + \ 'dynamicRegistration': v:false, + \ 'codeActionLiteralSupport': { + \ 'codeActionKind': { + \ 'valueSet': [] + \ } + \ } + \ }, + \ 'rename': { + \ 'dynamicRegistration': v:false, + \ }, + \ }, + \ }, + \ }, + \ }, + \ ], + \ 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#ToFileURI(expand('#' . a:buffer . ':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': a:buffer, + \ 'command': !empty(a:command) ? ale#job#PrepareCommand(a:buffer, 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:lsp_started + + unlet! g:socket_map + unlet! g:emulate_socket_failure + unlet! g:next_channel_id + + unlet! g:message_buffer + unlet! g:calls + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + + 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(bufnr('')) + call AssertInitSuccess('tsserver', 'tsserver', '', '', ale#Escape('tsserver'), bufnr('')) + +Execute(tsserver failures should be handled appropriately): + runtime ale_linters/typescript/tsserver.vim + + let g:emulate_job_failure = 1 + + Assert !Start(bufnr('')) + 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(bufnr('')) + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', 'foo', bufnr('')) + +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(bufnr('')) + 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(bufnr('')) + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', bufnr('')) + +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(bufnr('')) + 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(bufnr('')) + call ale#test#FlushJobs() + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c', bufnr('')) + +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(bufnr('')) + call ale#test#FlushJobs() + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c', bufnr('')) + +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(bufnr('')) + call ale#test#FlushJobs() + call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '', bufnr('')) + +Execute(Servers that have crashed should be restarted): + call ale#lsp#Register('foo', '/foo/bar', '', {}) + call extend(ale#lsp#GetConnections()['foo:/foo/bar'], {'initialized': 1}) + + " Starting the program again should reset initialized to `0`. + call ale#lsp#StartProgram('foo:/foo/bar', 'foobar', 'foobar --start') + + AssertEqual 0, ale#lsp#GetConnections()['foo:/foo/bar']['initialized'] + AssertEqual ['initialize'], map(PopMessages(), 'v:val[''method'']') + +Execute(Current LSP buffer should receive ALELSPStarted): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address': 'foo', + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + augroup VaderTest + autocmd! + autocmd User ALELSPStarted let g:lsp_started = 1 + augroup END + + Assert Start(bufnr('')) + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', bufnr('')) + AssertEqual g:lsp_started, 1 + +Execute(Target LSP buffer should receive ALELSPStarted): + call ale#linter#Define('foobar', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address': 'foo', + \ 'project_root': '/foo/bar', + \ 'initialization_options': {}, + \}) + + augroup VaderTest + autocmd! + autocmd User ALELSPStarted let g:lsp_started = 1 + augroup END + + let buffer = bufnr('') + + enew! + Assert Start(buffer) + call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', buffer) + execute 'buffer' . buffer + + AssertEqual g:lsp_started, 1 diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader new file mode 100644 index 00000000..4a6d89e0 --- /dev/null +++ b/test/lsp/test_other_initialize_message_handling.vader @@ -0,0 +1,223 @@ +Before: + runtime autoload/ale/lsp.vim + + let g:message_list = [] + + " Register a fake connection and get it for tests. + call ale#lsp#Register('ale-fake-lsp-server', '/code', '', {}) + let b:conn = ale#lsp#GetConnections()['ale-fake-lsp-server:/code'] + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + +After: + " Remove the connection with the ID. + call ale#lsp#RemoveConnectionWithID(b:conn.id) + + 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, { + \ 'result': {'capabilities': {}}, + \}) + + 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 be set up correctly): + call ale#lsp#HandleInitResponse(b:conn, { + \ '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 + \ }, + \ }, + \}) + + AssertEqual 1, b:conn.initialized + AssertEqual + \ { + \ 'code_actions': 1, + \ 'completion': 1, + \ 'completion_trigger_characters': ['.'], + \ 'definition': 1, + \ 'did_save': 0, + \ 'filerename': 0, + \ 'hover': 1, + \ 'implementation': 0, + \ 'includeText': 0, + \ 'references': 1, + \ 'rename': 1, + \ 'pull_model': 0, + \ 'symbol_search': 1, + \ 'typeDefinition': 0, + \ }, + \ b:conn.capabilities + AssertEqual [[1, 'initialized', {}]], g:message_list + +Execute(Disabled capabilities should be recognised correctly): + call ale#lsp#HandleInitResponse(b:conn, { + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'result': { + \ 'capabilities': { + \ 'renameProvider': v:false, + \ 'executeCommandProvider': { + \ 'commands': [], + \ }, + \ 'hoverProvider': v:false, + \ 'documentSymbolProvider': v:true, + \ 'documentRangeFormattingProvider': v:true, + \ 'codeLensProvider': { + \ 'resolveProvider': v:false + \ }, + \ 'referencesProvider': v:false, + \ 'textDocumentSync': 2, + \ 'documentFormattingProvider': v:true, + \ 'codeActionProvider': v:false, + \ 'signatureHelpProvider': { + \ 'triggerCharacters': ['(', ','], + \ }, + \ 'definitionProvider': v:false, + \ 'experimental': {}, + \ 'documentHighlightProvider': v:true, + \ 'diagnosticProvider': {}, + \ }, + \ }, + \}) + + AssertEqual 1, b:conn.initialized + AssertEqual + \ { + \ 'code_actions': 0, + \ 'completion': 0, + \ 'completion_trigger_characters': [], + \ 'definition': 0, + \ 'did_save': 0, + \ 'filerename': 0, + \ 'hover': 0, + \ 'implementation': 0, + \ 'includeText': 0, + \ 'references': 0, + \ 'rename': 0, + \ 'pull_model': 0, + \ 'symbol_search': 0, + \ 'typeDefinition': 0, + \ }, + \ b:conn.capabilities + AssertEqual [[1, 'initialized', {}]], g:message_list + +Execute(Capabilities should be enabled when sent as Dictionaries): + call ale#lsp#HandleInitResponse(b:conn, { + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'result': { + \ 'capabilities': { + \ 'renameProvider': {}, + \ 'executeCommandProvider': { + \ 'commands': [], + \ }, + \ 'hoverProvider': {}, + \ 'documentSymbolProvider': v:true, + \ 'documentRangeFormattingProvider': v:true, + \ 'codeLensProvider': { + \ 'resolveProvider': v:false + \ }, + \ 'completionProvider': { + \ 'triggerCharacters': ['.'], + \ 'resolveProvider': v:false + \ }, + \ 'referencesProvider': {}, + \ 'textDocumentSync': { + \ 'save': { + \ 'includeText': v:true + \ } + \ }, + \ 'documentFormattingProvider': v:true, + \ 'codeActionProvider': v:true, + \ 'signatureHelpProvider': { + \ 'triggerCharacters': ['(', ','], + \ }, + \ 'definitionProvider': {}, + \ 'typeDefinitionProvider': {}, + \ 'implementationProvider': {}, + \ 'experimental': {}, + \ 'documentHighlightProvider': v:true, + \ 'diagnosticProvider': { + \ 'interFileDependencies': v:false, + \ }, + \ 'workspaceSymbolProvider': {} + \ }, + \ }, + \}) + + AssertEqual 1, b:conn.initialized + AssertEqual + \ { + \ 'code_actions': 1, + \ 'completion': 1, + \ 'completion_trigger_characters': ['.'], + \ 'definition': 1, + \ 'did_save': 1, + \ 'filerename': 0, + \ 'hover': 1, + \ 'implementation': 1, + \ 'includeText': 1, + \ 'references': 1, + \ 'rename': 1, + \ 'pull_model': 1, + \ 'symbol_search': 1, + \ 'typeDefinition': 1, + \ }, + \ 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, { + \ 'jsonrpc': '2.0', + \ '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 new file mode 100644 index 00000000..80f2e4f0 --- /dev/null +++ b/test/lsp/test_read_lsp_diagnostics.vader @@ -0,0 +1,257 @@ +Before: + function Range(start_line, start_char, end_line, end_char) abort + return { + \ 'start': {'line': a:start_line, 'character': a:start_char}, + \ 'end': {'line': a:end_line, 'character': a:end_char}, + \} + endfunction + +After: + delfunction Range + +Execute(ale#lsp#response#ReadDiagnostics() should handle errors): + AssertEqual [ + \ { + \ 'type': 'E', + \ 'text': 'Something went wrong!', + \ 'lnum': 3, + \ 'col': 11, + \ 'end_lnum': 5, + \ 'end_col': 15, + \ 'code': 'some-error', + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'severity': 1, + \ 'range': Range(2, 10, 4, 15), + \ 'code': 'some-error', + \ 'message': 'Something went wrong!', + \ }, + \ ]) + +Execute(ale#lsp#response#ReadDiagnostics() should handle warnings): + AssertEqual [ + \ { + \ 'type': 'W', + \ 'text': 'Something went wrong!', + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 2, + \ 'end_col': 3, + \ 'code': 'some-warning', + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'severity': 2, + \ 'range': Range(1, 3, 1, 3), + \ 'code': 'some-warning', + \ 'message': 'Something went wrong!', + \ }, + \ ]) + +Execute(ale#lsp#response#ReadDiagnostics() should treat messages with missing severity as errors): + AssertEqual [ + \ { + \ 'type': 'E', + \ 'text': 'Something went wrong!', + \ 'lnum': 3, + \ 'col': 11, + \ 'end_lnum': 5, + \ 'end_col': 15, + \ 'code': 'some-error', + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'range': Range(2, 10, 4, 15), + \ 'code': 'some-error', + \ 'message': 'Something went wrong!', + \ }, + \ ]) + +Execute(ale#lsp#response#ReadDiagnostics() should handle messages without codes): + AssertEqual [ + \ { + \ 'type': 'E', + \ 'text': 'Something went wrong!', + \ 'lnum': 3, + \ 'col': 11, + \ 'end_lnum': 5, + \ 'end_col': 15, + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'range': Range(2, 10, 4, 15), + \ 'message': 'Something went wrong!', + \ }, + \ ]) + +Execute(ale#lsp#response#ReadDiagnostics() should include sources in detail): + AssertEqual [ + \ { + \ 'type': 'E', + \ 'text': 'Something went wrong!', + \ 'detail': '[tslint] Something went wrong!', + \ 'lnum': 10, + \ 'col': 15, + \ 'end_lnum': 12, + \ 'end_col': 22, + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'range': Range(9, 14, 11, 22), + \ 'message': 'Something went wrong!', + \ 'source': 'tslint', + \ } + \ ]) + +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([ + \ { + \ '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 [ + \ { + \ 'type': 'E', + \ 'text': 'Something went wrong!', + \ 'lnum': 3, + \ 'col': 11, + \ 'end_lnum': 5, + \ 'end_col': 15, + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'range': Range(2, 10, 4, 15), + \ 'message': 'Something went wrong!', + \ 'code': -1, + \ }, + \ ]) + +Execute(ale#lsp#response#ReadDiagnostics() should handle multiple messages): + AssertEqual [ + \ { + \ 'type': 'E', + \ 'text': 'Something went wrong!', + \ 'lnum': 1, + \ 'col': 3, + \ 'end_lnum': 1, + \ 'end_col': 2, + \ }, + \ { + \ 'type': 'W', + \ 'text': 'A warning', + \ 'lnum': 2, + \ 'col': 5, + \ 'end_lnum': 2, + \ 'end_col': 4, + \ }, + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'range': Range(0, 2, 0, 2), + \ 'message': 'Something went wrong!', + \ }, + \ { + \ 'severity': 2, + \ 'range': Range(1, 4, 1, 4), + \ 'message': 'A warning', + \ }, + \ ]) + +Execute(ale#lsp#response#ReadDiagnostics() should use relatedInformation for detail): + AssertEqual [ + \ { + \ 'type': 'E', + \ 'text': 'Something went wrong!', + \ 'lnum': 1, + \ 'col': 3, + \ 'end_lnum': 1, + \ 'end_col': 2, + \ 'detail': "Something went wrong!\n/tmp/someotherfile.txt:43:80:\n\tmight be this" + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics([ + \ { + \ 'range': Range(0, 2, 0, 2), + \ 'message': 'Something went wrong!', + \ 'relatedInformation': [{ + \ 'message': 'might be this', + \ 'location': { + \ 'uri': 'file:///tmp/someotherfile.txt', + \ 'range': { + \ 'start': { 'line': 42, 'character': 79 }, + \ 'end': { 'line': 142, 'character': 179}, + \ } + \ } + \ }] + \ } + \ ]) + +Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle tsserver responses): + AssertEqual + \ [ + \ { + \ 'type': 'E', + \ 'nr': 2365, + \ 'code': '2365', + \ 'text': 'Operator ''''+'''' cannot be applied to types ''''3'''' and ''''{}''''.', + \ 'lnum': 1, + \ 'col': 11, + \ 'end_lnum': 1, + \ 'end_col': 16, + \ }, + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"/bar/foo.ts","diagnostics":[{"start":{"line":1,"offset":11},"end":{"line":1,"offset":17},"text":"Operator ''+'' cannot be applied to types ''3'' and ''{}''.","code":2365}]}}) + +Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle warnings from tsserver): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 3, + \ 'nr': 2515, + \ 'code': '2515', + \ 'end_lnum': 27, + \ 'type': 'W', + \ 'end_col': 13, + \ 'text': 'Calls to ''console.log'' are not allowed. (no-console)', + \ } + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"","diagnostics":[{"start":{"line":27,"offset":3},"end":{"line":27,"offset":14},"text":"Calls to 'console.log' are not allowed. (no-console)","code":2515,"category":"warning","source":"tslint"}]}}) + +Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle suggestions from tsserver): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 3, + \ 'nr': 2515, + \ 'code': '2515', + \ 'end_lnum': 27, + \ 'type': 'I', + \ 'end_col': 13, + \ 'text': 'Some info', + \ } + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"","diagnostics":[{"start":{"line":27,"offset":3},"end":{"line":27,"offset":14},"text":"Some info","code":2515,"category":"suggestion","source":"tslint"}]}}) diff --git a/test/lsp/test_reset_lsp.vader b/test/lsp/test_reset_lsp.vader new file mode 100644 index 00000000..c6be47e6 --- /dev/null +++ b/test/lsp/test_reset_lsp.vader @@ -0,0 +1,169 @@ +Before: + Save g:ale_enabled + Save g:ale_set_signs + Save g:ale_set_quickfix + Save g:ale_set_loclist + Save g:ale_set_highlights + Save g:ale_echo_cursor + Save g:ale_buffer_info + + let g:ale_enabled = 0 + let g:ale_set_signs = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + let g:expr_list = [] + + function EmptyString() abort + return '' + endfunction + + runtime autoload/ale/util.vim + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + call ale#engine#InitBufferInfo(bufnr('')) + " Call this function first, to clear LSP data. + call ale#lsp_linter#ClearLSPData() + + call ale#linter#Define('testft', { + \ 'name': 'lsplinter', + \ 'lsp': 'tsserver', + \ 'executable': function('EmptyString'), + \ 'command': function('EmptyString'), + \ 'project_root': function('EmptyString'), + \ 'language': function('EmptyString'), + \}) + call ale#linter#Define('testft', { + \ 'name': 'lsplinter2', + \ 'lsp': 'tsserver', + \ 'executable': function('EmptyString'), + \ 'command': function('EmptyString'), + \ 'project_root': function('EmptyString'), + \ 'language': function('EmptyString'), + \}) + call ale#linter#Define('testft', { + \ 'name': 'otherlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + +After: + Restore + + delfunction EmptyString + unlet! g:expr_list + unlet! b:ale_save_event_fired + + " Clear LSP data after tests. + call ale#lsp_linter#ClearLSPData() + + runtime autoload/ale/util.vim + + call ale#linter#Reset() + +Given testft(Some file with an imaginary filetype): +Execute(ALEStopAllLSPs should clear the loclist): + " For these tests we only need to set the keys we need. + let g:ale_buffer_info[bufnr('')].loclist = [ + \ {'linter_name': 'lsplinter'}, + \ {'linter_name': 'otherlinter'}, + \] + let g:ale_buffer_info[bufnr('')].active_linter_list = [ + \ {'name': 'lsplinter'}, + \ {'name': 'otherlinter'}, + \] + + ALEStopAllLSPs + + " The loclist should be updated. + AssertEqual + \ ['otherlinter'], + \ map(copy(g:ale_buffer_info[bufnr('')].loclist), 'v:val.linter_name') + + " The LSP linter should be removed from the active linter list. + AssertEqual + \ ['otherlinter'], + \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name') + +Execute(ALEStopLSP should stop a named LSP): + let g:ale_buffer_info[bufnr('')].loclist = [ + \ {'linter_name': 'lsplinter'}, + \ {'linter_name': 'lsplinter2'}, + \ {'linter_name': 'otherlinter'}, + \] + let g:ale_buffer_info[bufnr('')].active_linter_list = [ + \ {'name': 'lsplinter'}, + \ {'name': 'lsplinter2'}, + \ {'name': 'otherlinter'}, + \] + call ale#lsp_linter#SetLSPLinterMap({ + \ 'conn1': {'name': 'lsplinter'}, + \ 'conn2': {'name': 'lsplinter2'}, + \ 'conn3': {'name': 'lsplinter'}, + \}) + + ALEStopLSP lsplinter + + " We should remove only the items for this linter. + AssertEqual + \ ['lsplinter2', 'otherlinter'], + \ map(copy(g:ale_buffer_info[bufnr('')].loclist), 'v:val.linter_name') + + " The linter should be removed from the active linter list. + AssertEqual + \ ['lsplinter2', 'otherlinter'], + \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name') + + " The connections linters with this name should be removed. + AssertEqual + \ {'conn2': {'name': 'lsplinter2'}}, + \ ale#lsp_linter#GetLSPLinterMap() + +Execute(ALEStopLSP should not clear results for linters not running): + let g:ale_buffer_info[bufnr('')].loclist = [ + \ {'linter_name': 'lsplinter'}, + \ {'linter_name': 'otherlinter'}, + \] + let g:ale_buffer_info[bufnr('')].active_linter_list = [ + \ {'name': 'lsplinter'}, + \ {'name': 'otherlinter'}, + \] + + ALEStopLSP lsplinter + + " We should emit a message saying the server isn't running. + AssertEqual + \ ['echom ''No running language server with name: lsplinter'''], + \ g:expr_list + + " We should keep the linter items. + AssertEqual + \ ['lsplinter', 'otherlinter'], + \ map(copy(g:ale_buffer_info[bufnr('')].loclist), 'v:val.linter_name') + AssertEqual + \ ['lsplinter', 'otherlinter'], + \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name') + +Execute(ALEStopLSP with a bang should not emit warnings): + ALEStopLSP! lsplinter + + AssertEqual [], g:expr_list + +Execute(ALEStopLSP's completion function should suggest running linter names): + call ale#lsp_linter#SetLSPLinterMap({ + \ 'conn1': {'name': 'pyright'}, + \ 'conn2': {'name': 'pylsp'}, + \ 'conn3': {'name': 'imaginaryserver'}, + \}) + + AssertEqual + \ ['imaginaryserver', 'pylsp', 'pyright'], + \ ale#lsp#reset#Complete('', '', 42) + AssertEqual ['imaginaryserver'], ale#lsp#reset#Complete('inary', '', 42) + AssertEqual ['pylsp'], ale#lsp#reset#Complete('LSP', '', 42) diff --git a/test/lsp/test_update_config.vader b/test/lsp/test_update_config.vader new file mode 100644 index 00000000..2a2c85e6 --- /dev/null +++ b/test/lsp/test_update_config.vader @@ -0,0 +1,21 @@ +Before: + runtime autoload/ale/lsp.vim + + 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 + + unlet! g:conn_id + + runtime autoload/ale/lsp.vim + +Execute(Only send updates when the configuration dictionary changes): + AssertEqual 0, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {}) + AssertEqual 1, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {'a': 1}) + AssertEqual 0, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {'a': 1}) + AssertEqual 1, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {}) diff --git a/test/lua/.luarc.json b/test/lua/.luarc.json new file mode 100644 index 00000000..e6c6668f --- /dev/null +++ b/test/lua/.luarc.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "diagnostics.globals": [ + "vim", + "describe", + "it", + "before_each", + "after_each", + "setup", + "teardown", + "pending", + "assert" + ], + "workspace.checkThirdParty": false, + "workspace.library": [ + "${3rd}/busted/library", + "${env:HOME}/.luarocks/share/lua/5.4", + "${env:HOME}/.luarocks/share/lua/5.3", + "${env:HOME}/.luarocks/share/lua/5.2", + "${env:HOME}/.luarocks/share/lua/5.1", + "../../lua" + ], + "runtime.pathStrict": true, + "runtime.path": [ + "?.lua", + "?/init.lua", + "../../lua/?.lua", + "../../lua/?/init.lua", + "${env:HOME}/.luarocks/share/lua/5.4/?.lua", + "${env:HOME}/.luarocks/share/lua/5.4/?/init.lua", + "${env:HOME}/.luarocks/share/lua/5.3/?.lua", + "${env:HOME}/.luarocks/share/lua/5.3/?/init.lua", + "${env:HOME}/.luarocks/share/lua/5.2/?.lua", + "${env:HOME}/.luarocks/share/lua/5.2/?/init.lua", + "${env:HOME}/.luarocks/share/lua/5.1/?.lua", + "${env:HOME}/.luarocks/share/lua/5.1/?/init.lua" + ], + "runtime.version": "LuaJIT", + "hint.enable": false +} diff --git a/test/lua/ale_diagnostics_spec.lua b/test/lua/ale_diagnostics_spec.lua new file mode 100644 index 00000000..ae2ad894 --- /dev/null +++ b/test/lua/ale_diagnostics_spec.lua @@ -0,0 +1,232 @@ +local eq = assert.are.same +local diagnostics + +describe("ale.diagnostics.send", function() + local buffer_map + local signs_config + local diagnostic_set_calls + + setup(function() + _G.vim = { + api = { + nvim_buf_get_var = function(buffer, key) + local buffer_table = buffer_map[buffer] or {} + local value = buffer_table[key] + + if value == nil then + error(key .. " is missing") + end + + return value + end, + nvim_create_namespace = function() + return 42 + end, + }, + diagnostic = { + severity = {ERROR = 1, WARN = 2, INFO = 3}, + config = function() + return {signs = signs_config} + end, + set = function(namespace, bufnr, _diagnostics, opts) + table.insert(diagnostic_set_calls, { + namespace = namespace, + bufnr = bufnr, + diagnostics = _diagnostics, + opts = opts, + }) + end, + }, + tbl_extend = function(behavior, ...) + assert(behavior == "force", "We should only use `force`") + + local merged = {} + + for _, arg in ipairs({...}) do + for key, value in pairs(arg) do + merged[key] = value + end + end + + return merged + end, + g = {}, + } + + diagnostics = require("ale.diagnostics") + end) + + teardown(function() + _G.vim = nil + end) + + before_each(function() + buffer_map = {} + diagnostic_set_calls = {} + signs_config = false + _G.vim.g = {} + end) + + it("should set an empty list of diagnostics correctly", function() + diagnostics.send(7, {}) + + eq( + { + { + namespace = 42, + bufnr = 7, + diagnostics = {}, + opts = {virtual_text = false} + }, + }, + diagnostic_set_calls + ) + end) + + it("should handle basic case with all fields", function() + diagnostics.send(1, { + { + bufnr = 1, + lnum = 2, + end_lnum = 3, + col = 4, + end_col = 5, + type = "W", + code = "123", + text = "Warning message", + linter_name = "eslint", + }, + }) + eq({ + { + lnum = 1, + end_lnum = 2, + col = 3, + end_col = 5, + severity = vim.diagnostic.severity.WARN, + code = "123", + message = "Warning message", + source = "eslint", + }, + }, diagnostic_set_calls[1].diagnostics) + end) + + it("should default end_lnum to lnum when missing", function() + diagnostics.send(1, { + { + bufnr = 1, + lnum = 5, + col = 2, + end_col = 8, + type = "E", + text = "Error message", + linter_name = "mylinter", + }, + }) + eq({ + { + lnum = 4, + end_lnum = 4, + col = 1, + end_col = 8, + severity = vim.diagnostic.severity.ERROR, + code = nil, + message = "Error message", + source = "mylinter", + }, + }, diagnostic_set_calls[1].diagnostics) + end) + + it("should default col to 0 when missing", function() + diagnostics.send(1, { + { + bufnr = 1, + lnum = 10, + end_lnum = 12, + end_col = 6, + type = "I", + text = "Info message", + }, + }) + eq({ + { + lnum = 9, + end_lnum = 11, + col = 0, + end_col = 6, + severity = vim.diagnostic.severity.INFO, + code = nil, + message = "Info message", + source = nil, + }, + }, diagnostic_set_calls[1].diagnostics) + end) + + it("should ignore non-matching buffers", function() + diagnostics.send(1, { + { + bufnr = 2, + lnum = 1, + end_lnum = 2, + col = 1, + end_col = 4, + type = "W", + text = "Message", + }, + }) + eq({}, diagnostic_set_calls[1].diagnostics) + end) + + for _, set_signs_value in ipairs {1, true} do + describe("signs with setting set_signs = " .. tostring(set_signs_value), function() + before_each(function() + _G.vim.g.ale_set_signs = set_signs_value + _G.vim.g.ale_sign_priority = 10 + end) + + it("and global config as `false` should enable signs with the given priority", function() + diagnostics.send(7, {}) + eq({priority = 10}, diagnostic_set_calls[1].opts.signs) + end) + + it("and global config as a table should enable signs with the given priority", function() + signs_config = {foo = "bar", priority = 5} + diagnostics.send(7, {}) + eq( + {foo = "bar", priority = 10}, + diagnostic_set_calls[1].opts.signs + ) + end) + + it("and global config as a function should enable signs with the given priority", function() + signs_config = function() + return {foo = "bar", priority = 5} + end + diagnostics.send(7, {}) + + local local_signs = diagnostic_set_calls[1].opts.signs + + eq("function", type(local_signs)) + eq({foo = "bar", priority = 10}, local_signs()) + end) + end) + end + + it("should toggle virtual_text correctly", function() + for _, value in ipairs({"all", "2", 2, "current", "1", 1, true}) do + diagnostic_set_calls = {} + _G.vim.g.ale_virtualtext_cursor = value + diagnostics.send(7, {}) + + eq({virtual_text = true}, diagnostic_set_calls[1].opts) + end + + for _, value in ipairs({"disabled", "0", 0, false, nil}) do + diagnostic_set_calls = {} + _G.vim.g.ale_virtualtext_cursor = value + diagnostics.send(7, {}) + + eq({virtual_text = false}, diagnostic_set_calls[1].opts) + end + end) +end) diff --git a/test/lua/ale_env_spec.lua b/test/lua/ale_env_spec.lua new file mode 100644 index 00000000..1cceb5be --- /dev/null +++ b/test/lua/ale_env_spec.lua @@ -0,0 +1,56 @@ +local eq = assert.are.same +local ale = require("ale") + +describe("ale.env", function() + local is_win32 = false + + setup(function() + _G.vim = { + o = setmetatable({}, { + __index = function(_, key) + if key == "shell" then + if is_win32 then + return "cmd.exe" + end + + return "bash" + end + + return nil + end + }), + fn = { + has = function(feature) + return feature == "win32" and is_win32 + end, + -- Mock a very poor version of shellescape() for Unix + -- This shouldn't be called for Windows + shellescape = function(str) + return "'" .. str .. "'" + end, + fnamemodify = function(shell, _) + return shell + end + } + } + end) + + teardown(function() + _G.vim = nil + end) + + before_each(function() + is_win32 = false + end) + + it("should escape values correctly on Unix", function() + eq("name='xxx' ", ale.env('name', 'xxx')) + eq("name='foo bar' ", ale.env('name', 'foo bar')) + end) + + it("should escape values correctly on Windows", function() + is_win32 = true + eq('set name=xxx && ', ale.env('name', 'xxx')) + eq('set "name=foo bar" && ', ale.env('name', 'foo bar')) + end) +end) diff --git a/test/lua/ale_get_filename_mappings_spec.lua b/test/lua/ale_get_filename_mappings_spec.lua new file mode 100644 index 00000000..1db45e32 --- /dev/null +++ b/test/lua/ale_get_filename_mappings_spec.lua @@ -0,0 +1,77 @@ +local eq = assert.are.same +local ale = require("ale") + +describe("ale.get_filename_mappings", function() + local buffer_map + + setup(function() + _G.vim = { + api = { + nvim_buf_get_var = function(buffer, key) + local buffer_table = buffer_map[buffer] or {} + local value = buffer_table[key] + + if value == nil then + error(key .. " is missing") + end + + return value + end, + }, + g = { + }, + } + end) + + teardown(function() + _G.vim = nil + end) + + before_each(function() + buffer_map = {[42] = {}} + _G.vim.g = {} + end) + + it("should return the correct mappings for given linters/fixers", function() + vim.g.ale_filename_mappings = { + a = {{"foo", "bar"}}, + b = {{"baz", "foo"}}, + } + + eq({{"foo", "bar"}}, ale.get_filename_mappings(42, "a")) + eq({{"baz", "foo"}}, ale.get_filename_mappings(42, "b")) + eq({}, ale.get_filename_mappings(42, "c")) + + buffer_map[42].ale_filename_mappings = {b = {{"abc", "xyz"}}} + + eq({}, ale.get_filename_mappings(42, "a")) + eq({{"abc", "xyz"}}, ale.get_filename_mappings(42, "b")) + eq({}, ale.get_filename_mappings(42, "c")) + end) + + it("should return arrays set for use with all tools", function() + vim.g.ale_filename_mappings = {{"foo", "bar"}} + + eq({{"foo", "bar"}}, ale.get_filename_mappings(42, "a")) + eq({{"foo", "bar"}}, ale.get_filename_mappings(42, "")) + eq({{"foo", "bar"}}, ale.get_filename_mappings(42, nil)) + + buffer_map[42].ale_filename_mappings = {{"abc", "xyz"}} + + eq({{"abc", "xyz"}}, ale.get_filename_mappings(42, "a")) + eq({{"abc", "xyz"}}, ale.get_filename_mappings(42, "")) + eq({{"abc", "xyz"}}, ale.get_filename_mappings(42, nil)) + end) + + it("should let you use * as a fallback", function() + vim.g.ale_filename_mappings = { + a = {{"foo", "bar"}}, + ["*"] = {{"abc", "xyz"}}, + } + + eq({{"foo", "bar"}}, ale.get_filename_mappings(42, "a")) + eq({{"abc", "xyz"}}, ale.get_filename_mappings(42, "b")) + eq({{"abc", "xyz"}}, ale.get_filename_mappings(42, "")) + eq({{"abc", "xyz"}}, ale.get_filename_mappings(42, nil)) + end) +end) diff --git a/test/lua/ale_has_spec.lua b/test/lua/ale_has_spec.lua new file mode 100644 index 00000000..e9120b28 --- /dev/null +++ b/test/lua/ale_has_spec.lua @@ -0,0 +1,27 @@ +local eq = assert.are.same +local ale = require("ale") + +describe("ale.has", function() + setup(function() + _G.vim = { + fn = { + ["ale#Has"] = function(feature) + if feature == "ale-4.0.0" then + return 1 + end + + return 0 + end, + }, + } + end) + + teardown(function() + _G.vim = nil + end) + + it("should return valuse from ale#Has correctly", function() + eq(true, ale.has("ale-4.0.0")) + eq(false, ale.has("ale-20.0.0")) + end) +end) diff --git a/test/lua/ale_lsp_spec.lua b/test/lua/ale_lsp_spec.lua new file mode 100644 index 00000000..b37fbe28 --- /dev/null +++ b/test/lua/ale_lsp_spec.lua @@ -0,0 +1,362 @@ +local eq = assert.are.same +local lsp = require("ale.lsp") + +describe("ale.lsp.start", function() + local start_calls + local rpc_connect_calls + local vim_fn_calls + local defer_calls + local nvim_default_capabilities + + setup(function() + _G.vim = { + defer_fn = function(func, delay) + table.insert(defer_calls, {func, delay}) + end, + fn = setmetatable({}, { + __index = function(_, key) + return function(...) + table.insert(vim_fn_calls, {key, ...}) + + if key == "ale#lsp#GetLanguage" then + return "python" + end + + if key ~= "ale#lsp_linter#HandleLSPDiagnostics" + and key ~= "ale#lsp#UpdateCapabilities" + and key ~= "ale#lsp#CallInitCallbacks" + then + assert(false, "Invalid ALE function: " .. key) + end + + return nil + end + end, + }), + lsp = { + rpc = { + connect = function(host, port) + return function(dispatch) + table.insert(rpc_connect_calls, { + host = host, + port = port, + dispatch = dispatch, + }) + end + end, + }, + start = function(...) + table.insert(start_calls, {...}) + + return 42 + end, + protocol = { + make_client_capabilities = function() + return nvim_default_capabilities + end, + }, + }, + } + end) + + teardown(function() + _G.vim = nil + end) + + before_each(function() + start_calls = {} + rpc_connect_calls = {} + vim_fn_calls = {} + defer_calls = {} + nvim_default_capabilities = { + textDocument = {}, + } + end) + + it("should start lsp programs with the correct arguments", function() + lsp.start({ + name = "server:/code", + cmd = "server", + root_dir = "/code", + -- This Boolean value somehow ends up in Dictionaries from + -- Vim for init_options, and we need to remove it. + init_options = {[true] = 123}, + }) + + -- Remove arguments with functions we can't apply equality checks + -- for easily. + for _, args in pairs(start_calls) do + args[1].handlers = nil + args[1].on_init = nil + args[1].get_language_id = nil + end + + eq({ + { + { + cmd = "server", + name = "server:/code", + root_dir = "/code", + init_options = {}, + }, + {attach = false, silent = true} + } + }, start_calls) + eq({}, vim_fn_calls) + end) + + it("should start lsp socket connections with the correct arguments", function() + lsp.start({ + name = "localhost:1234:/code", + host = "localhost", + port = 1234, + root_dir = "/code", + init_options = {foo = "bar"}, + }) + + local cmd + + -- Remove arguments with functions we can't apply equality checks + -- for easily. + for _, args in pairs(start_calls) do + cmd = args[1].cmd + args[1].cmd = nil + args[1].handlers = nil + args[1].on_init = nil + args[1].get_language_id = nil + end + + eq({ + { + { + name = "localhost:1234:/code", + root_dir = "/code", + init_options = {foo = "bar"}, + }, + {attach = false, silent = true} + } + }, start_calls) + + cmd("dispatch_value") + + eq({ + {dispatch = "dispatch_value", host = "localhost", port = 1234}, + }, rpc_connect_calls) + eq({}, vim_fn_calls) + end) + + it("should return the client_id value from vim.lsp.start", function() + eq(42, lsp.start({})) + end) + + it("should implement get_language_id correctly", function() + lsp.start({name = "server:/code"}) + + eq(1, #start_calls) + eq("python", start_calls[1][1].get_language_id(347, "ftype")) + eq({{"ale#lsp#GetLanguage", "server:/code", 347}}, vim_fn_calls) + end) + + it("should enable dynamicRegistration for the pull model", function() + nvim_default_capabilities = {textDocument = {diagnostic = {}}} + + lsp.start({name = "server:/code"}) + eq(1, #start_calls) + + eq( + { + textDocument = { + diagnostic = { + dynamicRegistration = true, + }, + }, + }, + start_calls[1][1].capabilities + ) + end) + + it("should initialize clients with ALE correctly", function() + lsp.start({name = "server:/code"}) + + eq(1, #start_calls) + + start_calls[1][1].on_init({server_capabilities = {cap = 1}}) + + eq({ + {"ale#lsp#UpdateCapabilities", "server:/code", {cap = 1}}, + }, vim_fn_calls) + eq(1, #defer_calls) + eq(2, #defer_calls[1]) + eq("function", type(defer_calls[1][1])) + eq(0, defer_calls[1][2]) + + defer_calls[1][1]() + + eq({ + {"ale#lsp#UpdateCapabilities", "server:/code", {cap = 1}}, + {"ale#lsp#CallInitCallbacks", "server:/code"}, + }, vim_fn_calls) + end) + + it("should configure handlers correctly", function() + lsp.start({name = "server:/code"}) + + eq(1, #start_calls) + + local handlers = start_calls[1][1].handlers + local handler_names = {} + + -- get keys from handlers + for key, _ in pairs(handlers) do + -- add key to handler_names mapping + handler_names[key] = true + end + + eq({ + ["textDocument/publishDiagnostics"] = true, + ["textDocument/diagnostic"] = true, + ["workspace/diagnostic/refresh"] = true, + }, handler_names) + end) + + it("should handle push model published diagnostics", function() + lsp.start({name = "server:/code"}) + + eq(1, #start_calls) + + local handlers = start_calls[1][1].handlers + + eq("function", type(handlers["textDocument/publishDiagnostics"])) + + handlers["textDocument/publishDiagnostics"](nil, { + uri = "file://code/foo.py", + diagnostics = { + { + lnum = 1, + end_lnum = 2, + col = 3, + end_col = 5, + severity = 1, + code = "123", + message = "Warning message", + } + }, + }) + + eq({ + { + "ale#lsp_linter#HandleLSPDiagnostics", + "server:/code", + "file://code/foo.py", + { + { + lnum = 1, + end_lnum = 2, + col = 3, + end_col = 5, + severity = 1, + code = "123", + message = "Warning message", + }, + }, + }, + }, vim_fn_calls) + end) + + it("should respond to workspace diagnostic refresh requests", function() + lsp.start({name = "server:/code"}) + + eq(1, #start_calls) + + local handlers = start_calls[1][1].handlers + + eq("function", type(handlers["workspace/diagnostic/refresh"])) + + eq({}, handlers["workspace/diagnostic/refresh"]()) + end) + + it("should handle pull model diagnostics", function() + lsp.start({name = "server:/code"}) + + eq(1, #start_calls) + + local handlers = start_calls[1][1].handlers + + eq("function", type(handlers["textDocument/diagnostic"])) + + handlers["textDocument/diagnostic"]( + nil, + { + kind = "full", + items = { + { + lnum = 1, + end_lnum = 2, + col = 3, + end_col = 5, + severity = 1, + code = "123", + message = "Warning message", + } + }, + }, + { + params = { + textDocument = { + uri = "file://code/foo.py", + }, + }, + } + ) + + eq({ + { + "ale#lsp_linter#HandleLSPDiagnostics", + "server:/code", + "file://code/foo.py", + { + { + lnum = 1, + end_lnum = 2, + col = 3, + end_col = 5, + severity = 1, + code = "123", + message = "Warning message", + }, + }, + }, + }, vim_fn_calls) + end) + + it("should handle unchanged pull model diagnostics", function() + lsp.start({name = "server:/code"}) + + eq(1, #start_calls) + + local handlers = start_calls[1][1].handlers + + eq("function", type(handlers["textDocument/diagnostic"])) + + handlers["textDocument/diagnostic"]( + nil, + {kind = "unchanged"}, + { + params = { + textDocument = { + uri = "file://code/foo.py", + }, + }, + } + ) + + eq({ + { + "ale#lsp_linter#HandleLSPDiagnostics", + "server:/code", + "file://code/foo.py", + "unchanged", + }, + }, vim_fn_calls) + end) +end) diff --git a/test/lua/ale_pad_spec.lua b/test/lua/ale_pad_spec.lua new file mode 100644 index 00000000..28f52469 --- /dev/null +++ b/test/lua/ale_pad_spec.lua @@ -0,0 +1,13 @@ +local eq = assert.are.same +local ale = require("ale") + +describe("ale.pad", function() + it("should pad non-empty strings", function() + eq(" foo", ale.pad("foo")) + end) + + it("should return empty strings for nil or empty strings", function() + eq("", ale.pad(nil)) + eq("", ale.pad("")) + end) +end) diff --git a/test/lua/ale_queue_spec.lua b/test/lua/ale_queue_spec.lua new file mode 100644 index 00000000..d9d5191f --- /dev/null +++ b/test/lua/ale_queue_spec.lua @@ -0,0 +1,40 @@ +local eq = assert.are.same +local ale = require("ale") + +describe("ale.queue", function() + local queue_calls + + setup(function() + _G.vim = { + fn = { + ["ale#Queue"] = function(...) + table.insert(queue_calls, {...}) + end, + }, + } + end) + + teardown(function() + _G.vim = nil + end) + + before_each(function() + queue_calls = {} + end) + + it("should call ale#Queue with the right arguments", function() + ale.queue(0) + ale.queue(0, "") + ale.queue(123, "lint_file") + ale.queue(0, "", 42) + ale.queue(123, "lint_file", 42) + + eq({ + {0, nil, nil}, + {0, "", nil}, + {123, "lint_file", nil}, + {0, "", 42}, + {123, "lint_file", 42}, + }, queue_calls) + end) +end) diff --git a/test/lua/ale_var_spec.lua b/test/lua/ale_var_spec.lua new file mode 100644 index 00000000..5c912724 --- /dev/null +++ b/test/lua/ale_var_spec.lua @@ -0,0 +1,51 @@ +local eq = assert.are.same +local ale = require("ale") + +describe("ale.var", function() + local buffer_map + + setup(function() + _G.vim = { + api = { + nvim_buf_get_var = function(buffer, key) + local buffer_table = buffer_map[buffer] or {} + local value = buffer_table[key] + + if value == nil then + error(key .. " is missing") + end + + return value + end, + }, + g = { + }, + } + end) + + teardown(function() + _G.vim = nil + end) + + before_each(function() + buffer_map = {} + _G.vim.g = {} + end) + + it("should return nil for undefined variables", function() + eq(nil, ale.var(1, "foo")) + end) + + it("should return buffer-local values, if set", function() + _G.vim.g.ale_foo = "global-value" + buffer_map[1] = {ale_foo = "buffer-value"} + + eq("buffer-value", ale.var(1, "foo")) + end) + + it("should return global values, if set", function() + _G.vim.g.ale_foo = "global-value" + + eq("global-value", ale.var(1, "foo")) + end) +end) diff --git a/test/lua/windows_escaping_spec.lua b/test/lua/windows_escaping_spec.lua new file mode 100644 index 00000000..86f5f52e --- /dev/null +++ b/test/lua/windows_escaping_spec.lua @@ -0,0 +1,61 @@ +local eq = assert.are.same +local ale = require("ale") + +describe("ale.escape for cmd.exe", function() + setup(function() + _G.vim = { + o = { + shell = "cmd.exe" + }, + fn = { + fnamemodify = function(shell, _) + return shell + end + } + } + end) + + teardown(function() + _G.vim = nil + end) + + it("should allow not escape paths without special characters", function() + eq("C:", ale.escape("C:")) + eq("C:\\", ale.escape("C:\\")) + eq("python", ale.escape("python")) + eq("C:\\foo\\bar", ale.escape("C:\\foo\\bar")) + eq("/bar/baz", ale.escape("/bar/baz")) + eq("nul", ale.escape("nul")) + eq("'foo'", ale.escape("'foo'")) + end) + + it("should escape Windows paths with spaces appropriately", function() + eq('"C:\\foo bar\\baz"', ale.escape('C:\\foo bar\\baz')) + eq('"^foo bar^"', ale.escape('^foo bar^')) + eq('"&foo bar&"', ale.escape('&foo bar&')) + eq('"|foo bar|"', ale.escape('|foo bar|')) + eq('"foo bar>"', ale.escape('>foo bar>')) + eq('"^foo bar^"', ale.escape('^foo bar^')) + eq('"\'foo\' \'bar\'"', ale.escape('\'foo\' \'bar\'')) + end) + + it("should use caret escapes on special characters", function() + eq('^^foo^^', ale.escape('^foo^')) + eq('^&foo^&', ale.escape('&foo&')) + eq('^|foo^|', ale.escape('|foo|')) + eq('^foo^>', ale.escape('>foo>')) + eq('^^foo^^', ale.escape('^foo^')) + eq('\'foo\'^^\'bar\'', ale.escape('\'foo\'^\'bar\'')) + end) + + it("should escape percent characters", function() + eq('%%foo%%', ale.escape('%foo%')) + eq('C:\foo%%\bar\baz%%', ale.escape('C:\foo%\bar\baz%')) + eq('"C:\foo bar%%\baz%%"', ale.escape('C:\foo bar%\baz%')) + eq('^^%%foo%%', ale.escape('^%foo%')) + eq('"^%%foo%% %%bar%%"', ale.escape('^%foo% %bar%')) + eq('"^%%foo%% %%bar%% """""', ale.escape('^%foo% %bar% ""')) + end) +end) diff --git a/test/python/test_deoplete_source.py b/test/python/test_deoplete_source.py new file mode 100644 index 00000000..74a42dc2 --- /dev/null +++ b/test/python/test_deoplete_source.py @@ -0,0 +1,121 @@ +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, commands): + self.__call_list = call_list + self.__call_results = call_results + + self.__commands = commands + + def call(self, function, *args): + self.__call_list.append((function, args)) + + return self.__call_results.get(function, 0) + + def command(self, command): + self.__commands.append(command) + + +class DeopleteSourceTest(unittest.TestCase): + def setUp(self): + super(DeopleteSourceTest, self).setUp() + + self.call_list = [] + self.call_results = {'ale#completion#CanProvideCompletions': 1} + self.commands = [] + self.source = ale_module.Source('vim') + self.source.vim = VimMock( + self.call_list, self.call_results, self.commands) + + 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, { + 'input_patterns': { + '_': r'\.\w*$', + 'rust': r'(\.|::)\w*$', + 'typescript': r'(\.|\'|")\w*$', + 'cpp': r'(\.|::|->)\w*$', + 'c': r'(\.|->)\w*$', + }, + 'is_bytepos': True, + 'is_volatile': True, + 'mark': '[L]', + 'min_pattern_length': 1, + 'name': 'ale', + 'rank': 1000, + }) + + def test_complete_position(self): + self.call_results['ale#completion#GetCompletionPositionForDeoplete'] = 2 + context = {'input': 'foo'} + + self.assertEqual(self.source.get_complete_position(context), 2) + self.assertEqual(self.call_list, [ + ('ale#completion#GetCompletionPositionForDeoplete', ('foo',)), + ]) + + def test_request_completion_results(self): + context = {'event': 'TextChangedI', 'is_refresh': True} + + self.assertEqual(self.source.gather_candidates(context), []) + self.assertEqual(self.call_list, [ + ('ale#completion#CanProvideCompletions', ()), + ]) + self.assertEqual(self.commands, [ + "call ale#completion#GetCompletions('ale-callback', " + \ + "{'callback': {completions -> deoplete#auto_complete() }})" + ]) + + def test_request_completion_results_from_buffer_without_providers(self): + self.call_results['ale#completion#CanProvideCompletions'] = 0 + context = {'event': 'TextChangedI', 'is_refresh': True} + + self.assertIsNone(self.source.gather_candidates(context), []) + self.assertEqual(self.call_list, [ + ('ale#completion#CanProvideCompletions', ()), + ]) + + def test_async_event(self): + context = {'event': 'Async', 'is_refresh': 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(self.call_list, [ + ('ale#completion#CanProvideCompletions', ()), + ('ale#completion#GetCompletionResult', ()), + ]) diff --git a/test/script/block-padding-checker b/test/script/block-padding-checker new file mode 100755 index 00000000..2feab6d0 --- /dev/null +++ b/test/script/block-padding-checker @@ -0,0 +1,145 @@ +#!/usr/bin/env python +""" +This script checks for missing or forbidden blank lines before or after +particular Vim commands. This script ensures that VimL scripts are padded +correctly, so they are easier to read. +""" + +import sys +import re + +INDENTATION_RE = re.compile(r'^ *') +COMMENT_LINE_RE = re.compile(r'^ *"') +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']) +MIDDLE_BLOCKS = set(['else', 'elseif', 'catch', 'finally']) +TERMINATORS = set(['return', 'throw']) + +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): + for line_number, line in enumerate(line_iter, 1): + if not COMMENT_LINE_RE.match(line): + yield (line_number, line) + + +def check_lines(line_iter): + previous_indentation_level = None + previous_command = None + previous_line_blank = False + + for line_number, line in remove_comment_lines(line_iter): + if len(line) == 0: + # Check for commands where we shouldn't have blank lines after + # them, like `else` or the start of blocks like `function`. + if ( + previous_command is not None + and previous_command in WHITESPACE_FORBIDDEN_AFTER_SET + ): + yield ( + line_number, + 'Blank line forbidden after `%s`' % (previous_command,) + ) + + previous_line_blank = True + previous_command = None + else: + indentation_level = INDENTATION_RE.match(line).end() + command_match = COMMAND_RE.match(line) + + 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 ( + command in WHITESPACE_BEFORE_SET + and previous_indentation_level is not None + and indentation_level == previous_indentation_level + and previous_line_blank is False + ): + yield ( + line_number, + 'Blank line required before `%s`' % (command,) + ) + + # Check for commands where we shouldn't have blank lines before + # them, like `else` or the end of blocks like `endfunction`. + if ( + command in WHITESPACE_FORBIDDEN_BEFORE_SET + and previous_line_blank is True + ): + yield ( + line_number - 1, + 'Blank line forbidden before `%s`' % (command,) + ) + + # Check for commands requiring blank lines after them, if they + # aren't at the end of a block. + if ( + previous_command is not None + and previous_command in WHITESPACE_AFTER_SET + and previous_indentation_level is not None + and indentation_level == previous_indentation_level + and previous_line_blank is False + ): + yield ( + line_number - 1, + 'Blank line required after `%s`' % (command,) + ) + + previous_command = command + 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 + + for filename in sys.argv[1:]: + with open(filename) as vim_file: + line_iter = (line.rstrip() for line in vim_file) + + for line_number, message in check_lines(line_iter): + print('%s:%d %s' % (filename, line_number, message)) + status = 1 + + sys.exit(status) + + +if __name__ == "__main__": + main() diff --git a/test/script/check-duplicate-tags b/test/script/check-duplicate-tags new file mode 100755 index 00000000..ec1de788 --- /dev/null +++ b/test/script/check-duplicate-tags @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +grep --exclude=tags -roh '\*.*\*$' doc | sort | uniq -d diff --git a/test/script/check-supported-tools-tables b/test/script/check-supported-tools-tables new file mode 100755 index 00000000..d238e77f --- /dev/null +++ b/test/script/check-supported-tools-tables @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -e +set -u + +# This script compares the table of supported tools in both supported-tools.md +# (for GitHub) and doc/ale-supported-languages-and-tools.txt (for vim), so we +# can complain if they don't match up. + +doc_file="$(mktemp -t doc.XXXXXXXX)" +doc_sorted_file="$(mktemp -t doc-sorted.XXXXXXXX)" +readme_file="$(mktemp -t readme.XXXXXXXX)" + +while read -r; do + if [[ "$REPLY" =~ ^! ]]; then + language="${REPLY/!/}" + else + echo "$language - $REPLY" + fi +done < <( + grep '^\*\|^ *\*' doc/ale-supported-languages-and-tools.txt \ + | sed -e '1,2d' \ + | sed 's/^\* */!/' \ + | sed -E 's/^ *\* *|!!|\^|\(.*\)|`//g' \ + | sed 's/ *$//' +) > "$doc_file" + +while read -r; do + if [[ "$REPLY" =~ ^! ]]; then + language="${REPLY/!/}" + else + echo "$language - $REPLY" + fi +done < <( + grep '^\*\|^ *\*' supported-tools.md \ + | sed 's/^\* */!/' \ + | sed -E '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. +LC_ALL=en_US.UTF-8 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-tag-alignment b/test/script/check-tag-alignment new file mode 100755 index 00000000..d41db160 --- /dev/null +++ b/test/script/check-tag-alignment @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +exit_code=0 + +# Documentation tags need to be aligned to the right margin, so look for +# tags which aren't at the right margin. +grep ' \*[^*]\+\*$' doc/ -r \ + | awk '{ sep = index($0, ":"); if (length(substr($0, sep + 1 )) < 79) { print } }' \ + | grep . && exit_code=1 + +exit $exit_code diff --git a/test/script/check-tag-references b/test/script/check-tag-references new file mode 100755 index 00000000..5f17618e --- /dev/null +++ b/test/script/check-tag-references @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -e + +exit_code=0 +tag_regex='[gb]\?:\?\(ale\|ALE\)[a-zA-Z_\-]\+' + +tags="$(mktemp -t tags.XXXXXXXX)" +refs="$(mktemp -t refs.XXXXXXXX)" +result="$(mktemp -t refs.XXXXXXXX)" +# Grep for tags and references, and complain if we find a reference without +# a tag for the reference. Only our tags will be included. +grep --exclude=tags -roh "\\*$tag_regex\\*" doc | sed 's/*//g' | sort -u > "$tags" +grep --exclude=tags -roh "|$tag_regex|" doc | sed 's/|//g' | sort -u > "$refs" + +# Collect tags in a file we can display to the user if there are differences. +comm -23 "$refs" "$tags" > "$result" + +exit_code=0 + +# If there are differences, show them and error out. +if ! [[ $(wc -l < "$result") -eq 0 ]]; then + cat "$result" + exit_code=1 +fi + +rm "$tags" +rm "$refs" +rm "$result" +exit $exit_code diff --git a/test/script/check-toc b/test/script/check-toc new file mode 100755 index 00000000..f3f8a9ea --- /dev/null +++ b/test/script/check-toc @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +set -e +set -u + +# This script checks that the table of contents for the supported tools is +# sorted, and that the table matches the files. + +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_section_start_line" + "$toc_start_offset" - 1)" +toc_section_size="$( \ + tail -n +"$toc_start_line" doc/ale.txt \ + | grep -m1 -n '^===*$' \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +# shellcheck disable=SC2003 +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)" +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/' \ + > "$toc_file" + +# Get all of the doc files in a natural sorted order. +doc_files="$(/usr/bin/env ls -1v doc | grep '^ale-' | sed 's/^/doc\//' | paste -sd ' ' -)" + +# shellcheck disable=SC2086 +grep -h '\*ale-.*-options\|^[a-z].*\*ale-.*\*$' $doc_files \ + | sed 's/^/ /' \ + | sed 's/ALE Shell Integration/ALE sh Integration/' \ + | sed 's/ALE BibTeX Integration/ALE bib Integration/' \ + | sed 's/ ALE \(.*\) Integration/\1/' \ + | sed 's/ *\*\(..*\)\*$/, \1/' \ + | tr '[:upper:]' '[:lower:]' \ + | sed 's/objective-c/objc/' \ + | sed 's/c++/cpp/' \ + > "$heading_file" + +exit_code=0 +in_section=0 +section_index=0 + +# Prefix numbers to table of contents entries so that sections aren't mixed up +# with sub-sections when they are sorted. +while read -r; do + if [[ "$REPLY" =~ ^\ ]]; then + if ! ((in_section)); then + let section_index='section_index + 1' + in_section=1 + fi + else + if ((in_section)); then + let section_index='section_index + 1' + in_section=0 + fi + fi + + echo "$section_index $REPLY" >> "$tagged_toc_file" +done < "$toc_file" + +# Sort the sections and sub-sections and remove the tags. +sort -sn "$tagged_toc_file" | sed 's/[0-9][0-9]* //' > "$sorted_toc_file" + +echo 'Check for bad ToC sorting:' +echo +diff -U2 "$sorted_toc_file" "$toc_file" || exit_code=$? + +echo 'Check for mismatched ToC and headings:' +echo +diff -U3 "$toc_file" "$heading_file" || exit_code=$? + +rm "$toc_file" +rm "$heading_file" +rm "$tagged_toc_file" +rm "$sorted_toc_file" + +exit "$exit_code" diff --git a/test/script/custom-checks b/test/script/custom-checks new file mode 100755 index 00000000..4cf1530c --- /dev/null +++ b/test/script/custom-checks @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -e +set -u + +exit_code=0 +docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$DOCKER_RUN_IMAGE") + +echo '========================================' +echo 'Running custom linting rules' +echo '========================================' +echo 'Custom warnings/errors follow:' +echo + +set -o pipefail +"$DOCKER" run "${docker_flags[@]}" test/script/custom-linting-rules . || exit_code=$? +set +o pipefail +echo + +echo '========================================' +echo 'Checking for duplicate tags' +echo '========================================' +echo 'Duplicate tags follow:' +echo + +set -o pipefail +"$DOCKER" run "${docker_flags[@]}" test/script/check-duplicate-tags . || exit_code=$? +set +o pipefail +echo + +echo '========================================' +echo 'Checking for invalid tag references' +echo '========================================' +echo 'Invalid tag references tags follow:' +echo + +set -o pipefail +"$DOCKER" run "${docker_flags[@]}" test/script/check-tag-references || exit_code=$? +set +o pipefail + +echo '========================================' +echo 'diff supported-tools.md and doc/ale-supported-languages-and-tools.txt tables' +echo '========================================' +echo 'Differences follow:' +echo + +set -o pipefail +"$DOCKER" run "${docker_flags[@]}" test/script/check-supported-tools-tables || exit_code=$? +set +o pipefail + +echo '========================================' +echo 'Look for badly aligned doc tags' +echo '========================================' +echo 'Badly aligned tags follow:' +echo + +set -o pipefail +"$DOCKER" run "${docker_flags[@]}" test/script/check-tag-alignment || exit_code=$? +set +o pipefail + +echo '========================================' +echo 'Look for table of contents issues' +echo '========================================' +echo + +set -o pipefail +"$DOCKER" run "${docker_flags[@]}" test/script/check-toc || exit_code=$? +set +o pipefail + +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 new file mode 100755 index 00000000..486a0db7 --- /dev/null +++ b/test/script/custom-linting-rules @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +set -e +set -u + +# This Bash script implements custom sanity checks for scripts beyond what +# Vint covers, which are easy to check with regex. + +# A flag for automatically fixing some errors. +FIX_ERRORS=0 +RETURN_CODE=0 + +function print_help() { + echo "Usage: test/script/custom-linting-rules [--fix] [DIRECTORY]" 1>&2 + echo 1>&2 + echo " -h, --help Print this help text" 1>&2 + echo " --fix Automatically fix some errors" 1>&2 + exit 1 +} + +while [ $# -ne 0 ]; do + case $1 in + -h) ;& --help) + print_help + ;; + --fix) + FIX_ERRORS=1 + shift + ;; + --) + shift + break + ;; + -?*) + echo "Invalid argument: $1" 1>&2 + exit 1 + ;; + *) + break + ;; + esac +done + +if [ $# -eq 0 ] || [ -z "$1" ]; then + print_help +fi + +shopt -s globstar + +directories=("$@") + +check_errors() { + regex="$1" + message="$2" + include_arg='' + exclude_arg='' + + if [ $# -gt 2 ]; then + include_arg="--include $3" + fi + + if [ $# -gt 3 ]; then + shift + shift + shift + + while (( "$#" )); do + exclude_arg="$exclude_arg --exclude $1" + shift + done + fi + + for directory in "${directories[@]}"; do + # shellcheck disable=SC2086 + while read -r; do + line=$(cut -d ":" -f2 <<< "$REPLY") + + if ((line > 1)); then + line=$((line - 1)) + file=$(cut -d ":" -f1 <<< "$REPLY") + + if sed -n "${line},${line}p" $file | grep -q '^ *" *no-custom-checks$'; then + continue + fi + fi + + RETURN_CODE=1 + echo "$REPLY $message" + done < <(grep -H -n "$regex" $include_arg $exclude_arg "$directory"/**/*.vim \ + | grep -v 'no-custom-checks' \ + | grep -o '^[^:]\+:[0-9]\+' \ + | sed 's:^\./::') + done +} + +if (( FIX_ERRORS )); then + for directory in "${directories[@]}"; do + sed -i "s/^\(function.*)\) *$/\1 abort/" "$directory"/**/*.vim + sed -i "s/shellescape(/ale#Escape(/" "$directory"/**/*.vim + sed -i 's/==#/is#/g' "$directory"/**/*.vim + sed -i 's/==?/is?/g' "$directory"/**/*.vim + sed -i 's/!=#/isnot#/g' "$directory"/**/*.vim + sed -i 's/!=?/isnot?/g' "$directory"/**/*.vim + # Improving type checks. + sed -i $'s/\\(==.\\?\\|is\\) type([\'"]\+)/is v:t_string/g' "$directory"/**/*.vim + sed -i 's/\(==.\?\|is\) type([0-9]\+)/is v:t_number/g' "$directory"/**/*.vim + sed -i 's/\(==.\?\|is\) type(\[\])/is v:t_list/g' "$directory"/**/*.vim + sed -i 's/\(==.\?\|is\) type({})/is v:t_dict/g' "$directory"/**/*.vim + sed -i 's/\(==.\?\|is\) type(function([^)]\+))/is v:t_func/g' "$directory"/**/*.vim + sed -i $'s/\\(!=.\\?\\|isnot\\) type([\'"]\+)/isnot v:t_string/g' "$directory"/**/*.vim + sed -i 's/\(!=.\?\|isnot\) type([0-9]\+)/isnot v:t_number/g' "$directory"/**/*.vim + sed -i 's/\(!=.\?\|isnot\) type(\[\])/isnot v:t_list/g' "$directory"/**/*.vim + sed -i 's/\(!=.\?\|isnot\) type({})/isnot v:t_dict/g' "$directory"/**/*.vim + sed -i 's/\(!=.\?\|isnot\) type(function([^)]\+))/isnot v:t_func/g' "$directory"/**/*.vim + done +fi + +# The arguments are: regex, explanation, [filename_filter], [list, of, exclusions] +check_errors \ + '^function.*) *$' \ + 'Function without abort keyword (See :help except-compat)' +check_errors '^function[^!]' 'function without !' +check_errors ' \+$' 'Trailing whitespace' +check_errors '^ * end\?i\? *$' 'Write endif, not en, end, or endi' +check_errors '^ [^ ]' 'Use four spaces, not two spaces' +check_errors $'\t' 'Use four spaces, not tabs' +# This check should prevent people from using a particular inconsistent name. +check_errors 'let g:ale_\w\+_\w\+_args =' 'Name your option g:ale___options instead' +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" +check_errors '==?' "Use 'is?' instead of '==?'. 0 ==? 'foobar' is true" +check_errors '!=#' "Use 'isnot#' instead of '!=#'. 0 !=# 'foobar' is false" +check_errors '!=?' "Use 'isnot?' instead of '!=?'. 0 !=? 'foobar' is false" +check_errors '^ *:\?echo' "Stray echo line. Ignore with \" no-custom-checks if needed" +check_errors '^ *:\?redir' 'User execute() instead of redir' +# Exclusions for grandfathered-in exceptions +exclusions="clojure/clj_kondo.vim elixir/elixir_ls.vim go/golangci_lint.vim swift/swiftformat.vim" +# shellcheck disable=SC2086 +check_errors $'name.:.*\'[a-z_]*[^a-z_0-9][a-z_0-9]*\',$' 'Use snake_case names for linters' '*/ale_linters/*' $exclusions +# Checks for improving type checks. +check_errors $'\\(==.\\?\\|is\\) type([\'"]\+)' "Use 'is v:t_string' instead" +check_errors '\(==.\?\|is\) type([0-9]\+)' "Use 'is v:t_number' instead" +check_errors '\(==.\?\|is\) type(\[\])' "Use 'is v:t_list' instead" +check_errors '\(==.\?\|is\) type({})' "Use 'is v:t_dict' instead" +check_errors '\(==.\?\|is\) type(function([^)]\+))' "Use 'is v:t_func' instead" +check_errors $'\\(!=.\\?\\|isnot\\) type([\'"]\+)' "Use 'isnot v:t_string' instead" +check_errors '\(!=.\?\|isnot\) type([0-9]\+)' "Use 'isnot v:t_number' instead" +check_errors '\(!=.\?\|isnot\) type(\[\])' "Use 'isnot v:t_list' instead" +check_errors '\(!=.\?\|isnot\) type({})' "Use 'isnot v:t_dict' instead" +check_errors '\(!=.\?\|isnot\) type(function([^)]\+))' "Use 'isnot v:t_func' instead" + +# Run a Python script to find lines that require padding around them. For +# users without Python installed, we'll skip these checks. GitHub Actions will +# run the script. +if command -v python > /dev/null; then + if ! test/script/block-padding-checker "$directory"/**/*.vim; then + RETURN_CODE=1 + fi +fi + +exit $RETURN_CODE diff --git a/test/script/dumb_named_pipe_server.py b/test/script/dumb_named_pipe_server.py new file mode 100644 index 00000000..a77e538c --- /dev/null +++ b/test/script/dumb_named_pipe_server.py @@ -0,0 +1,42 @@ +""" +This Python script creates a named pipe server that does nothing but send its input +back to the client that connects to it. Only one argument must be given, the path +of a named pipe to bind to. +""" +import os +import socket +import sys + + +def main(): + if len(sys.argv) < 2: + sys.exit('You must specify a filepath') + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if os.path.exists(sys.argv[1]): + os.remove(sys.argv[1]) + sock.bind(sys.argv[1]) + sock.listen(0) + + pid = os.fork() + + if pid: + print(pid) + sys.exit() + + while True: + connection = sock.accept()[0] + connection.settimeout(5) + + while True: + try: + connection.send(connection.recv(1024)) + except socket.timeout: + break + + connection.close() + + +if __name__ == "__main__": + main() diff --git a/test/script/dumb_tcp_client.py b/test/script/dumb_tcp_client.py new file mode 100644 index 00000000..3a728b02 --- /dev/null +++ b/test/script/dumb_tcp_client.py @@ -0,0 +1,33 @@ +""" +This is just a script for testing that the dumb TCP server actually works +correctly, for verifying that problems with tests are in Vim. Pass the +same port number given to the test server to check that it's working. +""" +import socket +import sys + + +def main(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex(('127.0.0.1', int(sys.argv[1]))) + + if result: + sock.close() + sys.exit("Couldn't connect to the socket!") + + data_sent = 'x' * 1024 + + sock.send(data_sent) + data_received = sock.recv(1024) + + if data_sent != data_received: + sock.close() + sys.exit("Data sent didn't match data received.") + + sock.close() + + print("Everything was just fine.") + + +if __name__ == "__main__": + main() diff --git a/test/script/dumb_tcp_server.py b/test/script/dumb_tcp_server.py new file mode 100644 index 00000000..c15db65e --- /dev/null +++ b/test/script/dumb_tcp_server.py @@ -0,0 +1,40 @@ +""" +This Python script creates a TCP server that does nothing but send its input +back to the client that connects to it. Only one argument must be given, a port +to bind to. +""" +import os +import socket +import sys + + +def main(): + if len(sys.argv) < 2 or not sys.argv[1].isdigit(): + sys.exit('You must specify a port number') + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('127.0.0.1', int(sys.argv[1]))) + sock.listen(0) + + pid = os.fork() + + if pid: + print(pid) + sys.exit() + + while True: + connection = sock.accept()[0] + connection.settimeout(5) + + while True: + try: + connection.send(connection.recv(1024)) + except socket.timeout: + break + + connection.close() + + +if __name__ == "__main__": + main() diff --git a/test/script/run-lua-tests b/test/script/run-lua-tests new file mode 100755 index 00000000..ac538425 --- /dev/null +++ b/test/script/run-lua-tests @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -e +set -u + +docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin/test/lua "$DOCKER_RUN_IMAGE") + +quiet=0 + +while [ $# -ne 0 ]; do + case $1 in + -q) + quiet=1 + shift + ;; + --) + shift + break + ;; + -?*) + echo "Invalid argument: $1" 1>&2 + exit 1 + ;; + *) + break + ;; + esac +done + +function filter-busted-output() { + local hit_failure_line=0 + + while read -r; do + if ((quiet)); then + # If we're using the quiet flag, the filter out lines until we hit + # the first line with "Failure" and then print the rest. + if ((hit_failure_line)); then + echo "$REPLY" + elif [[ "$REPLY" = *'Failure'* ]]; then + hit_failure_line=1 + echo "$REPLY" + fi + else + echo "$REPLY" + fi + done +} + +exit_code=0 + +set -o pipefail +"$DOCKER" run -a stdout "${docker_flags[@]}" /usr/bin/busted-5.1 \ + -m '../../lua/?.lua;../../lua/?/init.lua' . \ + --output utfTerminal | filter-busted-output || exit_code=$? +set +o pipefail + +exit "$exit_code" diff --git a/test/script/run-vader-tests b/test/script/run-vader-tests new file mode 100755 index 00000000..a7488116 --- /dev/null +++ b/test/script/run-vader-tests @@ -0,0 +1,165 @@ +#!/usr/bin/env bash + +set -e +set -u + +docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$DOCKER_RUN_IMAGE") +red='\033[0;31m' +green='\033[0;32m' +nc='\033[0m' +verbose=0 +quiet=0 + +while [ $# -ne 0 ]; do + case $1 in + -v) + verbose=1 + shift + ;; + -q) + quiet=1 + shift + ;; + --) + shift + break + ;; + -?*) + echo "Invalid argument: $1" 1>&2 + exit 1 + ;; + *) + break + ;; + esac +done + +vim="$1" +tests="$2" + +echo "$vim" + +case $vim in + # Neovim 0.6+ requires headless argument to load Vader tests. + neovim*) + headless='--headless' + ;; + *) + headless='' + ;; +esac + +# This file will be used to track if tests ran or not. +# We can't use a variable, because we need to set a value in a sub-shell. +run_file="$(mktemp -t tests_ran.XXXXXXXX)" + +function filter-vader-output() { + local hit_first_vader_line=0 + # When verbose mode is off, suppress output until Vader starts. + local start_output="$verbose" + local filtered_data='' + + while read -r; do + # Search for the first Vader output line. + # We can try starting tests again if they don't start. + if ((!hit_first_vader_line)); then + if [[ "$REPLY" = *'Starting Vader:'* ]]; then + hit_first_vader_line=1 + fi + fi + + if ((!start_output)); then + if ((hit_first_vader_line)); then + start_output=1 + else + continue + fi + fi + + if ((quiet)); then + if [[ "$REPLY" = *'Starting Vader:'* ]]; then + filtered_data="$REPLY" + elif [[ "$REPLY" = *'Success/Total'* ]]; then + success="$(echo -n "$REPLY" | grep -o '[0-9]\+/' | head -n1 | cut -d/ -f1)" + total="$(echo -n "$REPLY" | grep -o '/[0-9]\+' | head -n1 | cut -d/ -f2)" + + if [ "$success" -lt "$total" ]; then + echo "$filtered_data" + echo "$REPLY" + fi + + filtered_data='' + else + filtered_data="$filtered_data"$'\n'"$REPLY" + fi + else + echo "$REPLY" + fi + done + + # Note that we managed to get the Vader tests started if we did. + if ((hit_first_vader_line)); then + echo 1 > "$run_file" + fi +} + +function color-vader-output() { + while read -r; do + if [[ "$REPLY" = *'[EXECUTE] (X)'* ]]; then + echo -en "$red" + elif [[ "$REPLY" = *'[EXECUTE]'* ]] || [[ "$REPLY" = *'[ GIVEN]'* ]]; then + echo -en "$nc" + fi + + if [[ "$REPLY" = *'Success/Total'* ]]; then + success="$(echo -n "$REPLY" | grep -o '[0-9]\+/' | head -n1 | cut -d/ -f1)" + total="$(echo -n "$REPLY" | grep -o '/[0-9]\+' | head -n1 | cut -d/ -f2)" + + if [ "$success" -lt "$total" ]; then + echo -en "$red" + else + echo -en "$green" + fi + + echo "$REPLY" + echo -en "$nc" + else + echo "$REPLY" + fi + done + + echo -en "$nc" +} + +echo +echo '========================================' +echo "Running tests for $vim" +echo '========================================' +echo + +tries=0 + +while [ "$tries" -lt 5 ]; do + tries=$((tries + 1)) + + exit_code=0 + set -o pipefail + # shellcheck disable=SC2086 + "$DOCKER" run -a stderr -e VADER_OUTPUT_FILE=/dev/stderr "${docker_flags[@]}" \ + "/vim-build/bin/$vim" -u test/vimrc ${headless} \ + "+Vader! $tests" 2>&1 | filter-vader-output | color-vader-output || exit_code=$? + set +o pipefail + + if [ -s "$run_file" ]; then + break + fi +done + +if [ "$tries" -gt 1 ]; then + echo + echo "Tried to run tests $tries times" +fi + +rm "$run_file" + +exit "$exit_code" diff --git a/test/script/run-vint b/test/script/run-vint new file mode 100755 index 00000000..4eacbe77 --- /dev/null +++ b/test/script/run-vint @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e +set -u + +exit_code=0 +docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$DOCKER_RUN_IMAGE") + +echo '========================================' +echo 'Running Vint to lint our code' +echo '========================================' +echo 'Vint warnings/errors follow:' +echo + +set -o pipefail +"$DOCKER" run -a stdout "${docker_flags[@]}" vint -s . || exit_code=$? +set +o pipefail +echo + +exit $exit_code diff --git a/test/sign/test_linting_sets_signs.vader b/test/sign/test_linting_sets_signs.vader new file mode 100644 index 00000000..7438c2d6 --- /dev/null +++ b/test/sign/test_linting_sets_signs.vader @@ -0,0 +1,85 @@ +Given foobar (Some imaginary filetype): + var y = 3+3; + var y = 3 + +Before: + Save g:ale_buffer_info + Save g:ale_echo_cursor + 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 + Save g:ale_command_wrapper + Save g:ale_use_neovim_diagnostics_api + + let g:ale_command_wrapper = '' + 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 + let g:ale_set_loclist = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + let g:ale_use_neovim_diagnostics_api = 0 + + call ale#sign#Clear() + + function! TestCallback(buffer, output) + return [ + \ {'lnum': 1, 'text': 'foo', 'type': 'W'}, + \ {'lnum': 2, 'text': 'foo', 'type': 'E'}, + \] + endfunction + + function! CollectSigns() + redir => l:output + if has('nvim-0.4.2') || has('patch-8.1.614') + silent exec 'sign place group=ale_signs' + else + silent exec 'sign place' + endif + redir END + + let l:actual_sign_list = [] + + for l:line in split(l:output, "\n") + let l:match = matchlist(l:line, ale#sign#ParsePattern()) + + if len(l:match) > 0 + call add(l:actual_sign_list, [l:match[1], l:match[3]]) + endif + endfor + + return l:actual_sign_list + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \}) + +After: + delfunction TestCallback + delfunction CollectSigns + + unlet! g:ale_run_synchronously_callbacks + call ale#sign#Clear() + 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() + +Execute(Signs should not be set when diagnostics API integration is enabled): + let g:ale_use_neovim_diagnostics_api = 1 + ALELint + call ale#test#FlushJobs() + + AssertEqual [], CollectSigns() diff --git a/test/sign/test_sign_column_highlighting.vader b/test/sign/test_sign_column_highlighting.vader new file mode 100644 index 00000000..7ea5eb0f --- /dev/null +++ b/test/sign/test_sign_column_highlighting.vader @@ -0,0 +1,68 @@ +Before: + Save g:ale_change_sign_column_color + Save &verbose + + function! ParseHighlight(name) abort + redir => l:output + silent execute 'highlight ' . a:name + redir end + + return substitute(join(split(l:output)[2:]), ' Last set.*', '', '') + endfunction + + function! SetHighlight(name, syntax) abort + let l:match = matchlist(a:syntax, '\vlinks to (.+)$') + + if !empty(l:match) + execute 'highlight link ' . a:name . ' ' . l:match[1] + else + execute 'highlight ' . a:name . ' ' a:syntax + endif + endfunction + + let g:sign_highlight = ParseHighlight('SignColumn') + +After: + Restore + + delfunction ParseHighlight + call SetHighlight('SignColumn', g:sign_highlight) + delfunction SetHighlight + unlet! g:sign_highlight + + call ale#sign#Clear() + +Execute(The SignColumn highlight shouldn't be changed if the option is off): + let g:ale_change_sign_column_color = 0 + let b:sign_highlight = ParseHighlight('SignColumn') + + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'}, + \]) + AssertEqual b:sign_highlight, ParseHighlight('SignColumn') + + call ale#sign#SetSigns(bufnr(''), []) + AssertEqual b:sign_highlight, ParseHighlight('SignColumn') + +Execute(The SignColumn highlight should be set and reset): + let g:ale_change_sign_column_color = 1 + + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'}, + \]) + AssertEqual 'links to ALESignColumnWithErrors', ParseHighlight('SignColumn') + + call ale#sign#SetSigns(bufnr(''), []) + AssertEqual 'links to ALESignColumnWithoutErrors', ParseHighlight('SignColumn') + +Execute(The SignColumn should be correctly parsed when verbose=1): + set verbose=1 + highlight SignColumn ctermfg=246 ctermbg=7 guifg=#839496 guibg=Grey + + call ale#sign#SetUpDefaultColumnWithoutErrorsHighlight() + + AssertEqual + \ has('nvim') + \ ? 'ctermfg=246 ctermbg=7 guifg=#839496 guibg=Grey' + \ : 'term=standout ctermfg=246 ctermbg=7 guifg=#839496 guibg=Grey', + \ ParseHighlight('ALESignColumnWithoutErrors') diff --git a/test/sign/test_sign_limits.vader b/test/sign/test_sign_limits.vader new file mode 100644 index 00000000..2b4942e6 --- /dev/null +++ b/test/sign/test_sign_limits.vader @@ -0,0 +1,52 @@ +Before: + Save g:ale_max_signs + + let g:ale_max_signs = -1 + + function! SetNProblems(sign_count) + let l:loclist = [] + let l:range = range(1, a:sign_count) + call setline(1, l:range) + + for l:index in l:range + call add(l:loclist, { + \ 'bufnr': bufnr(''), + \ 'lnum': l:index, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'a', + \}) + endfor + + call ale#sign#SetSigns(bufnr(''), l:loclist) + + return sort(map(ale#sign#FindCurrentSigns(bufnr(''))[1], 'v:val[0]'), 'n') + endfunction + +After: + Restore + + unlet! b:ale_max_signs + + delfunction SetNProblems + + call ale#sign#Clear() + +Execute(There should be no limit on signs with negative numbers): + AssertEqual range(1, 42), SetNProblems(42) + +Execute(0 signs should be set when the max is 0): + let g:ale_max_signs = 0 + + AssertEqual [], SetNProblems(42) + +Execute(10 signs should be set when the max is 10): + let g:ale_max_signs = 10 + + " We'll check that we set signs for the first 10 items, not other lines. + AssertEqual range(1, 10), SetNProblems(42) + +Execute(5 signs should be set when the max is 5 for the buffer): + let b:ale_max_signs = 5 + + AssertEqual range(1, 5), SetNProblems(42) diff --git a/test/sign/test_sign_parsing.vader b/test/sign/test_sign_parsing.vader new file mode 100644 index 00000000..fb5f3977 --- /dev/null +++ b/test/sign/test_sign_parsing.vader @@ -0,0 +1,88 @@ +Execute (Parsing English signs should work): + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[9, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([ + \ 'Signs for app.js:', + \ ' line=9 id=1000001 group=ale_signs name=ALEWarningSign', + \ ]) + else + AssertEqual + \ [0, [[9, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([ + \ 'Signs for app.js:', + \ ' line=9 id=1000001 name=ALEWarningSign', + \ ]) + endif + +Execute (Parsing Russian signs should work): + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' строка=1 id=1000001 группа=ale_signs имя=ALEErrorSign']) + else + AssertEqual + \ [0, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' строка=1 id=1000001 имя=ALEErrorSign']) + endif + +Execute (Parsing Japanese signs should work): + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' 行=1 識別子=1000001 グループ=ale_signs 名前=ALEWarningSign']) + else + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' 行=1 識別子=1000001 名前=ALEWarningSign']) + endif + +Execute (Parsing Spanish signs should work): + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[12, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' línea=12 id=1000001 grupo=ale_signs nombre=ALEWarningSign']) + else + AssertEqual + \ [0, [[12, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' línea=12 id=1000001 nombre=ALEWarningSign']) + endif + +Execute (Parsing Italian signs should work): + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' riga=1 id=1000001, gruppo=ale_signs nome=ALEWarningSign']) + else + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' riga=1 id=1000001, nome=ALEWarningSign']) + endif + +Execute (Parsing German signs should work): + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[235, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' Zeile=235 id=1000001 Gruppe=ale_signs Name=ALEErrorSign']) + else + AssertEqual + \ [0, [[235, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' Zeile=235 id=1000001 Name=ALEErrorSign']) + endif + +Execute (The sign parser should indicate if the dummy sign is set): + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [1, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([ + \ ' строка=1 id=1000001 group=ale_signs имя=ALEErrorSign', + \ ' line=1 id=1000000 group=ale_signs name=ALEDummySign', + \ ]) + else + AssertEqual + \ [1, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([ + \ ' строка=1 id=1000001 имя=ALEErrorSign', + \ ' line=1 id=1000000 name=ALEDummySign', + \ ]) + endif diff --git a/test/sign/test_sign_placement.vader b/test/sign/test_sign_placement.vader new file mode 100644 index 00000000..62921e21 --- /dev/null +++ b/test/sign/test_sign_placement.vader @@ -0,0 +1,298 @@ +Before: + Save g:ale_buffer_info + Save g:ale_echo_cursor + 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 + Save g:ale_command_wrapper + + let g:ale_command_wrapper = '' + let g:ale_buffer_info = {} + let g:ale_run_synchronously = 1 + let g:ale_set_signs = 1 + " Disable features we don't need for these tests. + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + + call ale#linter#Reset() + call ale#sign#Clear() + + function! GenerateResults(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'foo', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'bar', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'baz', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'use this one', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 2, + \ 'type': 'W', + \ 'text': 'ignore this one', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'ignore this one', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 2, + \ 'type': 'E', + \ 'text': 'use this one', + \ }, + \] + endfunction + + function! ParseSigns() + redir => l:output + if has('nvim-0.4.2') || has('patch-8.1.614') + silent sign place group=ale_signs + else + silent sign place + endif + redir END + + return map( + \ split(l:output, '\n')[2:], + \ 'matchlist(v:val, ''' . ale#sign#ParsePattern() . ''')[1:3]', + \) + endfunction + + call ale#linter#Define('testft', { + \ 'name': 'x', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'true', + \ 'callback': 'GenerateResults', + \}) + +After: + Restore + + unlet! g:ale_run_synchronously_callbacks + unlet! g:loclist + delfunction GenerateResults + delfunction ParseSigns + call ale#linter#Reset() + call ale#sign#Clear() + +Execute(ale#sign#GetSignName should return the right sign names): + AssertEqual 'ALEErrorSign', ale#sign#GetSignName([{'type': 'E'}]) + AssertEqual 'ALEStyleErrorSign', ale#sign#GetSignName([{'type': 'E', 'sub_type': 'style'}]) + AssertEqual 'ALEWarningSign', ale#sign#GetSignName([{'type': 'W'}]) + AssertEqual 'ALEStyleWarningSign', ale#sign#GetSignName([{'type': 'W', 'sub_type': 'style'}]) + AssertEqual 'ALEInfoSign', ale#sign#GetSignName([{'type': 'I'}]) + AssertEqual 'ALEErrorSign', ale#sign#GetSignName([ + \ {'type': 'E'}, + \ {'type': 'W'}, + \ {'type': 'I'}, + \ {'type': 'E', 'sub_type': 'style'}, + \ {'type': 'W', 'sub_type': 'style'}, + \]) + AssertEqual 'ALEWarningSign', ale#sign#GetSignName([ + \ {'type': 'W'}, + \ {'type': 'I'}, + \ {'type': 'E', 'sub_type': 'style'}, + \ {'type': 'W', 'sub_type': 'style'}, + \]) + AssertEqual 'ALEInfoSign', ale#sign#GetSignName([ + \ {'type': 'I'}, + \ {'type': 'E', 'sub_type': 'style'}, + \ {'type': 'W', 'sub_type': 'style'}, + \]) + AssertEqual 'ALEStyleErrorSign', ale#sign#GetSignName([ + \ {'type': 'E', 'sub_type': 'style'}, + \ {'type': 'W', 'sub_type': 'style'}, + \]) + AssertEqual 'ALEStyleWarningSign', ale#sign#GetSignName([ + \ {'type': 'W', 'sub_type': 'style'}, + \]) + +Given testft(A file with warnings/errors): + Foo + Bar + Baz + Fourth line + Fifth line + +Execute(The current signs should be set for running a job): + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEErrorSign'], + \ ['2', '1000002', 'ALEWarningSign'], + \ ['3', '1000003', 'ALEErrorSign'], + \ ['4', '1000004', 'ALEErrorSign'], + \ ['5', '1000005', 'ALEErrorSign'], + \ ], + \ ParseSigns() + +Execute(Loclist items with sign_id values should be kept): + if has('nvim-0.4.2') || has('patch-8.1.614') + exec 'sign place 1000347 group=ale_signs line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 group=ale_signs line=15 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000349 group=ale_signs line=16 name=ALEWarningSign buffer=' . bufnr('') + else + exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 line=15 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000349 line=16 name=ALEWarningSign buffer=' . bufnr('') + endif + + let g:loclist = [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000348}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'b', 'sign_id': 1000349}, + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c', 'sign_id': 1000347}, + \ {'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd'}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f'}, + \] + + call ale#sign#SetSigns(bufnr(''), g:loclist) + + " Sign IDs from before should be kept, and new signs should use + " IDs that haven't been used yet. + AssertEqual + \ [ + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c', 'sign_id': 1000347}, + \ {'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd', 'sign_id': 1000350}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000348}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e', 'sign_id': 1000348}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 1, 'type': 'W', 'text': 'b', 'sign_id': 1000351}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f', 'sign_id': 1000351}, + \ ], + \ g:loclist + + " Items should be grouped again. We should see error signs, where there + " were warnings before, and errors where there were errors and where we + " now have new warnings. + AssertEqual + \ [ + \ ['15', '1000348', 'ALEErrorSign'], + \ ['16', '1000351', 'ALEErrorSign'], + \ ['3', '1000347', 'ALEErrorSign'], + \ ['4', '1000350', 'ALEWarningSign'], + \ ], + \ sort(ParseSigns()) + +Execute(Items for other buffers should be ignored): + let g:loclist = [ + \ {'bufnr': bufnr('') - 1, 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a'}, + \ {'bufnr': bufnr('') - 1, 'lnum': 2, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000347}, + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'b'}, + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 1, 'type': 'E', 'text': 'c'}, + \ {'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'type': 'W', 'text': 'd'}, + \ {'bufnr': bufnr(''), 'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}, + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f'}, + \ {'bufnr': bufnr('') + 1, 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a'}, + \] + + call ale#sign#SetSigns(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEErrorSign'], + \ ['15', '1000005', 'ALEWarningSign'], + \ ['16', '1000006', 'ALEErrorSign'], + \ ['2', '1000002', 'ALEWarningSign'], + \ ['3', '1000003', 'ALEErrorSign'], + \ ['4', '1000004', 'ALEWarningSign'], + \ ], + \ sort(ParseSigns()) + +Execute(Signs should be downgraded correctly): + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEErrorSign'], + \ ['2', '1000002', 'ALEWarningSign'], + \ ], + \ sort(ParseSigns()) + + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'I', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000003', 'ALEWarningSign'], + \ ['2', '1000004', 'ALEInfoSign'], + \ ], + \ sort(ParseSigns()) + +Execute(Signs should be upgraded correctly): + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'I', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000001', 'ALEWarningSign'], + \ ['2', '1000002', 'ALEInfoSign'], + \ ], + \ sort(ParseSigns()) + + call ale#sign#SetSigns(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1, 'type': 'W', 'text': 'x'}, + \]) + + AssertEqual + \ [ + \ ['1', '1000003', 'ALEErrorSign'], + \ ['2', '1000004', 'ALEWarningSign'], + \ ], + \ sort(ParseSigns()) + +Execute(It should be possible to clear signs with empty lists): + " We can fail to remove signs if there are multiple signs on one line, + " say after deleting lines in Vim, etc. + if has('nvim-0.4.2') || has('patch-8.1.614') + exec 'sign place 1000347 group=ale_signs line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 group=ale_signs line=3 name=ALEWarningSign buffer=' . bufnr('') + exec 'sign place 1000349 group=ale_signs line=10 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000350 group=ale_signs line=10 name=ALEWarningSign buffer=' . bufnr('') + else + exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 line=3 name=ALEWarningSign buffer=' . bufnr('') + exec 'sign place 1000349 line=10 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000350 line=10 name=ALEWarningSign buffer=' . bufnr('') + endif + + call ale#sign#SetSigns(bufnr(''), []) + AssertEqual [], ParseSigns() + +Execute(No exceptions should be thrown when setting signs for invalid buffers): + call ale#sign#SetSigns(123456789, [{'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}]) diff --git a/test/smoke_test.vader b/test/smoke_test.vader new file mode 100644 index 00000000..49634c34 --- /dev/null +++ b/test/smoke_test.vader @@ -0,0 +1,141 @@ +Before: + Save g:ale_enabled + Save g:ale_set_lists_synchronously + Save g:ale_buffer_info + Save &shell + + let g:ale_enabled = 1 + let g:ale_buffer_info = {} + let g:ale_set_lists_synchronously = 1 + + function! TestCallback(buffer, output) + " Windows adds extra spaces to the text from echo. + return [{ + \ 'lnum': 2, + \ 'col': 3, + \ 'text': substitute(a:output[0], ' *$', '', ''), + \}] + endfunction + function! TestCallback2(buffer, output) + return [{ + \ 'lnum': 3, + \ 'col': 4, + \ 'text': substitute(a:output[0], ' *$', '', ''), + \}] + endfunction + + " Running the command in another subshell seems to help here. + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \}) + +After: + Restore + + unlet! g:i + unlet! g:results + unlet! g:expected_results + + delfunction TestCallback + delfunction TestCallback2 + call ale#engine#Cleanup(bufnr('')) + call ale#linter#Reset() + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(Linters should run with the default options): + AssertEqual 'foobar', &filetype + + let g:expected_results = [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo bar', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }] + + " Try the test a few times over in NeoVim 0.3 or Windows or Vim 8.2, + " where tests fail randomly. + for g:i in range(has('nvim-0.3') || has('win32') || has('patch-8.2.2401') ? 5 : 1) + call ale#Queue(0, '') + call ale#test#WaitForJobs(2000) + + let g:results = ale#test#GetLoclistWithoutNewerKeys() + + if g:results == g:expected_results + break + endif + endfor + + AssertEqual g:expected_results, g:results + +Execute(Linters should run in PowerShell too): + if has('win32') + set shell=powershell + + AssertEqual 'foobar', &filetype + + " Replace the callback to handle two lines. + function! TestCallback(buffer, output) + " Windows adds extra spaces to the text from echo. + return [ + \ { + \ 'lnum': 1, + \ 'col': 3, + \ 'text': substitute(a:output[0], ' *$', '', ''), + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'text': substitute(a:output[1], ' *$', '', ''), + \ }, + \] + endfunction + + " Recreate the command string to use &&, which PowerShell does not support. + call ale#linter#Reset() + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': 'cmd', + \ 'command': 'echo foo && echo bar', + \}) + + call ale#Queue(0, '') + call ale#test#WaitForJobs(4000) + + AssertEqual [ + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'bar', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \], ale#test#GetLoclistWithoutNewerKeys() + endif diff --git a/test/test-files/.circleci/config.yml b/test/test-files/.circleci/config.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/.gitignore b/test/test-files/.gitignore new file mode 100644 index 00000000..7d6563e0 --- /dev/null +++ b/test/test-files/.gitignore @@ -0,0 +1,2 @@ +# Don't ignore hidden files for this directory +!.* diff --git a/test/test-files/ada/testfile.adb b/test/test-files/ada/testfile.adb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/alex/node-modules-2/node_modules/alex/cli.js b/test/test-files/alex/node-modules-2/node_modules/alex/cli.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/alex/node-modules/node_modules/.bin/alex b/test/test-files/alex/node-modules/node_modules/.bin/alex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/angular/node_modules/@angular/language-server/bin/ngserver b/test/test-files/angular/node_modules/@angular/language-server/bin/ngserver new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/angular/node_modules/@angular/language-service/dummy b/test/test-files/angular/node_modules/@angular/language-service/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ant/ant-project/build.xml b/test/test-files/ant/ant-project/build.xml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ant/bin/ant b/test/test-files/ant/bin/ant new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/ant/bin/ant.exe b/test/test-files/ant/bin/ant.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/bazel/BUILD b/test/test-files/bazel/BUILD new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/bazel/WORKSPACE b/test/test-files/bazel/WORKSPACE new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/bazel/defs.bzl b/test/test-files/bazel/defs.bzl new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/bib/dummy.bib b/test/test-files/bib/dummy.bib new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/biome/json/biome.json b/test/test-files/biome/json/biome.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/biome/json/src/test.ts b/test/test-files/biome/json/src/test.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/biome/jsonc/biome.jsonc b/test/test-files/biome/jsonc/biome.jsonc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/biome/jsonc/src/test.ts b/test/test-files/biome/jsonc/src/test.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/bzl/bazel-package/BUILD.bazel b/test/test-files/bzl/bazel-package/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/build_compile_commands_project/build/bad_folder_to_test_priority b/test/test-files/c/build_compile_commands_project/build/bad_folder_to_test_priority new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/build_compile_commands_project/build/compile_commands.json b/test/test-files/c/build_compile_commands_project/build/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/configure_project/Makefile b/test/test-files/c/configure_project/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/configure_project/configure b/test/test-files/c/configure_project/configure new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/configure_project/include/test.h b/test/test-files/c/configure_project/include/test.h new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/configure_project/subdir/Makefile b/test/test-files/c/configure_project/subdir/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/dummy.c b/test/test-files/c/dummy.c new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/git_and_nested_makefiles/include/test.h b/test/test-files/c/git_and_nested_makefiles/include/test.h new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/git_and_nested_makefiles/src/Makefile b/test/test-files/c/git_and_nested_makefiles/src/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/gnumakefile_project/GNUmakefile b/test/test-files/c/gnumakefile_project/GNUmakefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/gnumakefile_project/file.c b/test/test-files/c/gnumakefile_project/file.c new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/h_file_project/Makefile b/test/test-files/c/h_file_project/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/h_file_project/subdir/dummy b/test/test-files/c/h_file_project/subdir/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/h_file_project/test.h b/test/test-files/c/h_file_project/test.h new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/hpp_file_project/Makefile b/test/test-files/c/hpp_file_project/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/hpp_file_project/subdir/dummy b/test/test-files/c/hpp_file_project/subdir/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/hpp_file_project/test.hpp b/test/test-files/c/hpp_file_project/test.hpp new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/json_project/build/compile_commands.json b/test/test-files/c/json_project/build/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/json_project/include/test.h b/test/test-files/c/json_project/include/test.h new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/json_project/subdir/dummy b/test/test-files/c/json_project/subdir/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/makefile_project/Makefile b/test/test-files/c/makefile_project/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/makefile_project/_astylerc b/test/test-files/c/makefile_project/_astylerc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/makefile_project/args b/test/test-files/c/makefile_project/args new file mode 100644 index 00000000..ccaf82ad --- /dev/null +++ b/test/test-files/c/makefile_project/args @@ -0,0 +1,3 @@ +foolib.a +-DARGS1 +@subdir/args diff --git a/test/test-files/c/makefile_project/include/test.h b/test/test-files/c/makefile_project/include/test.h new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/makefile_project/subdir/args b/test/test-files/c/makefile_project/subdir/args new file mode 100644 index 00000000..3fe9c3fe --- /dev/null +++ b/test/test-files/c/makefile_project/subdir/args @@ -0,0 +1 @@ +-DARGS2 diff --git a/test/test-files/c/makefile_project/subdir/dummy b/test/test-files/c/makefile_project/subdir/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/c/makefile_project/subdir/file.c b/test/test-files/c/makefile_project/subdir/file.c new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cargo/Cargo.toml b/test/test-files/cargo/Cargo.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cargo/workspace_paths/Cargo.toml b/test/test-files/cargo/workspace_paths/Cargo.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cargo/workspace_paths/subpath/Cargo.toml b/test/test-files/cargo/workspace_paths/subpath/Cargo.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ccls/with_build_dir/unusual_build_dir_name/compile_commands.json b/test/test-files/ccls/with_build_dir/unusual_build_dir_name/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ccls/with_ccls-root/.ccls-root b/test/test-files/ccls/with_ccls-root/.ccls-root new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ccls/with_ccls/.ccls b/test/test-files/ccls/with_ccls/.ccls new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ccls/with_compile_commands_json/compile_commands.json b/test/test-files/ccls/with_compile_commands_json/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/checkstyle/other_config.xml b/test/test-files/checkstyle/other_config.xml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/clangd/with_build_dir/unusual_build_dir_name/compile_commands.json b/test/test-files/clangd/with_build_dir/unusual_build_dir_name/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/clangd/with_compile_commands/compile_commands.json b/test/test-files/clangd/with_compile_commands/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/clangformat/with_clangformat/.clang-format b/test/test-files/clangformat/with_clangformat/.clang-format new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cpp/.astylerc b/test/test-files/cpp/.astylerc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cpp/dummy.cpp b/test/test-files/cpp/dummy.cpp new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cppcheck/one/compile_commands.json b/test/test-files/cppcheck/one/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cppcheck/one/two/three/file.c b/test/test-files/cppcheck/one/two/three/file.c new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cppcheck/one/two/three/file.cpp b/test/test-files/cppcheck/one/two/three/file.cpp new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cppcheck/with_build_dir/build/compile_commands.json b/test/test-files/cppcheck/with_build_dir/build/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cquery/build/compile_commands.json b/test/test-files/cquery/build/compile_commands.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cquery/with_cquery/.cquery b/test/test-files/cquery/with_cquery/.cquery new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cspell/node-modules-2/node_modules/cspell/bin.js b/test/test-files/cspell/node-modules-2/node_modules/cspell/bin.js new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/cspell/node-modules/node_modules/.bin/cspell b/test/test-files/cspell/node-modules/node_modules/.bin/cspell new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/csslint/other-app/testfile.css b/test/test-files/csslint/other-app/testfile.css new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/csslint/some-app/.csslintrc b/test/test-files/csslint/some-app/.csslintrc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/csslint/some-app/subdir/testfile.css b/test/test-files/csslint/some-app/subdir/testfile.css new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cucumber/features/cuke.feature b/test/test-files/cucumber/features/cuke.feature new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/cucumber/features/step_definitions/base_steps.rb b/test/test-files/cucumber/features/step_definitions/base_steps.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/d/test.d b/test/test-files/d/test.d new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/dart/.packages b/test/test-files/dart/.packages new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/dart/testfile.dart b/test/test-files/dart/testfile.dart new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/dprint/blank.ts b/test/test-files/dprint/blank.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/dprint/dprint.json b/test/test-files/dprint/dprint.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elixir/mix_project/lib/app.ex b/test/test-files/elixir/mix_project/lib/app.ex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elixir/mix_project/mix.exs b/test/test-files/elixir/mix_project/mix.exs new file mode 100644 index 00000000..419685ae --- /dev/null +++ b/test/test-files/elixir/mix_project/mix.exs @@ -0,0 +1,3 @@ +defmodule Test.MixProject do + # fake mix project file +end diff --git a/test/test-files/elixir/testfile.ex b/test/test-files/elixir/testfile.ex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elixir/umbrella_project/apps/app1/lib/app.ex b/test/test-files/elixir/umbrella_project/apps/app1/lib/app.ex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elixir/umbrella_project/apps/app1/mix.exs b/test/test-files/elixir/umbrella_project/apps/app1/mix.exs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elixir/umbrella_project/apps/app2/lib/app.ex b/test/test-files/elixir/umbrella_project/apps/app2/lib/app.ex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elixir/umbrella_project/apps/app2/mix.exs b/test/test-files/elixir/umbrella_project/apps/app2/mix.exs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elixir/umbrella_project/mix.exs b/test/test-files/elixir/umbrella_project/mix.exs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp-notests/elm.json b/test/test-files/elm/newapp-notests/elm.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp-notests/node_modules/.bin/elm b/test/test-files/elm/newapp-notests/node_modules/.bin/elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp-notests/tests/TestMain.elm b/test/test-files/elm/newapp-notests/tests/TestMain.elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp/elm.json b/test/test-files/elm/newapp/elm.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp/node_modules/.bin/elm b/test/test-files/elm/newapp/node_modules/.bin/elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp/node_modules/.bin/elm-test b/test/test-files/elm/newapp/node_modules/.bin/elm-test new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp/src/Main.elm b/test/test-files/elm/newapp/src/Main.elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/newapp/tests/TestSuite.elm b/test/test-files/elm/newapp/tests/TestSuite.elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/node_modules/.bin/elm-format b/test/test-files/elm/node_modules/.bin/elm-format new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/oldapp/elm-package.json b/test/test-files/elm/oldapp/elm-package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/oldapp/node_modules/.bin/elm b/test/test-files/elm/oldapp/node_modules/.bin/elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/oldapp/node_modules/.bin/elm-test b/test/test-files/elm/oldapp/node_modules/.bin/elm-test new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/oldapp/src/Main.elm b/test/test-files/elm/oldapp/src/Main.elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/oldapp/tests/TestSuite.elm b/test/test-files/elm/oldapp/tests/TestSuite.elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/elm/src/subdir/testfile.elm b/test/test-files/elm/src/subdir/testfile.elm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/app_with_elvis_config/elvis.config b/test/test-files/erlang/app_with_elvis_config/elvis.config new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/app_with_erlang_ls_config/_build/default/lib/dep/erlang_ls.config b/test/test-files/erlang/app_with_erlang_ls_config/_build/default/lib/dep/erlang_ls.config new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/app_with_erlang_ls_config/deps/dep/erlang_ls.config b/test/test-files/erlang/app_with_erlang_ls_config/deps/dep/erlang_ls.config new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/app_with_erlang_ls_config/erlang_ls.config b/test/test-files/erlang/app_with_erlang_ls_config/erlang_ls.config new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/app_with_erlfmt/erlfmt b/test/test-files/erlang/app_with_erlfmt/erlfmt new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/erlang/erlang_mk_app/deps/dep/erlang.mk b/test/test-files/erlang/erlang_mk_app/deps/dep/erlang.mk new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/erlang_mk_app/erlang.mk b/test/test-files/erlang/erlang_mk_app/erlang.mk new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/kerl_otp_root/.kerl_config b/test/test-files/erlang/kerl_otp_root/.kerl_config new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/rebar3_app/_build/default/lib/dep/rebar.lock b/test/test-files/erlang/rebar3_app/_build/default/lib/dep/rebar.lock new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/rebar3_app/_checkouts/dep/_build/.gitkeep b/test/test-files/erlang/rebar3_app/_checkouts/dep/_build/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/rebar3_app/_checkouts/dep/rebar.lock b/test/test-files/erlang/rebar3_app/_checkouts/dep/rebar.lock new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/erlang/rebar3_app/rebar.lock b/test/test-files/erlang/rebar3_app/rebar.lock new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eruby/dummy.html.erb b/test/test-files/eruby/dummy.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/astro-app/.eslintrc.js b/test/test-files/eslint/astro-app/.eslintrc.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/astro-app/node_modules/eslint/bin/eslint.js b/test/test-files/eslint/astro-app/node_modules/eslint/bin/eslint.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/astro-app/package.json b/test/test-files/eslint/astro-app/package.json new file mode 100644 index 00000000..527a99fc --- /dev/null +++ b/test/test-files/eslint/astro-app/package.json @@ -0,0 +1,17 @@ +{ + "name": "astro-app", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "astro": "^4.8.3", + "@astrojs/check": "^0.7.0", + "typescript": "^5.4.5" + } +} \ No newline at end of file diff --git a/test/test-files/eslint/astro-app/src/pages/index.astro b/test/test-files/eslint/astro-app/src/pages/index.astro new file mode 100644 index 00000000..2d141073 --- /dev/null +++ b/test/test-files/eslint/astro-app/src/pages/index.astro @@ -0,0 +1,16 @@ +--- + +--- + + + + + + + + Astro + + +

Astro

+ + diff --git a/test/test-files/eslint/astro-app/tsconfig.json b/test/test-files/eslint/astro-app/tsconfig.json new file mode 100644 index 00000000..77da9dd0 --- /dev/null +++ b/test/test-files/eslint/astro-app/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} \ No newline at end of file diff --git a/test/test-files/eslint/node_modules/.bin/eslint b/test/test-files/eslint/node_modules/.bin/eslint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/other-app/subdir/testfile.js b/test/test-files/eslint/other-app/subdir/testfile.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/package.json b/test/test-files/eslint/package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/.eslintrc.js b/test/test-files/eslint/react-app/.eslintrc.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/node_modules/eslint/bin/eslint.js b/test/test-files/eslint/react-app/node_modules/eslint/bin/eslint.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/node_modules/standard/bin/cmd.js b/test/test-files/eslint/react-app/node_modules/standard/bin/cmd.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/node_modules/stylelint/bin/stylelint.js b/test/test-files/eslint/react-app/node_modules/stylelint/bin/stylelint.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/node_modules/xo/cli.js b/test/test-files/eslint/react-app/node_modules/xo/cli.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/subdir-with-config/.eslintrc b/test/test-files/eslint/react-app/subdir-with-config/.eslintrc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/subdir-with-package-json/node_modules/.gitkeep b/test/test-files/eslint/react-app/subdir-with-package-json/node_modules/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/subdir-with-package-json/package.json b/test/test-files/eslint/react-app/subdir-with-package-json/package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/subdir/testfile.css b/test/test-files/eslint/react-app/subdir/testfile.css new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/subdir/testfile.js b/test/test-files/eslint/react-app/subdir/testfile.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/react-app/subdir/testfile.ts b/test/test-files/eslint/react-app/subdir/testfile.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/yarn2-app/.eslintrc.js b/test/test-files/eslint/yarn2-app/.eslintrc.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/yarn2-app/.yarn/sdks/eslint/bin/eslint.js b/test/test-files/eslint/yarn2-app/.yarn/sdks/eslint/bin/eslint.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/eslint/yarn2-app/subdir/testfile.js b/test/test-files/eslint/yarn2-app/subdir/testfile.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/fecs/fecs b/test/test-files/fecs/fecs new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/fecs/fecs.exe b/test/test-files/fecs/fecs.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/fish/testfile.fish b/test/test-files/fish/testfile.fish new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/flow/a/.flowconfig b/test/test-files/flow/a/.flowconfig new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/flow/a/sub/dummy b/test/test-files/flow/a/sub/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/flow/b/sub/dummy b/test/test-files/flow/b/sub/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/fortls-project/.fortls b/test/test-files/fortls-project/.fortls new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/test/test-files/fortls-project/.fortls @@ -0,0 +1,2 @@ +{ +} diff --git a/test/test-files/gleam/gleam.toml b/test/test-files/gleam/gleam.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gleam/testfile.gleam b/test/test-files/gleam/testfile.gleam new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/go/go.mod b/test/test-files/go/go.mod new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/test-files/go/go.mod @@ -0,0 +1 @@ + diff --git a/test/test-files/go/go1/prj1/file.go b/test/test-files/go/go1/prj1/file.go new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/go/go2/prj2/file.go b/test/test-files/go/go2/prj2/file.go new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/go/gopath/bin/gopls b/test/test-files/go/gopath/bin/gopls new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/go/gopath/bin/staticcheck b/test/test-files/go/gopath/bin/staticcheck new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/go/testfile.go b/test/test-files/go/testfile.go new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/go/testfile2.go b/test/test-files/go/testfile2.go new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/build-gradle-project/build.gradle b/test/test-files/gradle/build-gradle-project/build.gradle new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/build-gradle-project/src/main/kotlin/dummy.kt b/test/test-files/gradle/build-gradle-project/src/main/kotlin/dummy.kt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/gradle b/test/test-files/gradle/gradle new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/gradle/non-gradle-project/src/main/kotlin/dummy.kt b/test/test-files/gradle/non-gradle-project/src/main/kotlin/dummy.kt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/settings-gradle-project/settings.gradle b/test/test-files/gradle/settings-gradle-project/settings.gradle new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/settings-gradle-project/src/main/kotlin/dummy.kt b/test/test-files/gradle/settings-gradle-project/src/main/kotlin/dummy.kt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/unwrapped-project/build.gradle b/test/test-files/gradle/unwrapped-project/build.gradle new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/unwrapped-project/settings.gradle b/test/test-files/gradle/unwrapped-project/settings.gradle new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/unwrapped-project/src/main/kotlin/dummy.kt b/test/test-files/gradle/unwrapped-project/src/main/kotlin/dummy.kt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/wrapped-project/build.gradle b/test/test-files/gradle/wrapped-project/build.gradle new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/wrapped-project/gradlew b/test/test-files/gradle/wrapped-project/gradlew new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/wrapped-project/settings.gradle b/test/test-files/gradle/wrapped-project/settings.gradle new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/gradle/wrapped-project/src/main/kotlin/dummy.kt b/test/test-files/gradle/wrapped-project/src/main/kotlin/dummy.kt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hamllint/haml-lint-and-rubocop/.haml-lint.yml b/test/test-files/hamllint/haml-lint-and-rubocop/.haml-lint.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hamllint/haml-lint-and-rubocop/.rubocop.yml b/test/test-files/hamllint/haml-lint-and-rubocop/.rubocop.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hamllint/haml-lint-and-rubocop/subdir/file.haml b/test/test-files/hamllint/haml-lint-and-rubocop/subdir/file.haml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hamllint/haml-lint-yml/.haml-lint.yml b/test/test-files/hamllint/haml-lint-yml/.haml-lint.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hamllint/haml-lint-yml/subdir/file.haml b/test/test-files/hamllint/haml-lint-yml/subdir/file.haml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hamllint/rubocop-yml/.rubocop.yml b/test/test-files/hamllint/rubocop-yml/.rubocop.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hamllint/rubocop-yml/subdir/file.haml b/test/test-files/hamllint/rubocop-yml/subdir/file.haml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/haskell/haskell-packages-project/cabal.project b/test/test-files/haskell/haskell-packages-project/cabal.project new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/haskell/haskell-packages-project/package-a/package-a.cabal b/test/test-files/haskell/haskell-packages-project/package-a/package-a.cabal new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/haskell/haskell-packages-project/package-a/src/folder/dummy.hs b/test/test-files/haskell/haskell-packages-project/package-a/src/folder/dummy.hs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/haskell/haskell-simple-package/package-a/package-a.cabal b/test/test-files/haskell/haskell-simple-package/package-a/package-a.cabal new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/haskell/haskell-simple-package/package-a/src/folder/dummy.hs b/test/test-files/haskell/haskell-simple-package/package-a/src/folder/dummy.hs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hdl_server/foo.vhd b/test/test-files/hdl_server/foo.vhd new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hdl_server/with_config_file/.hdl_checker.config b/test/test-files/hdl_server/with_config_file/.hdl_checker.config new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hdl_server/with_config_file/_hdl_checker.config b/test/test-files/hdl_server/with_config_file/_hdl_checker.config new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hdl_server/with_config_file/foo.vhd b/test/test-files/hdl_server/with_config_file/foo.vhd new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/hdl_server/with_git/files/foo.vhd b/test/test-files/hdl_server/with_git/files/foo.vhd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/test-files/hdl_server/with_git/files/foo.vhd @@ -0,0 +1 @@ + diff --git a/test/test-files/hie_paths/file.hs b/test/test-files/hie_paths/file.hs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/html_beautify/html-beautify b/test/test-files/html_beautify/html-beautify new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/html_beautify/test.html b/test/test-files/html_beautify/test.html new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/htmlhint/node_modules/.bin/htmlhint b/test/test-files/htmlhint/node_modules/.bin/htmlhint new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/htmlhint/with_config/.htmlhintrc b/test/test-files/htmlhint/with_config/.htmlhintrc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ink/story/main.ink b/test/test-files/ink/story/main.ink new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/inko/test.inko b/test/test-files/inko/test.inko new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/inko/tests/test/test_foo.inko b/test/test-files/inko/tests/test/test_foo.inko new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/java/no_main/src/test/java/com/something/dummy b/test/test-files/java/no_main/src/test/java/com/something/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/java/with_jaxb/src/main/java/com/something/dummy b/test/test-files/java/with_jaxb/src/main/java/com/something/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/java/with_jaxb/src/main/jaxb/com/something/dummy b/test/test-files/java/with_jaxb/src/main/jaxb/com/something/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/java/with_main/build/gen/main/java/com/something/dummy b/test/test-files/java/with_main/build/gen/main/java/com/something/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/java/with_main/build/gen2/main/java/com/something/dummy b/test/test-files/java/with_main/build/gen2/main/java/com/something/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/java/with_main/src/main/java/com/something/dummy b/test/test-files/java/with_main/src/main/java/com/something/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/java/with_main/src/test/java/com/something/dummy b/test/test-files/java/with_main/src/test/java/com/something/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/javascript/test.js b/test/test-files/javascript/test.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/javascript_deno/custom_import_map.json b/test/test-files/javascript_deno/custom_import_map.json new file mode 100644 index 00000000..9f5a19a1 --- /dev/null +++ b/test/test-files/javascript_deno/custom_import_map.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/test/test-files/javascript_deno/import_map.json b/test/test-files/javascript_deno/import_map.json new file mode 100644 index 00000000..9f5a19a1 --- /dev/null +++ b/test/test-files/javascript_deno/import_map.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/test/test-files/javascript_deno/main.js b/test/test-files/javascript_deno/main.js new file mode 100644 index 00000000..accefceb --- /dev/null +++ b/test/test-files/javascript_deno/main.js @@ -0,0 +1 @@ +console.log("Hello World"); diff --git a/test/test-files/javascript_deno/tsconfig.json b/test/test-files/javascript_deno/tsconfig.json new file mode 100644 index 00000000..152b034e --- /dev/null +++ b/test/test-files/javascript_deno/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowJs": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "isolatedModules": true, + "jsx": "react", + "lib": ["deno.window"], + "module": "esnext", + "strict": true, + "target": "esnext", + "useDefineForClassFields": true + }, + "includes": ["main.js"] +} diff --git a/test/test-files/json/testfile.json b/test/test-files/json/testfile.json new file mode 100644 index 00000000..fe317ebb --- /dev/null +++ b/test/test-files/json/testfile.json @@ -0,0 +1 @@ +{"answer":42} diff --git a/test/test-files/jsonlint/app-without-jsonlint/src/app.json b/test/test-files/jsonlint/app-without-jsonlint/src/app.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/jsonlint/app/node_modules/.bin/jsonlint b/test/test-files/jsonlint/app/node_modules/.bin/jsonlint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/jsonlint/app/src/app.json b/test/test-files/jsonlint/app/src/app.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/jsonlint/node_modules/jsonlint/lib/cli.js b/test/test-files/jsonlint/node_modules/jsonlint/lib/cli.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/julia/REQUIRE b/test/test-files/julia/REQUIRE new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/julia/test.jl b/test/test-files/julia/test.jl new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/kotlin/testfile.kt b/test/test-files/kotlin/testfile.kt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/lessc/node_modules/.bin/lessc b/test/test-files/lessc/node_modules/.bin/lessc new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/long-line/setup.cfg b/test/test-files/long-line/setup.cfg new file mode 100644 index 00000000..43d7a3a1 --- /dev/null +++ b/test/test-files/long-line/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 90 diff --git a/test/test-files/lua/testfile.lua b/test/test-files/lua/testfile.lua new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/markdown/testfile.md b/test/test-files/markdown/testfile.md new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/maven/maven-java-project/module1/mvnw b/test/test-files/maven/maven-java-project/module1/mvnw new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/maven/maven-java-project/module1/mvnw.cmd b/test/test-files/maven/maven-java-project/module1/mvnw.cmd new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/maven/maven-java-project/module1/pom.xml b/test/test-files/maven/maven-java-project/module1/pom.xml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/test-files/maven/maven-java-project/module1/pom.xml @@ -0,0 +1 @@ + diff --git a/test/test-files/maven/maven-java-project/module1/src/main/java/dummy1.java b/test/test-files/maven/maven-java-project/module1/src/main/java/dummy1.java new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/maven/maven-java-project/module2/pom.xml b/test/test-files/maven/maven-java-project/module2/pom.xml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/test-files/maven/maven-java-project/module2/pom.xml @@ -0,0 +1 @@ + diff --git a/test/test-files/maven/maven-java-project/module2/src/main/java/dummy2.java b/test/test-files/maven/maven-java-project/module2/src/main/java/dummy2.java new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/maven/maven-kotlin-project/pom.xml b/test/test-files/maven/maven-kotlin-project/pom.xml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/test-files/maven/maven-kotlin-project/pom.xml @@ -0,0 +1 @@ + diff --git a/test/test-files/maven/maven-kotlin-project/src/main/kotlin/dummy.kt b/test/test-files/maven/maven-kotlin-project/src/main/kotlin/dummy.kt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/test-files/maven/maven-kotlin-project/src/main/kotlin/dummy.kt @@ -0,0 +1 @@ + diff --git a/test/test-files/maven/mvn b/test/test-files/maven/mvn new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/maven/non-maven-project/src/main/java/dummy.java b/test/test-files/maven/non-maven-project/src/main/java/dummy.java new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/nim/with-git/src/source.nim b/test/test-files/nim/with-git/src/source.nim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ocaml/testfile.ml b/test/test-files/ocaml/testfile.ml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ocamllsp/dune-project b/test/test-files/ocamllsp/dune-project new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/odin/main.odin b/test/test-files/odin/main.odin new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ols/.merlin b/test/test-files/ols/.merlin new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ols/node_modules/.bin/ocaml-language-server b/test/test-files/ols/node_modules/.bin/ocaml-language-server new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/openscad/dummy.scad b/test/test-files/openscad/dummy.scad new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/pascal/test.pas b/test/test-files/pascal/test.pas new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-with-php-cs-fixer/test.php b/test/test-files/php/project-with-php-cs-fixer/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-with-php-cs-fixer/vendor/bin/php-cs-fixer b/test/test-files/php/project-with-php-cs-fixer/vendor/bin/php-cs-fixer new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-with-phpcbf/foo/test.php b/test/test-files/php/project-with-phpcbf/foo/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-with-phpcbf/vendor/bin/phpcbf b/test/test-files/php/project-with-phpcbf/vendor/bin/phpcbf new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-with-pint/test.php b/test/test-files/php/project-with-pint/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-with-pint/vendor/bin/pint b/test/test-files/php/project-with-pint/vendor/bin/pint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-without-php-cs-fixer/test.php b/test/test-files/php/project-without-php-cs-fixer/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-without-phpcbf/foo/test.php b/test/test-files/php/project-without-phpcbf/foo/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/project-without-pint/test.php b/test/test-files/php/project-without-pint/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/vendor/bin/php-language-server.php b/test/test-files/php/vendor/bin/php-language-server.php new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/php/with-composer/composer.json b/test/test-files/php/with-composer/composer.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/php/with-composer/vendor/bin/php-language-server.php b/test/test-files/php/with-composer/vendor/bin/php-language-server.php new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/php/with-git/vendor/bin/php-language-server.php b/test/test-files/php/with-git/vendor/bin/php-language-server.php new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/phpcs/project-with-phpcs/foo/test.php b/test/test-files/phpcs/project-with-phpcs/foo/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/phpcs/project-with-phpcs/vendor/bin/phpcs b/test/test-files/phpcs/project-with-phpcs/vendor/bin/phpcs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/phpcs/project-without-phpcs/foo/test.php b/test/test-files/phpcs/project-without-phpcs/foo/test.php new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/testfile b/test/test-files/prettier/testfile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/testfile.css b/test/test-files/prettier/testfile.css new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/testfile.js b/test/test-files/prettier/testfile.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/testfile.json b/test/test-files/prettier/testfile.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/testfile.scss b/test/test-files/prettier/testfile.scss new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/testfile.ts b/test/test-files/prettier/testfile.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/with_config/.prettierrc b/test/test-files/prettier/with_config/.prettierrc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/with_config/testfile.js b/test/test-files/prettier/with_config/testfile.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/with_prettierignore/.prettierignore b/test/test-files/prettier/with_prettierignore/.prettierignore new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/prettier/with_prettierignore/src/testfile.js b/test/test-files/prettier/with_prettierignore/src/testfile.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/proto/testfile.proto b/test/test-files/proto/testfile.proto new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/psalm/vendor/bin/psalm b/test/test-files/psalm/vendor/bin/psalm new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/puglint/node_modules/.bin/pug-lint b/test/test-files/puglint/node_modules/.bin/pug-lint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puglint/package.json b/test/test-files/puglint/package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puglint/puglint_rc_dir/.pug-lintrc b/test/test-files/puglint/puglint_rc_dir/.pug-lintrc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puglint/puglint_rc_js_dir/.pug-lintrc.js b/test/test-files/puglint/puglint_rc_js_dir/.pug-lintrc.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puglint/puglint_rc_json_dir/.pug-lintrc.json b/test/test-files/puglint/puglint_rc_json_dir/.pug-lintrc.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puppet/dummy.pp b/test/test-files/puppet/dummy.pp new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puppet/new-style-module/lib/puppet/types/exampletype.rb b/test/test-files/puppet/new-style-module/lib/puppet/types/exampletype.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puppet/new-style-module/metadata.json b/test/test-files/puppet/new-style-module/metadata.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puppet/new-style-module/template/template.epp b/test/test-files/puppet/new-style-module/template/template.epp new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puppet/old-style-module/manifests/init.pp b/test/test-files/puppet/old-style-module/manifests/init.pp new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/puppet/old-style-module/templates/template.epp b/test/test-files/puppet/old-style-module/templates/template.epp new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/purescript/bower/Foo.purs b/test/test-files/purescript/bower/Foo.purs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/purescript/bower/bower.json b/test/test-files/purescript/bower/bower.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/purescript/psc-package/Foo.purs b/test/test-files/purescript/psc-package/Foo.purs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/purescript/psc-package/psc-package.json b/test/test-files/purescript/psc-package/psc-package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/purescript/spago/Foo.purs b/test/test-files/purescript/spago/Foo.purs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/purescript/spago/spago.dhall b/test/test-files/purescript/spago/spago.dhall new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_manifest/MANIFEST.in b/test/test-files/python/namespace_package_manifest/MANIFEST.in new file mode 100644 index 00000000..4617b0eb --- /dev/null +++ b/test/test-files/python/namespace_package_manifest/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include *.ini *.cfg *.txt +include requirements/*.txt diff --git a/test/test-files/python/namespace_package_manifest/namespace/foo/__init__.py b/test/test-files/python/namespace_package_manifest/namespace/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_manifest/namespace/foo/bar.py b/test/test-files/python/namespace_package_manifest/namespace/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_pytest/namespace/foo/__init__.py b/test/test-files/python/namespace_package_pytest/namespace/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_pytest/namespace/foo/bar.py b/test/test-files/python/namespace_package_pytest/namespace/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_pytest/pytest.ini b/test/test-files/python/namespace_package_pytest/pytest.ini new file mode 100644 index 00000000..1433c6c6 --- /dev/null +++ b/test/test-files/python/namespace_package_pytest/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +DJANGO_SETTINGS_MODULE=foo.settings diff --git a/test/test-files/python/namespace_package_setup/namespace/foo/__init__.py b/test/test-files/python/namespace_package_setup/namespace/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_setup/namespace/foo/bar.py b/test/test-files/python/namespace_package_setup/namespace/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_setup/setup.cfg b/test/test-files/python/namespace_package_setup/setup.cfg new file mode 100644 index 00000000..791f075d --- /dev/null +++ b/test/test-files/python/namespace_package_setup/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 119 diff --git a/test/test-files/python/namespace_package_tox/namespace/foo/__init__.py b/test/test-files/python/namespace_package_tox/namespace/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_tox/namespace/foo/bar.py b/test/test-files/python/namespace_package_tox/namespace/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/namespace_package_tox/tox.ini b/test/test-files/python/namespace_package_tox/tox.ini new file mode 100644 index 00000000..edd8788c --- /dev/null +++ b/test/test-files/python/namespace_package_tox/tox.ini @@ -0,0 +1,3 @@ +[tox] +envlist = + py352 diff --git a/test/test-files/python/no_virtualenv/subdir/foo/COMMIT_EDITMSG b/test/test-files/python/no_virtualenv/subdir/foo/COMMIT_EDITMSG new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/no_virtualenv/subdir/foo/__init__.py b/test/test-files/python/no_virtualenv/subdir/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/no_virtualenv/subdir/foo/bar.py b/test/test-files/python/no_virtualenv/subdir/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/pipenv/Pipfile.lock b/test/test-files/python/pipenv/Pipfile.lock new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/poetry/poetry.lock b/test/test-files/python/poetry/poetry.lock new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/pyre_configuration_dir/.pyre_configuration.local b/test/test-files/python/pyre_configuration_dir/.pyre_configuration.local new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/pyre_configuration_dir/foo/__init__.py b/test/test-files/python/pyre_configuration_dir/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/pyre_configuration_dir/foo/bar.py b/test/test-files/python/pyre_configuration_dir/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/python-package-project/.flake8 b/test/test-files/python/python-package-project/.flake8 new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/python-package-project/package-name/module.py b/test/test-files/python/python-package-project/package-name/module.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/uv/.gitkeep b/test/test-files/python/uv/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/uv/uv.lock b/test/test-files/python/uv/uv.lock new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/uv/whatever.py b/test/test-files/python/uv/whatever.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_bandit/.bandit b/test/test-files/python/with_bandit/.bandit new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_bandit/namespace/foo/__init__.py b/test/test-files/python/with_bandit/namespace/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_bandit/namespace/foo/bar.py b/test/test-files/python/with_bandit/namespace/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_mypy_ini_and_pytest_ini/mypy.ini b/test/test-files/python/with_mypy_ini_and_pytest_ini/mypy.ini new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_mypy_ini_and_pytest_ini/tests/pytest.ini b/test/test-files/python/with_mypy_ini_and_pytest_ini/tests/pytest.ini new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py b/test/test-files/python/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/dir_with_yapf_config/.style.yapf b/test/test-files/python/with_virtualenv/dir_with_yapf_config/.style.yapf new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/activate b/test/test-files/python/with_virtualenv/env/Scripts/activate new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/autoflake.exe b/test/test-files/python/with_virtualenv/env/Scripts/autoflake.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/autoimport.exe b/test/test-files/python/with_virtualenv/env/Scripts/autoimport.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/autopep8.exe b/test/test-files/python/with_virtualenv/env/Scripts/autopep8.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/black.exe b/test/test-files/python/with_virtualenv/env/Scripts/black.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/flake8.exe b/test/test-files/python/with_virtualenv/env/Scripts/flake8.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/flakehell.exe b/test/test-files/python/with_virtualenv/env/Scripts/flakehell.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/gitlint.exe b/test/test-files/python/with_virtualenv/env/Scripts/gitlint.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/isort.exe b/test/test-files/python/with_virtualenv/env/Scripts/isort.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/jedi-language-server.exe b/test/test-files/python/with_virtualenv/env/Scripts/jedi-language-server.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/mypy.exe b/test/test-files/python/with_virtualenv/env/Scripts/mypy.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/pycln.exe b/test/test-files/python/with_virtualenv/env/Scripts/pycln.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/pyflakes.exe b/test/test-files/python/with_virtualenv/env/Scripts/pyflakes.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/pylama.exe b/test/test-files/python/with_virtualenv/env/Scripts/pylama.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/pylint.exe b/test/test-files/python/with_virtualenv/env/Scripts/pylint.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/pylsp.exe b/test/test-files/python/with_virtualenv/env/Scripts/pylsp.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/pyre.exe b/test/test-files/python/with_virtualenv/env/Scripts/pyre.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/pyright-langserver.exe b/test/test-files/python/with_virtualenv/env/Scripts/pyright-langserver.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/refurb.exe b/test/test-files/python/with_virtualenv/env/Scripts/refurb.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/reorder-python-imports.exe b/test/test-files/python/with_virtualenv/env/Scripts/reorder-python-imports.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/ruff.exe b/test/test-files/python/with_virtualenv/env/Scripts/ruff.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/tidy-imports.exe b/test/test-files/python/with_virtualenv/env/Scripts/tidy-imports.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/unimport.exe b/test/test-files/python/with_virtualenv/env/Scripts/unimport.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/vulture.exe b/test/test-files/python/with_virtualenv/env/Scripts/vulture.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/yamlfix.exe b/test/test-files/python/with_virtualenv/env/Scripts/yamlfix.exe new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/Scripts/yapf.exe b/test/test-files/python/with_virtualenv/env/Scripts/yapf.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/activate b/test/test-files/python/with_virtualenv/env/bin/activate new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/autoflake b/test/test-files/python/with_virtualenv/env/bin/autoflake new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/autoimport b/test/test-files/python/with_virtualenv/env/bin/autoimport new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/autopep8 b/test/test-files/python/with_virtualenv/env/bin/autopep8 new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/black b/test/test-files/python/with_virtualenv/env/bin/black new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/flake8 b/test/test-files/python/with_virtualenv/env/bin/flake8 new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/flakehell b/test/test-files/python/with_virtualenv/env/bin/flakehell new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/gitlint b/test/test-files/python/with_virtualenv/env/bin/gitlint new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/isort b/test/test-files/python/with_virtualenv/env/bin/isort new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/jedi-language-server b/test/test-files/python/with_virtualenv/env/bin/jedi-language-server new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/mypy b/test/test-files/python/with_virtualenv/env/bin/mypy new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/pycln b/test/test-files/python/with_virtualenv/env/bin/pycln new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/pyflakes b/test/test-files/python/with_virtualenv/env/bin/pyflakes new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/pylama b/test/test-files/python/with_virtualenv/env/bin/pylama new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/pylint b/test/test-files/python/with_virtualenv/env/bin/pylint new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/pylsp b/test/test-files/python/with_virtualenv/env/bin/pylsp new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/pyre b/test/test-files/python/with_virtualenv/env/bin/pyre new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/pyright-langserver b/test/test-files/python/with_virtualenv/env/bin/pyright-langserver new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/refurb b/test/test-files/python/with_virtualenv/env/bin/refurb new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/reorder-python-imports b/test/test-files/python/with_virtualenv/env/bin/reorder-python-imports new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/ruff b/test/test-files/python/with_virtualenv/env/bin/ruff new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/tidy-imports b/test/test-files/python/with_virtualenv/env/bin/tidy-imports new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/unimport b/test/test-files/python/with_virtualenv/env/bin/unimport new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/vulture b/test/test-files/python/with_virtualenv/env/bin/vulture new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/yamlfix b/test/test-files/python/with_virtualenv/env/bin/yamlfix new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/env/bin/yapf b/test/test-files/python/with_virtualenv/env/bin/yapf new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/subdir/foo/COMMIT_EDITMSG b/test/test-files/python/with_virtualenv/subdir/foo/COMMIT_EDITMSG new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/subdir/foo/__init__.py b/test/test-files/python/with_virtualenv/subdir/foo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/subdir/foo/bar.py b/test/test-files/python/with_virtualenv/subdir/foo/bar.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/python/with_virtualenv/subdir/foo/bar.pyi b/test/test-files/python/with_virtualenv/subdir/foo/bar.pyi new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/r/.Rprofile b/test/test-files/r/.Rprofile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/racket/many-inits/a/b/c/foo.rkt b/test/test-files/racket/many-inits/a/b/c/foo.rkt new file mode 100644 index 00000000..622f3eea --- /dev/null +++ b/test/test-files/racket/many-inits/a/b/c/foo.rkt @@ -0,0 +1,3 @@ +#lang racket/base + +(displayln "foo") diff --git a/test/test-files/racket/many-inits/a/b/c/init.rkt b/test/test-files/racket/many-inits/a/b/c/init.rkt new file mode 100644 index 00000000..e0c94f27 --- /dev/null +++ b/test/test-files/racket/many-inits/a/b/c/init.rkt @@ -0,0 +1 @@ +#lang info diff --git a/test/test-files/racket/many-inits/a/b/foo.rkt b/test/test-files/racket/many-inits/a/b/foo.rkt new file mode 100644 index 00000000..622f3eea --- /dev/null +++ b/test/test-files/racket/many-inits/a/b/foo.rkt @@ -0,0 +1,3 @@ +#lang racket/base + +(displayln "foo") diff --git a/test/test-files/racket/many-inits/a/b/init.rkt b/test/test-files/racket/many-inits/a/b/init.rkt new file mode 100644 index 00000000..e0c94f27 --- /dev/null +++ b/test/test-files/racket/many-inits/a/b/init.rkt @@ -0,0 +1 @@ +#lang info diff --git a/test/test-files/racket/many-inits/a/foo.rkt b/test/test-files/racket/many-inits/a/foo.rkt new file mode 100644 index 00000000..622f3eea --- /dev/null +++ b/test/test-files/racket/many-inits/a/foo.rkt @@ -0,0 +1,3 @@ +#lang racket/base + +(displayln "foo") diff --git a/test/test-files/racket/many-inits/a/init.rkt b/test/test-files/racket/many-inits/a/init.rkt new file mode 100644 index 00000000..e0c94f27 --- /dev/null +++ b/test/test-files/racket/many-inits/a/init.rkt @@ -0,0 +1 @@ +#lang info diff --git a/test/test-files/racket/many-inits/foo.rkt b/test/test-files/racket/many-inits/foo.rkt new file mode 100644 index 00000000..622f3eea --- /dev/null +++ b/test/test-files/racket/many-inits/foo.rkt @@ -0,0 +1,3 @@ +#lang racket/base + +(displayln "foo") diff --git a/test/test-files/racket/many-inits/init.rkt b/test/test-files/racket/many-inits/init.rkt new file mode 100644 index 00000000..e0c94f27 --- /dev/null +++ b/test/test-files/racket/many-inits/init.rkt @@ -0,0 +1 @@ +#lang info diff --git a/test/test-files/racket/simple-script/foo.rkt b/test/test-files/racket/simple-script/foo.rkt new file mode 100644 index 00000000..622f3eea --- /dev/null +++ b/test/test-files/racket/simple-script/foo.rkt @@ -0,0 +1,3 @@ +#lang racket/base + +(displayln "foo") diff --git a/test/test-files/reasonml/bsconfig.json b/test/test-files/reasonml/bsconfig.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/reasonml/testfile.re b/test/test-files/reasonml/testfile.re new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/remark_lint/with_bin_path/node_modules/.bin/remark b/test/test-files/remark_lint/with_bin_path/node_modules/.bin/remark new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/ruby/dummy.rb b/test/test-files/ruby/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/nested/dummy.rb b/test/test-files/ruby/nested/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/nested/foo/Steepfile b/test/test-files/ruby/nested/foo/Steepfile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/nested/foo/dummy.rb b/test/test-files/ruby/nested/foo/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/nested/foo/one/dummy.rb b/test/test-files/ruby/nested/foo/one/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/nested/foo/two/Steepfile b/test/test-files/ruby/nested/foo/two/Steepfile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/nested/foo/two/dummmy.rb b/test/test-files/ruby/nested/foo/two/dummmy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/nested/foo/two/three/dummy.rb b/test/test-files/ruby/nested/foo/two/three/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/not_a_rails_app/file.rb b/test/test-files/ruby/not_a_rails_app/file.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_rails_app/app/dummy.rb b/test/test-files/ruby/valid_rails_app/app/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_rails_app/app/models/thing.rb b/test/test-files/ruby/valid_rails_app/app/models/thing.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_rails_app/app/views/my_great_view.html.erb b/test/test-files/ruby/valid_rails_app/app/views/my_great_view.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_rails_app/config/dummy.rb b/test/test-files/ruby/valid_rails_app/config/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_rails_app/db/dummy.rb b/test/test-files/ruby/valid_rails_app/db/dummy.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_ruby_app1/Rakefile b/test/test-files/ruby/valid_ruby_app1/Rakefile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_ruby_app1/lib/file.rb b/test/test-files/ruby/valid_ruby_app1/lib/file.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_ruby_app2/Gemfile b/test/test-files/ruby/valid_ruby_app2/Gemfile new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_ruby_app2/lib/file.rb b/test/test-files/ruby/valid_ruby_app2/lib/file.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_ruby_app3/.solargraph.yml b/test/test-files/ruby/valid_ruby_app3/.solargraph.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/valid_ruby_app3/lib/file.rb b/test/test-files/ruby/valid_ruby_app3/lib/file.rb new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/with_config/.rubocop.yml b/test/test-files/ruby/with_config/.rubocop.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/ruby/with_config/.standard.yml b/test/test-files/ruby/with_config/.standard.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/rust/cargo/Cargo.toml b/test/test-files/rust/cargo/Cargo.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/rust/cargo/testfile.rs b/test/test-files/rust/cargo/testfile.rs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/rust/rust-project/rust-project.json b/test/test-files/rust/rust-project/rust-project.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/rust/rust-project/testfile.rs b/test/test-files/rust/rust-project/testfile.rs new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/rustywind/test.html b/test/test-files/rustywind/test.html new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/sasslint/with-bin/node_modules/.bin/sass-lint b/test/test-files/sasslint/with-bin/node_modules/.bin/sass-lint new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/sasslint/with-source/node_modules/sass-lint/bin/sass-lint.js b/test/test-files/sasslint/with-source/node_modules/sass-lint/bin/sass-lint.js new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/scala/dummy.scala b/test/test-files/scala/dummy.scala new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/scala/invalid_sbt_project/Main.scala b/test/test-files/scala/invalid_sbt_project/Main.scala new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/scala/valid_sbt_project/Main.scala b/test/test-files/scala/valid_sbt_project/Main.scala new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/scala/valid_sbt_project/build.sbt b/test/test-files/scala/valid_sbt_project/build.sbt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/slimlint/.rubocop.yml b/test/test-files/slimlint/.rubocop.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/slimlint/subdir/file.slim b/test/test-files/slimlint/subdir/file.slim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/smlnj/cm/foo.sml b/test/test-files/smlnj/cm/foo.sml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/smlnj/cm/path/to/bar.sml b/test/test-files/smlnj/cm/path/to/bar.sml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/smlnj/cm/sources.cm b/test/test-files/smlnj/cm/sources.cm new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/smlnj/file/qux.sml b/test/test-files/smlnj/file/qux.sml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/solhint/Contract.sol b/test/test-files/solhint/Contract.sol new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/solhint/node_modules/.bin/solhint b/test/test-files/solhint/node_modules/.bin/solhint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/solhint/node_modules/solhint/index.js b/test/test-files/solhint/node_modules/solhint/index.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/solhint/package.json b/test/test-files/solhint/package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/spectral/node_modules/.bin/spectral b/test/test-files/spectral/node_modules/.bin/spectral new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/spectral/openapi.yaml b/test/test-files/spectral/openapi.yaml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/stack/stack.yaml b/test/test-files/stack/stack.yaml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/standard/with-bin/node_modules/.bin/standard b/test/test-files/standard/with-bin/node_modules/.bin/standard new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/standard/with-cmd/node_modules/standard/bin/cmd.js b/test/test-files/standard/with-cmd/node_modules/standard/bin/cmd.js new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/stylelint/node_modules/.bin/stylelint b/test/test-files/stylelint/node_modules/.bin/stylelint new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/stylua/stylua_config_dir/stylua.toml b/test/test-files/stylua/stylua_config_dir/stylua.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/stylua/stylua_dot_config_dir/.stylua.toml b/test/test-files/stylua/stylua_dot_config_dir/.stylua.toml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swaglint/docs/swagger.yaml b/test/test-files/swaglint/docs/swagger.yaml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swaglint/node_modules/.bin/swaglint b/test/test-files/swaglint/node_modules/.bin/swaglint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swift/dummy.swift b/test/test-files/swift/dummy.swift new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swift/non-swift-package-project/src/folder/dummy.swift b/test/test-files/swift/non-swift-package-project/src/folder/dummy.swift new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swift/swift-package-project-with-config/.swift-format b/test/test-files/swift/swift-package-project-with-config/.swift-format new file mode 100644 index 00000000..19fb8b96 --- /dev/null +++ b/test/test-files/swift/swift-package-project-with-config/.swift-format @@ -0,0 +1,10 @@ +{ + "version": 1, + "lineLength": 100, + "indentation": { + "spaces": 4 + }, + "respectsExistingLineBreaks": true, + "lineBreakBeforeControlFlowKeywords": true, + "lineBreakBeforeEachArgument": true +} diff --git a/test/test-files/swift/swift-package-project-with-config/Package.swift b/test/test-files/swift/swift-package-project-with-config/Package.swift new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swift/swift-package-project-with-config/src/folder/dummy.swift b/test/test-files/swift/swift-package-project-with-config/src/folder/dummy.swift new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swift/swift-package-project/Package.swift b/test/test-files/swift/swift-package-project/Package.swift new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swift/swift-package-project/src/folder/dummy.swift b/test/test-files/swift/swift-package-project/src/folder/dummy.swift new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swiftlint/cocoapods-and-react-native/Pods/SwiftLint/swiftlint b/test/test-files/swiftlint/cocoapods-and-react-native/Pods/SwiftLint/swiftlint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swiftlint/cocoapods-and-react-native/ios/Pods/SwiftLint/swiftlint b/test/test-files/swiftlint/cocoapods-and-react-native/ios/Pods/SwiftLint/swiftlint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swiftlint/cocoapods/Pods/SwiftLint/swiftlint b/test/test-files/swiftlint/cocoapods/Pods/SwiftLint/swiftlint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/swiftlint/react-native/ios/Pods/SwiftLint/swiftlint b/test/test-files/swiftlint/react-native/ios/Pods/SwiftLint/swiftlint new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/terraform/.terraform/dummy b/test/test-files/terraform/.terraform/dummy new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/terraform/main.tf b/test/test-files/terraform/main.tf new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tex/sample1.tex b/test/test-files/tex/sample1.tex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tex/sample2.tex b/test/test-files/tex/sample2.tex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tex/testfile.tex b/test/test-files/tex/testfile.tex new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/textlint/with_bin_path/node_modules/.bin/textlint b/test/test-files/textlint/with_bin_path/node_modules/.bin/textlint new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js b/test/test-files/textlint/with_textlint_bin_path/node_modules/textlint/bin/textlint.js new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/tflint/foo/.tflint.hcl b/test/test-files/tflint/foo/.tflint.hcl new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tflint/foo/bar.tf b/test/test-files/tflint/foo/bar.tf new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tfsec/json/.tfsec/config.json b/test/test-files/tfsec/json/.tfsec/config.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tfsec/json/main.tf b/test/test-files/tfsec/json/main.tf new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tfsec/yml/.tfsec/config.yml b/test/test-files/tfsec/yml/.tfsec/config.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tfsec/yml/main.tf b/test/test-files/tfsec/yml/main.tf new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tidy/.tidyrc b/test/test-files/tidy/.tidyrc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tidy/test.html b/test/test-files/tidy/test.html new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tidy/tidy b/test/test-files/tidy/tidy new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/tidy/tidy.exe b/test/test-files/tidy/tidy.exe new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/top/ale-special-directory-name-dont-use-this-please/empty-file b/test/test-files/top/ale-special-directory-name-dont-use-this-please/empty-file new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/top/example.ini b/test/test-files/top/example.ini new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/top/middle/bottom/dummy.txt b/test/test-files/top/middle/bottom/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tsserver/src/file1.ts b/test/test-files/tsserver/src/file1.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tsserver/src/level-1/file2.ts b/test/test-files/tsserver/src/level-1/file2.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tsserver/src/level-1/level-2/file3.ts b/test/test-files/tsserver/src/level-1/level-2/file3.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tsserver/src/level-1/tsconfig.json b/test/test-files/tsserver/src/level-1/tsconfig.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/tsserver/tsconfig.json b/test/test-files/tsserver/tsconfig.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/typescript/custom_import_map.json b/test/test-files/typescript/custom_import_map.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/typescript/import_map.json b/test/test-files/typescript/import_map.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/typescript/test.ts b/test/test-files/typescript/test.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/typescript/tsconfig.json b/test/test-files/typescript/tsconfig.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/invalid_vim_project/test.vim b/test/test-files/vim/invalid_vim_project/test.vim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/node_modules/.bin/vim-language-server b/test/test-files/vim/node_modules/.bin/vim-language-server new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/path_with_autoload/autoload/test.vim b/test/test-files/vim/path_with_autoload/autoload/test.vim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/path_with_autoload/test.vim b/test/test-files/vim/path_with_autoload/test.vim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/path_with_initvim/init.vim b/test/test-files/vim/path_with_initvim/init.vim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/path_with_plugin/plugin/test.vim b/test/test-files/vim/path_with_plugin/plugin/test.vim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/path_with_plugin/test.vim b/test/test-files/vim/path_with_plugin/test.vim new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/vim/path_with_vimrc/.vimrc b/test/test-files/vim/path_with_vimrc/.vimrc new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/volar/node_modules/.bin/vue-language-server b/test/test-files/volar/node_modules/.bin/vue-language-server new file mode 100755 index 00000000..e69de29b diff --git a/test/test-files/volar/node_modules/typescript/lib/tsserverlibrary.js b/test/test-files/volar/node_modules/typescript/lib/tsserverlibrary.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/volar/package.json b/test/test-files/volar/package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/volar/src/App.vue b/test/test-files/volar/src/App.vue new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/write-good/node-modules-2/node_modules/write-good/bin/write-good.js b/test/test-files/write-good/node-modules-2/node_modules/write-good/bin/write-good.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/write-good/node-modules/node_modules/.bin/write-good b/test/test-files/write-good/node-modules/node_modules/.bin/write-good new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/xml/dummy.xml b/test/test-files/xml/dummy.xml new file mode 100644 index 00000000..18b29531 --- /dev/null +++ b/test/test-files/xml/dummy.xml @@ -0,0 +1,4 @@ + + + + diff --git a/test/test-files/xo/monorepo/node_modules/xo/cli.js b/test/test-files/xo/monorepo/node_modules/xo/cli.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/xo/monorepo/package.json b/test/test-files/xo/monorepo/package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/xo/monorepo/packages/a/index.js b/test/test-files/xo/monorepo/packages/a/index.js new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/xo/monorepo/packages/a/index.ts b/test/test-files/xo/monorepo/packages/a/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/xo/monorepo/packages/a/package.json b/test/test-files/xo/monorepo/packages/a/package.json new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/yaml/test.yaml b/test/test-files/yaml/test.yaml new file mode 100644 index 00000000..e69de29b diff --git a/test/test-files/yara/dummy.yar b/test/test-files/yara/dummy.yar new file mode 100644 index 00000000..eb1e9359 --- /dev/null +++ b/test/test-files/yara/dummy.yar @@ -0,0 +1,5 @@ +rule dummy +{ + condition: + false +} diff --git a/test/test-files/zig/build.zig b/test/test-files/zig/build.zig new file mode 100644 index 00000000..e69de29b diff --git a/test/test_ale_has.vader b/test/test_ale_has.vader new file mode 100644 index 00000000..a5a381d2 --- /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-4.0.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 new file mode 100644 index 00000000..e996169f --- /dev/null +++ b/test/test_ale_info.vader @@ -0,0 +1,772 @@ +Before: + Save g:ale_buffer_info + Save g:ale_cache_executable_check_failures + Save g:ale_change_sign_column_color + Save g:ale_command_wrapper + Save g:ale_completion_delay + Save g:ale_completion_enabled + Save g:ale_completion_max_suggestions + Save g:ale_disable_lsp + Save g:ale_echo_cursor + Save g:ale_echo_msg_error_str + Save g:ale_echo_msg_format + Save g:ale_echo_msg_info_str + Save g:ale_echo_msg_warning_str + Save g:ale_fix_on_save + Save g:ale_fixers + Save g:ale_history_enabled + Save g:ale_history_log_output + Save g:ale_info_default_mode + Save g:ale_keep_list_window_open + Save g:ale_lint_delay + Save g:ale_lint_on_enter + Save g:ale_lint_on_filetype_changed + Save g:ale_lint_on_insert_leave + Save g:ale_lint_on_save + Save g:ale_lint_on_text_changed + Save g:ale_linters + Save g:ale_linters_explicit + Save g:ale_linters_ignore + Save g:ale_list_vertical + Save g:ale_list_window_size + Save g:ale_loclist_msg_format + Save g:ale_lsp_error_messages + Save g:ale_max_buffer_history_size + Save g:ale_max_signs + Save g:ale_maximum_file_size + Save g:ale_open_list + Save g:ale_pattern_options + Save g:ale_pattern_options_enabled + Save g:ale_root + Save g:ale_set_balloons + Save g:ale_set_highlights + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_set_signs + Save g:ale_sign_column_always + Save g:ale_sign_error + Save g:ale_sign_info + Save g:ale_sign_offset + Save g:ale_sign_style_error + Save g:ale_sign_style_warning + Save g:ale_sign_warning + Save g:ale_sign_highlight_linenrs + Save g:ale_type_map + Save g:ale_use_neovim_diagnostics_api + Save g:ale_use_global_executables + Save g:ale_virtualtext_cursor + Save g:ale_warn_about_trailing_blank_lines + Save g:ale_warn_about_trailing_whitespace + + unlet! b:ale_history + + let g:ale_buffer_info = {} + let g:ale_cache_executable_check_failures = 0 + let g:ale_change_sign_column_color = 0 + let g:ale_command_wrapper = '' + let g:ale_completion_delay = 100 + let g:ale_completion_enabled = 0 + let g:ale_completion_max_suggestions = 50 + let g:ale_disable_lsp = 0 + let g:ale_echo_cursor = 1 + let g:ale_echo_msg_error_str = 'Error' + let g:ale_echo_msg_format = '%code: %%s' + let g:ale_echo_msg_info_str = 'Info' + let g:ale_echo_msg_warning_str = 'Warning' + let g:ale_fix_on_save = 0 + let g:ale_history_enabled = 1 + let g:ale_history_log_output = 1 + " This needs to be set to echo for this series of tests. + let g:ale_info_default_mode = 'echo' + let g:ale_keep_list_window_open = 0 + let g:ale_lint_delay = 200 + let g:ale_lint_on_enter = 1 + let g:ale_lint_on_filetype_changed = 1 + let g:ale_lint_on_insert_leave = 1 + let g:ale_lint_on_save = 1 + let g:ale_lint_on_text_changed = 'normal' + let g:ale_linters_explicit = 0 + let g:ale_linters_ignore = {'python': ['pyright']} + 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_error_messages = {} + let g:ale_max_buffer_history_size = 20 + let g:ale_max_signs = -1 + let g:ale_maximum_file_size = 0 + let g:ale_open_list = 0 + let g:ale_pattern_options = {} + let g:ale_pattern_options_enabled = 0 + let g:ale_root = {} + let g:ale_set_balloons = 0 + let g:ale_set_highlights = 1 + let g:ale_set_loclist = 1 + let g:ale_set_quickfix = 0 + let g:ale_set_signs = 1 + let g:ale_sign_column_always = 0 + let g:ale_sign_error = '>>' + let g:ale_sign_info = '--' + let g:ale_sign_offset = 1000000 + let g:ale_sign_style_error = '>>' + let g:ale_sign_style_warning = '--' + let g:ale_sign_warning = '--' + let g:ale_sign_highlight_linenrs = 0 + let g:ale_type_map = {} + let g:ale_use_neovim_diagnostics_api = 0 + let g:ale_use_global_executables = v:null + let g:ale_virtualtext_cursor = 'disabled' + let g:ale_warn_about_trailing_blank_lines = 1 + let g:ale_warn_about_trailing_whitespace = 1 + + let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout'} + let g:testlinter2 = {'name': 'testlinter2', 'executable': 'testlinter2', 'command': 'testlinter2', 'callback': 'testCB2', 'output_stream': 'stdout'} + + call ale#engine#ResetExecutableCache() + call ale#linter#Reset() + call ale#linter#PreventLoading('testft') + let g:ale_linters = {} + let g:ale_fixers = {} + let g:ale_linter_aliases = {} + let g:ale_buffer_info = {} + let g:fixer_lines = [ + \ ' Suggested Fixers:', + \ ' ''foo'' - Fix things the foo way', + \ ' ', + \] + let g:globals_lines = [ + \ ' Global Variables:', + \ 'let g:ale_cache_executable_check_failures = 0', + \ 'let g:ale_change_sign_column_color = 0', + \ 'let g:ale_command_wrapper = ''''', + \ 'let g:ale_completion_delay = 100', + \ 'let g:ale_completion_enabled = 0', + \ 'let g:ale_completion_max_suggestions = 50', + \ 'let g:ale_disable_lsp = 0', + \ 'let g:ale_echo_cursor = 1', + \ 'let g:ale_echo_msg_error_str = ''Error''', + \ 'let g:ale_echo_msg_format = ''%code: %%s''', + \ 'let g:ale_echo_msg_info_str = ''Info''', + \ 'let g:ale_echo_msg_warning_str = ''Warning''', + \ 'let g:ale_enabled = 1', + \ 'let g:ale_fix_on_save = 0', + \ 'let g:ale_fixers = {}', + \ 'let g:ale_history_enabled = 1', + \ 'let g:ale_info_default_mode = ''echo''', + \ 'let g:ale_history_log_output = 1', + \ 'let g:ale_keep_list_window_open = 0', + \ 'let g:ale_lint_delay = 200', + \ 'let g:ale_lint_on_enter = 1', + \ 'let g:ale_lint_on_filetype_changed = 1', + \ 'let g:ale_lint_on_insert_leave = 1', + \ 'let g:ale_lint_on_save = 1', + \ 'let g:ale_lint_on_text_changed = ''normal''', + \ 'let g:ale_linter_aliases = {}', + \ 'let g:ale_linters = {}', + \ 'let g:ale_linters_explicit = 0', + \ 'let g:ale_linters_ignore = {''python'': [''pyright'']}', + \ 'let g:ale_list_vertical = 0', + \ 'let g:ale_list_window_size = 10', + \ 'let g:ale_loclist_msg_format = ''%code: %%s''', + \ 'let g:ale_max_buffer_history_size = 20', + \ 'let g:ale_max_signs = -1', + \ 'let g:ale_maximum_file_size = 0', + \ 'let g:ale_open_list = 0', + \ 'let g:ale_pattern_options = {}', + \ 'let g:ale_pattern_options_enabled = 0', + \ 'let g:ale_root = {}', + \ 'let g:ale_set_balloons = 0', + \ 'let g:ale_set_highlights = 1', + \ 'let g:ale_set_loclist = 1', + \ 'let g:ale_set_quickfix = 0', + \ 'let g:ale_set_signs = 1', + \ 'let g:ale_sign_column_always = 0', + \ 'let g:ale_sign_error = ''>>''', + \ 'let g:ale_sign_info = ''--''', + \ 'let g:ale_sign_offset = 1000000', + \ 'let g:ale_sign_style_error = ''>>''', + \ 'let g:ale_sign_style_warning = ''--''', + \ 'let g:ale_sign_warning = ''--''', + \ 'let g:ale_sign_highlight_linenrs = 0', + \ 'let g:ale_type_map = {}', + \ 'let g:ale_use_neovim_diagnostics_api = 0', + \ 'let g:ale_use_global_executables = v:null', + \ 'let g:ale_virtualtext_cursor = ''disabled''', + \ 'let g:ale_warn_about_trailing_blank_lines = 1', + \ 'let g:ale_warn_about_trailing_whitespace = 1', + \ ' ', + \] + let g:command_header = [ + \ ' Command History:', + \] + + function CheckInfo(expected_list) abort + let l:output = '' + + redir => l:output + noautocmd silent ALEInfo + redir END + + AssertEqual a:expected_list, split(l:output, "\n") + endfunction + + call ale#test#SetDirectory('/testplugin/test') + + call ale#fix#registry#Clear() + call ale#fix#registry#Add('foo', 'x', [], 'Fix things the foo way') + +After: + Restore + + if exists('g:info_test_file') && filereadable(g:info_test_file) + call delete(g:info_test_file) + endif + + unlet! g:testlinter1 + unlet! g:testlinter2 + unlet! b:ale_history + unlet! b:ale_linters + unlet! g:output + unlet! g:fixer_lines + unlet! g:variables_lines + unlet! g:globals_string + unlet! g:command_header + unlet! g:ale_testft_testlinter1_foo + unlet! g:ale_testft_testlinter1_bar + unlet! g:ale_testft2_testlinter2_foo + unlet! b:ale_testft2_testlinter2_foo + unlet! g:ale_testft2_testlinter2_bar + unlet! g:info_test_file + unlet! g:ale_testft_build_dir_names + unlet! g:ale_testft_testlinter2_option + delfunction CheckInfo + + call ale#test#RestoreDirectory() + call ale#fix#registry#ResetToDefaults() + +Given nolintersft (Empty buffer with no linters): +Execute (ALEInfo with no linters should return the right output): + call CheckInfo( + \ [ + \ ' Current Filetype: nolintersft', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given (Empty buffer with no filetype): +Execute (ALEInfo should return buffer-local global ALE settings): + let b:ale_linters = {'x': ['y']} + + call insert( + \ g:globals_lines, + \ 'let b:ale_linters = {''x'': [''y'']}', + \ index(g:globals_lines, 'let g:ale_linters = {}') + 1 + \) + + call CheckInfo( + \ [ + \ ' Current Filetype: ', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given (Empty buffer with no filetype): +Execute (ALEInfo with no filetype should return the right output): + call CheckInfo( + \ [ + \ ' Current Filetype: ', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft (Empty buffer): +Execute (ALEInfo with a single linter should return the right output): + call ale#linter#Define('testft', g:testlinter1) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft (Empty buffer): +Execute (ALEInfo with two linters should return the right output): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft (Empty buffer): +Execute (ALEInfo should calculate enabled linters correctly): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter2']} + + let g:globals_lines[index(g:globals_lines, 'let g:ale_linters = {}')] + \ = 'let g:ale_linters = {''testft'': [''testlinter2'']}' + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft (Empty buffer): +Execute (ALEInfo should only return linters for current filetype): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo with compound filetypes should return linters for both of them): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should return appropriately named global variables): + let g:ale_testft_testlinter1_foo = 'abc' + let g:ale_testft_testlinter1_bar = ['abc'] + let g:ale_testft2_testlinter2_foo = 123 + let g:ale_testft2_testlinter2_bar = {'x': 'y'} + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ 'let g:ale_testft2_testlinter2_bar = {''x'': ''y''}', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let g:ale_testft_testlinter1_bar = [''abc'']', + \ 'let g:ale_testft_testlinter1_foo = ''abc''', + \ ' ', + \ ] + \ + g:globals_lines + \ + g:command_header + \) + +Execute (ALEInfoToFile should write to a file correctly): + let g:ale_testft_testlinter1_foo = 'abc' + let g:ale_testft_testlinter1_bar = ['abc'] + let g:ale_testft2_testlinter2_foo = 123 + let g:ale_testft2_testlinter2_bar = {'x': 'y'} + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + let g:info_test_file = tempname() + execute 'ALEInfoToFile ' . fnameescape(g:info_test_file) + + AssertEqual + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ 'let g:ale_testft2_testlinter2_bar = {''x'': ''y''}', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let g:ale_testft_testlinter1_bar = [''abc'']', + \ 'let g:ale_testft_testlinter1_foo = ''abc''', + \ ' ', + \ ] + \ + g:globals_lines + \ + g:command_header, + \ readfile(g:info_test_file) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should buffer-local linter variables): + let g:ale_testft2_testlinter2_foo = 123 + let b:ale_testft2_testlinter2_foo = 456 + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let b:ale_testft2_testlinter2_foo = 456', + \ ' ', + \ ] + \ + g:globals_lines + \ + g:command_header + \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should output linter aliases): + let g:testlinter1.aliases = ['testftalias1', 'testftalias2'] + let g:testlinter2.aliases = ['testftalias3', 'testftalias4'] + + let g:ale_testft2_testlinter2_foo = 123 + let b:ale_testft2_testlinter2_foo = 456 + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Linter Aliases:', + \ '''testlinter1'' -> [''testftalias1'', ''testftalias2'']', + \ '''testlinter2'' -> [''testftalias3'', ''testftalias4'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + [ + \ ' Linter Variables:', + \ 'let g:ale_testft2_testlinter2_foo = 123', + \ 'let b:ale_testft2_testlinter2_foo = 456', + \ ' ', + \ ] + \ + g:globals_lines + \ + g:command_header + \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should return command history): + let b:ale_history = [ + \ {'status': 'started', 'job_id': 347, 'command': 'first command'}, + \ {'status': 'started', 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, + \] + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(started) ''first command''', + \ '(started) [''/bin/bash'', ''\c'', ''last command'']', + \ ] + \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo command history should print exit codes correctly): + let b:ale_history = [ + \ {'status': 'finished', 'exit_code': 0, 'job_id': 347, 'command': 'first command'}, + \ {'status': 'finished', 'exit_code': 1, 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, + \] + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(finished - exit code 0) ''first command''', + \ '(finished - exit code 1) [''/bin/bash'', ''\c'', ''last command'']', + \ ] + \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo command history should print command output if logging is on): + let g:ale_history_log_output = 1 + + let b:ale_history = [ + \ { + \ 'status': 'finished', + \ 'exit_code': 0, + \ 'job_id': 347, + \ 'command': 'first command', + \ 'output': ['some', 'first command output'], + \ }, + \ { + \ 'status': 'finished', + \ 'exit_code': 1, + \ 'job_id': 347, + \ 'command': ['/bin/bash', '\c', 'last command'], + \ 'output': ['different second command output'], + \ }, + \ { + \ 'status': 'finished', + \ 'exit_code': 0, + \ 'job_id': 347, + \ 'command': 'command with no output', + \ 'output': [], + \ }, + \] + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(finished - exit code 0) ''first command''', + \ '', + \ '<<>>', + \ 'some', + \ 'first command output', + \ '<<>>', + \ '', + \ '(finished - exit code 1) [''/bin/bash'', ''\c'', ''last command'']', + \ '', + \ '<<>>', + \ 'different second command output', + \ '<<>>', + \ '', + \ '(finished - exit code 0) ''command with no output''', + \ '', + \ '<<>>', + \ ] + \) + +Execute (ALEInfo should include executable checks in the history): + call ale#linter#Define('testft', g:testlinter1) + call ale#engine#IsExecutable(bufnr(''), has('win32') ? 'cmd' : 'echo') + call ale#engine#IsExecutable(bufnr(''), has('win32') ? 'cmd' : 'echo') + call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') + call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(executable check - success) ' . (has('win32') ? 'cmd' : 'echo'), + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \ ] + \) + +Execute (The option for caching failing executable checks should work): + let g:ale_cache_executable_check_failures = 1 + " Replace output for the variable we have to modify above. + call map(g:globals_lines, { + \ _, val -> val =~ 'ale_cache_executable_check_failures' ? 'let g:ale_cache_executable_check_failures = 1' : val + \}) + + call ale#linter#Define('testft', g:testlinter1) + + call ale#engine#IsExecutable(bufnr(''), has('win32') ? 'cmd' : 'echo') + call ale#engine#IsExecutable(bufnr(''), has('win32') ? 'cmd' : 'echo') + call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') + call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \ + [ + \ '', + \ '(executable check - success) ' . (has('win32') ? 'cmd' : 'echo'), + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \ ] + \) + +Given testft (Empty buffer): +Execute (LSP errors for a linter should be outputted): + let g:ale_lsp_error_messages = {'testlinter1': ['foo', 'bar']} + call ale#linter#Define('testft', g:testlinter1) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + [ + \ ' LSP Error Messages:', + \ '', + \ '(Errors for testlinter1)', + \ 'foo', + \ 'bar', + \ ] + \ + g:command_header + \) + +Given testft (Empty buffer): +Execute (LSP errors for other linters shouldn't appear): + let g:ale_lsp_error_messages = {'testlinter2': ['foo']} + call ale#linter#Define('testft', g:testlinter1) + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should include linter global options): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + " eg: like g:c_build_dir_names + let g:ale_testft_build_dir_names = ['build', 'bin'] + + let g:variables_lines = [ + \ ' Linter Variables:', + \ 'let g:ale_testft_build_dir_names = [''build'', ''bin'']', + \ ' ', + \] + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft (Empty buffer with two filetypes): +Execute (ALEInfo should include linter global options for enabled linters): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + + let g:ale_linters = {'testft': ['testlinter1']} + + " should not appear, since not enabled + let g:ale_testft_testlinter2_option = 'test' + + let g:globals_lines[index(g:globals_lines, 'let g:ale_linters = {}')] + \ = 'let g:ale_linters = {''testft'': [''testlinter1'']}' + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', + \ ] + \ + g:fixer_lines + \ + g:globals_lines + \ + g:command_header + \) + + +Given ale-info (An ALEInfo buffer): +Execute(ALEInfo should not report information about its own window): + call CheckInfo([]) diff --git a/test/test_ale_info_to_clipboard.vader b/test/test_ale_info_to_clipboard.vader new file mode 100644 index 00000000..d0131da1 --- /dev/null +++ b/test/test_ale_info_to_clipboard.vader @@ -0,0 +1,15 @@ +After: + unlet! g:output + +Execute(ALEInfo -clipboard should tell the user that clipboard support is required): + " When run in the Docker image, there's no clipboard support, so this test + " will actually run. + if !has('clipboard') + let g:output = '' + + redir => g:output + :ALEInfo -clipboard + redir END + + AssertEqual 'clipboard not available. Try :ALEInfoToFile instead.', join(split(g:output)) + endif diff --git a/test/test_ale_lint_command.vader b/test/test_ale_lint_command.vader new file mode 100644 index 00000000..800c0827 --- /dev/null +++ b/test/test_ale_lint_command.vader @@ -0,0 +1,77 @@ +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('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo bar', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}] + + function! ToggleTestCallback(buffer, output) + return [{ + \ 'bufnr': a:buffer, + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': join(split(a:output[0])), + \ 'type': 'E', + \ 'nr': -1, + \}] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'ToggleTestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': 'echo foo bar', + \}) + +After: + Restore + + unlet! g:expected_loclist + unlet! b:i + + call ale#engine#Cleanup(bufnr('')) + call ale#linter#Reset() + + let g:ale_buffer_info = {} + + delfunction ToggleTestCallback + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALELint should run the linters): + AssertEqual 'foobar', &filetype + + " Try to run the linter a few times, as it fails randomly in NeoVim. + for b:i in range(5) + ALELint + call ale#test#WaitForJobs(2000) + + if !has('nvim') + " Sleep so the delayed list function can run. + " This breaks the tests in NeoVim for some reason. + sleep 1ms + endif + + if ale#test#GetLoclistWithoutNewerKeys() == g:expected_loclist + break + endif + endfor + + " Check the loclist + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() diff --git a/test/test_ale_lint_stop_command.vader b/test/test_ale_lint_stop_command.vader new file mode 100644 index 00000000..c50db5a8 --- /dev/null +++ b/test/test_ale_lint_stop_command.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + + call ale#linter#PreventLoading('testft') + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': {-> []}, + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'sleep 9001', + \}) + +After: + Restore + + call ale#linter#Reset() + +Given testft (An empty file): +Execute(ALELintStop should stop ALE from linting): + ALELint + + Assert ale#engine#IsCheckingBuffer(bufnr('')), 'ALE did not start checking the buffer' + + ALELintStop + + Assert !ale#engine#IsCheckingBuffer(bufnr('')), 'ALELintStop didn''t work' diff --git a/test/test_ale_populate_command.vader b/test/test_ale_populate_command.vader new file mode 100644 index 00000000..14789c72 --- /dev/null +++ b/test/test_ale_populate_command.vader @@ -0,0 +1,96 @@ +Before: + Save g:ale_buffer_info + Save g:ale_enabled + Save g:ale_set_quickfix + Save g:ale_set_loclist + Save g:ale_open_list + + let g:ale_buffer_info = {} + let g:ale_enabled = 1 + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 + let g:ale_open_list = 1 + + let g:expected_loclist = [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo bar', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}] + + function! ToggleTestCallback(buffer, output) + return [{ + \ 'bufnr': a:buffer, + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': join(split(a:output[0])), + \ 'type': 'E', + \ 'nr': -1, + \}] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'ToggleTestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': 'echo foo bar', + \}) + +After: + Restore + + unlet! g:expected_loclist + unlet! b:i + + call ale#engine#Cleanup(bufnr('')) + call ale#linter#Reset() + + " Not sure this is necessary since it was Save/Restore-d + let g:ale_buffer_info = {} + + delfunction ToggleTestCallback + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALEPopulateQuickfix should have results): + AssertEqual 'foobar', &filetype + + " Clear so we can check that they're unmodified. + call setqflist([]) + call setloclist(winnr(), []) + + " Try to run the linter a few times, as it fails randomly in NeoVim. + for b:i in range(5) + ALELint + call ale#test#WaitForJobs(2000) + + if !has('nvim') + " Sleep so the delayed list function can run. + " This breaks the tests in NeoVim for some reason. + sleep 1ms + endif + + if ale#test#GetLoclistWithoutNewerKeys() == g:expected_loclist + break + endif + endfor + + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + AssertEqual [], ale#test#GetQflistWithoutNewerKeys() + + ALEPopulateLocList + AssertNotEqual 0, get(getloclist(0, {'winid':0}), 'winid', 0) + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() + + ALEPopulateQuickfix + AssertEqual g:expected_loclist, ale#test#GetQflistWithoutNewerKeys() diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader new file mode 100644 index 00000000..98df3f75 --- /dev/null +++ b/test/test_ale_toggle.vader @@ -0,0 +1,444 @@ +Before: + Save g:ale_buffer_info + Save g:ale_set_signs + Save g:ale_set_lists_synchronously + Save g:ale_run_synchronously + Save g:ale_pattern_options + Save g:ale_pattern_options_enabled + Save g:ale_set_balloons + + let g:ale_set_signs = 1 + let g:ale_set_lists_synchronously = 1 + let g:ale_run_synchronously = 1 + unlet! g:ale_run_synchronously_callbacks + let g:ale_pattern_options = {} + let g:ale_pattern_options_enabled = 1 + let g:ale_set_balloons = + \ has('balloon_eval') && has('gui_running') || + \ has('balloon_eval_term') && !has('gui_running') + + unlet! b:ale_enabled + + let g:ale_buffer_info = {} + let g:expected_loclist = [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo bar', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}] + let g:expected_groups = [ + \ 'ALECleanupGroup', + \ 'ALEEvents', + \ 'ALEHighlightBufferGroup', + \] + let g:has_nvim_highlight = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') + + function! ToggleTestCallback(buffer, output) + return [{ + \ 'bufnr': a:buffer, + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo bar', + \ 'type': 'E', + \ 'nr': -1, + \}] + endfunction + + function! ParseAuGroups() + redir => l:output + silent exec 'autocmd' + redir end + + let l:results = [] + + for l:line in split(l:output, "\n") + let l:match = matchlist(l:line, '^ALE[a-zA-Z]\+') + + " We don't care about some groups here. + if !empty(l:match) + \&& l:match[0] !=# 'ALECompletionGroup' + \&& l:match[0] !=# 'ALEBufferFixGroup' + \&& l:match[0] !=# 'ALEPatternOptionsGroup' + call add(l:results, l:match[0]) + endif + endfor + + call uniq(sort(l:results)) + + return l:results + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'ToggleTestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': 'echo', + \ 'read_buffer': 0, + \}) + + call ale#sign#Clear() + +After: + Restore + + unlet! g:ale_run_synchronously_callbacks + unlet! g:expected_loclist + unlet! g:expected_groups + unlet! b:ale_enabled + unlet! g:output + unlet! g:has_nvim_highlight + + call ale#linter#Reset() + + " Toggle ALE back on if we fail when it's disabled. + if !g:ale_enabled + ALEToggle + endif + + delfunction ToggleTestCallback + delfunction ParseAuGroups + + call setloclist(0, []) + call ale#sign#Clear() + call clearmatches() + +Given foobar (Some imaginary filetype): + foo + bar + baz + +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#GetLoclistWithoutNewerKeys() + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + + " Only check the legacy matches if not using the new NeoVIM API. + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + + AssertEqual g:expected_groups, ParseAuGroups() + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + + " Now Toggle ALE off. + ALEToggle + + " Everything should be cleared. + Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys(), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif + + AssertEqual g:expected_groups, ParseAuGroups() + + " Toggle ALE on, everything should be set up and run again. + ALEToggle + call ale#test#FlushJobs() + + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + + AssertEqual g:expected_groups, ParseAuGroups() + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + +Execute(ALEToggle should skip filename keys and preserve them): + AssertEqual 'foobar', &filetype + + let g:ale_buffer_info['/foo/bar/baz.txt'] = { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \} + + ALELint + call ale#test#FlushJobs() + + " Now Toggle ALE off. + ALEToggle + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) + + " Toggle ALE on again. + ALEToggle + call ale#test#FlushJobs() + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) + +Execute(ALEDisable should reset everything and stay disabled): + ALELint + call ale#test#FlushJobs() + + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() + + ALEDisable + call ale#test#FlushJobs() + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + AssertEqual 0, g:ale_enabled + + ALEDisable + call ale#test#FlushJobs() + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + AssertEqual 0, g:ale_enabled + +Execute(ALEEnable should enable ALE and lint again): + let g:ale_enabled = 0 + + ALEEnable + call ale#test#FlushJobs() + + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() + AssertEqual 1, g:ale_enabled + +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#GetLoclistWithoutNewerKeys() + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + + " 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' + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys(), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif + + AssertEqual 1, g:ale_enabled + +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#GetLoclistWithoutNewerKeys() + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + + " Now Toggle ALE off. + ALEToggleBuffer + + " Everything should be cleared. + Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys(), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif + + " Toggle ALE on, everything should be set up and run again. + ALEToggleBuffer + call ale#test#FlushJobs() + + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + + AssertEqual g:expected_groups, ParseAuGroups() + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + +Execute(ALEDisableBuffer should reset everything and stay disabled): + ALELint + call ale#test#FlushJobs() + + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() + + ALEDisableBuffer + call ale#test#FlushJobs() + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + AssertEqual 0, b:ale_enabled + +Execute(ALEEnableBuffer should enable ALE and lint again): + let b:ale_enabled = 0 + + ALEEnableBuffer + call ale#test#FlushJobs() + + AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutNewerKeys() + AssertEqual 1, b:ale_enabled + +Execute(ALEEnableBuffer should complain when ALE is disabled globally): + let g:ale_enabled = 0 + let b:ale_enabled = 0 + + redir => g:output + ALEEnableBuffer + redir END + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + AssertEqual 0, b:ale_enabled + AssertEqual 0, g:ale_enabled + AssertEqual + \ 'ALE cannot be enabled locally when disabled globally', + \ join(split(g:output)) + +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#GetLoclistWithoutNewerKeys() + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + + " 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' + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys(), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif + + AssertEqual 1, g:ale_enabled + AssertEqual 1, get(b:, 'ale_enabled', 1) + +Execute(Disabling ALE should disable balloons): + " These tests won't run in the console, but we can run them manually in GVim. + if has('balloon_eval') && has('gui_running') + \|| (has('balloon_eval_term') && !has('gui_running')) + call ale#linter#Reset() + + " Enable balloons, so we can check the expr value. + call ale#balloon#Enable() + + if has('balloon_eval') && has('gui_running') + AssertEqual 1, &ballooneval + else + AssertEqual 1, &balloonevalterm + endif + + AssertEqual 'ale#balloon#Expr()', &balloonexpr + + " Toggle ALE off. + ALEToggle + + " The balloon settings should be reset. + if has('balloon_eval') && has('gui_running') + AssertEqual 0, &ballooneval + else + AssertEqual 0, &balloonevalterm + endif + + AssertEqual '', &balloonexpr + endif + +Execute(Enabling ALE should enable balloons if the setting is on): + if has('balloon_eval') && has('gui_running') + \|| (has('balloon_eval_term') && !has('gui_running')) + call ale#linter#Reset() + call ale#balloon#Disable() + ALEDisable + let g:ale_set_balloons = 0 + ALEEnable + + if has('balloon_eval') && has('gui_running') + AssertEqual 0, &ballooneval + else + AssertEqual 0, &balloonevalterm + endif + + AssertEqual '', &balloonexpr + + ALEDisable + let g:ale_set_balloons = 1 + ALEEnable + + if has('balloon_eval') && has('gui_running') + AssertEqual 1, &ballooneval + else + AssertEqual 1, &balloonevalterm + endif + + AssertEqual 'ale#balloon#Expr()', &balloonexpr + endif diff --git a/test/test_ale_var.vader b/test/test_ale_var.vader new file mode 100644 index 00000000..419a9983 --- /dev/null +++ b/test/test_ale_var.vader @@ -0,0 +1,24 @@ +Before: + let g:ale_some_variable = 'abc' + +After: + unlet! g:ale_some_variable + unlet! b:undefined_variable_name + +Execute(ale#Var should return global variables): + AssertEqual 'abc', ale#Var(bufnr(''), 'some_variable') + +Execute(ale#Var should return buffer overrides): + let b:ale_some_variable = 'def' + + AssertEqual 'def', ale#Var(bufnr(''), 'some_variable') + +Execute(ale#Var should return buffer overrides for buffer numbers as strings): + let b:ale_some_variable = 'def' + + AssertEqual 'def', ale#Var(string(bufnr('')), 'some_variable') + +Execute(ale#Var should throw exceptions for undefined variables): + let b:undefined_variable_name = 'def' + + AssertThrows call ale#Var(bufnr(''), 'undefined_variable_name') diff --git a/test/test_alejobstarted_autocmd.vader b/test/test_alejobstarted_autocmd.vader new file mode 100644 index 00000000..6fa1ff8e --- /dev/null +++ b/test/test_alejobstarted_autocmd.vader @@ -0,0 +1,46 @@ +Before: + Save g:ale_buffer_info + + let g:job_started_success = 0 + let g:ale_run_synchronously = 1 + + unlet! b:ale_linted + + function! TestCallback(buffer, output) + return [] + endfunction + + call ale#linter#PreventLoading('testft') + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'true', + \}) + +After: + Restore + + let g:ale_run_synchronously = 0 + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + + unlet! g:job_started_success + + delfunction TestCallback + call ale#linter#Reset() + +Given testft (An empty file): +Execute(Run a lint cycle with an actual job to check for ALEJobStarted): + augroup VaderTest + autocmd! + autocmd User ALEJobStarted let g:job_started_success = 1 + augroup END + + ALELint + + AssertEqual g:job_started_success, 1 diff --git a/test/test_alelint_autocmd.vader b/test/test_alelint_autocmd.vader new file mode 100644 index 00000000..332cb36f --- /dev/null +++ b/test/test_alelint_autocmd.vader @@ -0,0 +1,40 @@ +Before: + let g:pre_success = 0 + let g:post_success = 0 + let g:ale_run_synchronously = 1 + + unlet! b:ale_linted + +After: + let g:ale_run_synchronously = 0 + let g:ale_buffer_info = {} + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + +Given testft(An empty file): +Execute(Run a lint cycle, and check that a variable is set in the autocmd): + augroup VaderTest + autocmd! + autocmd User ALELintPre let g:pre_success = 1 + autocmd User ALELintPost let g:post_success = 1 + augroup END + + call ale#Queue(0) + + AssertEqual g:pre_success, 1 + AssertEqual g:post_success, 1 + +Execute(b:ale_linted should be increased after each lint cycle): + AssertEqual get(b:, 'ale_linted'), 0 + + call ale#Queue(0) + + AssertEqual get(b:, 'ale_linted'), 1 + + call ale#Queue(0) + + AssertEqual get(b:, 'ale_linted'), 2 diff --git a/test/test_ant_build_classpath_command.vader b/test/test_ant_build_classpath_command.vader new file mode 100644 index 00000000..b97dc594 --- /dev/null +++ b/test/test_ant_build_classpath_command.vader @@ -0,0 +1,27 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/java/javac.vim + + Save $PATH + let $PATH = ale#path#Simplify(g:dir . '/test-files/ant/bin') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should return `cd '[dir]' && 'ant' classpath -S -q`): + call ale#test#SetFilename('test-files/ant/ant-project/Main.java') + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir . '/test-files/ant/ant-project'), + \ ale#Escape('ant') . ' classpath' . ' -S' . ' -q', + \ ], + \ ale#ant#BuildClasspathCommand(bufnr('')) + +Execute(Should return empty string if ant cannot be executed): + call ale#test#SetFilename('test-files/ant/not-an-ant-project/Main.java') + + AssertEqual ['', ''], ale#ant#BuildClasspathCommand(bufnr('')) diff --git a/test/test_ant_find_project_root.vader b/test/test_ant_find_project_root.vader new file mode 100644 index 00000000..b0868ad7 --- /dev/null +++ b/test/test_ant_find_project_root.vader @@ -0,0 +1,35 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/java/javac.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should return current directory if called on the project root): + call ale#test#SetFilename('test-files/ant/ant-project/Main.java') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/ant/ant-project'), + \ ale#ant#FindProjectRoot(bufnr('')) + +Execute(Should return root directory if called on a deeply nested source file): + call ale#test#SetFilename('test-files/ant/ant-project/src/several/namespace/layers/A.java') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/ant/ant-project'), + \ ale#ant#FindProjectRoot(bufnr('')) + +Execute(Should return empty string if called on a non-ant project): + call ale#test#SetFilename('test-files/ant/non-ant-project/Main.java') + + AssertEqual + \ '', + \ ale#ant#FindProjectRoot(bufnr('')) + +Execute(Should return empty string if called on a file in a non-ant project): + call ale#test#SetFilename('test-files/ant/non-ant-project/several/namespace/layers/A.java') + + AssertEqual + \ '', + \ ale#ant#FindProjectRoot(bufnr('')) diff --git a/test/test_autocmd_commands.vader b/test/test_autocmd_commands.vader new file mode 100644 index 00000000..b43b0263 --- /dev/null +++ b/test/test_autocmd_commands.vader @@ -0,0 +1,286 @@ +Before: + function! CheckAutocmd(group) + call ale#events#Init() + + redir => l:output + execute 'silent! autocmd ' . a:group + redir END + + let l:matches = [] + let l:header = '' + " Some event names have aliases, and NeoVim and Vim produce + " different output. The names are remapped to fix this. + let l:event_name_corrections = { + \ 'BufWrite': 'BufWritePre', + \ 'BufRead': 'BufReadPost', + \} + + " autocmd commands are split across two lines in output, so we + " must merge the lines back into one simple line. + for l:line in split(l:output, "\n") + if l:line =~# '^ALE' && split(l:line)[0] ==# a:group + let l:header = split(l:line)[1] + let l:header = get(l:event_name_corrections, l:header, l:header) + elseif !empty(l:header) + " There's an extra line for buffer events, and we should only look + " for the one matching the current buffer. + if l:line =~# '' + let l:header .= ' ' + elseif l:line[:0] is# ' ' + call add(l:matches, join(split(l:header . l:line))) + else + let l:header = '' + endif + endif + endfor + + call sort(l:matches) + + return l:matches + endfunction + + Save g:ale_completion_enabled + Save g:ale_echo_cursor + Save g:ale_enabled + Save g:ale_fix_on_save + Save g:ale_lint_on_enter + Save g:ale_lint_on_filetype_changed + Save g:ale_lint_on_insert_leave + Save g:ale_lint_on_save + Save g:ale_lint_on_text_changed + Save g:ale_pattern_options_enabled + Save g:ale_hover_cursor + Save g:ale_close_preview_on_insert + + " Turn everything on by default for these tests. + let g:ale_completion_enabled = 1 + let g:ale_echo_cursor = 1 + let g:ale_enabled = 1 + let g:ale_fix_on_save = 1 + let g:ale_lint_on_enter = 1 + let g:ale_lint_on_filetype_changed = 1 + let g:ale_lint_on_insert_leave = 1 + let g:ale_lint_on_save = 1 + let g:ale_lint_on_text_changed = 1 + let g:ale_pattern_options_enabled = 1 + let g:ale_hover_cursor = 1 + let g:ale_close_preview_on_insert = 0 + +After: + delfunction CheckAutocmd + Restore + + if g:ale_completion_enabled + call ale#completion#Enable() + else + call ale#completion#Disable() + endif + + call ale#events#Init() + +Execute (All events should be set up when everything is on): + let g:ale_echo_cursor = 1 + + " The InsertEnter event is only added when a mapping is not set. + AssertEqual + \ [ + \ 'BufEnter * call ale#events#ReadOrEnterEvent(str2nr(expand('''')))', + \ 'BufReadPost * call ale#events#ReadOrEnterEvent(str2nr(expand('''')))', + \ 'BufWinEnter * call ale#events#LintOnEnter(str2nr(expand('''')))', + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand('''')))', + \ 'CursorHold * if exists(''*ale#engine#Cleanup'') | call ale#cursor#EchoCursorWarningWithDelay() | endif', + \ 'CursorHold if exists(''*ale#lsp#Send'') | call ale#hover#ShowTruncatedMessageAtCursor() | endif', + \ 'CursorMoved * if exists(''*ale#engine#Cleanup'') | call ale#cursor#EchoCursorWarningWithDelay() | endif', + \ 'FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand('''')))', + \ 'FileType * call ale#events#FileTypeEvent( str2nr(expand('''')), expand(''''))', + \ ] + ( + \ maparg("\", 'i') isnot# '' && !exists('##ModeChanged') + \ ? [ + \ 'InsertEnter * call ale#events#InsertEnterEvent(str2nr(expand('''')))', + \ 'InsertLeave * call ale#events#InsertLeaveEvent(str2nr(expand('''')))', + \ ] + \ : [] + \ ) + ( + \ exists('##ModeChanged') + \ ? ['ModeChanged i*:* call ale#events#InsertLeaveEvent(str2nr(expand('''')))'] + \ : [] + \ ) + [ + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ ], + \ CheckAutocmd('ALEEvents') + +Execute (Only the required events should be bound even if various settings are off): + let g:ale_enabled = 1 + let g:ale_completion_enabled = 0 + let g:ale_echo_cursor = 0 + let g:ale_fix_on_save = 0 + let g:ale_lint_on_enter = 0 + let g:ale_lint_on_filetype_changed = 0 + let g:ale_lint_on_insert_leave = 0 + let g:ale_lint_on_save = 0 + let g:ale_lint_on_text_changed = 0 + let g:ale_pattern_options_enabled = 0 + let g:ale_hover_cursor = 0 + + AssertEqual + \ [ + \ 'BufEnter * call ale#events#ReadOrEnterEvent(str2nr(expand('''')))', + \ 'BufReadPost * call ale#events#ReadOrEnterEvent(str2nr(expand('''')))', + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand('''')))', + \ ], + \ CheckAutocmd('ALEEvents') + +Execute (The cursor hover event should be enabled with g:ale_hover_cursor = 1): + let g:ale_enabled = 1 + let g:ale_completion_enabled = 0 + let g:ale_echo_cursor = 0 + let g:ale_fix_on_save = 0 + let g:ale_lint_on_enter = 0 + let g:ale_lint_on_filetype_changed = 0 + let g:ale_lint_on_insert_leave = 0 + let g:ale_lint_on_save = 0 + let g:ale_lint_on_text_changed = 0 + let g:ale_pattern_options_enabled = 0 + let g:ale_hover_cursor = 1 + + AssertEqual + \ [ + \ 'BufEnter * call ale#events#ReadOrEnterEvent(str2nr(expand('''')))', + \ 'BufReadPost * call ale#events#ReadOrEnterEvent(str2nr(expand('''')))', + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand('''')))', + \ 'CursorHold * if exists(''*ale#lsp#Send'') | call ale#hover#ShowTruncatedMessageAtCursor() | endif', + \ ], + \ CheckAutocmd('ALEEvents') + +Execute (g:ale_lint_on_text_changed = 1 bind both events): + let g:ale_lint_on_text_changed = 1 + + AssertEqual + \ [ + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') + +Execute (g:ale_lint_on_text_changed = 'always' should bind both events): + let g:ale_lint_on_text_changed = 'always' + + AssertEqual + \ [ + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') + +Execute (g:ale_lint_on_text_changed = 'normal' should bind only TextChanged): + let g:ale_lint_on_text_changed = 'normal' + + AssertEqual + \ [ + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') + +Execute (g:ale_lint_on_text_changed = 'insert' should bind only TextChangedI): + let g:ale_lint_on_text_changed = 'insert' + + AssertEqual + \ [ + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand('''')), ''lint_delay''))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') + +Execute (g:ale_lint_on_insert_leave = 1 should bind InsertLeave or ModeChanged if available): + let g:ale_lint_on_insert_leave = 1 + let g:ale_echo_cursor = 0 + + " CI at least should run this check. + " There isn't an easy way to save an restore a mapping during running the test. + if maparg("\", 'i') isnot# '' && !exists('##ModeChanged') + AssertEqual + \ [ + \ 'InsertEnter * call ale#events#InsertEnterEvent(str2nr(expand('''')))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^InsertEnter''') + else + " If the ModeChanged event is available, starting the timer in InsertEnter is not necessary. + AssertEqual + \ [ + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^InsertEnter''') + endif + + if !exists('##ModeChanged') + " If the ModeChanged event is not available, bind InsertLeave. + AssertEqual + \ [ + \ 'InsertLeave * call ale#events#InsertLeaveEvent(str2nr(expand('''')))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^InsertLeave''') + else + AssertEqual + \ [ + \ 'ModeChanged i*:* call ale#events#InsertLeaveEvent(str2nr(expand('''')))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^ModeChanged''') + endif + +Execute (g:ale_lint_on_filetype_changed = 1 should bind the FileType event): + let g:ale_lint_on_filetype_changed = 1 + + AssertEqual + \ [ + \ 'FileType * call ale#events#FileTypeEvent( ' + \ . 'str2nr(expand('''')), ' + \ . 'expand('''')' + \ . ')', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''\v^FileType''') + +Execute (ALECleanupGroup should include the right commands): + if exists('##VimSuspend') + AssertEqual [ + \ 'BufDelete * if exists(''*ale#engine#Cleanup'') | call ale#engine#Cleanup(str2nr(expand(''''))) | endif', + \ 'QuitPre * call ale#events#QuitEvent(str2nr(expand('''')))', + \ 'VimSuspend * if exists(''*ale#engine#CleanupEveryBuffer'') | call ale#engine#CleanupEveryBuffer() | endif', + \], CheckAutocmd('ALECleanupGroup') + else + AssertEqual [ + \ 'BufDelete * if exists(''*ale#engine#Cleanup'') | call ale#engine#Cleanup(str2nr(expand(''''))) | endif', + \ 'QuitPre * call ale#events#QuitEvent(str2nr(expand('''')))', + \], CheckAutocmd('ALECleanupGroup') + endif + +Execute(ALECompletionActions should always be set up): + AssertEqual [ + \ 'CompleteDone * call ale#completion#HandleUserData(v:completed_item)', + \], CheckAutocmd('ALECompletionActions') + +Execute(Enabling completion should set up autocmd events correctly): + let g:ale_completion_enabled = 0 + call ale#completion#Enable() + + AssertEqual [ + \ 'CompleteDone * call ale#completion#Done()', + \ 'TextChangedI * call ale#completion#Queue()', + \], CheckAutocmd('ALECompletionGroup') + AssertEqual 1, g:ale_completion_enabled + +Execute(Disabling completion should remove autocmd events correctly): + let g:ale_completion_enabled = 1 + call ale#completion#Enable() + call ale#completion#Disable() + + AssertEqual [], CheckAutocmd('ALECompletionGroup') + AssertEqual 0, g:ale_completion_enabled + +Execute(ALE should try to close the preview window on InsertEnter): + let g:ale_lint_on_insert_leave = 0 + let g:ale_close_preview_on_insert = 1 + + AssertEqual + \ [ + \ 'InsertEnter * call ale#events#InsertEnterEvent(str2nr(expand('''')))', + \ ], + \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^InsertEnter''') diff --git a/test/test_balloon_messages.vader b/test/test_balloon_messages.vader new file mode 100644 index 00000000..d0724c21 --- /dev/null +++ b/test/test_balloon_messages.vader @@ -0,0 +1,87 @@ +Before: + Save g:ale_buffer_info + Save g:ale_enabled + Save g:ale_set_balloons + + let g:ale_set_balloons = 1 + + let g:ale_buffer_info[bufnr('')] = {'loclist': [ + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'col': 10, + \ 'linter_name': 'eslint', + \ 'type': 'W', + \ 'text': 'Ignore me.', + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'Missing semicolon. (semi)', + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'lnum': 2, + \ 'col': 10, + \ 'text': 'Infix operators must be spaced. (space-infix-ops)' + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'lnum': 2, + \ 'col': 15, + \ 'text': 'Missing radix parameter (radix)' + \ }, + \]} + +After: + Restore + + unlet! b:ale_enabled + unlet! b:ale_set_balloons + +Execute(Balloon messages should be shown for the correct lines): + AssertEqual + \ 'Missing semicolon. (semi)', + \ ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(Balloon messages should be shown for earlier columns): + AssertEqual + \ 'Infix operators must be spaced. (space-infix-ops)', + \ ale#balloon#MessageForPos(bufnr(''), 2, 1) + +Execute(Balloon messages should be shown for later columns): + AssertEqual + \ 'Missing radix parameter (radix)', + \ ale#balloon#MessageForPos(bufnr(''), 2, 16) + +Execute(Balloon messages should be disabled if ALE is disabled globally): + let g:ale_enabled = 0 + " Enabling the buffer should not make a difference. + let b:ale_enabled = 1 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(Balloon messages should be disabled if ALE is disabled for a buffer): + let b:ale_enabled = 0 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(Balloon messages should be disabled if the global setting is off): + let g:ale_set_balloons = 0 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(Balloon messages should be disabled if the buffer setting is off): + let b:ale_set_balloons = 0 + + AssertEqual '', ale#balloon#MessageForPos(bufnr(''), 1, 1) + +Execute(The balloon buffer setting should override the global one): + let g:ale_set_balloons = 0 + let b:ale_set_balloons = 1 + + AssertEqual + \ 'Missing semicolon. (semi)', + \ ale#balloon#MessageForPos(bufnr(''), 1, 1) diff --git a/test/test_c_flag_parsing.vader b/test/test_c_flag_parsing.vader new file mode 100644 index 00000000..c661651e --- /dev/null +++ b/test/test_c_flag_parsing.vader @@ -0,0 +1,689 @@ +Before: + Save g:ale_c_parse_makefile + Save g:ale_c_always_make + Save b:ale_c_always_make + + call ale#test#SetDirectory('/testplugin/test') + + let g:ale_c_parse_makefile = 1 + let g:ale_c_always_make = 1 + let b:ale_c_always_make = 1 + + function SplitAndParse(path_prefix, command) abort + let l:args = ale#c#ShellSplit(a:command) + + return ale#c#ParseCFlags(a:path_prefix, 0, l:args) + endfunction + +After: + delfunction SplitAndParse + + Restore + + call ale#test#RestoreDirectory() + +Execute(The make command should be correct): + call ale#test#SetFilename('test-files/c/makefile_project/subdir/file.c') + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'make -n --always-make', + \ ], + \ ale#c#GetMakeCommand(bufnr('')) + + " You should be able to disable --always-make for a buffer. + let b:ale_c_always_make = 0 + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'make -n', + \ ], + \ ale#c#GetMakeCommand(bufnr('')) + +Execute(Should recognize GNUmakefile as a makefile): + call ale#test#SetFilename('test-files/c/gnumakefile_project/file.c') + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir. '/test-files/c/gnumakefile_project'), + \ 'make -n --always-make', + \ ], + \ ale#c#GetMakeCommand(bufnr('')) + +Execute(The CFlags parser should be able to parse include directives): + call ale#test#SetFilename('test-files/c/makefile_project/subdir/file.c') + + AssertEqual + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')), + \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -c file.c']) + + AssertEqual + \ '-isystem ' . ale#Escape('/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-files/c/makefile_project/subdir/file.c') + + AssertEqual + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/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-files/c/makefile_project/subdir/file.c') + + AssertEqual + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' -DTEST=1', + \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=1 -c file.c']) + +Execute(The CFlags parser should be able to parse macro directives with spaces): + call ale#test#SetFilename('test-files/c/makefile_project/subdir/file.c') + + AssertEqual + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' -DTEST=$(( 2 * 4 ))', + \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=$(( 2 * 4 )) -c file.c']) + +Execute(The CFlags parser should be able to parse shell directives with spaces): + call ale#test#SetFilename('test-files/c/makefile_project/subdir/file.c') + + AssertEqual + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/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 + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/kernel/include')) + \ . ' -DTEST=`date +%s`', + \ SplitAndParse( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'gcc -Isubdir ' + \ . '-I'. ale#path#Simplify('kernel/include') + \ . ' -DTEST=`date +%s` -c file.c' + \ ) + +Execute(We should handle paths with spaces in double quotes): + AssertEqual + \ '-Dgoal=9' + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir with spaces')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/kernel/include')) + \ . ' -DTEST=`date +%s`', + \ SplitAndParse( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . '-I"dir with spaces"' . ' -I'. ale#path#Simplify('kernel/include') + \ . ' -DTEST=`date +%s` -c file.c' + \ ) + +Execute(ParseCFlags should handle paths with spaces in single quotes): + AssertEqual + \ '-Dgoal=9' + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir with spaces')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/kernel/include')) + \ . ' -DTEST=`date +%s`', + \ SplitAndParse( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -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' + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir with spaces')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir-with-dash')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/kernel/include')) + \ . ' -DTEST=`date +%s`', + \ SplitAndParse( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . '-I''dir with spaces''' . ' -Idir-with-dash' + \ . ' -I'. ale#path#Simplify('kernel/include') + \ . ' -DTEST=`date +%s` -c file.c' + \ ) + +Execute(We should handle -D with minuses): + AssertEqual + \ '-Dgoal=9' + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' -Dmacro-with-dash' + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir with spaces')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir-with-dash')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/kernel/include')) + \ . ' -DTEST=`date +%s`', + \ SplitAndParse( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . '-Dmacro-with-dash ' + \ . '-I''dir with spaces''' . ' -Idir-with-dash' + \ . ' -I'. ale#path#Simplify('kernel/include') + \ . ' -DTEST=`date +%s` -c file.c' + \ ) + +Execute(We should handle flags at the end of the line): + AssertEqual + \ '-Dgoal=9' + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/subdir')) + \ . ' -Dmacro-with-dash' + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir with spaces')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/dir-with-dash')) + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/kernel/include')), + \ SplitAndParse( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . '-Dmacro-with-dash ' + \ . '-I''dir with spaces''' . ' -Idir-with-dash' + \ . ' -I'. ale#path#Simplify('kernel/include') + \ ) + +Execute(FlagsFromCompileCommands should tolerate empty values): + AssertEqual '', ale#c#FlagsFromCompileCommands(bufnr(''), '') + +Execute(ParseCompileCommandsFlags should tolerate empty values): + AssertEqual '', ale#c#ParseCompileCommandsFlags(bufnr(''), {}, {}) + +Execute(ParseCompileCommandsFlags should parse some basic flags): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + + " We should read the absolute path filename entry, not the other ones. + AssertEqual + \ '-I ' . ale#Escape(ale#path#Simplify('/usr/include/xmms2')), + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ { + \ ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'): [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/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'), + \ }, + \ ], + \ "xmms2-mpris.c": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -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'), + \ }, + \ ], + \ }, + \ { + \ ale#path#Simplify('/foo/bar/xmms2-mpris/src'): [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris/src'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': 'other.c', + \ }, + \ ], + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -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((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c'), + \ }, + \ ], + \ }, + \ ) + +Execute(ParseCompileCommandsFlags should fall back to files with the same name): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + + " We should prefer the basename file flags, not the base dirname flags. + AssertEqual + \ '-I ' . ale#Escape(ale#path#Simplify('/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') + \ . ' -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'), + \ }, + \ ], + \ }, + \ { + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -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((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c'), + \ }, + \ ], + \ }, + \ ) + +Execute(ParseCompileCommandsFlags should parse flags for exact directory matches): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + + " We should ues the exact directory flags, not the file basename flags. + AssertEqual + \ '-I ' . ale#Escape(ale#path#Simplify('/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/ignoreme') + \ . ' -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'), + \ }, + \ ], + \ }, + \ { + \ ale#path#Simplify('/foo/bar/xmms2-mpris/src'): [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris/src'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/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': 'other.c', + \ }, + \ ], + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -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((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.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 ' . ale#Escape(ale#path#Simplify('/usr/include/xmms2')), + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ {}, + \ { + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/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((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.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 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 ' . ale#Escape('/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 ' . ale#Escape('/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 ' . ale#Escape('/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(ShellSplit should not merge flags): + AssertEqual + \ [ + \ 'gcc', + \ '-Dgoal=9', + \ '-Tlinkerfile.ld', + \ 'blabla', + \ '-Isubdir', + \ 'subdir/somedep1.o', + \ 'subdir/somedep2.o', + \ '-I''dir with spaces''', + \ '-Idir-with-dash', + \ 'subdir/somedep3.o', + \ 'subdir/somedep4.o', + \ '-I' . ale#path#Simplify('kernel/include'), + \ 'subdir/somedep5.o', + \ 'subdir/somedep6.o', + \ ], + \ ale#c#ShellSplit( + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . 'subdir/somedep1.o ' . 'subdir/somedep2.o ' + \ . '-I''dir with spaces''' . ' -Idir-with-dash ' + \ . 'subdir/somedep3.o ' . 'subdir/somedep4.o ' + \ . ' -I'. ale#path#Simplify('kernel/include') . ' ' + \ . 'subdir/somedep5.o ' . 'subdir/somedep6.o' + \ ) + +Execute(ShellSplit should handle parenthesis and quotes): + AssertEqual + \ [ + \ 'gcc', + \ '-Dgoal=9', + \ '-Tlinkerfile.ld', + \ 'blabla', + \ '-Dtest1="('' '')"', + \ 'file1.o', + \ '-Dtest2=''(` `)''', + \ 'file2.o', + \ '-Dtest3=`(" ")`', + \ 'file3.o', + \ ] , + \ ale#c#ShellSplit( + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla ' + \ . '-Dtest1="('' '')" file1.o ' + \ . '-Dtest2=''(` `)'' file2.o ' + \ . '-Dtest3=`(" ")` file3.o' + \ ) + +Execute(We should include several important flags): + AssertEqual + \ '-I ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/inc')) + \ . ' -I ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/include')) + \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/incquote')) + \ . ' -isystem ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/incsystem')) + \ . ' -idirafter ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/incafter')) + \ . ' -iframework ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/incframework')) + \ . ' -include file.h' + \ . ' -imacros macros.h' + \ . ' -Dmacro="value"' + \ . ' -DGoal=9' + \ . ' -D macro2' + \ . ' -D macro3="value"' + \ . ' -Bbdir' + \ . ' -B bdir2' + \ . ' -iprefix prefix -iwithprefix prefix2 -iwithprefixbefore prefix3' + \ . ' -isysroot sysroot --sysroot=test --no-sysroot-suffix -imultilib multidir' + \ . ' -Wsome-warning -std=c89 -pedantic -pedantic-errors -ansi' + \ . ' -foption -O2 -C -CC -trigraphs -nostdinc -nostdinc++' + \ . ' -iplugindir=dir -march=native -w', + \ ale#c#ParseCFlags( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 0, + \ [ + \ 'gcc', + \ '-Iinc', + \ '-I', + \ 'include', + \ '-iquote', + \ 'incquote', + \ '-isystem', + \ 'incsystem', + \ '-idirafter', + \ 'incafter', + \ '-iframework', + \ 'incframework', + \ '-include', + \ 'file.h', + \ '-imacros', + \ 'macros.h', + \ '-Dmacro="value"', + \ '-DGoal=9', + \ '-D', + \ 'macro2', + \ '-D', + \ 'macro3="value"', + \ '-Bbdir', + \ '-B', + \ 'bdir2', + \ '-iprefix', + \ 'prefix', + \ '-iwithprefix', + \ 'prefix2', + \ '-iwithprefixbefore', + \ 'prefix3', + \ '-isysroot', + \ 'sysroot', + \ '--sysroot=test', + \ '--no-sysroot-suffix', + \ '-imultilib', + \ 'multidir', + \ '-Wsome-warning', + \ '-std=c89', + \ '-pedantic', + \ '-pedantic-errors', + \ '-ansi', + \ '-foption', + \ '-O2', + \ '-C', + \ '-CC', + \ '-trigraphs', + \ '-nostdinc', + \ '-nostdinc++', + \ '-iplugindir=dir', + \ '-march=native', + \ '-w', + \ ], + \ ) + +Execute(We should quote the flags we need to quote): + AssertEqual + \ '-I ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/inc')) + \ . ' -I ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/include')) + \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/incquote')) + \ . ' -isystem ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/incsystem')) + \ . ' -idirafter ' . ale#Escape(ale#path#Simplify(g:dir. '/test-files/c/makefile_project/incafter')) + \ . ' -iframework ' . ale#Escape(ale#path#Simplify(g:dir . '/test-files/c/makefile_project/incframework')) + \ . ' -include file.h' + \ . ' -imacros macros.h' + \ . ' ' . ale#Escape('-Dmacro="value"') + \ . ' -DGoal=9' + \ . ' -D macro2' + \ . ' -D ' . ale#Escape('macro3="value"') + \ . ' -Bbdir' + \ . ' -B bdir2' + \ . ' -iprefix prefix -iwithprefix prefix2 -iwithprefixbefore prefix3' + \ . ' -isysroot sysroot --sysroot=test' + \ . ' ' . ale#Escape('--sysroot="quoted"') + \ . ' ' . ale#Escape('--sysroot=foo bar') + \ . ' --no-sysroot-suffix -imultilib multidir' + \ . ' -Wsome-warning -std=c89 -pedantic -pedantic-errors -ansi' + \ . ' -foption -O2 -C -CC -trigraphs -nostdinc -nostdinc++' + \ . ' -iplugindir=dir -march=native -w', + \ ale#c#ParseCFlags( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 1, + \ [ + \ 'gcc', + \ '-Iinc', + \ '-I', + \ 'include', + \ '-iquote', + \ 'incquote', + \ '-isystem', + \ 'incsystem', + \ '-idirafter', + \ 'incafter', + \ '-iframework', + \ 'incframework', + \ '-include', + \ 'file.h', + \ '-imacros', + \ 'macros.h', + \ '-Dmacro="value"', + \ '-DGoal=9', + \ '-D', + \ 'macro2', + \ '-D', + \ 'macro3="value"', + \ '-Bbdir', + \ '-B', + \ 'bdir2', + \ '-iprefix', + \ 'prefix', + \ '-iwithprefix', + \ 'prefix2', + \ '-iwithprefixbefore', + \ 'prefix3', + \ '-isysroot', + \ 'sysroot', + \ '--sysroot=test', + \ '--sysroot="quoted"', + \ '--sysroot=foo bar', + \ '--no-sysroot-suffix', + \ '-imultilib', + \ 'multidir', + \ '-Wsome-warning', + \ '-std=c89', + \ '-pedantic', + \ '-pedantic-errors', + \ '-ansi', + \ '-foption', + \ '-O2', + \ '-C', + \ '-CC', + \ '-trigraphs', + \ '-nostdinc', + \ '-nostdinc++', + \ '-iplugindir=dir', + \ '-march=native', + \ '-w', + \ ], + \ ) + +Execute(We should exclude other flags that cause problems): + AssertEqual + \ '', + \ ale#c#ParseCFlags( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 0, + \ [ + \ 'gcc', + \ '-Wl,option', + \ '-Wa,option', + \ '-Wp,option', + \ '-c', + \ 'filename.c', + \ 'somelib.a', + \ '-fdump-file=name', + \ '-fdiagnostics-arg', + \ '-fno-show-column', + \ '-fstack-usage', + \ '-Tlinkerfile.ld', + \ ], + \ ) + +Execute(We should expand @file in CFlags): + AssertEqual + \ '-DARGS1 -DARGS2 -O2', + \ ale#c#ParseCFlags( + \ ale#path#Simplify(g:dir. '/test-files/c/makefile_project'), + \ 0, + \ [ + \ 'gcc', + \ '-g', + \ '@./args', + \ '-O2', + \ ], + \ ) diff --git a/test/test_cleanup.vader b/test/test_cleanup.vader new file mode 100644 index 00000000..232874aa --- /dev/null +++ b/test/test_cleanup.vader @@ -0,0 +1,14 @@ +After: + unlet! g:buffer + let g:ale_buffer_info = {} + +Execute('ALE globals should be cleared when the buffer is deleted): + new + + let g:ale_buffer_info = { + \ bufnr(''): {'temporary_file_list': [], 'temporary_directory_list': []}, + \ 10347: {'temporary_file_list': [], 'temporary_directory_list': []}, + \} + + bdelete + AssertEqual {10347: {'temporary_file_list': [], 'temporary_directory_list': []}}, g:ale_buffer_info diff --git a/test/test_code_action.vader b/test/test_code_action.vader new file mode 100644 index 00000000..80e2b1d8 --- /dev/null +++ b/test/test_code_action.vader @@ -0,0 +1,605 @@ +Before: + let g:notified_changes = [] + + runtime autoload/ale/lsp.vim + + function! ale#lsp#NotifyForChanges(conn_id, buffer) abort + call add(g:notified_changes, { + \ 'conn_id': a:conn_id, + \ 'buffer': a:buffer + \}) + endfunction + + Save g:ale_enabled + let g:ale_enabled = 0 + + let g:file1 = tempname() + let g:file2 = tempname() + let g:test = {} + + let g:test.create_change = {line, offset, end_line, end_offset, value -> + \{ + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': line, + \ 'offset': offset, + \ }, + \ 'end': { + \ 'line': end_line, + \ 'offset': end_offset, + \ }, + \ 'newText': value, + \ }], + \ }] + \}} + + function! WriteFileAndEdit() abort + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + execute 'edit ' . g:file1 + endfunction! + +After: + " Close the extra buffers if we opened it. + if bufnr(g:file1) != -1 && buflisted(bufnr(g:file1)) + execute ':bp! | :bd! ' . bufnr(g:file1) + endif + if bufnr(g:file2) != -1 && buflisted(bufnr(g:file2)) + execute ':bp! | :bd! ' . bufnr(g:file2) + endif + + if filereadable(g:file1) + call delete(g:file1) + endif + if filereadable(g:file2) + call delete(g:file2) + endif + + unlet! g:notified_changes + " unlet! g:expected_notified_changes + unlet! g:file1 + unlet! g:file2 + unlet! g:test + unlet! g:changes + delfunction WriteFileAndEdit + + runtime autoload/ale/lsp.vim + + Restore + + +Execute(It should modify and save multiple files): + call writefile([ + \ 'class Name {', + \ ' value: string', + \ '}', + \ '', + \ 'class B {', + \ ' constructor(readonly a: Name) {}', + \ '}' + \], g:file1, 'S') + call writefile([ + \ 'import A from "A"', + \ 'import {', + \ ' B,', + \ ' C,', + \ '} from "module"', + \ 'import D from "D"', + \], g:file2, 'S') + + call ale#code_action#HandleCodeAction( + \ { + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 1, + \ 'offset': 7, + \ }, + \ 'end': { + \ 'line': 1, + \ 'offset': 11, + \ }, + \ 'newText': 'Value', + \ }, { + \ 'start': { + \ 'line': 6, + \ 'offset': 27, + \ }, + \ 'end': { + \ 'line': 6, + \ 'offset': 31, + \ }, + \ 'newText': 'Value', + \ }], + \ }, { + \ 'fileName': g:file2, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 2, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 6, + \ 'offset': 1, + \ }, + \ 'newText': "import {A, B} from 'module'\n\n", + \ }] + \ }], + \ }, + \ {'should_save': 1, 'conn_id': 'test_conn'}, + \) + + AssertEqual [ + \ 'class Value {', + \ ' value: string', + \ '}', + \ '', + \ 'class B {', + \ ' constructor(readonly a: Value) {}', + \ '}', + \ '', + \], readfile(g:file1, 'b') + + AssertEqual [ + \ 'import A from "A"', + \ 'import {A, B} from ''module''', + \ '', + \ 'import D from "D"', + \ '', + \], readfile(g:file2, 'b') + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(g:file1), + \}, { + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(g:file2), + \}], g:notified_changes + +Execute(Beginning of file can be modified): + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + + call ale#code_action#HandleCodeAction( + \ { + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'newText': "type A: string\ntype B: number\n", + \ }], + \ }] + \ }, + \ {'should_save': 1, 'conn_id': 'test_conn'}, + \) + + AssertEqual [ + \ 'type A: string', + \ 'type B: number', + \] + g:test.text + [''], readfile(g:file1, 'b') + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(g:file1), + \}], g:notified_changes + + +Execute(End of file can be modified): + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + + call ale#code_action#HandleCodeAction( + \ { + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 4, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 4, + \ 'offset': 1, + \ }, + \ 'newText': "type A: string\ntype B: number\n", + \ }], + \ }] + \ }, + \ {'should_save': 1, 'conn_id': 'test_conn'}, + \) + + AssertEqual g:test.text + [ + \ 'type A: string', + \ 'type B: number', + \ '', + \], readfile(g:file1, 'b') + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(g:file1), + \}], g:notified_changes + + +Execute(Current buffer contents will be reloaded): + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + + execute 'edit ' . g:file1 + let g:test.buffer = bufnr(g:file1) + + call ale#code_action#HandleCodeAction( + \ { + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'newText': "type A: string\ntype B: number\n", + \ }], + \ }] + \ }, + \ {'should_save': 1, 'conn_id': 'test_conn'}, + \) + + AssertEqual [ + \ 'type A: string', + \ 'type B: number', + \] + g:test.text + [''], readfile(g:file1, 'b') + + AssertEqual [ + \ 'type A: string', + \ 'type B: number', + \] + g:test.text, getbufline(g:test.buffer, 1, '$') + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(g:file1), + \}], g:notified_changes + + +Execute(Unlisted buffer contents will be modified correctly): + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + + execute 'edit ' . g:file1 + let g:test.buffer = bufnr(g:file1) + + execute 'bd' + AssertEqual bufnr(g:file1), g:test.buffer + + call ale#code_action#HandleCodeAction( + \ { + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'newText': "type A: string\ntype B: number\n", + \ }], + \ }] + \ }, + \ {'should_save': 1, 'conn_id': 'test_conn'}, + \) + + AssertEqual [ + \ 'type A: string', + \ 'type B: number', + \] + g:test.text + [''], readfile(g:file1, 'b') + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(g:file1), + \}], g:notified_changes + +# Tests for cursor repositioning. In comments `=` designates change range, and +# `C` cursor position + +# C === +Execute(Cursor will not move when it is before text change): + call WriteFileAndEdit() + let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2') + + call setpos('.', [0, 1, 1, 0]) + call ale#code_action#HandleCodeAction(g:test.changes, { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \}) + AssertEqual [1, 1], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + + call setpos('.', [0, 2, 2, 0]) + call ale#code_action#HandleCodeAction(g:test.changes, { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \}) + AssertEqual [2, 2], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}, { + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +# ====C==== +Execute(Cursor column will move to the change end when cursor between start/end): + let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2') + + for r in range(3, 8) + call WriteFileAndEdit() + call setpos('.', [0, 2, r, 0]) + AssertEqual ' value: string', getline('.') + call ale#code_action#HandleCodeAction(g:test.changes, { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \}) + AssertEqual ' value2: string', getline('.') + AssertEqual [2, 9], getpos('.')[1:2] + endfor + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}, { + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}, { + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}, { + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}, { + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}, { + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + + +# ====C +Execute(Cursor column will move back when new text is shorter): + call WriteFileAndEdit() + call setpos('.', [0, 2, 8, 0]) + AssertEqual ' value: string', getline('.') + call ale#code_action#HandleCodeAction( + \ g:test.create_change(2, 3, 2, 8, 'val'), + \ { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \ }) + AssertEqual ' val: string', getline('.') + AssertEqual [2, 6], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + + +# ==== C +Execute(Cursor column will move forward when new text is longer): + call WriteFileAndEdit() + + call setpos('.', [0, 2, 8, 0]) + AssertEqual ' value: string', getline('.') + call ale#code_action#HandleCodeAction( + \ g:test.create_change(2, 3, 2, 8, 'longValue'), + \ { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \ }) + AssertEqual ' longValue: string', getline('.') + AssertEqual [2, 12], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +# ========= +# = +# C +Execute(Cursor line will move when updates are happening on lines above): + call WriteFileAndEdit() + call setpos('.', [0, 3, 1, 0]) + AssertEqual '}', getline('.') + call ale#code_action#HandleCodeAction( + \ g:test.create_change(1, 1, 2, 1, "test\ntest\n"), + \ { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \ }) + AssertEqual '}', getline('.') + AssertEqual [4, 1], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + + +# ========= +# =C +Execute(Cursor line and column will move when change on lines above and just before cursor column): + call WriteFileAndEdit() + call setpos('.', [0, 2, 2, 0]) + AssertEqual ' value: string', getline('.') + call ale#code_action#HandleCodeAction( + \ g:test.create_change(1, 1, 2, 1, "test\ntest\n123"), + \ { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \ }) + AssertEqual '123 value: string', getline('.') + AssertEqual [3, 5], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +# ========= +# ======C== +# = +Execute(Cursor line and column will move at the end of changes): + call WriteFileAndEdit() + call setpos('.', [0, 2, 10, 0]) + AssertEqual ' value: string', getline('.') + call ale#code_action#HandleCodeAction( + \ g:test.create_change(1, 1, 3, 1, "test\n"), + \ { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \ }) + AssertEqual '}', getline('.') + AssertEqual [2, 1], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +# C == +# === +Execute(Cursor will not move when changes happening on lines >= cursor, but after cursor): + call WriteFileAndEdit() + call setpos('.', [0, 2, 3, 0]) + AssertEqual ' value: string', getline('.') + call ale#code_action#HandleCodeAction( + \ g:test.create_change(2, 10, 3, 1, "number\n"), + \ { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \ }) + AssertEqual ' value: number', getline('.') + AssertEqual [2, 3], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +Execute(Cursor will not move when change covers entire file): + call WriteFileAndEdit() + call setpos('.', [0, 2, 3, 0]) + call ale#code_action#HandleCodeAction( + \ g:test.create_change(1, 1, len(g:test.text) + 1, 1, + \ join(g:test.text + ['x'], "\n")), + \ { + \ 'should_save': 1, + \ 'conn_id': 'test_conn', + \ }) + AssertEqual [2, 3], getpos('.')[1:2] + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +Execute(It should just modify file when should_save is set to v:false): + call WriteFileAndEdit() + let g:test.change = g:test.create_change(1, 1, 1, 1, "import { writeFile } from 'fs';\n") + call ale#code_action#HandleCodeAction(g:test.change, { + \ 'conn_id': 'test_conn', + \}) + AssertEqual 1, getbufvar(bufnr(''), '&modified') + AssertEqual [ + \ 'import { writeFile } from ''fs'';', + \ 'class Name {', + \ ' value: string', + \ '}', + \], getline(1, '$') + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +Given typescript(An example TypeScript file): + type Foo = {} + + export interface ISomething { + fooLongName: Foo | null + } + + export class SomethingElse implements ISomething { + // Bindings + fooLongName!: ISomething['fooLongName'] + } + +Execute(): + let g:changes = [ + \ {'end': {'offset': 14, 'line': 4}, 'newText': 'foo', 'start': {'offset': 3, 'line': 4}}, + \ {'end': {'offset': 40, 'line': 9}, 'newText': 'foo', 'start': {'offset': 29, 'line': 9}}, + \ {'end': {'offset': 14, 'line': 9}, 'newText': 'foo', 'start': {'offset': 3, 'line': 9}}, + \] + + call ale#code_action#ApplyChanges(expand('%:p'), g:changes, { + \ 'conn_id': 'test_conn', + \}) + + AssertEqual [{ + \ 'conn_id': 'test_conn', + \ 'buffer': bufnr(''), + \}], g:notified_changes + +Expect(The changes should be applied correctly): + type Foo = {} + + export interface ISomething { + foo: Foo | null + } + + export class SomethingElse implements ISomething { + // Bindings + foo!: ISomething['foo'] + } diff --git a/test/test_code_action_corner_cases.vader b/test/test_code_action_corner_cases.vader new file mode 100644 index 00000000..b5741d43 --- /dev/null +++ b/test/test_code_action_corner_cases.vader @@ -0,0 +1,141 @@ +" Tests for various corner cases of applying code changes from LSP. +" +" These can be verified against the reference vscode implementation using the +" following javascript program: +" +" const { TextDocument } = require('vscode-languageserver-textdocument'); +" const { TextEdit, Position, Range } = require('vscode-languageserver-types'); +" function MkPos(line, offset) { return Position.create(line - 1, offset - 1); } +" function MkInsert(pos, newText) { return TextEdit.insert(pos, newText); } +" function MkDelete(start, end) { return TextEdit.del(Range.create(start, end)); } +" function TestChanges(s, es) { +" return TextDocument.applyEdits(TextDocument.create(null, null, null, s), es); +" } +" +" const fs = require("fs"); +" const assert = require('assert').strict; +" const testRegex = /(? ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + function! ale#code_action#HandleCodeAction(code_action, options) abort + let g:handle_code_action_called = 1 + Assert !get(a:options, 'should_save') + call add(g:code_actions, a:code_action) + endfunction + + function! ale#util#Input(message, value) abort + return '2' + endfunction + +After: + Restore + + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + 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:code_actions + unlet! g:handle_code_action_called + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/codefix.vim + runtime autoload/ale/code_action.vim + +Execute(Failed codefix responses should be handled correctly): + call ale#codefix#HandleTSServerResponse( + \ 1, + \ {'command': 'getCodeFixes', 'request_seq': 3} + \) + AssertEqual g:handle_code_action_called, 0 + +Given typescript(Some typescript file): + foo + somelongerline () + bazxyzxyzxyz + +Execute(getCodeFixes from tsserver should be handled): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleTSServerResponse(1, { + \ 'command': 'getCodeFixes', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'type': 'response', + \ 'body': [ + \ { + \ 'description': 'Import default "x" from module "./z"', + \ 'fixName': 'import', + \ 'changes': [ + \ { + \ 'fileName': "/foo/bar/file1.ts", + \ 'textChanges': [ + \ { + \ 'end': { + \ 'line': 2, + \ 'offset': 1, + \ }, + \ 'newText': 'import x from "./z";^@', + \ 'start': { + \ 'line': 2, + \ 'offset': 1, + \ } + \ } + \ ] + \ } + \ ] + \ } + \ ] + \}) + + AssertEqual g:handle_code_action_called, 1 + AssertEqual + \ [ + \ { + \ 'description': 'codefix', + \ 'changes': [ + \ { + \ 'fileName': "/foo/bar/file1.ts", + \ 'textChanges': [ + \ { + \ 'end': { + \ 'line': 2, + \ 'offset': 1 + \ }, + \ 'newText': 'import x from "./z";^@', + \ 'start': { + \ 'line': 2, + \ 'offset': 1 + \ } + \ } + \ ] + \ } + \ ] + \ } + \ ], + \ g:code_actions + +Execute(getCodeFixes from tsserver should be handled with user input if there are more than one action): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleTSServerResponse(1, { + \ 'command': 'getCodeFixes', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'type': 'response', + \ 'body': [ + \ { + \ 'description': 'Import default "x" from module "./z"', + \ 'fixName': 'import', + \ 'changes': [ + \ { + \ 'fileName': "/foo/bar/file1.ts", + \ 'textChanges': [ + \ { + \ 'end': { + \ 'line': 2, + \ 'offset': 1, + \ }, + \ 'newText': 'import x from "./z";^@', + \ 'start': { + \ 'line': 2, + \ 'offset': 1, + \ } + \ } + \ ] + \ } + \ ] + \ }, + \ { + \ 'description': 'Import default "x" from module "./y"', + \ 'fixName': 'import', + \ 'changes': [ + \ { + \ 'fileName': "/foo/bar/file1.ts", + \ 'textChanges': [ + \ { + \ 'end': { + \ 'line': 2, + \ 'offset': 1, + \ }, + \ 'newText': 'import x from "./y";^@', + \ 'start': { + \ 'line': 2, + \ 'offset': 1, + \ } + \ } + \ ] + \ } + \ ] + \ } + \ ] + \}) + + AssertEqual g:handle_code_action_called, 1 + AssertEqual + \ [ + \ { + \ 'description': 'codefix', + \ 'changes': [ + \ { + \ 'fileName': "/foo/bar/file1.ts", + \ 'textChanges': [ + \ { + \ 'end': { + \ 'line': 2, + \ 'offset': 1 + \ }, + \ 'newText': 'import x from "./y";^@', + \ 'start': { + \ 'line': 2, + \ 'offset': 1 + \ } + \ } + \ ] + \ } + \ ] + \ } + \ ], + \ g:code_actions + +Execute(Prints a tsserver error message when getCodeFixes unsuccessful): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleTSServerResponse(1, { + \ 'command': 'getCodeFixes', + \ 'request_seq': 3, + \ 'success': v:false, + \ 'message': 'something is wrong', + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''Error while getting code fixes. Reason: something is wrong'''], g:expr_list + +Execute(Does nothing when where are no code fixes): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleTSServerResponse(1, { + \ 'command': 'getCodeFixes', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [] + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No code fixes available.'''], g:expr_list + +Execute(tsserver codefix requests should be sent): + call ale#linter#Reset() + + runtime ale_linters/typescript/tsserver.vim + let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'code': 2304, 'linter_name': 'tsserver'}]}} + call setpos('.', [bufnr(''), 2, 16, 0]) + + " ALECodeAction + call ale#codefix#Execute(0) + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'code_actions', g:capability_checked + AssertEqual + \ 'function(''ale#codefix#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@getCodeFixes', { + \ 'startLine': 2, + \ 'startOffset': 16, + \ 'endLine': 2, + \ 'endOffset': 17, + \ 'file': expand('%:p'), + \ 'errorCodes': [2304], + \ }] + \ ], + \ g:message_list + +Execute(tsserver codefix requests should be sent only for error with code): + call ale#linter#Reset() + + runtime ale_linters/typescript/tsserver.vim + let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 16, 'linter_name': 'tsserver'}, {'lnum': 2, 'col': 16, 'code': 2304, 'linter_name': 'tsserver'}]}} + call setpos('.', [bufnr(''), 2, 16, 0]) + + " ALECodeAction + call ale#codefix#Execute(0) + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'code_actions', g:capability_checked + AssertEqual + \ 'function(''ale#codefix#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@getCodeFixes', { + \ 'startLine': 2, + \ 'startOffset': 16, + \ 'endLine': 2, + \ 'endOffset': 17, + \ 'file': expand('%:p'), + \ 'errorCodes': [2304], + \ }] + \ ], + \ g:message_list + +Execute(getApplicableRefactors from tsserver should be handled): + call ale#codefix#SetMap({3: { + \ 'buffer': expand('%:p'), + \ 'line': 1, + \ 'column': 2, + \ 'end_line': 3, + \ 'end_column': 4, + \ 'connection_id': 0, + \}}) + call ale#codefix#HandleTSServerResponse(1, + \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [{'actions': [{'description': 'Extract to constant in enclosing scope', 'name': 'constant_scope_0'}], 'description': 'Extract constant', 'name': 'Extract Symbol'}, {'actions': [{'description': 'Extract to function in module scope', 'name': 'function_scope_1'}], 'description': 'Extract function', 'name': 'Extract Symbol'}], 'command': 'getApplicableRefactors'}) + + AssertEqual + \ [ + \ [0, 'ts@getEditsForRefactor', { + \ 'startLine': 1, + \ 'startOffset': 2, + \ 'endLine': 3, + \ 'endOffset': 5, + \ 'file': expand('%:p'), + \ 'refactor': 'Extract Symbol', + \ 'action': 'function_scope_1', + \ }] + \ ], + \ g:message_list + +Execute(getApplicableRefactors should print error on failure): + call ale#codefix#SetMap({3: { + \ 'buffer': expand('%:p'), + \ 'line': 1, + \ 'column': 2, + \ 'end_line': 3, + \ 'end_column': 4, + \ 'connection_id': 0, + \}}) + call ale#codefix#HandleTSServerResponse(1, + \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getApplicableRefactors'}) + + AssertEqual ['echom ''Error while getting applicable refactors. Reason: oops'''], g:expr_list + +Execute(getApplicableRefactors should do nothing if there are no refactors): + call ale#codefix#SetMap({3: { + \ 'buffer': expand('%:p'), + \ 'line': 1, + \ 'column': 2, + \ 'end_line': 3, + \ 'end_column': 4, + \ 'connection_id': 0, + \}}) + call ale#codefix#HandleTSServerResponse(1, + \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [], 'command': 'getApplicableRefactors'}) + + AssertEqual ['echom ''No applicable refactors available.'''], g:expr_list + +Execute(getEditsForRefactor from tsserver should be handled): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleTSServerResponse(1, + \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': {'edits': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@ app.use(booExpressCsrf());^@ app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}], 'renameLocation': {'offset': 3, 'line': 8}, 'renameFilename': '/foo/bar/file.ts'}, 'command': 'getEditsForRefactor' } + \) + + AssertEqual g:handle_code_action_called, 1 + AssertEqual + \ [ + \ { + \ 'description': 'editsForRefactor', + \ 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@ app.use(booExpressCsrf());^@ app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}], + \ } + \ ], + \ g:code_actions + +Execute(getEditsForRefactor should print error on failure): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleTSServerResponse(1, + \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getEditsForRefactor' } + \) + + AssertEqual ['echom ''Error while getting edits for refactor. Reason: oops'''], g:expr_list + +Execute(Failed LSP responses should be handled correctly): + call ale#codefix#HandleLSPResponse( + \ 1, + \ {'method': 'workspace/applyEdit', 'request_seq': 3} + \) + AssertEqual g:handle_code_action_called, 0 + +Given python(Some python file): + def main(): + a = 1 + b = a + 2 + +Execute("workspace/applyEdit" from LSP should be handled): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleLSPResponse(1, + \ {'id': 0, 'jsonrpc': '2.0', 'method': 'workspace/applyEdit', 'params': {'edit': {'changes': {'file:///foo/bar/file.ts': [{'range': {'end': {'character': 27, 'line': 7}, 'start': {'character': 27, 'line': 7}}, 'newText': ', Config'}, {'range': {'end': {'character': 12, 'line': 96}, 'start': {'character': 2, 'line': 94}}, 'newText': 'await newFunction(redis, imageKey, cover, config);'}, {'range': {'end': {'character': 2, 'line': 99}, 'start': {'character': 2, 'line': 99}}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@ try {^@ await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@ }^@ catch { }^@}^@'}]}}}}) + + AssertEqual g:handle_code_action_called, 1 + AssertEqual + \ [{'description': 'applyEdit', 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 28, 'line': 8}, 'newText': ', Config', 'start': {'offset': 28, 'line': 8}}, {'end': {'offset': 13, 'line': 97}, 'newText': 'await newFunction(redis, imageKey, cover, config);', 'start': {'offset': 3, 'line': 95}}, {'end': {'offset': 3, 'line': 100}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@ try {^@ await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@ }^@ catch { }^@}^@', 'start': {'offset': 3, 'line': 100}}]}]}], + \ g:code_actions + +Execute(Code Actions from LSP should be handled when returned with documentChanges): + call ale#codefix#SetMap({2: {}}) + call ale#codefix#HandleLSPResponse(1, + \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 4, 'line': 2}, 'start': {'character': 4, 'line': 1}}, 'newText': ''}, {'range': {'end': {'character': 9, 'line': 2}, 'start': {'character': 8, 'line': 2}}, 'newText': '(1)'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.inline', 'title': 'Inline variable', 'command': v:null}, {'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 0, 'line': 0}, 'start': {'character': 0, 'line': 0}}, 'newText': 'def func_bomdjnxh():^@ a = 1return a^@^@^@'}, {'range': {'end': {'character': 9, 'line': 1}, 'start': {'character': 8, 'line': 1}}, 'newText': 'func_bomdjnxh()^@'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.extract', 'title': 'Extract expression into function ''func_bomdjnxh''', 'command': v:null}]}) + + AssertEqual g:handle_code_action_called, 1 + AssertEqual + \ [{'description': 'codeaction', 'changes': [{'fileName': '/foo/bar/test.py', 'textChanges': [{'end': {'offset': 1, 'line': 1}, 'newText': 'def func_bomdjnxh():^@ a = 1return a^@^@^@', 'start': {'offset': 1, 'line': 1}}, {'end': {'offset': 10, 'line': 2}, 'newText': 'func_bomdjnxh()^@', 'start': {'offset': 9, 'line': 2}}]}]}], + \ g:code_actions + +Execute(LSP Code Actions handles CodeAction responses): + call ale#codefix#SetMap({3: { + \ 'connection_id': 0, + \}}) + call ale#codefix#HandleLSPResponse(1, + \ {'id': 3, 'jsonrpc': '2.0', 'result': [{'kind': 'refactor', 'title': 'Extract to inner function in function ''getVideo''', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_0', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to inner function in function ''getVideo''', 'command': '_typescript.applyRefactoring'}}, {'kind': 'refactor', 'title': 'Extract to function in module scope', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_1', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to function in module scope', 'command': '_typescript.applyRefactoring'}}]}) + + AssertEqual + \ [[0, 'workspace/executeCommand', {'arguments': [{'file': '/foo/bar/file.ts', 'action': 'function_scope_1', 'endOffset': 0, 'refactor': 'Extract Symbol', 'endLine': 68, 'startLine': 65, 'startOffset': 1}], 'command': '_typescript.applyRefactoring'}]], + \ g:message_list + +Execute(LSP Code Actions handles Command responses): + call ale#codefix#SetMap({2: { + \ 'connection_id': 2, + \}}) + call ale#codefix#HandleLSPResponse(1, + \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'title': 'fake for testing'}, {'arguments': [{'documentChanges': [{'edits': [{'range': {'end': {'character': 31, 'line': 2}, 'start': {'character': 31, 'line': 2}}, 'newText': ', createVideo'}], 'textDocument': {'uri': 'file:///foo/bar/file.ts', 'version': 1}}]}], 'title': 'Add ''createVideo'' to existing import declaration from "./video"', 'command': '_typescript.applyWorkspaceEdit'}]}) + + AssertEqual + \ [[0, 'workspace/executeCommand', {'arguments': [{'documentChanges': [{'edits': [{'range': {'end': {'character': 31, 'line': 2}, 'start': {'character': 31, 'line': 2}}, 'newText': ', createVideo'}], 'textDocument': {'uri': 'file:///foo/bar/file.ts', 'version': 1}}]}], 'command': '_typescript.applyWorkspaceEdit'}]], + \ g:message_list + +Execute(Prints message when LSP code action returns no results): + call ale#codefix#SetMap({3: {}}) + call ale#codefix#HandleLSPResponse(1, + \ {'id': 3, 'jsonrpc': '2.0', 'result': []}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No code actions received from server'''], g:expr_list + +Execute(LSP code action requests should be sent): + call ale#linter#Reset() + + runtime ale_linters/python/jedils.vim + let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}} + call setpos('.', [bufnr(''), 2, 5, 0]) + + " ALECodeAction + call ale#codefix#Execute(0) + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'code_actions', g:capability_checked + AssertEqual + \ 'function(''ale#codefix#HandleLSPResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ [0, 'textDocument/codeAction', { + \ 'context': { + \ 'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}] + \ }, + \ 'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}}, + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))} + \ }] + \ ], + \ g:message_list[-1:] + +Execute(LSP code action requests should be sent only for error with code): + call ale#linter#Reset() + + runtime ale_linters/python/jedils.vim + let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}} + call setpos('.', [bufnr(''), 2, 5, 0]) + + " ALECodeAction + call ale#codefix#Execute(0) + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'code_actions', g:capability_checked + AssertEqual + \ 'function(''ale#codefix#HandleLSPResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ [0, 'textDocument/codeAction', { + \ 'context': { + \ 'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}] + \ }, + \ 'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}}, + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))} + \ }] + \ ], + \ g:message_list[-1:] diff --git a/test/test_computed_lint_file_values.vader b/test/test_computed_lint_file_values.vader new file mode 100644 index 00000000..6c3d209d --- /dev/null +++ b/test/test_computed_lint_file_values.vader @@ -0,0 +1,150 @@ +Before: + Save g:ale_enabled + Save g:ale_run_synchronously + Save g:ale_set_lists_synchronously + Save g:ale_buffer_info + + let g:ale_enabled = 1 + let g:ale_buffer_info = {} + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + + function! TestCallback(buffer, output) + " Windows adds extra spaces to the text from echo. + return [{ + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'testlinter1', + \}] + endfunction + function! TestCallback2(buffer, output) + " Windows adds extra spaces to the text from echo. + return [{ + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'testlinter2', + \}] + endfunction + function! TestCallback3(buffer, output) + " Windows adds extra spaces to the text from echo. + return [{ + \ 'lnum': 3, + \ 'col': 3, + \ 'text': 'testlinter3', + \}] + endfunction + + " These two linters computer their lint_file values after running commands. + call ale#linter#Define('foobar', { + \ 'name': 'testlinter1', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \ 'lint_file': {b -> ale#command#Run(b, 'echo', {-> 1})}, + \}) + call ale#linter#Define('foobar', { + \ 'name': 'testlinter2', + \ 'callback': 'TestCallback2', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \ 'lint_file': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 1})})}, + \}) + " This one directly computes the result. + call ale#linter#Define('foobar', { + \ 'name': 'testlinter3', + \ 'callback': 'TestCallback3', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \ 'lint_file': {b -> 1}, + \}) + + let g:filename = tempname() + call writefile([], g:filename) + call ale#test#SetFilename(g:filename) + +After: + delfunction TestCallback + + call ale#engine#Cleanup(bufnr('')) + Restore + call ale#linter#Reset() + + " Items and markers, etc. + call setloclist(0, []) + call clearmatches() + call ale#sign#Clear() + + if filereadable(g:filename) + call delete(g:filename) + endif + + unlet g:filename + +Given foobar(A file with some lines): + foo + bar + baz + +Execute(lint_file results where the result is eventually computed should be run): + call ale#Queue(0, 'lint_file') + call ale#test#FlushJobs() + + AssertEqual + \ [ + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'testlinter2', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'testlinter1', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 3, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'testlinter3', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +Execute(Linters where lint_file eventually evaluates to 1 shouldn't be run if we don't want to run them): + call ale#Queue(0, '') + call ale#test#FlushJobs() + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + +Execute(Keeping computed lint_file jobs running should work): + AssertEqual 'testlinter2', ale#linter#Get('foobar')[1].name + + call ale#engine#InitBufferInfo(bufnr('')) + + call ale#engine#MarkLinterActive( + \ g:ale_buffer_info[bufnr('')], + \ ale#linter#Get('foobar')[1] + \) + call ale#engine#RunLinters(bufnr(''), ale#linter#Get('foobar'), 0) + + Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list), + \ 'The active linter list was empty' + Assert ale#engine#IsCheckingBuffer(bufnr('')), + \ 'The IsCheckingBuffer function returned 0' diff --git a/test/test_cursor_warnings.vader b/test/test_cursor_warnings.vader new file mode 100644 index 00000000..99108c4a --- /dev/null +++ b/test/test_cursor_warnings.vader @@ -0,0 +1,276 @@ +Before: + Save g:ale_echo_msg_format + Save g:ale_echo_cursor + Save b:ale_lint_on_insert_leave + + let g:ale_echo_msg_format = '%code: %%s' + let b:ale_lint_on_insert_leave = 0 + + " We should prefer the error message at column 10 instead of the warning. + let g:ale_buffer_info = { + \ bufnr('%'): { + \ 'loclist': [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'W', + \ 'code': 'semi', + \ 'text': 'Ignore me.', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'E', + \ 'code': 'semi', + \ 'text': "Missing semicolon.\r", + \ 'detail': "Every statement should end with a semicolon\nsecond line", + \ }, + \ { + \ 'lnum': 1, + \ 'col': 14, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'I', + \ 'text': 'Some information', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'W', + \ 'code': 'space-infix-ops', + \ 'text': 'Infix operators must be spaced.', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 15, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'E', + \ 'code': 'radix', + \ 'text': 'Missing radix parameter', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'lowercase error', + \ }, + \ ], + \ }, + \} + + " Turn off other features, we only care about this one feature in this test. + let g:ale_set_loclist = 0 + let g:ale_set_signs = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 1 + + runtime autoload/ale/cursor.vim + + let g:last_message = '' + + function! ale#cursor#Echom(message) abort + let g:last_message = a:message + endfunction + + call ale#linter#Reset() + call ale#linter#PreventLoading('javascript') + +After: + Restore + + unlet! g:last_message + + runtime autoload/ale/cursor.vim + + call cursor(1, 1) + + let g:ale_set_loclist = 1 + let g:ale_set_signs = 1 + let g:ale_set_highlights = 1 + + let g:ale_buffer_info = {} + + unlet! g:output + unlet! b:ale_loclist_msg_format + + " Clearing the messages breaks tests on NeoVim for some reason, but all + " we need to do for these tests is just make it so the last message isn't + " carried over between test cases. + echomsg '' + + " Close the preview window if it's open. + if &filetype is# 'ale-preview' + noautocmd :q! + endif + + call ale#linter#Reset() + +Given javascript(A Javscript file with warnings/errors): + var x = 3 + 12345678 + var x = 5*2 + parseInt("10"); + //" comment + +Execute(Messages should be shown for the correct lines): + call cursor(1, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'semi: Missing semicolon.', g:last_message + +Execute(Messages should be shown for earlier columns): + call cursor(2, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'space-infix-ops: Infix operators must be spaced.', g:last_message + +Execute(Messages should be shown for later columns): + call cursor(2, 16) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'radix: Missing radix parameter', g:last_message + +Execute(The message at the cursor should be shown when linting ends): + call cursor(1, 1) + call ale#engine#SetResults( + \ bufnr('%'), + \ g:ale_buffer_info[bufnr('%')].loclist, + \) + + AssertEqual 'semi: Missing semicolon.', g:last_message + +Execute(The message at the cursor should be shown when leaving insert mode): + call cursor(2, 9) + call feedkeys("i\", 'tnix') + + AssertEqual 'space-infix-ops: Infix operators must be spaced.', g:last_message + +Execute(ALEDetail should print 'detail' attributes): + call cursor(1, 1) + + ALEDetail + + AssertEqual + \ ['Every statement should end with a semicolon', 'second line'], + \ getline(1, '$') + +Execute(ALEDetail should print regular 'text' attributes): + call cursor(2, 10) + + ALEDetail + + " ALEDetail opens a window, so check the text in it. + AssertEqual + \ ['Infix operators must be spaced.'], + \ getline(1, '$') + +Execute(ALEDetail should not capitlise cursor messages): + call cursor(3, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'lowercase error', g:last_message + +Execute(The linter name should be formatted into the message correctly): + let g:ale_echo_msg_format = '%linter%: %s' + + call cursor(2, 9) + call ale#cursor#EchoCursorWarning() + + AssertEqual + \ 'bettercode: Infix operators must be spaced.', + \ g:last_message + +Execute(The severity should be formatted into the message correctly): + let g:ale_echo_msg_format = '%severity%: %s' + + call cursor(2, 9) + call ale#cursor#EchoCursorWarning() + + AssertEqual + \ 'Warning: Infix operators must be spaced.', + \ g:last_message + + call cursor(1, 10) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Error: Missing semicolon.', g:last_message + + call cursor(1, 14) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Info: Some information', g:last_message + +Execute(The type should be formatted into the message correctly): + let g:ale_echo_msg_format = '%type%: %s' + + call cursor(2, 9) + call ale#cursor#EchoCursorWarning() + + AssertEqual + \ 'W: Infix operators must be spaced.', + \ g:last_message + + call cursor(1, 10) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'E: Missing semicolon.', g:last_message + + call cursor(1, 14) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'I: Some information', g:last_message + +Execute(The %code% and %ifcode% should show the code and some text): + let g:ale_echo_msg_format = '%(code) %%s' + + call cursor(2, 9) + call ale#cursor#EchoCursorWarning() + + AssertEqual + \ '(space-infix-ops) Infix operators must be spaced.', + \ g:last_message + +Execute(The %code% and %ifcode% should be removed when there's no code): + let g:ale_echo_msg_format = '%(code) %%s' + + call cursor(1, 14) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Some information', g:last_message + +Execute(The buffer message format option should take precedence): + let g:ale_echo_msg_format = '%(code) %%s' + let b:ale_echo_msg_format = 'FOO %s' + + call cursor(1, 14) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'FOO Some information', g:last_message + +Execute(The cursor message shouldn't be echoed if the option is off): + let g:ale_echo_cursor = 0 + let g:last_message = 'foo' + + call cursor(1, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'foo', g:last_message diff --git a/test/test_deferred_command_string.vader b/test/test_deferred_command_string.vader new file mode 100644 index 00000000..b34338a2 --- /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': has('win32') ? 'cmd' : '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 a command based on the result of other commands): + AssertLinter has('win32') ? 'cmd' : 'echo', 'foo' + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ 1, + \ len(filter(copy(b:ale_history), 'string(v:val.command) =~# ''foo''')) + +Execute(We should handle the deferered command failing): + let g:ale_emulate_job_failure = 1 + + AssertLinter has('win32') ? 'cmd' : '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..7a7bb6c1 --- /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(We should handle the deferered executable 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_deno_executable_detection.vader b/test/test_deno_executable_detection.vader new file mode 100644 index 00000000..87690bfe --- /dev/null +++ b/test/test_deno_executable_detection.vader @@ -0,0 +1,20 @@ +Before: + Save g:ale_deno_executable + runtime autoload/ale/handlers/deno.vim + +After: + unlet! b:ale_deno_executable + + call ale#linter#Reset() + +Execute(Default executable should be detected correctly): + AssertEqual + \ 'deno', + \ ale#handlers#deno#GetExecutable(bufnr('')) + +Execute(User specified executable should override default): + let g:ale_deno_executable = '/path/to/deno-bin' + AssertEqual + \ '/path/to/deno-bin', + \ ale#handlers#deno#GetExecutable(bufnr('')) + diff --git a/test/test_disabling_ale.vader b/test/test_disabling_ale.vader new file mode 100644 index 00000000..660e4aa2 --- /dev/null +++ b/test/test_disabling_ale.vader @@ -0,0 +1,105 @@ +Before: + Save g:ale_buffer_info + Save g:ale_enabled + Save b:ale_enabled + Save g:ale_maximum_file_size + Save b:ale_maximum_file_size + + function! SetUpCursorData() + let g:ale_buffer_info = { + \ bufnr('%'): { + \ 'loclist': [ + \ { + \ 'lnum': 2, + \ 'col': 10, + \ 'linter_name': 'testlinter', + \ 'type': 'W', + \ 'text': 'X' + \ }, + \ ], + \ }, + \} + + call cursor(2, 16) + endfunction + + function! TestCallback(buffer, output) + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': 'echo', + \ 'command': 'true', + \}) + + function GetLastMessage() + redir => l:output + silent mess + redir END + + " Filter out messages that could come from saving this test file. + let l:lines = filter(split(l:output, "\n"), 'v:val !~ ''written\|No line''') + + return empty(l:lines) ? '' : l:lines[-1] + endfunction + + echomsg '' + +After: + Restore + call ale#linter#Reset() + delfunction TestCallback + delfunction GetLastMessage + delfunction SetUpCursorData + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(Linting shouldn't happen when ALE is disabled globally): + let g:ale_enabled = 0 + let g:ale_buffer_info = {} + + call ale#Queue(0) + + AssertEqual {}, g:ale_buffer_info + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() + +Execute(Linting shouldn't happen when ALE is disabled locally): + let b:ale_enabled = 0 + let g:ale_buffer_info = {} + + call ale#Queue(0) + + AssertEqual {}, g:ale_buffer_info + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() + +Execute(Linting shouldn't happen when the file is too large with global options): + let g:ale_maximum_file_size = 12 + let g:ale_buffer_info = {} + + call ale#Queue(0) + + AssertEqual {}, g:ale_buffer_info + " We shouldn't show cursor warnings. + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() + +Execute(Linting shouldn't happen when the file is too large with local options): + let b:ale_maximum_file_size = 12 + let g:ale_buffer_info = {} + + call ale#Queue(0) + + AssertEqual {}, g:ale_buffer_info + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() diff --git a/test/test_env_function.vader b/test/test_env_function.vader new file mode 100644 index 00000000..429eeae6 --- /dev/null +++ b/test/test_env_function.vader @@ -0,0 +1,8 @@ +Execute(ale#Env should produce the correct syntax): + if has('win32') + AssertEqual 'set name=xxx && ', ale#Env('name', 'xxx') + AssertEqual 'set "name=foo bar" && ', ale#Env('name', 'foo bar') + else + AssertEqual 'name=''xxx'' ', ale#Env('name', 'xxx') + AssertEqual 'name=''foo bar'' ', ale#Env('name', 'foo bar') + endif diff --git a/test/test_errors_removed_after_filetype_changed.vader b/test/test_errors_removed_after_filetype_changed.vader new file mode 100644 index 00000000..7c6c55af --- /dev/null +++ b/test/test_errors_removed_after_filetype_changed.vader @@ -0,0 +1,78 @@ +Before: + Save &filetype + Save g:ale_buffer_info + Save g:ale_echo_cursor + 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 + let g:ale_set_loclist = 1 + let g:ale_set_highlights = 0 + 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' + + function! TestCallback(buffer, output) + return [{'text': 'x', 'lnum': 1}] + endfunction + + call ale#linter#PreventLoading('foobar') + call ale#linter#Define('foobar', { + \ 'name': 'buffer_linter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + call ale#linter#PreventLoading('foobar2') + call ale#linter#Define('foobar2', { + \ 'name': 'buffer_linter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + +After: + Restore + + unlet! g:ale_run_synchronously_callbacks + delfunction TestCallback + + call ale#linter#Reset() + call setloclist(0, []) + +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#GetLoclistWithoutNewerKeys()) + + noautocmd let &filetype = 'foobar2' + + call ale#Queue(0) + call ale#test#FlushJobs() + sleep 1ms + + " We should get some items from the second filetype. + AssertEqual 1, len(ale#test#GetLoclistWithoutNewerKeys()) + + noautocmd let &filetype = 'xxx' + + call ale#Queue(0) + call ale#test#FlushJobs() + sleep 1ms + + AssertEqual 0, len(ale#test#GetLoclistWithoutNewerKeys()) diff --git a/test/test_filename_mapping.vader b/test/test_filename_mapping.vader new file mode 100644 index 00000000..e9af539a --- /dev/null +++ b/test/test_filename_mapping.vader @@ -0,0 +1,62 @@ +Before: + Save g:ale_filename_mappings + Save b:ale_filename_mappings + + let g:ale_filename_mappings = {} + unlet! b:ale_filename_mappings + +After: + Restore + +Execute(ale#GetFilenameMappings should return the correct mappings for given linters/fixers): + let g:ale_filename_mappings = {'a': [['foo', 'bar']], 'b': [['baz', 'foo']]} + + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['baz', 'foo']], ale#GetFilenameMappings(bufnr(''), 'b') + AssertEqual [], ale#GetFilenameMappings(bufnr(''), 'c') + + let b:ale_filename_mappings = {'b': [['abc', 'xyz']]} + + AssertEqual [], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), 'b') + AssertEqual [], ale#GetFilenameMappings(bufnr(''), 'c') + +Execute(ale#GetFilenameMappings should return Lists set for use with all tools): + let g:ale_filename_mappings = [['foo', 'bar']] + + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), '') + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), v:null) + + let b:ale_filename_mappings = [['abc', 'xyz']] + + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), '') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), v:null) + +Execute(ale#GetFilenameMappings should let you use * as a fallback): + let g:ale_filename_mappings = {'a': [['foo', 'bar']], '*': [['abc', 'xyz']]} + + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), 'b') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), '') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), v:null) + +Execute(ale#filename_mapping#Invert should invert filename mappings): + AssertEqual + \ [['b', 'a'], ['y', 'x']], + \ ale#filename_mapping#Invert([['a', 'b'], ['x', 'y']]) + \ +Execute(ale#filename_mapping#Map return the filename as-is if there are no mappings): + AssertEqual + \ '/foo//bar', + \ ale#filename_mapping#Map('/foo//bar', [['/bar', '/data/']]) + +Execute(ale#filename_mapping#Map should map filenames): + AssertEqual + \ '/data/bar', + \ ale#filename_mapping#Map('/foo//bar', [ + \ ['/data/', '/baz/'], + \ ['/foo/', '/data/'], + \ ['/foo/', '/xyz/'], + \ ]) diff --git a/test/test_filerename.vader b/test/test_filerename.vader new file mode 100644 index 00000000..1fc655c3 --- /dev/null +++ b/test/test_filerename.vader @@ -0,0 +1,224 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:old_filename = expand('%:p') + let g:Callback = '' + let g:expr_list = [] + let g:message_list = [] + let g:handle_code_action_called = 0 + let g:code_actions = [] + let g:options = {} + let g:capability_checked = '' + let g:conn_id = 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/filerename.vim + runtime autoload/ale/code_action.vim + + 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) + + 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', + \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + function! ale#code_action#HandleCodeAction(code_action, options) abort + let g:handle_code_action_called = 1 + Assert get(a:options, 'should_save') + call add(g:code_actions, a:code_action) + endfunction + + function! ale#util#Input(message, value, completion) abort + return 'a-new-name' + endfunction + + call ale#filerename#SetMap({ + \ 3: { + \ 'old_name': 'oldName', + \ 'new_name': 'aNewName', + \ }, + \}) + +After: + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#filerename#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + 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:code_actions + unlet! g:handle_code_action_called + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/filerename.vim + runtime autoload/ale/code_action.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#filerename#HandleTSServerResponse(1, {'command': 'foo'}) + AssertEqual g:handle_code_action_called, 0 + +Execute(Failed file rename responses should be handled correctly): + call ale#filerename#SetMap({3: {'old_name': 'oldName', 'new_name': 'a-test'}}) + call ale#filerename#HandleTSServerResponse( + \ 1, + \ {'command': 'getEditsForFileRename', 'request_seq': 3} + \) + AssertEqual g:handle_code_action_called, 0 + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Code actions from tsserver should be handled): + call ale#filerename#HandleTSServerResponse(1, { + \ 'command': 'getEditsForFileRename', + \ 'seq': 0, + \ 'request_seq': 3, + \ 'type': 'response', + \ 'success': v:true, + \ 'body': [ + \ { + \ 'fileName': '/foo/bar/file1.tsx', + \ 'textChanges': [ + \ { + \ 'end': {'offset': 55, 'line': 22}, + \ 'newText': './file2', + \ 'start': {'offset': 34, 'line': 22}, + \ } + \ ] + \ } + \ ], + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'filerename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.tsx', + \ 'textChanges': [ + \ { + \ 'end': {'offset': 55, 'line': 22}, + \ 'newText': './file2', + \ 'start': {'offset': 34, 'line': 22}, + \ } + \ ] + \ } + \ ], + \ } + \ ], + \ g:code_actions + +Execute(HandleTSServerResponse does nothing when no data in filerename_map): + call ale#filerename#HandleTSServerResponse(1, { + \ 'command': 'getEditsForFileRename', + \ 'request_seq': -9, + \ 'success': v:true, + \ 'body': {} + \}) + + AssertEqual g:handle_code_action_called, 0 + +Execute(Prints a tsserver error message when unsuccessful): + call ale#filerename#HandleTSServerResponse(1, { + \ 'command': 'getEditsForFileRename', + \ 'request_seq': 3, + \ 'success': v:false, + \ 'message': 'This file cannot be renamed', + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''Error renaming file "oldName" to "aNewName". ' . + \ 'Reason: This file cannot be renamed'''], g:expr_list + +Execute(Does nothing when no changes): + call ale#filerename#HandleTSServerResponse(1, { + \ 'command': 'getEditsForFileRename', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [], + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No changes while renaming "oldName" to "aNewName"'''], g:expr_list + +Execute(tsserver file rename requests should be sent): + call ale#filerename#SetMap({}) + call ale#linter#Reset() + + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFileRename + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'filerename', g:capability_checked + AssertEqual + \ 'function(''ale#filerename#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@getEditsForFileRename', { + \ 'oldFilePath': expand('%:p'), + \ 'newFilePath': 'a-new-name', + \ }] + \ ], + \ g:message_list + AssertEqual {'42': {'old_name': expand('%:p'), 'new_name': 'a-new-name'}}, + \ ale#filerename#GetMap() diff --git a/test/test_filetype_guessing.vader b/test/test_filetype_guessing.vader new file mode 100644 index 00000000..fa543129 --- /dev/null +++ b/test/test_filetype_guessing.vader @@ -0,0 +1,20 @@ +Before: + augroup TestFiletypeGroup + autocmd! + autocmd BufEnter,BufRead *.x setf xfiletype + autocmd BufEnter,BufRead *.y set filetype=yfiletype + autocmd BufEnter,BufRead *.z setlocal filetype=zfiletype + autocmd BufEnter,BufRead *.jsx set filetype=javascript.jsx + augroup END + +After: + augroup TestFiletypeGroup + autocmd! + augroup END + augroup! TestFiletypeGroup + +Execute(ALE should guess file extensions appropriately): + " The whole string should be used, if there's a match. + AssertEqual '.jsx', ale#filetypes#GuessExtension('javascript.jsx') + " The first part should be used. + AssertEqual '.x', ale#filetypes#GuessExtension('xfiletype.yfiletype') diff --git a/test/test_filetype_linter_defaults.vader b/test/test_filetype_linter_defaults.vader new file mode 100644 index 00000000..69d83d84 --- /dev/null +++ b/test/test_filetype_linter_defaults.vader @@ -0,0 +1,140 @@ +Before: + Save g:ale_linters + Save g:ale_linters_explicit + + let g:ale_linters_explicit = 0 + let g:ale_linters = {} + + function! GetLinterNames(filetype) abort + return sort(map(ale#linter#Get(a:filetype), 'v:val.name')) + endfunction + +After: + Restore + + call ale#linter#Reset() + +Execute(The defaults for the apkbuild filetype should be correct): + AssertEqual ['apkbuild_lint', 'secfixes_check'], GetLinterNames('apkbuild') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('apkbuild') + +Execute(The defaults for the csh filetype should be correct): + AssertEqual ['shell'], GetLinterNames('csh') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('csh') + +Execute(The defaults for the elixir filetype should be correct): + AssertEqual ['credo', 'dialyxir', 'dogma'], GetLinterNames('elixir') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('elixir') + +Execute(The defaults for the go filetype should be correct): + AssertEqual ['gofmt', 'golangci-lint', 'gopls', 'govet'], GetLinterNames('go') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('go') + +Execute(The defaults for the hack filetype should be correct): + AssertEqual ['hack'], GetLinterNames('hack') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('hack') + +Execute(The defaults for the help filetype should be correct): + AssertEqual [], GetLinterNames('help') + +Execute(The defaults for the inko filetype should be correct): + AssertEqual ['inko'], GetLinterNames('inko') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('inko') + +Execute(The defaults for the json filetype should be correct): + AssertEqual ['biome', 'jsonlint', 'spectral', 'vscodejson'], GetLinterNames('json') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('json') + +Execute(The defaults for the json5 filetype should be correct): + AssertEqual [], GetLinterNames('json5') + +Execute(The defaults for the jsonc filetype should be correct): + AssertEqual ['biome'], GetLinterNames('jsonc') + +Execute(The defaults for the perl filetype should be correct): + AssertEqual ['perlcritic'], GetLinterNames('perl') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('perl') + +Execute(The defaults for the perl6 filetype should be correct): + AssertEqual [], GetLinterNames('perl6') + +Execute(The defaults for the python filetype should be correct): + AssertEqual ['flake8', 'mypy', 'pylint', 'pyright', 'ruff'], GetLinterNames('python') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('python') + +Execute(The defaults for the rust filetype should be correct): + AssertEqual ['analyzer', 'cargo'], GetLinterNames('rust') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('rust') + +Execute(The defaults for the spec filetype should be correct): + AssertEqual [], GetLinterNames('spec') + +Execute(The defaults for the text filetype should be correct): + AssertEqual [], GetLinterNames('text') + +Execute(The defaults for the vue filetype should be correct): + AssertEqual ['eslint', 'vls'], GetLinterNames('vue') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('vue') + +Execute(The defaults for the zsh filetype should be correct): + AssertEqual ['shell'], GetLinterNames('zsh') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('zsh') + +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 ['hdl_checker', 'iverilog', 'slang', 'verilator', 'vlog', 'xvlog', 'yosys'], GetLinterNames('verilog') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('verilog') + +Execute(The defaults for the vader filetype should be correct): + AssertEqual ['vimls'], GetLinterNames('vader') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('vader') + +Execute(Default aliases for React should be defined): + AssertEqual ['javascript', 'jsx'], ale#linter#ResolveFiletype('javascriptreact') + AssertEqual ['typescript', 'tsx'], ale#linter#ResolveFiletype('typescriptreact') + +Execute(The defaults for the yaml filetype should be correct): + AssertEqual ['actionlint', 'spectral', 'yaml-language-server', 'yamllint'], GetLinterNames('yaml') diff --git a/test/test_find_nearest_directory.vader b/test/test_find_nearest_directory.vader new file mode 100644 index 00000000..740668da --- /dev/null +++ b/test/test_find_nearest_directory.vader @@ -0,0 +1,17 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(We should be able to find a directory some directory down): + call ale#test#SetFilename('test-files/top/middle/bottom/dummy.txt') + + AssertEqual + \ ale#path#Simplify(expand('%:p:h:h:h:h:h') . '/test-files/top/ale-special-directory-name-dont-use-this-please/'), + \ ale#path#FindNearestDirectory(bufnr('%'), 'ale-special-directory-name-dont-use-this-please') + +Execute(We shouldn't find anything for files which don't match): + AssertEqual + \ '', + \ ale#path#FindNearestDirectory(bufnr('%'), 'ale-this-should-never-match-anything') diff --git a/test/test_find_references.vader b/test/test_find_references.vader new file mode 100644 index 00000000..ad462394 --- /dev/null +++ b/test/test_find_references.vader @@ -0,0 +1,480 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + Save g:ale_default_navigation + + let g:old_filename = expand('%:p') + let g:Callback = '' + let g:expr_list = [] + 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:InitCallback = v:null + let g:ale_default_navigation = 'buffer' + + 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, Callback) abort + let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {}) + call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) + + 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', + \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + function! ale#preview#ShowSelection(item_list, options) abort + let g:preview_called = 1 + let g:item_list = a:item_list + let g:options = a:options + + call ale#preview#SetLastSelection(a:item_list, a:options) + endfunction + +After: + Restore + + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#references#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + 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 + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/preview.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#references#HandleTSServerResponse(1, {'command': 'foo'}) + +Execute(Failed reference responses should be handled correctly): + call ale#references#SetMap({3: {}}) + call ale#references#HandleTSServerResponse( + \ 1, + \ {'command': 'references', 'request_seq': 3} + \) + AssertEqual {}, ale#references#GetMap() + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Results should be shown for tsserver responses): + " We should remember these options when we repeat the selection. + call ale#references#SetMap( + \ { + \ 3: { + \ 'ignorethis': 'x', + \ 'open_in': 'tab', + \ 'use_relative_paths': 1, + \ } + \ } + \) + call ale#references#HandleTSServerResponse(1, { + \ 'command': 'references', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'symbolStartOffset': 9, + \ 'refs': [ + \ { + \ 'file': '/foo/bar/app.ts', + \ 'isWriteAccess': v:true, + \ 'lineText': 'import {doSomething} from ''./whatever''', + \ 'end': {'offset': 24, 'line': 9}, + \ 'start': {'offset': 9, 'line': 9}, + \ 'isDefinition': v:true, + \ }, + \ { + \ 'file': '/foo/bar/app.ts', + \ 'isWriteAccess': v:false, + \ 'lineText': ' doSomething()', + \ 'end': {'offset': 18, 'line': 804}, + \ 'start': {'offset': 3, 'line': 804}, + \ 'isDefinition': v:false, + \ }, + \ { + \ 'file': '/foo/bar/other/app.ts', + \ 'isWriteAccess': v:false, + \ 'lineText': ' doSomething()', + \ 'end': {'offset': 18, 'line': 51}, + \ 'start': {'offset': 3, 'line': 51}, + \ 'isDefinition': v:false, + \ }, + \ ], + \ 'symbolDisplayString': 'import doSomething', + \ 'symbolName': 'doSomething()', + \ }, + \}) + + AssertEqual + \ [ + \ {'filename': '/foo/bar/app.ts', 'column': 9, 'line': 9, '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() + + " We should be able to repeat selections with ALERepeatSelection + let g:item_list = [] + ALERepeatSelection + + AssertEqual + \ [ + \ {'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() + AssertEqual + \ { + \ 'open_in': 'tab', + \ 'use_relative_paths': 1, + \ }, + \ g:options + +Execute(Results should be put to quickfix for tsserver responses): + call ale#references#SetMap( + \ { + \ 3: { + \ 'ignorethis': 'x', + \ 'open_in': 'quickfix', + \ } + \ } + \) + call ale#references#HandleTSServerResponse(1, { + \ 'command': 'references', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'symbolStartOffset': 9, + \ 'refs': [ + \ { + \ 'file': '/foo/bar/app.ts', + \ 'isWriteAccess': v:true, + \ 'lineText': 'import {doSomething} from ''./whatever''', + \ 'end': {'offset': 24, 'line': 9}, + \ 'start': {'offset': 9, 'line': 9}, + \ 'isDefinition': v:true, + \ }, + \ { + \ 'file': '/foo/bar/app.ts', + \ 'isWriteAccess': v:false, + \ 'lineText': ' doSomething()', + \ 'end': {'offset': 18, 'line': 804}, + \ 'start': {'offset': 3, 'line': 804}, + \ 'isDefinition': v:false, + \ }, + \ { + \ 'file': '/foo/bar/other/app.ts', + \ 'isWriteAccess': v:false, + \ 'lineText': ' doSomething()', + \ 'end': {'offset': 18, 'line': 51}, + \ 'start': {'offset': 3, 'line': 51}, + \ 'isDefinition': v:false, + \ }, + \ ], + \ 'symbolDisplayString': 'import doSomething', + \ 'symbolName': 'doSomething()', + \ }, + \}) + + AssertEqual + \ 3, + \ len(getqflist()) + AssertEqual {}, ale#references#GetMap() + +Execute(The preview window should not be opened for empty tsserver responses): + call ale#references#SetMap({3: {}}) + call ale#references#HandleTSServerResponse(1, { + \ 'command': 'references', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'symbolStartOffset': 9, + \ 'refs': [ + \ ], + \ 'symbolDisplayString': 'import doSomething', + \ 'symbolName': 'doSomething()', + \ }, + \}) + + Assert !g:preview_called + AssertEqual {}, ale#references#GetMap() + AssertEqual ['echom ''No references found.'''], g:expr_list + +Execute(tsserver reference requests should be sent): + call ale#linter#Reset() + + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'references', g:capability_checked + AssertEqual + \ 'function(''ale#references#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], + \ g:message_list + AssertEqual {'42': {'open_in': 'current-buffer', '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': {'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap() + +Execute(`-tab` should display results in tabs): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -tab + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute(The default navigation type should be used): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + let g:ale_default_navigation = 'tab' + ALEFindReferences + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute(`-split` should display results in splits): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -split + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'split', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute(`-vsplit` should display results in vsplits): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -vsplit + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'vsplit', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute(`-quickfix` should display results in quickfix): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -quickfix + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'quickfix', 'use_relative_paths': 0}}, ale#references#GetMap() + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(LSP reference responses should be handled): + call ale#references#SetMap({3: {}}) + call ale#references#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/other_file')), + \ 'range': { + \ 'start': {'line': 7, 'character': 15}, + \ }, + \ }, + \ ], + \ } + \) + + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(g:dir . '/completion_dummy_file'), + \ 'line': 3, + \ 'column': 8, + \ }, + \ { + \ 'filename': ale#path#Simplify(g:dir . '/other_file'), + \ 'line': 8, + \ 'column': 16, + \ }, + \ ], + \ g:item_list + AssertEqual {}, ale#references#GetMap() + +Execute(LSP reference responses should be put to quickfix): + call ale#references#SetMap({3: { 'open_in': 'quickfix' }}) + call ale#references#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/other_file')), + \ 'range': { + \ 'start': {'line': 7, 'character': 15}, + \ }, + \ }, + \ ], + \ } + \) + + AssertEqual + \ 2, + \ len(getqflist()) + +Execute(Preview windows should not be opened for empty LSP reference responses): + call ale#references#SetMap({3: {}}) + call ale#references#HandleLSPResponse(1, {'id': 3, 'result': []}) + + Assert !g:preview_called + AssertEqual {}, ale#references#GetMap() + AssertEqual ['echom ''No references found.'''], g:expr_list + +Execute(LSP reference 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() + AssertEqual ['echom ''No references found.'''], g:expr_list + +Execute(LSP reference requests should be sent): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEFindReferences + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'references', g:capability_checked + AssertEqual + \ 'function(''ale#references#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/references', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ 'context': {'includeDeclaration': v:false}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'current-buffer', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResponse): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEFindReferences -relative + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap() diff --git a/test/test_floating_preview.vader b/test/test_floating_preview.vader new file mode 100644 index 00000000..f765f3f8 --- /dev/null +++ b/test/test_floating_preview.vader @@ -0,0 +1,93 @@ +Before: + let g:ale_floating_preview = 0 + let g:ale_hover_to_floating_preview = 0 + let g:ale_detail_to_floating_preview = 0 + + runtime autoload/ale/floating_preview.vim + + let g:floated_lines = [] + let g:floating_preview_show_called = 0 + + " Stub out so we can track the call + function! ale#floating_preview#Show(lines, ...) abort + let g:floating_preview_show_called = 1 + let g:floated_lines = a:lines + return win_getid() + endfunction + + let g:ale_buffer_info = { + \ bufnr('%'): { + \ 'loclist': [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'notalinter', + \ 'nr': -1, + \ 'type': 'E', + \ 'code': 'semi', + \ 'text': "Missing semicolon.\r", + \ 'detail': "Every statement should end with a semicolon\nsecond line", + \ }, + \ ], + \ } + \} + + call ale#linter#Reset() + call ale#linter#PreventLoading('javascript') + +After: + Restore + + let g:ale_floating_preview = 0 + let g:ale_hover_to_floating_preview = 0 + let g:ale_detail_to_floating_preview = 0 + + call cursor(1, 1) + + let g:ale_buffer_info = {} + + " Close the preview window if it's open. + if &filetype is# 'ale-preview' + noautocmd :q! + endif + + call ale#linter#Reset() + + +Given javascript(A file with warnings/errors): + var x = 3 + 12345678 + var x = 5*2 + parseInt("10"); + // comment + +Execute(Floating preview is used with ALEDetail when g:ale_floating_preview set): + let g:ale_floating_preview = 1 + + call cursor(1, 10) + + ALEDetail + + let expected = ["Every statement should end with a semicolon", "second line"] + + AssertEqual 1, g:floating_preview_show_called + AssertEqual expected, g:floated_lines + +Execute(Floating preview is used with ALEDetail when g:ale_detail_to_floating_preview set): + let g:ale_detail_to_floating_preview = 1 + + call cursor(1, 10) + + ALEDetail + + let expected = ["Every statement should end with a semicolon", "second line"] + + AssertEqual 1, g:floating_preview_show_called + AssertEqual expected, g:floated_lines + +Execute(Floating preview is not used with ALEDetail by default): + call cursor(1, 10) + + ALEDetail + + AssertEqual 0, g:floating_preview_show_called diff --git a/test/test_format_command.vader b/test/test_format_command.vader new file mode 100644 index 00000000..3b7ee98b --- /dev/null +++ b/test/test_format_command.vader @@ -0,0 +1,186 @@ +Before: + silent! cd /testplugin/test + silent file top/middle/bottom/dummy.txt + + function! CheckTempFile(filename) abort + " Check every part of the temporary filename, except the random part. + AssertEqual fnamemodify(tempname(), ':h'), fnamemodify(a:filename, ':h:h') + 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', 0], + \ ale#command#FormatCommand(bufnr('%'), '', 'awesome-linter do something', 0, v:null, v:null, []) + +Execute(FormatCommand should handle %%, and ignore other percents): + AssertEqual + \ ['', '% %%d %%f %x %', 0], + \ ale#command#FormatCommand(bufnr('%'), '', '%% %%%d %%%f %x %', 0, v:null, v:null, []) + +Execute(FormatCommand should convert %s to the current filename): + AssertEqual + \ [ + \ '', + \ 'foo ' . ale#Escape(expand('%:p')) . ' bar ' . ale#Escape(expand('%:p')), + \ 0, + \ ], + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s bar %s', 0, v:null, v:null, []) + +Execute(FormatCommand should convert %t to a new temporary filename): + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0, v:null, v:null, []) + + call CheckTempFile(g:result[0]) + + let g:match = matchlist(g:result[1], '\v^foo (.*) bar (.*)$') + + Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] + " The first item of the result should be a temporary filename, and it should + " be the same as the escaped name in the command string. + AssertEqual ale#Escape(g:result[0]), g:match[1] + " 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, v:null, []) + + 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, v:null, [])[2] + + AssertEqual + \ 0, + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s', 0, v:null, v:null, [])[2] + +Execute(FormatCommand should let you combine %s and %t): + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %s', 0, v:null, v:null, []) + + call CheckTempFile(g:result[0]) + + let g:match = matchlist(g:result[1], '\v^foo (.*) bar (.*)$') + + Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] + " The first item of the result should be a temporary filename, and it should + " be the same as the escaped name in the command string. + AssertEqual ale#Escape(g:result[0]), g:match[1] + " The second item should be equal to the original filename. + AssertEqual ale#Escape(expand('%:p')), g:match[2] + +Execute(FormatCommand should replace %e with the escaped executable): + if has('win32') + AssertEqual + \ ['', 'foo foo', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null, v:null, []) + AssertEqual + \ ['', '"foo bar"', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null, v:null, []) + AssertEqual + \ ['', '%e %e', 0], + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null, v:null, []) + else + AssertEqual + \ ['', '''foo'' ''foo''', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null, v:null, []) + AssertEqual + \ ['', '''foo bar''', 0], + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null, v:null, []) + AssertEqual + \ ['', '%e %e', 0], + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null, 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, v:null, v:null, []) + + call CheckTempFile(g:result[0]) + + let g:match = matchlist(g:result[1], '\v^foo bar \< (.*)$') + 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, v:null, v:null, []) + + call CheckTempFile(g:result[0]) + + let g:match = matchlist(g:result[1], '\v^foo bar (.*)$') + Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] + AssertEqual ale#Escape(g:result[0]), g:match[1] + +Execute(FormatCommand should apply filename modifiers to the current file): + AssertEqual + \ ale#Escape(expand('%:p:h')) + \ . ' ' . ale#Escape('dummy.txt') + \ . ' ' . ale#Escape(expand('%:p:h:t')) + \ . ' ' . ale#Escape('txt') + \ . ' ' . ale#Escape(expand('%:p:r')), + \ ale#command#FormatCommand(bufnr(''), '', '%s:h %s:t %s:h:t %s:e %s:r', 0, v:null, v:null, [])[1] + +Execute(FormatCommand should apply filename modifiers to the temporary file): + let g:result = ale#command#FormatCommand(bufnr(''), '', '%t:h %t:t %t:h:t %t:e %t:r', 0, v:null, v:null, []) + + AssertEqual + \ ale#Escape(fnamemodify(g:result[0], ':h')) + \ . ' ' . ale#Escape('dummy.txt') + \ . ' ' . ale#Escape(fnamemodify(g:result[0], ':h:t')) + \ . ' ' . ale#Escape('txt') + \ . ' ' . ale#Escape(fnamemodify(g:result[0], ':r')), + \ g:result[1] + +Execute(FormatCommand should apply filename mappings the current file): + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%s', 0, v:null, v:null, [ + \ [expand('%:p:h'), '/foo/bar'], + \]) + + Assert g:result[1] =~# '/foo/bar' + +Execute(FormatCommand should apply filename mappings to temporary files): + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%t', 0, v:null, v:null, [ + \ [fnamemodify(tempname(), ':h:h'), '/foo/bar'] + \]) + + Assert g:result[1] =~# '/foo/bar' + +Execute(FormatCommand should apply filename modifiers to mapped filenames): + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%s:h', 0, v:null, v:null, [ + \ [expand('%:p:h'), '/foo/bar'], + \]) + + AssertEqual ale#Escape('/foo/bar'), g:result[1] + + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%t:h:h:h', 0, v:null, v:null, [ + \ [fnamemodify(tempname(), ':h:h'), '/foo/bar'] + \]) + + AssertEqual ale#Escape('/foo/bar'), g:result[1] + +Execute(FormatCommand should apply regular cwd paths): + AssertEqual + \ 'cd ' . (has('unix') ? '' : '/d ') . ale#Escape('/foo /bar') . ' && abc', + \ ale#command#FormatCommand(bufnr('%'), '', 'abc', 0, v:null, '/foo /bar', [])[1] + \ +Execute(FormatCommand should apply cwd substitution and formatting): + call ale#test#SetFilename('foo.txt') + + AssertEqual + \ 'cd ' . (has('unix') ? '' : '/d ') . ale#Escape(getcwd()) . ' && abc', + \ ale#command#FormatCommand(bufnr('%'), '', 'abc', 0, v:null, '%s:h', [])[1] diff --git a/test/test_format_temporary_file_creation.vader b/test/test_format_temporary_file_creation.vader new file mode 100644 index 00000000..10409400 --- /dev/null +++ b/test/test_format_temporary_file_creation.vader @@ -0,0 +1,63 @@ +Before: + Save g:ale_buffer_info + Save g:ale_echo_cursor + Save g:ale_enabled + 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 + + " Disable the features we don't need to check. + let g:ale_buffer_info = {} + 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 + let g:ale_set_signs = 0 + + let g:output = [] + + function! TestCallback(buffer, output) + " Extract just letters from the output. + let g:output = filter( + \ map(a:output, 'matchstr(v:val, ''[a-zA-Z]\+'')'), + \ '!empty(v:val)' + \) + + return [] + endfunction + + call ale#linter#PreventLoading('foobar') + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'cat', + \ 'command': has('win32') ? 'type %t' : 'cat %t', + \}) + +After: + Restore + + unlet! g:ale_run_synchronously_callbacks + unlet! g:output + delfunction TestCallback + + call ale#engine#Cleanup(bufnr('')) + call ale#linter#Reset() + +Given foobar (Some imaginary filetype): + foo + bar + baz + +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_function_arg_count.vader b/test/test_function_arg_count.vader new file mode 100644 index 00000000..d256c403 --- /dev/null +++ b/test/test_function_arg_count.vader @@ -0,0 +1,45 @@ +Before: + function! Func0() + endfunction + function! Func1(x) + endfunction + function! Func2(x,y) + endfunction + function! Func3(x,y,z) + endfunction + function! Func3a(x,y,z,...) + endfunction + +After: + delfunction Func0 + delfunction Func1 + delfunction Func2 + delfunction Func3 + delfunction Func3a + +Execute(We should be able to compute the argument count for function names): + AssertEqual 0, ale#util#FunctionArgCount('Func0') + AssertEqual 1, ale#util#FunctionArgCount('Func1') + AssertEqual 2, ale#util#FunctionArgCount('Func2') + AssertEqual 3, ale#util#FunctionArgCount('Func3') + AssertEqual 3, ale#util#FunctionArgCount('Func3a') + +Execute(We should be able to compute the argument count for Funcrefs): + AssertEqual 0, ale#util#FunctionArgCount(function('Func0')) + AssertEqual 1, ale#util#FunctionArgCount(function('Func1')) + AssertEqual 2, ale#util#FunctionArgCount(function('Func2')) + AssertEqual 3, ale#util#FunctionArgCount(function('Func3')) + AssertEqual 3, ale#util#FunctionArgCount(function('Func3a')) + +Execute(We should be able to compute the argument count for lambdas): + if has('lambda') + AssertEqual 0, ale#util#FunctionArgCount({->1}) + AssertEqual 1, ale#util#FunctionArgCount({x->1}) + AssertEqual 2, ale#util#FunctionArgCount({x,y->1}) + AssertEqual 3, ale#util#FunctionArgCount({x,y,z->1}) + AssertEqual 3, ale#util#FunctionArgCount({x,y,z,...->1}) + endif + +Execute(We should be able to compute the argument count autoload functions not yet loaded): + AssertEqual 1, ale#util#FunctionArgCount(function('ale#fixers#yapf#Fix')) + AssertEqual 1, ale#util#FunctionArgCount('ale#fixers#yapf#Fix') diff --git a/test/test_fuzzy_json_decode.vader b/test/test_fuzzy_json_decode.vader new file mode 100644 index 00000000..4b1c6088 --- /dev/null +++ b/test/test_fuzzy_json_decode.vader @@ -0,0 +1,29 @@ +Execute(FuzzyJSONDecode should return the default for empty Lists): + AssertEqual [], ale#util#FuzzyJSONDecode([], []) + AssertEqual {}, ale#util#FuzzyJSONDecode([], {}) + +Execute(FuzzyJSONDecode should return the default for empty Strings): + AssertEqual [], ale#util#FuzzyJSONDecode('', []) + AssertEqual {}, ale#util#FuzzyJSONDecode('', {}) + +Execute(FuzzyJSONDecode should return the default value for ['']): + AssertEqual [], ale#util#FuzzyJSONDecode([''], []) + AssertEqual {}, ale#util#FuzzyJSONDecode([''], {}) + +Execute(FuzzyJSONDecode should return the default value for only whitespace lines): + AssertEqual [], ale#util#FuzzyJSONDecode(['', "\n"], []) + AssertEqual {}, ale#util#FuzzyJSONDecode(['', "\n"], {}) + +Execute(FuzzyJSONDecode should return the default for Lists with invalid JSON): + AssertEqual [], ale#util#FuzzyJSONDecode(['x'], []) + AssertEqual {}, ale#util#FuzzyJSONDecode(['x'], {}) + +Execute(FuzzyJSONDecode should return the default for Strings with invalid JSON): + AssertEqual [], ale#util#FuzzyJSONDecode('x', []) + AssertEqual {}, ale#util#FuzzyJSONDecode('x', {}) + +Execute(FuzzyJSONDecode should return the JSON from the JSON string): + AssertEqual {'x': 3}, ale#util#FuzzyJSONDecode('{"x": 3}', []) + AssertEqual {'x': 3}, ale#util#FuzzyJSONDecode('{"x": 3}', {}) + AssertEqual {'x': 3}, ale#util#FuzzyJSONDecode(['{"x"', ': 3}'], []) + AssertEqual {'x': 3}, ale#util#FuzzyJSONDecode(['{"x"', ': 3}'], {}) diff --git a/test/test_get_abspath.vader b/test/test_get_abspath.vader new file mode 100644 index 00000000..7e1b5930 --- /dev/null +++ b/test/test_get_abspath.vader @@ -0,0 +1,29 @@ +Execute(Relative paths should be resolved correctly): + AssertEqual + \ has('win32') ? '\foo\bar\baz\whatever.txt' : '/foo/bar/baz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', '../baz/whatever.txt') + AssertEqual + \ has('win32') ? '\foo\bar\xyz\whatever.txt' : '/foo/bar/xyz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', './whatever.txt') + AssertEqual + \ has('win32') ? '\foo\bar\xyz\whatever.txt' : '/foo/bar/xyz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', 'whatever.txt') + + if has('win32') + AssertEqual + \ 'C:\foo\bar\baz\whatever.txt', + \ ale#path#GetAbsPath('C:\foo\bar\baz\xyz', '../whatever.txt') + endif + +Execute(Absolute paths should be resolved correctly): + AssertEqual + \ has('win32') ? '\ding\dong' : '/ding/dong', + \ ale#path#GetAbsPath('/foo/bar/xyz', '/ding/dong') + + AssertEqual + \ has('win32') ? '\ding\dong' : '/ding/dong', + \ ale#path#GetAbsPath('/foo/bar/xyz', '//ding/dong') + + if has('win32') + AssertEqual '\ding', ale#path#GetAbsPath('/foo/bar/xyz', '\\ding') + endif diff --git a/test/test_get_loclist.vader b/test/test_get_loclist.vader new file mode 100644 index 00000000..14696998 --- /dev/null +++ b/test/test_get_loclist.vader @@ -0,0 +1,31 @@ +Before: + let g:loclist = [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'E', + \ 'col': 10, + \ 'text': 'Missing semicolon. (semi)' + \ }, + \ { + \ 'lnum': 2, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'W', + \ 'col': 10, + \ 'text': 'Infix operators must be spaced. (space-infix-ops)' + \ }, + \] + let g:ale_buffer_info = {'1': {'loclist': g:loclist}} + +After: + unlet g:loclist + let g:ale_buffer_info = {} + +Execute(GetLoclist should return the loclist): + AssertEqual g:loclist, ale#engine#GetLoclist(1) diff --git a/test/test_getmatches.vader b/test/test_getmatches.vader new file mode 100644 index 00000000..edf84f6e --- /dev/null +++ b/test/test_getmatches.vader @@ -0,0 +1,163 @@ +Execute (ale#util#GetMatches should return matches for many lines): + AssertEqual + \ [ + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '47', + \ '14', + \ 'Missing trailing comma.', + \ 'Warning/comma-dangle', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ [ + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ '56', + \ '41', + \ 'Missing semicolon.', + \ 'Error/semi', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ ], + \ ale#util#GetMatches( + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ ], + \ [ + \ '^.*:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)\]$', + \ ] + \ ) + +Execute (ale#util#GetMatches should accept a string for a single pattern): + AssertEqual + \ [ + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '47', + \ '14', + \ 'Missing trailing comma.', + \ 'Warning/comma-dangle', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ [ + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ '56', + \ '41', + \ 'Missing semicolon.', + \ 'Error/semi', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ ], + \ ale#util#GetMatches( + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ ], + \ '^.*:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)\]$' + \ ) + +Execute (ale#util#MapMatches should map matches): + AssertEqual + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ ], + \ ale#util#MapMatches( + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ ], + \ '^.*:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)\]$', + \ {match -> match[0]} + \ ) + +Execute (ale#util#GetMatches should accept a single line as a string): + AssertEqual + \ [ + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '47', + \ '14', + \ 'Missing trailing comma.', + \ 'Warning/comma-dangle', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ ], + \ ale#util#GetMatches( + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ [ + \ '^.*:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)\]$', + \ ] + \ ) + +Execute (ale#util#GetMatches should match multiple patterns correctly): + AssertEqual + \ [ + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '47', + \ '14', + \ 'Missing trailing comma.', + \ 'Warning/comma-dangle', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ [ + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ '56', + \ '41', + \ 'Missing semicolon.', + \ 'Error/semi', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ [ + \ '/path/to/some-filename.js:13:3: Parsing error: Unexpected token', + \ '13', + \ '3', + \ 'Parsing error: Unexpected token', + \ '', + \ '', + \ '', + \ '', + \ '', + \ '', + \ ], + \ ], + \ ale#util#GetMatches( + \ [ + \ '/path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]', + \ '/path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]', + \ '/path/to/some-filename.js:13:3: Parsing error: Unexpected token', + \ ], + \ [ + \ '^.*:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)\]$', + \ '^.*:\(\d\+\):\(\d\+\): \(.\+\)$', + \ ] + \ ) diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader new file mode 100644 index 00000000..6a42bdcb --- /dev/null +++ b/test/test_go_to_definition.vader @@ -0,0 +1,685 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + Save g:ale_default_navigation + + let g:old_filename = expand('%:p') + let g:Callback = '' + let g:message_list = [] + let g:expr_list = [] + let g:capability_checked = '' + let g:conn_id = v:null + let g:InitCallback = v:null + let g:ale_default_navigation = 'buffer' + + 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, Callback) abort + let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {}) + call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) + + 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', + \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + +After: + Restore + + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#definition#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + 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 + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#definition#HandleTSServerResponse(1, {'command': 'foo'}) + +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': 'current-buffer'}}) + call ale#definition#HandleTSServerResponse( + \ 1, + \ {'command': 'definition', 'request_seq': 3} + \) + AssertEqual {}, ale#definition#GetMap() + +Execute(Failed definition responses with no files should be handled correctly): + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) + call ale#definition#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'definition', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [], + \ } + \) + AssertEqual {}, ale#definition#GetMap() + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Other files should be jumped to for definition responses): + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) + 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 + \ [ + \ 'edit +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 tabs too): + call ale#definition#SetMap({3: {'open_in': 'tab'}}) + 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 + \ [ + \ 'tabedit +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 splits too): + call ale#definition#SetMap({3: {'open_in': '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': 'vsplit'}}) + 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]) + + ALEGoToDefinition + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'definition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], + \ g:message_list + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() + +Execute(tsserver type definition requests should be sent): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 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#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@typeDefinition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], + \ g:message_list + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() + +Execute(tsserver implementation requests should be sent): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEGoToImplementation + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'implementation', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@implementation', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], + \ g:message_list + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() + +Execute(tsserver tab definition requests should be sent): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEGoToDefinition -tab + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'definition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], + \ g:message_list + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() + +Execute(The default navigation type should be used): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + let g:ale_default_navigation = 'tab' + ALEGoToDefinition + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'definition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], + \ g:message_list + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Other files should be jumped to for LSP definition responses): + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ } + \) + + AssertEqual + \ [ + \ 'edit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 8], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Newer LocationLink items should be supported): + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': { + \ 'targetUri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'targetRange': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ } + \) + + AssertEqual + \ [ + \ 'edit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 8], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Locations inside the same file should be jumped to without using :edit): + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(expand('%:p'))), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ } + \) + + AssertEqual + \ [ + \ ], + \ g:expr_list + AssertEqual [3, 8], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Other files should be jumped to in tabs for LSP definition responses): + call ale#definition#SetMap({3: {'open_in': 'tab'}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ } + \) + + AssertEqual + \ [ + \ 'tabedit +3 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 8], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Definition responses with lists should be handled): + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/other_file')), + \ 'range': { + \ 'start': {'line': 20, 'character': 3}, + \ }, + \ }, + \ ], + \ } + \) + + " Multiple results should either open the ALEPreview or go to quickfix + AssertEqual [1, 1], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Definition responses with null response should be handled): + call ale#definition#SetMap({3: {'open_in': 'current-buffer'}}) + call ale#definition#HandleLSPResponse(1, {'id': 3, 'result': v:null}) + + AssertEqual ['echom ''No definitions found'''], g:expr_list + +Execute(LSP definition requests should be sent): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToDefinition + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'definition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/definition', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ '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/pylsp.vim + let b:ale_linters = ['pylsp'] + 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#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/typeDefinition', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() + +Execute(LSP implementation requests should be sent): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToImplementation + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'implementation', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/implementation', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'current-buffer'}}, ale#definition#GetMap() + +Execute(LSP tab definition requests should be sent): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToDefinition -tab + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'definition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/definition', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ '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/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToTypeDefinition -tab + + " 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#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/typeDefinition', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() + +Execute(LSP tab implementation requests should be sent): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToImplementation -tab + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'implementation', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/implementation', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() diff --git a/test/test_gradle_build_classpath_command.vader b/test/test_gradle_build_classpath_command.vader new file mode 100644 index 00000000..9557aa0d --- /dev/null +++ b/test/test_gradle_build_classpath_command.vader @@ -0,0 +1,52 @@ +Before: + Save $PATH + Save $PATHEXT + + let $PATHEXT = '.' + + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/kotlin/kotlinc.vim + + let g:command_tail = ' -I ' . ale#Escape(ale#gradle#GetInitPath()) + \ . ' -q printClasspath' + + let g:gradle_init_path = ale#path#Simplify(g:dir . '../../autoload/ale/gradle/init.gradle') + +After: + Restore + + unlet! g:gradle_init_path + unlet! g:command_tail + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should return 'gradlew' command if project includes gradle wapper): + call ale#test#SetFilename('test-files/gradle/wrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir . '/test-files/gradle/wrapped-project'), + \ ale#Escape(ale#path#Simplify(g:dir . '/test-files/gradle/wrapped-project/gradlew')) + \ . g:command_tail, + \ ], + \ ale#gradle#BuildClasspathCommand(bufnr('')) + +Execute(Should return 'gradle' command if project does not include gradle wapper): + call ale#test#SetFilename('test-files/gradle/unwrapped-project/src/main/kotlin/dummy.kt') + let $PATH .= (has('win32') ? ';' : ':') + \ . ale#path#Simplify(g:dir . '/test-files/gradle') + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir . '/test-files/gradle/unwrapped-project'), + \ ale#Escape('gradle') . g:command_tail + \ ], + \ ale#gradle#BuildClasspathCommand(bufnr('')) + +Execute(Should return empty string if gradle cannot be executed): + call ale#test#SetFilename('test-files/gradle/non-gradle-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ['', ''], + \ ale#gradle#BuildClasspathCommand(bufnr('')) diff --git a/test/test_gradle_find_executable.vader b/test/test_gradle_find_executable.vader new file mode 100644 index 00000000..f874748c --- /dev/null +++ b/test/test_gradle_find_executable.vader @@ -0,0 +1,37 @@ +Before: + Save $PATH + Save $PATHEXT + + " Count the gradle executable without .exe as executable on Windows + let $PATHEXT = '.' + + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/kotlin/kotlinc.vim + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should return 'gradlew' if found in parent directory): + call ale#test#SetFilename('test-files/gradle/wrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/gradle/wrapped-project/gradlew'), + \ ale#gradle#FindExecutable(bufnr('')) + +Execute(Should return 'gradle' if 'gradlew' not found in parent directory): + call ale#test#SetFilename('test-files/gradle/unwrapped-project/src/main/kotlin/dummy.kt') + let $PATH .= (has('win32') ? ';': ':') . ale#path#Simplify(g:dir . '/test-files/gradle') + + AssertEqual + \ 'gradle', + \ ale#gradle#FindExecutable(bufnr('')) + +Execute(Should return empty string if 'gradlew' not in parent directory and gradle not in path): + call ale#test#SetFilename('test-files/gradle/unwrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ '', + \ ale#gradle#FindExecutable(bufnr('')) diff --git a/test/test_gradle_find_project_root.vader b/test/test_gradle_find_project_root.vader new file mode 100644 index 00000000..b6159188 --- /dev/null +++ b/test/test_gradle_find_project_root.vader @@ -0,0 +1,35 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/kotlin/kotlinc.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should return directory for 'gradlew' if found in parent directory): + call ale#test#SetFilename('test-files/gradle/wrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/gradle/wrapped-project'), + \ ale#gradle#FindProjectRoot(bufnr('')) + +Execute(Should return directory for 'settings.gradle' if found in parent directory): + call ale#test#SetFilename('test-files/gradle/settings-gradle-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/gradle/settings-gradle-project'), + \ ale#gradle#FindProjectRoot(bufnr('')) + +Execute(Should return directory for 'build.gradle' if found in parent directory): + call ale#test#SetFilename('test-files/gradle/build-gradle-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/gradle/build-gradle-project'), + \ ale#gradle#FindProjectRoot(bufnr('')) + +Execute(Should return empty string if gradle files are not found in parent directory): + call ale#test#SetFilename('test-files/gradle/non-gradle-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ '', + \ ale#gradle#FindProjectRoot(bufnr('')) diff --git a/test/test_helptags.vader b/test/test_helptags.vader new file mode 100644 index 00000000..8c9c5469 --- /dev/null +++ b/test/test_helptags.vader @@ -0,0 +1,2 @@ +Execute (helptags should run without issue): + helptags ALL diff --git a/test/test_highlight_placement.vader b/test/test_highlight_placement.vader new file mode 100644 index 00000000..e8b7ac27 --- /dev/null +++ b/test/test_highlight_placement.vader @@ -0,0 +1,465 @@ +Before: + Save g:ale_buffer_info + Save g:ale_echo_cursor + Save g:ale_enabled + 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 + Save g:ale_exclude_highlights + Save b:ale_exclude_highlights + + runtime autoload/ale/virtualtext.vim + runtime autoload/ale/highlight.vim + + 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 = {} + + " Disable features we don't need for these tests. + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 + let g:ale_echo_cursor = 0 + let g:ale_exclude_highlights = [] + let b:ale_exclude_highlights = [] + + function! GenerateResults(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'foo', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'bar', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'wat', + \ }, + \] + endfunction + + let g:has_nvim_highlight = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') + let g:nvim_highlight_matches = {} + + function! ale#highlight#nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end) abort + if a:line_end != -1 + throw 'nvim api behavior not supported' + endif + + let l:matches = get(g:nvim_highlight_matches, a:buffer, []) + call filter( + \ l:matches, + \ {_, val -> val.pos1[0] < (a:line_start + 1) }, + \) + endfunction + + function! ale#highlight#nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) abort + if a:col_end == -1 + throw 'nvim api behavior not supported' + endif + + let l:matches = get(g:nvim_highlight_matches, a:buffer, []) + let g:nvim_highlight_matches[a:buffer] = l:matches + + let l:new_match = { + \ 'group': a:hl_group, + \ 'priority': 10, + \ 'pos1': [a:line + 1, a:col_start + 1, a:col_end - a:col_start], + \} + + call add(l:matches, l:new_match) + " sort by line number to emulate getmatches faithfully + call sort(l:matches, {m1, m2 -> m1.pos1[0] - m2.pos1[0]}) + endfunction + + " We don't care what the IDs are, just that we have some matches. + " The IDs are generated. + function! GetMatchesWithoutIDs() abort + if g:has_nvim_highlight + return get(g:nvim_highlight_matches, bufnr(''), []) + else + let l:list = getmatches() + + for l:item in l:list + call remove(l:item, 'id') + endfor + + return l:list + endif + endfunction + + function! GetLinkedGroup(grp) abort + return synIDattr(synIDtrans(hlID(a:grp)), 'name') + endfunction + + call ale#linter#Define('testft', { + \ 'name': 'x', + \ 'executable': has('win32') ? 'cmd': 'echo', + \ 'command': has('win32') ? 'echo' : '/bin/sh -c ''echo''', + \ 'callback': 'GenerateResults', + \}) + highlight link SomeOtherGroup SpellBad + +After: + Restore + + unlet! g:ale_run_synchronously_callbacks + unlet! g:items + unlet! b:ale_enabled + unlet! g:has_nvim_highlight + unlet! g:nvim_highlight_matches + + delfunction GenerateResults + call ale#linter#Reset() + call clearmatches() + call ale#sign#Clear() + if has('textprop') && has('popupwin') + call prop_type_delete('ale') + endif + highlight clear SomeOtherGroup + + runtime autoload/ale/highlight.vim + +Given testft(A Javscript file with warnings/errors): + foo + bar + baz wat + line four + +" Autoloading virtualtext.vim first should still properly initialize hl-groups +Execute(Loading virtualtext first does not break highlight groups): + AssertEqual + \ "SpellBad", + \ GetLinkedGroup("ALEError") + AssertEqual + \ "SpellCap", + \ GetLinkedGroup("ALEWarning") + +Execute(Highlights should be set when a linter runs): + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [2, 1, 1]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 5, 1]} + \ ], + \ GetMatchesWithoutIDs() + +" This test is important for preventing ALE from showing highlights for +" the wrong files. +Execute(Highlights set by ALE should be removed when buffer cleanup is done): + call ale#engine#InitBufferInfo(bufnr('%')) + + call ale#highlight#SetHighlights(bufnr('%'), [ + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 2}, + \]) + + if !g:has_nvim_highlight + " This check doesn't work with the new API, for some reason. + AssertEqual + \ [{'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}], + \ GetMatchesWithoutIDs() + endif + + call ale#engine#Cleanup(bufnr('%')) + + AssertEqual [], GetMatchesWithoutIDs() + +Execute(Highlights should be cleared when buffers are hidden): + call ale#engine#InitBufferInfo(bufnr('%')) + " The second item should be ignored, as it has no column infomration. + let g:ale_buffer_info[bufnr('%')].loclist = [ + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 2}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 4, 'col': 0}, + \] + call ale#highlight#SetHighlights( + \ bufnr('%'), + \ g:ale_buffer_info[bufnr('%')].loclist + \) + + AssertEqual 1, len(GetMatchesWithoutIDs()), 'The highlights weren''t initially set!' + + call ale#highlight#BufferHidden(bufnr('%')) + + AssertEqual 0, len(GetMatchesWithoutIDs()), 'The highlights weren''t cleared!' + + call ale#highlight#UpdateHighlights() + + AssertEqual 1, len(GetMatchesWithoutIDs()), 'The highlights weren''t set again!' + +Execute(Only ALE highlights should be restored when buffers are restored): + call ale#engine#InitBufferInfo(bufnr('%')) + let g:ale_buffer_info[bufnr('%')].loclist = [ + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 2}, + \] + call ale#highlight#SetHighlights( + \ bufnr('%'), + \ g:ale_buffer_info[bufnr('%')].loclist + \) + + call matchaddpos('SomeOtherGroup', [[1, 1, 1]]) + + " We should have both highlights. + if g:has_nvim_highlight + " When the newer NeoVim API is used, we don't have to worry about + " other highlights, namespacing is available. + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ ], + \ GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ ], + \ sort(GetMatchesWithoutIDs(), {m1, m2 -> m1.group < m2.group ? -1 : 1}) + endif + + call ale#highlight#BufferHidden(bufnr('%')) + + " We should remove our highlight, but not the other one. + if g:has_nvim_highlight + AssertEqual [], GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]} + \ ], + \ GetMatchesWithoutIDs() + endif + + call ale#highlight#UpdateHighlights() + + " Our highlight should apper again. + if g:has_nvim_highlight + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ ], + \ GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ ], + \ sort(GetMatchesWithoutIDs(), {m1, m2 -> m1.group < m2.group ? -1 : 1}) + endif + +Execute(Highlight end columns should set an appropriate size): + call ale#highlight#SetHighlights(bufnr('%'), [ + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 2, 'end_col': 5}, + \ {'bufnr': bufnr('%'), 'type': 'W', 'lnum': 4, 'col': 1, 'end_col': 5}, + \]) + + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 4]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [4, 1, 5]}, + \ ], + \ GetMatchesWithoutIDs() + +Execute(Highlight end columns should set an appropriate size): + call ale#highlight#SetHighlights(bufnr('%'), [ + \ {'bufnr': bufnr('%') - 1, 'type': 'E', 'lnum': 1, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 1, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 2, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'sub_type': 'style', 'lnum': 3, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'W', 'lnum': 4, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'W', 'lnum': 5, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'W', 'sub_type': 'style', 'lnum': 6, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'I', 'lnum': 7, 'col': 1}, + \ {'bufnr': bufnr('%') + 1, 'type': 'E', 'lnum': 1, 'col': 1}, + \]) + + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [2, 1, 1]}, + \ {'group': 'ALEStyleError', 'priority': 10, 'pos1': [3, 1, 1]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [4, 1, 1]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [5, 1, 1]}, + \ {'group': 'ALEStyleWarning', 'priority': 10, 'pos1': [6, 1, 1]}, + \ {'group': 'ALEInfo', 'priority': 10, 'pos1': [7, 1, 1]}, + \ ], + \ GetMatchesWithoutIDs() + +Execute(Highlighting should support errors spanning many lines): + let g:items = [ + \ {'bufnr': bufnr(''), 'type': 'E', 'lnum': 1, 'col': 1, 'end_lnum': 10, 'end_col': 3}, + \] + + call ale#highlight#SetHighlights(bufnr(''), g:items) + + if g:has_nvim_highlight + " The newer NeoVim highlight API produces different output. + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [2, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [4, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [5, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [6, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [7, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [8, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [9, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [10, 1, 3]}, + \ ], + \ GetMatchesWithoutIDs() + else + " We should set 2 highlights for the item, as we can only add 8 at a time. + AssertEqual + \ [ + \ { + \ 'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1073741824], + \ 'pos2': [2], 'pos3': [3], 'pos4': [4], 'pos5': [5], 'pos6': [6], + \ 'pos7': [7], 'pos8': [8], + \ }, + \ { + \ 'group': 'ALEError', 'priority': 10, + \ 'pos1': [9], 'pos2': [10, 1, 3] + \ }, + \ ], + \ GetMatchesWithoutIDs() + endif + +Execute(Highlights should always be cleared when the buffer highlight list is empty): + if g:has_nvim_highlight + " The newer API uses namespacing. We'll emulate it here. + call ale#highlight#nvim_buf_add_highlight( + \ bufnr(''), + \ 1, + \ 'ALEError', + \ 0, + \ 0, + \ 1, + \) + + AssertEqual + \ [{'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}], + \ GetMatchesWithoutIDs() + else + " Add our highlights and something else. + call matchaddpos('ALEError', [[1, 1, 1]]) + call matchaddpos('SomeOtherGroup', [[1, 1, 1]]) + + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ ], + \ GetMatchesWithoutIDs() + endif + + + " Set the List we use for holding highlights for buffers. + let b:ale_highlight_items = [] + + " Call the function for updating the highlights called when buffers + " are entered, or when problems are presented. + call ale#highlight#UpdateHighlights() + + " Check that we remove our highlights. + if g:has_nvim_highlight + AssertEqual [], GetMatchesWithoutIDs() + else + AssertEqual + \ [{'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}], + \ GetMatchesWithoutIDs() + endif + +Execute(Highlights should be hidden when excluded): + let b:ale_exclude_highlights = ['ig.*ore', 'nope'] + + call ale#highlight#SetHighlights(bufnr('%'), [ + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 1, 'col': 1, 'text': 'hello'}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 2, 'col': 1, 'text': 'ignore'}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 1, 'text': 'nope'}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 4, 'col': 1, 'text': 'world'}, + \]) + + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [4, 1, 1]}, + \ ], + \ GetMatchesWithoutIDs() + +Execute(Highlights should be cleared when ALE is disabled): + let g:ale_enabled = 1 + call ale#highlight#SetHighlights(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E', 'lnum': 1, 'col': 1, 'end_lnum': 10, 'end_col': 3}, + \]) + + let g:ale_enabled = 0 + call ale#highlight#UpdateHighlights() + + AssertEqual [], GetMatchesWithoutIDs() + + let g:ale_enabled = 1 + call ale#highlight#SetHighlights(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E', 'lnum': 1, 'col': 1, 'end_lnum': 10, 'end_col': 3}, + \]) + + let b:ale_enabled = 0 + call ale#highlight#UpdateHighlights() + + AssertEqual [], GetMatchesWithoutIDs() + +Execute(Line highlights should be set when signs are disabled): + " This will mess with your settings, but it needs to be tested. + " We need to match highlights case-insensitively when removing them. + hi link aleerrorline spellbad + + let g:ale_set_signs = 0 + + call ale#highlight#SetHighlights(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E', 'lnum': 1, 'col': 1}, + \ {'bufnr': bufnr(''), 'type': 'W', 'lnum': 2, 'col': 1}, + \ {'bufnr': bufnr(''), 'type': 'I', 'lnum': 3, 'col': 1}, + \]) + + if g:has_nvim_highlight + " The output is different with the newer NeoVIM highlight API. + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEErrorLine', 'priority': 10, 'pos1': [1, 1, 1073741824]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [2, 1, 1]}, + \ {'group': 'ALEWarningLine', 'priority': 10, 'pos1': [2, 1, 1073741824]}, + \ {'group': 'ALEInfo', 'priority': 10, 'pos1': [3, 1, 1]}, + \ {'group': 'ALEInfoLine', 'priority': 10, 'pos1': [3, 1, 1073741824]} + \ ], + \ GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [2, 1, 1]}, + \ {'group': 'ALEInfo', 'priority': 10, 'pos1': [3, 1, 1]}, + \ {'group': 'aleerrorline', 'priority': 10, 'pos1': [1]}, + \ {'group': 'ALEWarningLine', 'priority': 10, 'pos1': [2]}, + \ {'group': 'ALEInfoLine', 'priority': 10, 'pos1': [3]}, + \ ], + \ GetMatchesWithoutIDs() + endif + + " All of the highlights should be removed. + call ale#highlight#RemoveHighlights() + AssertEqual [], GetMatchesWithoutIDs() diff --git a/test/test_highlight_position_chunking.vader b/test/test_highlight_position_chunking.vader new file mode 100644 index 00000000..cd9161b5 --- /dev/null +++ b/test/test_highlight_position_chunking.vader @@ -0,0 +1,76 @@ +Execute(CreatePositions() should support single character matches): + AssertEqual [[[1, 5, 1]]], ale#highlight#CreatePositions(1, 5, 1, 5) + " When the end column is behind the start column, ignore it. + AssertEqual [[[2, 5, 1]]], ale#highlight#CreatePositions(2, 5, 1, 5) + +Execute(CreatePositions() should support multiple character matches on a single line): + AssertEqual [[[1, 5, 6]]], ale#highlight#CreatePositions(1, 5, 1, 10) + " When the end column is behind the start column, ignore it. + AssertEqual [[[2, 5, 6]]], ale#highlight#CreatePositions(2, 5, 1, 10) + +Execute(CreatePositions() should support character matches two lines): + AssertEqual [[[1, 5, 1073741824], [2, 1, 10]]], ale#highlight#CreatePositions(1, 5, 2, 10) + +Execute(CreatePositions() should support character matches across many lines): + " Test chunks from 1,3 to 1,17 + AssertEqual [ + \ [[1, 5, 1073741824], 2, [3, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 3, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, [4, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 4, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, [5, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 5, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, [6, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 6, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, [7, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 7, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, [8, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 8, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [[9, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 9, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, [10, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 10, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, 10, [11, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 11, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, 10, 11, [12, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 12, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, 10, 11, 12, [13, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 13, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, 10, 11, 12, 13, [14, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 14, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, 10, 11, 12, 13, 14, [15, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 15, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, 10, 11, 12, 13, 14, 15, [16, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 16, 10) + AssertEqual [ + \ [[1, 5, 1073741824], 2, 3, 4, 5, 6, 7, 8], + \ [9, 10, 11, 12, 13, 14, 15, 16], + \ [[17, 1, 10]], + \], ale#highlight#CreatePositions(1, 5, 17, 10) + " Test another random sample at higher lines. + AssertEqual [ + \ [[21, 8, 1073741824], 22, 23, 24, 25, 26, 27, 28], + \ [29, 30, 31, 32, 33, 34, 35, 36], + \ [[37, 1, 2]], + \], ale#highlight#CreatePositions(21, 8, 37, 2) diff --git a/test/test_history_saving.vader b/test/test_history_saving.vader new file mode 100644 index 00000000..5d81c2a3 --- /dev/null +++ b/test/test_history_saving.vader @@ -0,0 +1,177 @@ +Before: + Save g:ale_max_buffer_history_size + Save g:ale_history_enabled + 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 + unlet! b:ale_history + + " Temporarily set the shell to /bin/sh, if it isn't already set that way. + " This will make it so the test works when running it directly. + let g:current_shell = &shell + + if !has('win32') + let &shell = '/bin/sh' + endif + + let g:history = [] + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + let g:ale_history_log_output = 0 + + function! TestFixer(buffer) + return {'command': 'echo foo'} + endfunction + + function! CollectResults(buffer, output) + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'CollectResults', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') + \ ? 'echo command history test' + \ : '/bin/sh -c ''echo command history test''', + \ 'read_buffer': 0, + \}) + +After: + Restore + + unlet! g:expected_results + unlet! b:ale_fixers + unlet! b:ale_enabled + " Clear the history we changed. + unlet! b:ale_history + + " Reset the shell back to what it was before. + let &shell = g:current_shell + unlet g:current_shell + unlet g:history + + call ale#engine#Cleanup(bufnr('')) + call ale#linter#Reset() + + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + delfunction TestFixer + delfunction CollectResults + +Given foobar (Some imaginary filetype): + anything + +Execute(History should be set when commands are run): + AssertEqual 'foobar', &filetype + + let b:ale_history = [] + ALELint + call ale#test#FlushJobs() + + let g:history = filter( + \ copy(ale#history#Get(bufnr(''))), + \ 'v:val.job_id isnot# ''executable''', + \) + + 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 + else + AssertEqual ['/bin/sh', '-c', '/bin/sh -c ''echo command history test'''], g:history[0].command + endif + + AssertEqual 'finished', g:history[0].status + AssertEqual 0, g:history[0].exit_code + " The Job ID will change each time, but we can check the type. + AssertEqual type(1), type(g:history[0].job_id) + +Execute(History should be not set when disabled): + AssertEqual 'foobar', &filetype + + let g:ale_history_enabled = 0 + + ALELint + call ale#test#FlushJobs() + + AssertEqual [], ale#history#Get(bufnr('')) + +Execute(History should include command output if logging is enabled): + AssertEqual 'foobar', &filetype + + let g:ale_history_log_output = 1 + + " Retry this test until it works. This one can randomly fail. + let b:ale_history = [] + ALELint + call ale#test#FlushJobs() + + let g:history = ale#history#Get(bufnr('')) + + 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''}') + + call ale#history#Add(bufnr(''), 'started', 347, 'last command') + + AssertEqual + \ ( + \ map(range(1, 19), '{''status'': ''started'', ''job_id'': v:val, ''command'': ''foobar''}') + \ + [{'status': 'started', 'job_id': 347, 'command': 'last command'}] + \ ), + \ ale#history#Get(bufnr('')) + +Execute(Nothing should be added to history if the size is too low): + let g:ale_max_buffer_history_size = 0 + + call ale#history#Add(bufnr(''), 'started', 347, 'last command') + + AssertEqual [], ale#history#Get(bufnr('')) + + let g:ale_max_buffer_history_size = -2 + + call ale#history#Add(1, 'started', 347, 'last command') + + AssertEqual [], ale#history#Get(bufnr('')) + +Given foobar(Some file with an imaginary filetype): + a + b + c + +Execute(The history should be updated when fixers are run): + call ale#test#SetFilename('dummy.txt') + + let b:ale_fixers = {'foobar': ['TestFixer']} + let b:ale_enabled = 0 + + 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') + AssertEqual 'cmd /s/c "echo foo ', split(b:ale_history[0].command, '<')[0] + else + AssertEqual '/bin/sh -c echo foo ', split(join(b:ale_history[0].command), '<')[0] + endif diff --git a/test/test_hover.vader b/test/test_hover.vader new file mode 100644 index 00000000..db302585 --- /dev/null +++ b/test/test_hover.vader @@ -0,0 +1,273 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:Callback = 0 + let g:message_list = [] + let g:item_list = [] + let g:show_message_arg_list = [] + + let g:ale_floating_preview = 0 + let g:ale_hover_to_floating_preview = 0 + let g:ale_detail_to_floating_preview = 0 + + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/util.vim + runtime autoload/ale/floating_preview.vim + runtime autoload/ale/hover.vim + + let g:floated_lines = [] + let g:floating_preview_show_called = 0 + + " Stub out so we can track the call + function! ale#floating_preview#Show(lines, ...) abort + let g:floating_preview_show_called = 1 + let g:floated_lines = a:lines + return win_getid() + endfunction + + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort + let g:Callback = a:callback + + return { + \ 'command': 'foobar', + \ 'connection_id': 347, + \ 'project_root': '/foo/bar', + \} + endfunction + + function! ale#lsp#Send(conn_id, message, root) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#ShowMessage(string, ...) abort + call add(g:show_message_arg_list, [a:string] + a:000) + endfunction + + function! HandleValidLSPResult(result) abort + " The cursor is beyond the length of the line. + " We will clamp the cursor position with the line length. + call setpos('.', [bufnr(''), 1, 5, 0]) + + call ale#hover#SetMap({3: { + \ 'buffer': bufnr(''), + \ 'line': 1, + \ 'column': 5, + \}}) + call ale#hover#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': a:result, + \ } + \) + endfunction + + +After: + call ale#hover#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:Callback + unlet! g:message_list + unlet! b:ale_linters + unlet! g:show_message_arg_list + + delfunction HandleValidLSPResult + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/floating_preview.vim + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Other messages for the tsserver handler should be ignored): + call ale#hover#HandleTSServerResponse(1, {'command': 'foo'}) + +Execute(Failed hover responses should be handled correctly): + call ale#hover#SetMap({3: {}}) + call ale#hover#HandleTSServerResponse( + \ 1, + \ {'command': 'quickinfo', 'request_seq': 3} + \) + AssertEqual {}, ale#hover#GetMap() + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(tsserver quickinfo responses will null missing bodies should be handled): + call ale#hover#SetMap({3: {}}) + call ale#hover#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'quickinfo', + \ 'request_seq': 3, + \ 'success': v:true, + \ } + \) + + AssertEqual {}, ale#hover#GetMap() + +Execute(tsserver quickinfo displayString values should be displayed): + call ale#hover#SetMap({3: {'buffer': bufnr('')}}) + call ale#hover#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'quickinfo', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': {'displayString': 'foo bar'}, + \ } + \) + + AssertEqual [['foo bar']], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover responses with just a string should be handled): + call HandleValidLSPResult({'contents': 'foobar'}) + + AssertEqual [['foobar', {'commands': []}]], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover null responses should be handled): + call HandleValidLSPResult(v:null) + + AssertEqual [], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover responses with markup content should be handled): + call HandleValidLSPResult({'contents': {'kind': 'markdown', 'value': 'markup'}}) + + AssertEqual [['markup', {'commands': []}]], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover responses with markup content missing values should be handled): + call HandleValidLSPResult({'contents': {'kind': 'markdown'}}) + + AssertEqual [], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover response with lists of strings should be handled): + call HandleValidLSPResult({'contents': [ + \ "foo\n", + \ "bar\n", + \]}) + + AssertEqual [["foo\n\nbar", {'commands': []}]], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover response with lists of strings and marked strings should be handled): + call HandleValidLSPResult({'contents': [ + \ {'language': 'python', 'value': 'foo'}, + \ "bar\n", + \]}) + + AssertEqual [ + \ [ + \ "foo\n\nbar", + \ { + \ 'commands': [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%2l/ contains=@ALE_hover_python', + \ ], + \ }, + \ ], + \], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover with ale_floating_preview should float): + let g:ale_floating_preview = 1 + + call HandleValidLSPResult({'contents': "the message\ncontinuing"}) + + AssertEqual 1, g:floating_preview_show_called + AssertEqual ["the message", "continuing"], g:floated_lines + +Execute(LSP hover ale_hover_to_floating_preview should float): + let g:ale_hover_to_floating_preview = 1 + + call HandleValidLSPResult({'contents': "the message\ncontinuing"}) + + AssertEqual 1, g:floating_preview_show_called + AssertEqual ["the message", "continuing"], g:floated_lines + + +Execute(LSP hover by default should not float): + call HandleValidLSPResult({'contents': "the message\ncontinuing"}) + + AssertEqual 0, g:floating_preview_show_called + +Execute(tsserver responses for documentation requests should be handled): + call ale#hover#SetMap({3: {'show_documentation': 1, 'buffer': bufnr('')}}) + + 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 + +Execute(hover with show_documentation should be in the preview window, not floating): + let g:ale_hover_to_floating_preview = 1 + let g:ale_floating_preview = 1 + + call ale#hover#SetMap({3: {'show_documentation': 1, 'buffer': bufnr('')}}) + + call ale#hover#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'quickinfo', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'documentation': 'foo is a very good method', + \ 'displayString': 'foo bar ', + \ }, + \ } + \) + + let expected = ["Every statement should end with a semicolon", "second line"] + + AssertEqual 0, g:floating_preview_show_called + +Execute(TSServer hover without show_documentation and ale_floating_preview should float): + let g:ale_floating_preview = 1 + + call ale#hover#SetMap({3: {'buffer': bufnr('')}}) + + call ale#hover#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'quickinfo', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'displayString': "the message\ncontinuing", + \ }, + \ } + \) + + AssertEqual 1, g:floating_preview_show_called + AssertEqual ["the message", "continuing"], g:floated_lines diff --git a/test/test_hover_parsing.vader b/test/test_hover_parsing.vader new file mode 100644 index 00000000..b9e56de8 --- /dev/null +++ b/test/test_hover_parsing.vader @@ -0,0 +1,228 @@ +Execute(Invalid results should be handled): + AssertEqual [[], []], ale#hover#ParseLSPResult(0) + AssertEqual [[], []], ale#hover#ParseLSPResult([0]) + AssertEqual [[], []], ale#hover#ParseLSPResult('') + AssertEqual [[], []], ale#hover#ParseLSPResult({}) + AssertEqual [[], []], ale#hover#ParseLSPResult([{}]) + AssertEqual [[], []], ale#hover#ParseLSPResult(['']) + AssertEqual [[], []], ale#hover#ParseLSPResult({'value': ''}) + AssertEqual [[], []], ale#hover#ParseLSPResult([{'value': ''}]) + AssertEqual [[], []], ale#hover#ParseLSPResult({'kind': 'markdown'}) + AssertEqual [[], []], ale#hover#ParseLSPResult({'kind': 'plaintext'}) + AssertEqual [[], []], ale#hover#ParseLSPResult({'kind': 'x', 'value': 'xxx'}) + +Execute(A string with a code fence should be handled): + AssertEqual + \ [ + \ [ + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```', + \ 'def foo():', + \ ' pass', + \ '```', + \ ], "\n")) + + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ ], "\n")) + + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_javascript syntax/javascript.vim', + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_2 start=/\%5l/ end=/\%8l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_3 start=/\%8l/ end=/\%10l/ contains=@ALE_hover_javascript', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ 'middle line', + \ '', + \ 'def bar():', + \ ' pass', + \ '', + \ 'const baz = () => undefined', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ 'middle line', + \ '```python', + \ 'def bar():', + \ ' pass', + \ '```', + \ '```javascript', + \ 'const baz = () => undefined', + \ '```', + \ ], "\n")) + +Execute(Multiple strings with fences should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_javascript syntax/javascript.vim', + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_2 start=/\%5l/ end=/\%8l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_3 start=/\%8l/ end=/\%10l/ contains=@ALE_hover_javascript', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ 'middle line', + \ '', + \ 'def bar():', + \ ' pass', + \ '', + \ 'const baz = () => undefined', + \ ], + \ ], + \ ale#hover#ParseLSPResult([ + \ join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ ], "\n"), + \ join([ + \ 'middle line', + \ '```python', + \ 'def bar():', + \ ' pass', + \ '```', + \ '```javascript', + \ 'const baz = () => undefined', + \ '```', + \ ], "\n"), + \ ]) + +Execute(Objects with kinds should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ '```javascript', + \ 'const baz = () => undefined', + \ '```', + \ ], + \ ], + \ ale#hover#ParseLSPResult([ + \ { + \ 'kind': 'markdown', + \ 'value': join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ ], "\n"), + \ }, + \ { + \ 'kind': 'plaintext', + \ 'value': join([ + \ '```javascript', + \ 'const baz = () => undefined', + \ '```', + \ ], "\n"), + \ }, + \ ]) + +Execute(Simple markdown formatting should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ 'formatted _ line _', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ 'formatted \_ line \_', + \ ], "\n")) + +Execute(Fences padded with spaces should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ 'formatted _ line _', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '``` python ', + \ 'def foo():', + \ ' pass', + \ '```', + \ 'formatted \_ line \_', + \ ], "\n")) + +Execute(Non-existent syntax files shouldn't be loaded): + AssertEqual + \ [ + \ [ + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%2l/ contains=@ALE_hover_text', + \ ], + \ [ + \ 'hello', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```text', + \ 'hello', + \ '```', + \ ], "\n")) diff --git a/test/test_ignoring_linters.vader b/test/test_ignoring_linters.vader new file mode 100644 index 00000000..c84a8943 --- /dev/null +++ b/test/test_ignoring_linters.vader @@ -0,0 +1,613 @@ +Before: + Save g:lspconfig + Save g:ale_linters_ignore + Save g:ale_buffer_info + Save g:ale_disable_lsp + + let g:lspconfig = 0 + let g:lspconfig_names = {} + let g:ale_disable_lsp = 0 + let g:linters = [] + let g:loclist = [] + let g:run_linters_called = 0 + + runtime autoload/ale/engine.vim + runtime autoload/ale/engine/ignore.vim + + " Mock the engine function so we can set it up. + function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort + let g:linters = a:linters + let g:run_linters_called = 1 + endfunction + + function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort + let g:loclist = a:loclist + endfunction + + function! ale#engine#ignore#GetLSPConfigNames() abort + return g:lspconfig_names + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': has('win32') ? 'echo' : 'true', + \}) + call ale#test#SetDirectory('/testplugin/test') + +After: + unlet! b:ale_linted + 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 + unlet! g:lspconfig_names + + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + call ale#lsp_linter#ClearLSPData() + + runtime autoload/ale/engine.vim + runtime autoload/ale/engine/ignore.vim + +Execute(GetList should ignore some invalid values): + AssertEqual [], ale#engine#ignore#GetList('', 'foo') + AssertEqual [], ale#engine#ignore#GetList('', 0) + AssertEqual [], ale#engine#ignore#GetList('', v:null) + +Execute(GetList should handle Lists): + AssertEqual ['foo', 'bar'], ale#engine#ignore#GetList('', ['foo', 'bar']) + +Execute(GetList should handle Dictionaries): + AssertEqual + \ ['linter1', 'linter2'], + \ uniq(sort(ale#engine#ignore#GetList('x.y.z', { + \ 'x': ['linter1'], + \ 'abc': ['linter3'], + \ 'z': ['linter2'], + \ }))) + +Execute(Exclude should ignore some invalid values): + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ 'foo', + \ 0, + \ ) + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ 0, + \ 0, + \ ) + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ v:null, + \ 0, + \ ) + +Execute(Exclude should handle Lists): + AssertEqual + \ [ + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ['linter1', 'alias1'], + \ 0, + \ ) + +Execute(Exclude should handle Dictionaries): + AssertEqual + \ [ + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ {'foo': ['linter1'], 'bar': ['alias1']}, + \ 0, + \ ) + +Execute(Exclude should filter LSP linters when ale_disable_lsp is set to 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 remove lspconfig linters with ale_disable_lsp = 'auto'): + let g:lspconfig = 1 + " A map is used here so you can easily see what the ignore mapping should be + " remapping the nvim-lspconfig names to. + let g:lspconfig_names = keys({ + \ 'als': 'adals', + \ 'ansiblels': 'ansible-language-server', + \ 'bicep': 'bicep_language_server', + \ 'cmake': 'cmake_language_server', + \ 'denols': 'deno', + \ 'erlangls': 'erlang_ls', + \ 'html': 'vscodehtml', + \ 'ocamlls': 'ocaml-language-server', + \ 'puppet': 'puppet_languageserver', + \ 'pyright': 'pyright', + \}) + + " We should keep bicep, as it's different tool. + AssertEqual + \ [ + \ {'name': 'bicep', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo', + \ [ + \ {'name': 'adals', 'aliases': []}, + \ {'name': 'language_server', 'aliases': ['ansible-language-server']}, + \ {'name': 'cmake_language_server', 'aliases': []}, + \ {'name': 'deno', 'aliases': []}, + \ {'name': 'erlang_ls', 'aliases': []}, + \ {'name': 'vscodehtml', 'aliases': []}, + \ {'name': 'bicep', 'aliases': []}, + \ {'name': 'ols', 'aliases': ['ocaml-language-server']}, + \ {'name': 'languageserver', 'aliases': ['puppet_languageserver']}, + \ {'name': 'pyright', 'aliases': []}, + \ ], + \ [], + \ 'auto', + \ ) + +Execute(Exclude should check that the nvim-lspconfig plugin is installed with ale_disable_lsp = 'auto'): + let g:lspconfig = 0 + let g:lspconfig_names = ['pyright'] + + " We should keep pyright here, because g:lspconfig is 0. + AssertEqual + \ [ + \ {'name': 'pyright', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo', + \ [ + \ {'name': 'pyright', 'aliases': []}, + \ ], + \ [], + \ 'auto', + \ ) + +Execute(Exclude should handle the lspconfig result being a Dictionary): + let g:lspconfig = 1 + let g:lspconfig_names = {} + + " We should keep pyright here, because the configuration is empty. + AssertEqual + \ [ + \ {'name': 'pyright', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo', + \ [ + \ {'name': 'pyright', 'aliases': []}, + \ ], + \ [], + \ 'auto', + \ ) + +Given foobar(An empty file): +Execute(Global ignore lists should be applied for linters): + " We have to set up buffer info so RunLinters is called. + let g:ale_buffer_info = {bufnr(''): {}} + + ALELint + Assert g:run_linters_called, "The mock callback wasn't called" + AssertEqual ['testlinter'], map(g:linters, 'v:val.name') + + let g:ale_linters_ignore = ['testlinter'] + ALELint + AssertEqual [], g:linters + +Execute(buffer ignore lists should be applied for linters): + " We have to set up buffer info so RunLinters is called. + let g:ale_buffer_info = {bufnr(''): {}} + + ALELint + Assert g:run_linters_called, "The mock callback wasn't called" + AssertEqual ['testlinter'], map(g:linters, 'v:val.name') + + let b:ale_linters_ignore = ['testlinter'] + ALELint + AssertEqual [], g:linters + +Execute(Buffer ignore lists should be applied for tsserver): + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:lsp_message = { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': ''','' expected.', + \ "code":1005 + \ }, + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 14, + \ 'nr': 1005, + \ 'code': '1005', + \ 'type': 'E', + \ 'end_col': 14, + \ 'end_lnum': 2, + \ 'text': ''','' expected.', + \ }, + \ ], + \ g:loclist + + let g:loclist = [] + let b:ale_linters_ignore = ['tsserver'] + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(Buffer ignore lists should be applied for LSP linters): + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'347': {'name': 'lsplinter', 'aliases': [], 'lsp': 'stdio'}}) + + let g:lsp_message = { + \ 'jsonrpc': '2.0', + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'diagnostics': [ + \ { + \ 'severity': 1, + \ 'message': 'x', + \ 'range': { + \ 'start': {'line': 0, 'character': 9}, + \ 'end': {'line': 0, 'character': 9}, + \ }, + \ } + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'type': 'E', + \ 'end_col': 9, + \ 'end_lnum': 1, + \ 'text': 'x', + \ } + \ ], + \ g:loclist + + let b:ale_linters_ignore = ['lsplinter'] + let g:loclist = [] + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(ale_disable_lsp should be applied for tsserver): + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:lsp_message = { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': ''','' expected.', + \ "code":1005 + \ }, + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 14, + \ 'nr': 1005, + \ 'code': '1005', + \ 'type': 'E', + \ 'end_col': 14, + \ 'end_lnum': 2, + \ 'text': ''','' expected.', + \ }, + \ ], + \ g:loclist + + let g:loclist = [] + let b:ale_disable_lsp = 1 + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(ale_disable_lsp = 'auto' should be applied for tsserver): + let g:lspconfig = 1 + let g:lspconfig_names = ['tsserver'] + + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:lsp_message = { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': ''','' expected.', + \ "code":1005 + \ }, + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 14, + \ 'nr': 1005, + \ 'code': '1005', + \ 'type': 'E', + \ 'end_col': 14, + \ 'end_lnum': 2, + \ 'text': ''','' expected.', + \ }, + \ ], + \ g:loclist + + let g:loclist = [] + let g:ale_disable_lsp = 'auto' + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(ale_disable_lsp should be applied for LSP linters): + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'347': {'name': 'lsplinter', 'aliases': [], 'lsp': 'stdio'}}) + + let g:lsp_message = { + \ 'jsonrpc': '2.0', + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'diagnostics': [ + \ { + \ 'severity': 1, + \ 'message': 'x', + \ 'range': { + \ 'start': {'line': 0, 'character': 9}, + \ 'end': {'line': 0, 'character': 9}, + \ }, + \ } + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'type': 'E', + \ 'end_col': 9, + \ 'end_lnum': 1, + \ 'text': 'x', + \ } + \ ], + \ g:loclist + + let b:ale_disable_lsp = 1 + let g:loclist = [] + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(ale_disable_lsp = 'auto' should be applied for LSP linters): + let g:lspconfig = 1 + let g:lspconfig_names = ['lsplinter'] + + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'347': {'name': 'lsplinter', 'aliases': [], 'lsp': 'stdio'}}) + + let g:lsp_message = { + \ 'jsonrpc': '2.0', + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'diagnostics': [ + \ { + \ 'severity': 1, + \ 'message': 'x', + \ 'range': { + \ 'start': {'line': 0, 'character': 9}, + \ 'end': {'line': 0, 'character': 9}, + \ }, + \ } + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'type': 'E', + \ 'end_col': 9, + \ 'end_lnum': 1, + \ 'text': 'x', + \ } + \ ], + \ g:loclist + + let g:ale_disable_lsp = 'auto' + let g:loclist = [] + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(ale_disable_lsp = 'auto' should ignore LSP linters by alias too): + let g:lspconfig = 1 + let g:lspconfig_names = ['lsplinter'] + + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'347': {'name': 'notthis', 'aliases': ['lsplinter'], 'lsp': 'stdio'}}) + + let g:lsp_message = { + \ 'jsonrpc': '2.0', + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'diagnostics': [ + \ { + \ 'severity': 1, + \ 'message': 'x', + \ 'range': { + \ 'start': {'line': 0, 'character': 9}, + \ 'end': {'line': 0, 'character': 9}, + \ }, + \ } + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'type': 'E', + \ 'end_col': 9, + \ 'end_lnum': 1, + \ 'text': 'x', + \ } + \ ], + \ g:loclist + + let g:ale_disable_lsp = 'auto' + let g:loclist = [] + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist diff --git a/test/test_line_join.vader b/test/test_line_join.vader new file mode 100644 index 00000000..9356a2b7 --- /dev/null +++ b/test/test_line_join.vader @@ -0,0 +1,84 @@ +Before: + let g:lines = [] + let g:data = '' + + function! LineCallback(job_id, line) abort + call add(g:lines, a:line) + endfunction + + function! RawCallback(job_id, some_data) abort + let g:data .= a:some_data + endfunction + +After: + unlet! g:last_line + unlet! g:lines + unlet! g:data + delfunction LineCallback + delfunction RawCallback + +Execute (ALE should handle empty Lists for the lines): + let g:last_line = ale#util#JoinNeovimOutput(1, '', [], 'nl', function('LineCallback')) + + AssertEqual [], g:lines + AssertEqual '', g:last_line + +Execute (ALE should pass on full lines for NeoVim): + let g:last_line = ale#util#JoinNeovimOutput(1, '', ['x', 'y', ''], 'nl', function('LineCallback')) + + AssertEqual ['x', 'y'], g:lines + AssertEqual '', g:last_line + +Execute (ALE should pass on a single long line): + let g:last_line = ale#util#JoinNeovimOutput(1, '', ['x'], 'nl', function('LineCallback')) + + AssertEqual [], g:lines + AssertEqual 'x', g:last_line + +Execute (ALE should handle just a single line of output): + let g:last_line = ale#util#JoinNeovimOutput(1, '', ['x', ''], 'nl', function('LineCallback')) + + AssertEqual ['x'], g:lines + AssertEqual '', g:last_line + +Execute (ALE should join two incomplete pieces of large lines together): + let g:last_line = ale#util#JoinNeovimOutput(1, 'x', ['y'], 'nl', function('LineCallback')) + + AssertEqual [], g:lines + AssertEqual 'xy', g:last_line + +Execute (ALE join incomplete lines, and set new ones): + let g:last_line = ale#util#JoinNeovimOutput(1, 'x', ['y', 'z', 'a'], 'nl', function('LineCallback')) + + AssertEqual ['xy', 'z'], g:lines + AssertEqual 'a', g:last_line + +Execute (ALE join incomplete lines, and set new ones, with two elements): + let g:last_line = ale#util#JoinNeovimOutput(1, 'x', ['y', 'z'], 'nl', function('LineCallback')) + + AssertEqual ['xy'], g:lines + AssertEqual 'z', g:last_line + +Execute (ALE should pass on full lines for NeoVim for raw data): + let g:last_line = ale#util#JoinNeovimOutput(1, '', ['x', 'y', ''], 'raw', function('RawCallback')) + + AssertEqual "x\ny\n", g:data + AssertEqual '', g:last_line + +Execute (ALE should pass on a single long line): + let g:last_line = ale#util#JoinNeovimOutput(1, '', ['x'], 'raw', function('RawCallback')) + + AssertEqual 'x', g:data + AssertEqual '', g:last_line + +Execute (ALE should handle just a single line of output): + let g:last_line = ale#util#JoinNeovimOutput(1, '', ['x', ''], 'raw', function('RawCallback')) + + AssertEqual "x\n", g:data + AssertEqual '', g:last_line + +Execute (ALE should pass on two lines and one incomplete one): + let g:last_line = ale#util#JoinNeovimOutput(1, '', ['y', 'z', 'a'], 'raw', function('RawCallback')) + + AssertEqual "y\nz\na", g:data + AssertEqual '', g:last_line diff --git a/test/test_lint_file_linters.vader b/test/test_lint_file_linters.vader new file mode 100644 index 00000000..682e4130 --- /dev/null +++ b/test/test_lint_file_linters.vader @@ -0,0 +1,317 @@ +Before: + Save g:ale_fix_on_save + Save g:ale_enabled + Save g:ale_run_synchronously + Save g:ale_set_lists_synchronously + Save g:ale_buffer_info + Save g:ale_linters + + 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 + + let g:buffer_result = [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'buffer error', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'buffer warning', + \ 'type': 'W', + \ }, + \] + + function! LintFileCallback(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'file warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'file error', + \ 'type': 'E', + \ }, + \] + endfunction + + function! BufferCallback(buffer, output) + return deepcopy(g:buffer_result) + endfunction + + function! GetSimplerLoclist() + let l:loclist = [] + + for l:item in ale#test#GetLoclistWithoutNewerKeys() + call add(l:loclist, { + \ 'lnum': l:item.lnum, + \ 'col': l:item.col, + \ 'text': l:item.text, + \ 'type': l:item.type, + \}) + endfor + + return l:loclist + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'lint_file_linter', + \ 'callback': 'LintFileCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': 'echo', + \ 'lint_file': 1, + \}) + + call ale#linter#Define('foobar', { + \ 'name': 'buffer_linter', + \ 'callback': 'BufferCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': 'echo', + \ 'read_buffer': 0, + \}) + + let g:filename = tempname() + call writefile([], g:filename) + call ale#test#SetFilename(g:filename) + +After: + if !g:ale_run_synchronously + call ale#engine#Cleanup(bufnr('')) + endif + + Restore + + unlet! g:ale_run_synchronously_callbacks + unlet! b:ale_save_event_fired + unlet! b:ale_enabled + unlet g:buffer_result + let g:ale_buffer_info = {} + call ale#linter#Reset() + call setloclist(0, []) + delfunction LintFileCallback + delfunction BufferCallback + + if filereadable(g:filename) + call delete(g:filename) + endif + + unlet g:filename + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(Running linters without 'lint_file' should run only buffer linters): + call ale#Queue(0) + call ale#test#FlushJobs() + + AssertEqual [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'buffer error', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'buffer warning', + \ 'type': 'W', + \ }, + \], GetSimplerLoclist() + +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 [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'buffer error', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'file warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'buffer warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'file error', + \ 'type': 'E', + \ }, + \], GetSimplerLoclist() + +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 = [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'new buffer error', + \ 'type': 'E', + \ }, + \] + + call ale#Queue(0) + call ale#test#FlushJobs() + + AssertEqual [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'new buffer error', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'file warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'file error', + \ 'type': 'E', + \ }, + \], GetSimplerLoclist() + +Execute(Linter errors from files should be kept when no other linters are run): + let g:ale_linters = {'foobar': ['lint_file_linter']} + Assert filereadable(expand('%:p')), 'The file was not readable' + + call ale#Queue(0, 'lint_file') + call ale#test#FlushJobs() + + AssertEqual [ + \ { + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'file warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'file error', + \ 'type': 'E', + \ }, + \], GetSimplerLoclist() + + call ale#Queue(0) + + AssertEqual [ + \ { + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'file warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'file error', + \ 'type': 'E', + \ }, + \], GetSimplerLoclist() + +Execute(The Save event should respect the buffer number): + let g:ale_linters = {'foobar': ['lint_file_linter']} + 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 [ + \ { + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'file warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'file error', + \ 'type': 'E', + \ }, + \], GetSimplerLoclist() + +Execute(The Save event should set b:ale_save_event_fired to 1): + let g:ale_lint_on_save = 1 + let b:ale_enabled = 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 + +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 + +Execute(lint_file linters should stay running after checking without them): + let g:ale_run_synchronously = 0 + + " Run all linters, then just the buffer linters. + call ale#Queue(0, 'lint_file') + call ale#Queue(0) + + " The lint_file linter should still be running. + AssertEqual + \ ['lint_file_linter', 'buffer_linter'], + \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name') + " We should have 1 job for each linter. + AssertEqual + \ 2, + \ len(keys(get(get(ale#command#GetData(), bufnr(''), {}), 'jobs', {}))) + + 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 new file mode 100644 index 00000000..9d5e64e2 --- /dev/null +++ b/test/test_lint_on_enter_when_file_changed.vader @@ -0,0 +1,84 @@ +Before: + Save &filetype + Save g:ale_buffer_info + Save g:ale_lint_on_enter + Save g:ale_set_lists_synchronously + + let g:buf = bufnr('') + let g:ale_lint_on_enter = 1 + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + + function! TestCallback(buffer, output) + return [{ + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'baz boz', + \}] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': has('win32') ? 'echo' : 'true', + \}) + +After: + Restore + unlet! g:buf + let g:ale_run_synchronously = 0 + delfunction TestCallback + call ale#linter#Reset() + call setloclist(0, []) + +Execute(The file changed event function should set b:ale_file_changed): + let g:ale_lint_on_enter = 0 + + if has('gui_running') + new + else + e test + endif + + call ale#events#FileChangedEvent(g:buf) + close + + " We should set the flag in the other buffer + AssertEqual 1, getbufvar(g:buf, '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(''), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'baz boz', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }], ale#test#GetLoclistWithoutNewerKeys() + +Execute(The buffer should be checked after entering it after the file has changed): + let b:ale_file_changed = 1 + + set filetype=foobar + call ale#events#ReadOrEnterEvent(bufnr('')) + call ale#test#FlushJobs() + + AssertEqual [{ + \ 'bufnr': bufnr(''), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'baz boz', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }], ale#test#GetLoclistWithoutNewerKeys() diff --git a/test/test_lint_on_filetype_changed.vader b/test/test_lint_on_filetype_changed.vader new file mode 100644 index 00000000..cfc99d58 --- /dev/null +++ b/test/test_lint_on_filetype_changed.vader @@ -0,0 +1,77 @@ +Before: + Save &filetype + Save g:ale_lint_on_filetype_changed + + let g:ale_lint_on_filetype_changed = 1 + let g:queue_calls = [] + + function! ale#Queue(...) + call add(g:queue_calls, a:000) + endfunction + +After: + Restore + + unlet! g:queue_calls + + " Reload the ALE code to load the real function again. + runtime autoload/ale.vim + + unlet! b:ale_original_filetype + +Execute(The original filetype should be set on BufEnter): + let &filetype = 'foobar' + + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 'foobar', b:ale_original_filetype + + let &filetype = 'bazboz' + + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 'bazboz', b:ale_original_filetype + +Execute(Linting should not be queued when the filetype is the same): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'foobar') + + AssertEqual [], g:queue_calls + +Execute(Linting should be queued when the filetype changes): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls + " The original filetype should be updated, so we don't trigger linting + " by setting a filetype equal to what it already is. + AssertEqual 'bazboz', b:ale_original_filetype + +Execute(Linting should be done when the original filetype was blank): + let b:ale_original_filetype = '' + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls + AssertEqual 'bazboz', b:ale_original_filetype + +Execute(Linting should not be done when the setting is off): + let b:ale_original_filetype = 'foobar' + let g:ale_lint_on_filetype_changed = 0 + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [], g:queue_calls + " We should still update the old filetype + AssertEqual 'bazboz', b:ale_original_filetype + +Execute(Linting should be done when the original filetype was not set): + unlet! b:ale_original_filetype + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [], g:queue_calls diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader new file mode 100644 index 00000000..4c096a5e --- /dev/null +++ b/test/test_linter_defintion_processing.vader @@ -0,0 +1,501 @@ +Before: + Save g:ale_root + Save b:ale_root + + let g:ale_root = {} + unlet! b:ale_root + + let g:linter = {} + +After: + unlet g:linter + +Execute (PreProcess should throw when the linter object is not a Dictionary): + AssertThrows call ale#linter#PreProcess('testft', '') + AssertEqual 'The linter object must be a Dictionary', g:vader_exception + +Execute (PreProcess should throw when there is no name): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + AssertEqual '`name` must be defined to name the linter', g:vader_exception + +Execute (PreProcess should throw when there is no callback): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + AssertEqual '`callback` must be defined with a callback to accept output', g:vader_exception + +Execute (PreProcess should throw when then callback is not a function): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 1, + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + AssertEqual '`callback` must be defined with a callback to accept output', g:vader_exception + +Execute (PreProcess should throw when there is no executable): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'command': 'echo', + \}) + AssertEqual '`executable` must be defined', g:vader_exception + +Execute (PreProcess should throw when executable is not a string): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 123, + \ 'command': 'echo', + \}) + AssertEqual '`executable` must be a String or Function 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', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \}) + AssertEqual '`command` must be defined', g:vader_exception + +Execute (PreProcess should throw when command is not a string): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': [], + \}) + 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 cwd is not a string): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'cwd': [], + \ 'command': 'echo', + \}) + AssertEqual '`cwd` must be a String or Function if defined', g:vader_exception + +Execute (PreProcess should allow cwd to be a callback): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'cwd': function('type'), + \ 'command': 'echo', + \}) + +Execute (PreProcess should allow cwd to be a string): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'cwd': '/foo/bar', + \ 'command': 'echo', + \}) + +Execute (PreProcess should when the output stream isn't a valid string): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'xxx', + \}) + AssertEqual "`output_stream` must be 'stdout', 'stderr', or 'both'", g:vader_exception + +Execute (PreProcess should not throw when everything is correct): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + +Execute (PreProcess should accept an stdout output_stream): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'stdout', + \}) + +Execute (PreProcess should accept an stderr output_stream): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'stderr', + \}) + +Execute (PreProcess should accept a 'both' output_stream): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'both', + \}) + +Execute(PreProcess should process the read_buffer option correctly): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'read_buffer': '0', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`read_buffer` must be `0` or `1`', g:vader_exception + + let g:linter.read_buffer = 0 + + call ale#linter#PreProcess('testft', g:linter) + + let g:linter.read_buffer = 1 + + call ale#linter#PreProcess('testft', g:linter) + +Execute(PreProcess should set a default value for read_buffer): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \} + + AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer + +Execute(PreProcess should process the lint_file option correctly): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'lint_file': 'x', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`lint_file` must be `0`, `1`, or a Function', g:vader_exception + + let g:linter.lint_file = 0 + + AssertEqual 0, ale#linter#PreProcess('testft', g:linter).lint_file + " The default for read_buffer should be 1 when lint_file is 0 + AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer + + let g:linter.lint_file = 1 + + AssertEqual 1, ale#linter#PreProcess('testft', g:linter).lint_file + " The default for read_buffer should still be 1 + AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer + + let g:linter.read_buffer = 1 + + " We should be able to set `read_buffer` and `lint_file` at the same time. + AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer + + let g:linter.lint_file = function('type') + + Assert type(ale#linter#PreProcess('testft', g:linter).lint_file) is v:t_func + +Execute(PreProcess should set a default value for lint_file): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \} + + AssertEqual 0, ale#linter#PreProcess('testft', g:linter).lint_file + +Execute(PreProcess should set a default value for aliases): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \} + + AssertEqual [], ale#linter#PreProcess('testft', g:linter).aliases + +Execute(PreProcess should complain about invalid `aliases` values): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'aliases': 'foo', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`aliases` must be a List of String values', g:vader_exception + + let g:linter.aliases = [1] + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`aliases` must be a List of String values', g:vader_exception + +Execute(PreProcess should accept `aliases` lists): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'aliases': [], + \} + + AssertEqual [], ale#linter#PreProcess('testft', g:linter).aliases + + let g:linter.aliases = ['foo', 'bar'] + + AssertEqual ['foo', 'bar'], ale#linter#PreProcess('testft', g:linter).aliases + +Execute(PreProcess should accept tsserver LSP configuration): + let g:linter = { + \ 'name': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'lsp': 'tsserver', + \ 'language': 'x', + \ 'project_root': 'x', + \} + + AssertEqual 'tsserver', ale#linter#PreProcess('testft', g:linter).lsp + +Execute(PreProcess should accept stdio LSP configuration): + let g:linter = { + \ 'name': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'lsp': 'stdio', + \ 'language': 'x', + \ 'project_root': 'x', + \} + + AssertEqual 'stdio', ale#linter#PreProcess('testft', g:linter).lsp + +Execute(PreProcess should accept LSP server configurations): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'language': 'foobar', + \ 'project_root': 'x', + \} + + AssertEqual 'socket', ale#linter#PreProcess('testft', g:linter).lsp + +Execute(PreProcess should accept let you specify the `language` as a Function): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'language': {-> 'foobar'}, + \ 'project_root': 'x', + \} + + AssertEqual 'foobar', ale#linter#PreProcess('testft', g:linter).language(bufnr('')) + +Execute(PreProcess should complain about invalid language values): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'language': 0, + \ 'project_root': 'x', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`language` must be a String or Function if defined', g:vader_exception + +Execute(PreProcess should use the filetype as the language string by default): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'project_root': 'x', + \} + + AssertEqual 'testft', ale#linter#PreProcess('testft', g:linter).language + +Execute(PreProcess should require an `address` for LSP socket configurations): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`address` must be defined for getting the LSP address', g:vader_exception + +Execute(PreProcess should complain about `address` for non-LSP linters): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'address': 'X', + \} + + AssertThrows call ale#linter#PreProcess('testft', g:linter) + AssertEqual '`address` cannot be used when lsp != ''socket''', g:vader_exception + +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': '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': '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': 'x', + \}) + AssertEqual '`address` must be a String or Function if defined', g:vader_exception + +Execute(PreProcess should allow the `project_root` to 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 `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 `project_root` 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', 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': 'X', + \ 'language': 'x', + \ 'project_root': 'x', + \ 'initialization_options': 0, + \}) + AssertEqual '`initialization_options` must be a Dictionary 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': 'X', + \ 'language': 'x', + \ 'project_root': '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 Function): + let g:linter = ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'language': 'x', + \ 'project_root': 'x', + \ 'initialization_options': {-> {'foo': v:true}}, + \}) + + AssertEqual {'foo': v:true}, ale#lsp_linter#GetOptions(0, g:linter) + +Execute(PreProcess should accept `lsp_config` as a Dictionary): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'language': 'x', + \ 'project_root': 'x', + \ 'lsp_config': {'foo': 'bar'}, + \} + + AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter) + +Execute(PreProcess should accept `lsp_config` as a Function): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'language': 'x', + \ 'project_root': 'x', + \ 'lsp_config': {-> {'foo': 'bar'}}, + \} + + AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter) + +Execute(PreProcess should throw when `lsp_config` is not a Dictionary or Function): + AssertThrows call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address': 'X', + \ 'language': 'x', + \ 'project_root': 'x', + \ 'lsp_config': 'x', + \}) + 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 new file mode 100644 index 00000000..88885b71 --- /dev/null +++ b/test/test_linter_retrieval.vader @@ -0,0 +1,190 @@ +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': ''} + 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') + call ale#linter#PreventLoading('typescript') + +After: + Restore + + unlet! g:testlinter1 + unlet! g:testlinter2 + unlet! b:ale_linters + unlet! b:ale_linter_aliases + call ale#linter#Reset() + +Execute (You should be able to get a defined linter): + call ale#linter#Define('testft', g:testlinter1) + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able get select a single linter): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able to select a linter by an alias): + let g:testlinter1.aliases = ['foo', 'linter1alias'] + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['linter1alias']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able to select linters with a buffer option): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1', 'testlinter2']} + let b:ale_linters = {'testft': ['testlinter1']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (b:ale_linters should work when set to a List): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1', 'testlinter2']} + let b:ale_linters = ['testlinter1'] + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (b:ale_linters should disable all linters when set to an empty List): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1', 'testlinter2']} + let b:ale_linters = [] + + AssertEqual [], ale#linter#Get('testft') + +Execute (b:ale_linters should enable all available linters when set to 'all'): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1']} + let b:ale_linters = 'all' + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft') + +Execute (Buffer settings shouldn't completely replace global settings): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1']} + let b:ale_linters = {'testft2': ['testlinter1', 'testlinter2']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able to alias linters from one filetype to another): + call ale#linter#Define('testft1', g:testlinter1) + let g:ale_linter_aliases = {'testft2': 'testft1'} + + AssertEqual [g:testlinter1], ale#linter#Get('testft2') + +Execute (You should be able to filter aliased linters): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft1', g:testlinter2) + let g:ale_linters = {'testft1': ['testlinter1'], 'testft2': ['testlinter2']} + let g:ale_linter_aliases = {'testft2': 'testft1'} + + AssertEqual [g:testlinter1], ale#linter#Get('testft1') + AssertEqual [g:testlinter2], ale#linter#Get('testft2') + +Execute (Dot-separated filetypes should be handled correctly): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1.testft2') + +Execute (Linters for multiple aliases should be loaded): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let ale_linter_aliases = {'testft3': ['testft1', 'testft2']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft3') + +Execute (You should be able to alias filetypes to themselves and another): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') + +Execute (Buffer-local overrides for aliases should be used): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let g:ale_linter_aliases = {'testft1': ['testft2']} + let b:ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') + +Execute (The local alias option shouldn't completely replace the global one): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let g:ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + " This is a key set for a different filetype. + " We should look for a key in this Dictionary first, and then check the + " global Dictionary. + let b:ale_linter_aliases = {'testft3': ['testft1']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') + +Execute (Lists should be accepted for local aliases): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let g:ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + " We should load the testft2 linters for this buffer, with no duplicates. + let b:ale_linter_aliases = ['testft2'] + + AssertEqual [g:testlinter2], ale#linter#Get('anything.else') + +Execute (Strings should be accepted for local aliases): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let g:ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + " We should load the testft2 linters for this buffer, with no duplicates. + let b:ale_linter_aliases = 'testft2' + + AssertEqual [g:testlinter2], ale#linter#Get('anything.else') + +Execute (Buffer-local overrides for aliases should be used): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let g:ale_linter_aliases = {'testft1': ['testft2']} + let b:ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') + +Execute (Linters new linters with the same name should replace old ones): + let g:testlinter1.name = g:testlinter2.name + + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft1', g:testlinter2) + + AssertEqual [g:testlinter2], ale#linter#GetAll(['testft1']) + +Execute (Linters should be loaded from disk appropriately): + call ale#linter#Reset() + AssertEqual [{'name': 'testlinter', 'output_stream': 'stdout', 'executable': 'testlinter', 'command': 'testlinter', 'callback': 'testCB', 'read_buffer': 1, 'lint_file': 0, 'aliases': [], 'lsp': ''}], ale#linter#Get('testft') + + +Execute (Linters for later filetypes should replace the former ones): + call ale#linter#Define('javascript', { + \ 'name': 'eslint', + \ 'executable': 'y', + \ 'command': 'y', + \ 'callback': 'y', + \}) + call ale#linter#Define('typescript', { + \ 'name': 'eslint', + \ 'executable': 'x', + \ 'command': 'x', + \ 'callback': 'x', + \}) + + AssertEqual [ + \ {'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_linter_type_mapping.vader b/test/test_linter_type_mapping.vader new file mode 100644 index 00000000..0ec22a56 --- /dev/null +++ b/test/test_linter_type_mapping.vader @@ -0,0 +1,120 @@ +Before: + Save g:ale_type_map + +After: + Restore + unlet! b:ale_type_map + +Execute(It should be possible to remap errors to style errors): + let g:ale_type_map = {'foo': {'E': 'ES'}} + + AssertEqual + \ [ + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ ], + \ ale#engine#FixLocList(bufnr(''), 'foo', 0, [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ ]) + +Execute(It should be possible to remap errors to style errors with buffer-local variables): + let b:ale_type_map = {'foo': {'E': 'ES'}} + + AssertEqual + \ [ + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ ], + \ ale#engine#FixLocList(bufnr(''), 'foo', 0, [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ ]) + +Execute(It should be possible to remap warnings to style warnings): + let g:ale_type_map = {'foo': {'W': 'WS'}} + + AssertEqual + \ [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ ], + \ ale#engine#FixLocList(bufnr(''), 'foo', 0, [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ ]) + +Execute(It should be possible to remap style errors to errors): + let g:ale_type_map = {'foo': {'ES': 'E'}} + + AssertEqual + \ [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ ], + \ ale#engine#FixLocList(bufnr(''), 'foo', 0, [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ ]) + +Execute(It should be possible to remap style warnings to warnings): + let g:ale_type_map = {'foo': {'WS': 'W'}} + + AssertEqual + \ [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ ], + \ ale#engine#FixLocList(bufnr(''), 'foo', 0, [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ ]) + +Execute(It should be possible to info problems to warnings): + let g:ale_type_map = {'foo': {'I': 'W'}} + + AssertEqual + \ [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1, 'linter_name': 'foo'}, + \ ], + \ ale#engine#FixLocList(bufnr(''), 'foo', 0, [ + \ {'type': 'E', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'E', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'W', 'sub_type': 'style', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ {'type': 'I', 'lnum': 1, 'text': 'x', 'bufnr': bufnr(''), 'col': 0, 'vcol': 0, 'nr': -1}, + \ ]) diff --git a/test/test_linting_blacklist.vader b/test/test_linting_blacklist.vader new file mode 100644 index 00000000..2bcc9576 --- /dev/null +++ b/test/test_linting_blacklist.vader @@ -0,0 +1,16 @@ +Before: + let g:ale_buffer_info = {} + +After: + call ale#engine#Cleanup(bufnr('')) + + let g:ale_buffer_info = {} + +Given unite (A Unite.vim file): + anything + +Execute(Running ALE on a blacklisted file shouldn't change anything): + call ale#Queue(0) + 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 new file mode 100644 index 00000000..41c86522 --- /dev/null +++ b/test/test_linting_updates_loclist.vader @@ -0,0 +1,96 @@ +Before: + Save g:ale_echo_cursor + Save g:ale_set_highlights + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_set_signs + Save g:ale_run_synchronously + Save g:ale_set_lists_synchronously + Save g:ale_buffer_info + Save g:ale_sign_offset + + " We want to check that sign IDs are set for this test. + let g:ale_set_signs = 1 + let g:ale_set_loclist = 1 + let g:ale_sign_offset = 2000000 + " Disable features we don't need for these tests. + let g:ale_set_quickfix = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + let g:ale_buffer_info = {} + + function! TestCallback(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'type': 'W', + \ 'col': 10, + \ 'text': 'Infix operators must be spaced. [Warning/space-infix-ops]', + \ }, + \ { + \ 'lnum': 2, + \ 'type': 'E', + \ 'col': 10, + \ 'text': 'Missing semicolon. [Error/semi]', + \ } + \] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + + sign unplace * + + call ale#engine#Cleanup(bufnr('')) + +After: + Restore + + delfunction TestCallback + + call ale#linter#Reset() + + sign unplace * + +Given foobar (Some JavaScript with problems): + var y = 3+3; + var y = 3 + +Execute(The loclist should be updated after linting is done): + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'testlinter', + \ 'nr': -1, + \ 'type': 'W', + \ 'col': 10, + \ 'text': 'Infix operators must be spaced. [Warning/space-infix-ops]', + \ 'sign_id': 2000001, + \ }, + \ { + \ 'lnum': 2, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'testlinter', + \ 'nr': -1, + \ 'type': 'E', + \ 'col': 10, + \ 'text': 'Missing semicolon. [Error/semi]', + \ 'sign_id': 2000002, + \ } + \ ], + \ get(get(g:ale_buffer_info, bufnr('%'), {}), 'loclist', []) diff --git a/test/test_list_formatting.vader b/test/test_list_formatting.vader new file mode 100644 index 00000000..eaa67a9d --- /dev/null +++ b/test/test_list_formatting.vader @@ -0,0 +1,188 @@ +Before: + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_loclist_msg_format + Save g:ale_open_list + Save g:ale_buffer_info + Save g:ale_set_lists_synchronously + + let g:ale_set_lists_synchronously = 1 + let g:ale_loclist_msg_format = '%code: %%s' + let g:ale_open_list = 0 + let g:loclist = [] + let g:ale_buffer_info = {bufnr(''): {'loclist': g:loclist}} + + function! AddItem(data) abort + let l:item = { + \ 'bufnr': bufnr(''), + \ 'lnum': 1, + \ 'col': 1, + \ 'type': 'E', + \ 'linter_name': 'some_linter', + \} + + call add(g:loclist, extend(l:item, a:data)) + endfunction + +After: + Restore + + unlet! g:loclist + unlet! b:ale_loclist_msg_format + + delfunction AddItem + + call setloclist(0, []) + call setqflist([]) + +Execute(Formatting with codes should work for the loclist): + call AddItem({'text': "nocode\r"}) + call ale#list#SetLists(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': 'nocode', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + + call remove(g:loclist, 0) + call AddItem({'text': 'withcode', 'code': 'E123'}) + call ale#list#SetLists(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': 'E123: withcode', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +Execute(Formatting with codes should work for the quickfix list): + let g:ale_set_loclist = 0 + let g:ale_set_quickfix = 1 + + call AddItem({'text': "nocode\r"}) + call ale#list#SetLists(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': 'nocode', + \ }, + \ ], + \ ale#test#GetQflistWithoutNewerKeys() + + call remove(g:loclist, 0) + call AddItem({'text': 'withcode', 'code': 'E123'}) + call ale#list#SetLists(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': 'E123: withcode', + \ }, + \ ], + \ ale#test#GetQflistWithoutNewerKeys() + +Execute(Formatting with the linter name should work for the loclist): + let g:ale_loclist_msg_format = '(%linter%) %s' + + call AddItem({'text': 'whatever'}) + call ale#list#SetLists(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': '(some_linter) whatever', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +Execute(Formatting with the linter name should work for the quickfix list): + let g:ale_loclist_msg_format = '(%linter%) %s' + let g:ale_set_loclist = 0 + let g:ale_set_quickfix = 1 + + call AddItem({'text': 'whatever'}) + call ale#list#SetLists(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': '(some_linter) whatever', + \ }, + \ ], + \ ale#test#GetQflistWithoutNewerKeys() + +Execute(The buffer loclist format option should take precedence): + let g:ale_loclist_msg_format = '(%linter%) %s' + let b:ale_loclist_msg_format = 'FOO %s' + + call AddItem({'text': 'whatever'}) + call ale#list#SetLists(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 1, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': 'FOO whatever', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() diff --git a/test/test_list_opening.vader b/test/test_list_opening.vader new file mode 100644 index 00000000..e6e33cda --- /dev/null +++ b/test/test_list_opening.vader @@ -0,0 +1,284 @@ +" Author: Yann Fery +Before: + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_open_list + Save g:ale_keep_list_window_open + Save g:ale_list_window_size + Save g:ale_list_vertical + Save g:ale_buffer_info + Save g:ale_set_lists_synchronously + + let g:ale_set_loclist = 1 + let g:ale_set_quickfix = 0 + let g:ale_open_list = 0 + let g:ale_keep_list_window_open = 0 + let g:ale_list_window_size = 10 + let g:ale_list_vertical = 0 + let g:ale_set_lists_synchronously = 1 + + let g:loclist = [ + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': bufnr(''), 'lnum': 3, 'col': 2, 'text': 'x'}, + \] + let g:ale_buffer_info = {bufnr(''): {'loclist': g:loclist}} + + function GetQuickfixHeight() abort + for l:win in range(1, winnr('$')) + if getwinvar(l:win, '&buftype') ==# 'quickfix' + return winheight(l:win) + endif + endfor + + return 0 + endfunction + + " If the window is vertical, window size should match column size/width + function GetQuickfixIsVertical(cols) abort + for l:win in range(1, winnr('$')) + if getwinvar(l:win, '&buftype') is# 'quickfix' + return winwidth(l:win) == a:cols + endif + endfor + + return 0 + endfunction + +After: + Restore + + unlet! g:loclist + unlet! b:ale_list_vertical + unlet! b:ale_list_window_size + unlet! b:ale_open_list + unlet! b:ale_keep_list_window_open + unlet! b:ale_save_event_fired + + delfunction GetQuickfixHeight + delfunction GetQuickfixIsVertical + + " Close quickfix window after every execute block + lcl + ccl + call setloclist(0, []) + call setqflist([]) + +Execute(IsQuickfixOpen should return the right output): + AssertEqual 0, ale#list#IsQuickfixOpen() + call setloclist(0, g:loclist) + lopen + AssertEqual 1, ale#list#IsQuickfixOpen() + lcl + AssertEqual 0, ale#list#IsQuickfixOpen() + call setqflist(g:loclist) + copen + AssertEqual 1, ale#list#IsQuickfixOpen() + ccl + AssertEqual 0, ale#list#IsQuickfixOpen() + +Execute(The quickfix window should not open by default for the loclist): + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert !ale#list#IsQuickfixOpen() + +Execute(The quickfix window should open for just the loclist): + let g:ale_open_list = 1 + + " It should not open for an empty list. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen() + + " With a non-empty loclist, the window must open. + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen() + + " Clear the list and it should close again. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen() + +Execute(The quickfix window should open on the correct threshold): + " The window should open for a value lower than number of entries. + let g:ale_open_list = len(g:loclist) - 1 + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen() + + " Clear the list to be ready for a new value. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen() + + " It should also open for a value equal to the number of entries. + let g:ale_open_list = len(g:loclist) + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen() + + " Clear the list again, preparing for a final value. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen() + + " Window should not open for values higher than number of loclist entries. + let g:ale_open_list = len(g:loclist) + 1 + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert !ale#list#IsQuickfixOpen() + + " Clear the list just to clean up. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen() + +Execute(The quickfix window height should be correct for the loclist): + let g:ale_open_list = 1 + let g:ale_list_window_size = 7 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 7, GetQuickfixHeight() + +Execute(The quickfix window height should be correct for the loclist with buffer variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 8, GetQuickfixHeight() + +Execute(The quickfix window should be vertical for the loclist with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 1, GetQuickfixIsVertical(8) + +Execute(The quickfix window should be horizontal for the loclist with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 0 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 0, GetQuickfixIsVertical(8) + +Execute(The quickfix window should stay open for just the loclist): + let g:ale_open_list = 1 + let g:ale_keep_list_window_open = 1 + + " The window should stay open after even after it is made blank again. + call ale#list#SetLists(bufnr('%'), g:loclist) + call ale#list#SetLists(bufnr('%'), []) + Assert ale#list#IsQuickfixOpen() + +Execute(The quickfix window should not open by default when quickfix is on): + let g:ale_set_quickfix = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert !ale#list#IsQuickfixOpen() + +Execute(The quickfix window should open for the quickfix list): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + + let g:ale_buffer_info[bufnr('') + 1] = { + \ 'loclist': [{'bufnr': -1, 'filename': '/foo/bar', 'lnum': 5, 'col': 5, 'text': 'x'}], + \} + + " It should not open for an empty list. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was opened when the list was empty' + + " With a non-empty quickfix list, the window must open. + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen(), 'The quickfix window was closed when the list was not empty' + + " Clear this List. The window should stay open, as there are other items. + let g:ale_buffer_info[bufnr('')].loclist = [] + call ale#list#SetLists(bufnr('%'), []) + Assert ale#list#IsQuickfixOpen(), 'The quickfix window closed even though there are items in another buffer' + + " Clear the other List now. Now the window should close. + call remove(g:ale_buffer_info, bufnr('') + 1) + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was not closed' + +Execute(The quickfix window should stay open for the quickfix list): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + let g:ale_keep_list_window_open = 1 + + " The window should stay open after even after it is made blank again. + call ale#list#SetLists(bufnr('%'), g:loclist) + call ale#list#SetLists(bufnr('%'), []) + Assert ale#list#IsQuickfixOpen() + +Execute(The quickfix window height should be correct for the quickfix list): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + let g:ale_list_window_size = 7 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 7, GetQuickfixHeight() + +Execute(The quickfix window height should be correct for the quickfix list with buffer variables): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 8, GetQuickfixHeight() + +Execute(The quickfix window should be vertical for the quickfix with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 1, GetQuickfixIsVertical(8) + +Execute(The quickfix window should be horizontal for the quickfix with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 0 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 0, GetQuickfixIsVertical(8) + +Execute(buffer-local options should be respected): + let b:ale_open_list = 1 + let b:ale_keep_list_window_open = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + call ale#list#SetLists(bufnr('%'), []) + + Assert ale#list#IsQuickfixOpen() + +Execute(The ale_open_list='on_save' option should work): + let b:ale_open_list = 'on_save' + + call ale#list#SetLists(bufnr('%'), g:loclist) + " The list shouldn't open yet, the event wasn't fired. + Assert !ale#list#IsQuickfixOpen() + + " Turn this option off, to ensure that we update lists immediately when we + " save buffers. + let g:ale_set_lists_synchronously = 0 + let b:ale_save_event_fired = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + " Now the list should have opened. + Assert ale#list#IsQuickfixOpen() + + call ale#list#SetLists(bufnr('%'), []) + " The window should close again when the loclist is empty. + Assert !ale#list#IsQuickfixOpen() + +Execute(The window shouldn't open on save when ale_open_list=0): + let b:ale_open_list = 0 + let b:ale_save_event_fired = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + " Now the list should have opened. + Assert !ale#list#IsQuickfixOpen() diff --git a/test/test_list_titles.vader b/test/test_list_titles.vader new file mode 100644 index 00000000..dfb042f5 --- /dev/null +++ b/test/test_list_titles.vader @@ -0,0 +1,77 @@ +Before: + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_buffer_info + Save g:ale_set_lists_synchronously + + let g:ale_buffer_info = {} + let g:ale_set_loclist = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_lists_synchronously = 1 + + call ale#test#SetDirectory('/testplugin/test') + +After: + Restore + + call setloclist(0, []) + call setqflist([]) + + call ale#test#RestoreDirectory() + +Execute(The loclist titles should be set appropriately): + silent noautocmd file foo + + let g:ale_set_loclist = 1 + + call ale#list#SetLists(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x', 'type': 'E'}, + \]) + + AssertEqual [{ + \ 'lnum': 5, + \ 'bufnr': bufnr(''), + \ 'col': 5, + \ 'text': 'x', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \}], ale#test#GetLoclistWithoutNewerKeys() + + if !has('nvim') + AssertEqual + \ {'title': ale#path#Simplify(getcwd() . '/foo')}, + \ getloclist(0, {'title': ''}) + endif + +Execute(The quickfix titles should be set appropriately): + silent noautocmd file foo + + let g:ale_set_quickfix = 1 + + let g:ale_buffer_info[bufnr('')] = { + \ 'loclist': [{'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x', 'type': 'E'}], + \} + + call ale#list#SetLists(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x', 'type': 'E'}, + \]) + AssertEqual [{ + \ 'lnum': 5, + \ 'bufnr': bufnr(''), + \ 'col': 5, + \ 'text': 'x', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \}], ale#test#GetQflistWithoutNewerKeys() + + if !has('nvim') + AssertEqual + \ {'title': ale#path#Simplify(getcwd() . '/foo')}, + \ getqflist({'title': ''}) + endif diff --git a/test/test_load_all_linters.vader b/test/test_load_all_linters.vader new file mode 100644 index 00000000..6806719a --- /dev/null +++ b/test/test_load_all_linters.vader @@ -0,0 +1,6 @@ +Execute(Exceptions shouldn't be thrown when loading all linters): + " This test will look for errors when loading any of the linter files. + runtime! ale_linters/*/*.vim + +After: + call ale#linter#Reset() diff --git a/test/test_loclist_binary_search.vader b/test/test_loclist_binary_search.vader new file mode 100644 index 00000000..219fb314 --- /dev/null +++ b/test/test_loclist_binary_search.vader @@ -0,0 +1,66 @@ +Before: + let g:loclist = [ + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 3, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 12}, + \ {'bufnr': 1, 'lnum': 3, 'col': 25}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 1, 'lnum': 9, 'col': 5}, + \ {'bufnr': 1, 'lnum': 10, 'col': 1}, + \ {'bufnr': 2, 'lnum': 7, 'col': 10}, + \ {'bufnr': 2, 'lnum': 9, 'col': 2}, + \ {'bufnr': 2, 'lnum': 10, 'col': 2}, + \ {'bufnr': 2, 'lnum': 11, 'col': 2}, + \] + +After: + unlet g:loclist + +Execute(Exact column matches should be correct): + AssertEqual 1, ale#util#BinarySearch(g:loclist, 1, 3, 2) + +Execute(Off lines, there should be no match): + AssertEqual -1, ale#util#BinarySearch(g:loclist, 1, 4, 2) + +Execute(Near column matches should be taken): + AssertEqual 2, ale#util#BinarySearch(g:loclist, 1, 3, 11) + AssertEqual 3, ale#util#BinarySearch(g:loclist, 1, 3, 13) + +Execute(Columns before should be taken when the cursor is far ahead): + AssertEqual 4, ale#util#BinarySearch(g:loclist, 1, 3, 300) + +Execute(The only problems on lines in later columns should be matched): + AssertEqual 7, ale#util#BinarySearch(g:loclist, 1, 9, 1) + +Execute(The only problems on lines in earlier columns should be matched): + AssertEqual 8, ale#util#BinarySearch(g:loclist, 1, 10, 30) + +Execute(Lines for other buffers should not be matched): + AssertEqual -1, ale#util#BinarySearch(g:loclist, 1, 7, 10) + +Execute(Searches for buffers later in the list should work): + AssertEqual 10, ale#util#BinarySearch(g:loclist, 2, 9, 10) + +Execute(Searches should work with just one item): + let g:loclist = [{'bufnr': 1, 'lnum': 3, 'col': 10}] + + AssertEqual 0, ale#util#BinarySearch(g:loclist, 1, 3, 2) + +Execute(Searches should return the last item on a single column): + let g:loclist = [ + \ {'bufnr': 1, 'lnum': 1, 'col': 10, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 10, 'type': 'E'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 11, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 11, 'type': 'E'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 20, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 20, 'type': 'E'}, + \] + + " We should return the index for the last item at some column to the right. + AssertEqual 1, ale#util#BinarySearch(g:loclist, 1, 1, 1) + " We should return the index for the last item at the column we are on. + AssertEqual 3, ale#util#BinarySearch(g:loclist, 1, 1, 11) + " We should prefer items to the left of the cursor, over the right. + AssertEqual 3, ale#util#BinarySearch(g:loclist, 1, 1, 19) diff --git a/test/test_loclist_corrections.vader b/test/test_loclist_corrections.vader new file mode 100644 index 00000000..60b1eba7 --- /dev/null +++ b/test/test_loclist_corrections.vader @@ -0,0 +1,469 @@ +Before: + Save g:ale_filename_mappings + + let g:ale_filename_mappings = {} + +After: + unlet! b:temp_name + unlet! b:other_bufnr + + Restore + + +Execute(FixLocList should map filenames): + " Paths converted back into temporary filenames shouldn't be included. + let g:ale_filename_mappings = { + \ 'linter2': [['/xxx/', '/data/']], + \ 'linter1': [ + \ ['/bar/', '/data/special/'], + \ ['/foo/', '/data/'], + \ [ + \ ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h:h')) . '/', + \ '/x-tmp/', + \ ], + \ ], + \} + + AssertEqual + \ [ + \ '/foo/file.txt', + \ v:null, + \ '/bar/file.txt', + \ ], + \ map( + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'linter1', + \ 0, + \ [ + \ {'text': 'x', 'lnum': 1, 'filename': '/data/file.txt'}, + \ {'text': 'x', 'lnum': 1, 'filename': '/x-tmp/file.txt'}, + \ {'text': 'x', 'lnum': 1, 'filename': '/data/special/file.txt'}, + \ ], + \ ), + \ 'get(v:val, ''filename'', v:null)', + \ ) + + +Given foo (Some file with lines to count): + foo12345678 + bar12345678 + baz12345678 + four12345678 + five12345678 + six12345678 + seven12345678 + eight12345678 + nine12345678 + ten12345678 + +Execute(FixLocList should set all the default values correctly): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'b', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [{'text': 'a', 'lnum': 2}, {'text': 'b', 'lnum': 2}], + \ ) + +Execute(FixLocList should use the values we supply): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 4, + \ 'bufnr': 10000, + \ 'vcol': 0, + \ 'type': 'W', + \ 'nr': 42, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [{ + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 4, + \ 'bufnr': 10000, + \ 'vcol': 1, + \ 'type': 'W', + \ 'nr': 42, + \ }], + \ ) + +Execute(FixLocList should set items with lines beyond the end to the last line): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'end_lnum': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [{'text': 'a', 'lnum': 11, 'end_lnum': 12}], + \ ) + +Execute(FixLocList should move line 0 to line 1): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 1, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [{'text': 'a', 'lnum': 0}], + \ ) + +Execute(FixLocList should convert line and column numbers correctly): + " The numbers should be 10, not 8 as octals. + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [{'text': 'a', 'lnum': '010', 'col': '010'}], + \ ) + +Execute(FixLocList should pass on end_col values): + " The numbers should be 10, not 8 as octals. + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 10, + \ 'end_col': 12, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 11, + \ 'end_col': 12, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [ + \ {'text': 'a', 'lnum': '010', 'col': '010', 'end_col': '012'}, + \ {'text': 'a', 'lnum': '010', 'col': '011', 'end_col': 12}, + \ ], + \ ) + +Execute(FixLocList should pass on end_lnum values): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 7, + \ 'col': 10, + \ 'end_lnum': 10, + \ 'end_col': 12, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 7, + \ 'col': 11, + \ 'end_lnum': 10, + \ 'end_col': 12, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [ + \ {'text': 'a', 'lnum': '07', 'col': '010', 'end_col': '012', 'end_lnum': '010'}, + \ {'text': 'a', 'lnum': '07', 'col': '011', 'end_col': 12, 'end_lnum': 10}, + \ ], + \ ) + +Execute(FixLocList should allow subtypes to be set): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'sub_type': 'style', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [{'text': 'a', 'lnum': 11, 'sub_type': 'style'}], + \ ) + +Execute(FixLocList should accept filenames): + let b:other_bufnr = bufnr('/foo/bar/baz', 1) + + " Make sure we actually get another buffer number, or the test is invalid. + AssertNotEqual -1, b:other_bufnr + + call ale#test#SetFilename('test.txt') + + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'filename': expand('%:p'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'filename': expand('%:p'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 4, + \ 'col': 0, + \ 'bufnr': b:other_bufnr, + \ 'filename': '/foo/bar/baz', + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 5, + \ 'col': 0, + \ 'bufnr': b:other_bufnr, + \ 'filename': '/foo/bar/baz', + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [ + \ {'text': 'a', 'lnum': 2, 'filename': expand('%:p')}, + \ {'text': 'a', 'lnum': 3, 'filename': expand('%:p')}, + \ {'text': 'a', 'lnum': 4, 'filename': '/foo/bar/baz'}, + \ {'text': 'a', 'lnum': 5, 'filename': '/foo/bar/baz'}, + \ ], + \ ) + +Execute(FixLocList should interpret temporary filenames as being the current buffer): + let b:temp_name = tempname() + + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr(''), + \ 'foobar', + \ 0, + \ [ + \ {'text': 'a', 'lnum': 2, 'filename': b:temp_name}, + \ {'text': 'a', 'lnum': 3, 'filename': substitute(b:temp_name, '\\', '/', 'g')}, + \ ], + \ ) + +Execute(The error code should be passed on): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ 'code': 'some-code' + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [{'text': 'a', 'lnum': 11, 'code': 'some-code'}], + \ ) + +Execute(FixLocList should mark problems as coming from other sources if requested): + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ 'from_other_source': 1, + \ }, + \ { + \ 'text': 'b', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ 'from_other_source': 1, + \ }, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 1, + \ [{'text': 'a', 'lnum': 2}, {'text': 'b', 'lnum': 2}], + \ ) + +Given(A file with Japanese multi-byte text): + はじめまして! + -私はワープです。 +Execute(character positions should be converted to byte positions): + AssertEqual + \ [ + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 0, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 1, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 4, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 13, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 13, 'end_lnum': 1, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 17, 'end_lnum': 2, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ {'lnum': 2, 'bufnr': bufnr(''), 'col': 17, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, + \ ], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ 0, + \ [ + \ {'text': 'a', 'lnum': 1, 'col': 0, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 1, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 2, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'end_col': 5, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'end_col': 5, 'end_lnum': 1, 'vcol': 1}, + \ {'text': 'a', 'lnum': 1, 'col': 3, 'end_col': 7, 'end_lnum': 2, 'vcol': 1}, + \ {'text': 'a', 'lnum': 2, 'col': 7, 'vcol': 1}, + \ ], + \ ) diff --git a/test/test_loclist_jumping.vader b/test/test_loclist_jumping.vader new file mode 100644 index 00000000..8ec4e583 --- /dev/null +++ b/test/test_loclist_jumping.vader @@ -0,0 +1,121 @@ +Before: + let g:ale_buffer_info = { + \ bufnr(''): { + \ 'loclist': [ + \ {'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': 'W', 'sub_type': 'style', 'bufnr': bufnr(''), 'lnum': 2, 'col': 1}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 2}, + \ {'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, 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, a:filter, + \ a:subtype_filter) + endif + + return getcurpos()[1:2] + endfunction + +After: + let g:ale_buffer_info = {} + delfunction TestJump + +Given foobar (Some imaginary filetype): + 12345678 + 12345678 + +Execute(loclist jumping should jump correctly when not wrapping): + 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, '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, '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, '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, 'any', 'any', [1, 6]) + +Execute(We should be able to jump to the first item): + 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, 'any', 'any', [1, 6]) + +Execute(We should be able to jump when the error line is blank): + " Add a blank line at the end. + call setline(1, getline('.', '$') + ['']) + " Add a problem on the blank line. + call add(g:ale_buffer_info[bufnr('%')].loclist, {'type': 'E', 'bufnr': bufnr(''), 'lnum': 3, 'col': 1}) + + AssertEqual 0, len(getline(3)) + 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]) + +Execute(ALE should jump to column 1 instead of 0): + let g:ale_buffer_info = { + \ bufnr(''): { + \ 'loclist': [ + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 1, 'col': 5}, + \ {'type': 'E', 'bufnr': bufnr(''), 'lnum': 2, 'col': 0}, + \ ], + \ }, + \} + + AssertEqual [2, 1], TestJump('after', 1, 'any', 'any', [1, 5]) + AssertEqual [1, 5], TestJump('after', 1, 'any', 'any', [2, 1]) + AssertEqual [2, 1], TestJump('before', 1, 'any', 'any', [1, 5]) + AssertEqual [1, 5], TestJump('before', 1, 'any', 'any', [2, 1]) diff --git a/test/test_loclist_sorting.vader b/test/test_loclist_sorting.vader new file mode 100644 index 00000000..376e743a --- /dev/null +++ b/test/test_loclist_sorting.vader @@ -0,0 +1,43 @@ +Execute(loclist item should be sorted): + AssertEqual [ + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ ], + \ sort([ + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \], 'ale#util#LocItemCompare') + +Execute(Items should be sorted in by their problem priority when they lie on the same column): + AssertEqual [ + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'I'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E'}, + \ ], + \ sort([ + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'I'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': 1, 'lnum': 1, 'col': 1, 'type': 'W', 'sub_type': 'style'}, + \], 'ale#util#LocItemCompare') diff --git a/test/test_maven_build_classpath_command.vader b/test/test_maven_build_classpath_command.vader new file mode 100644 index 00000000..c10f457b --- /dev/null +++ b/test/test_maven_build_classpath_command.vader @@ -0,0 +1,53 @@ +Before: + Save $PATH + Save $PATHEXT + + let $PATHEXT = '.' + + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/java/javac.vim + let g:expected_wrapper = '' + if has('unix') + let g:expected_wrapper = 'mvnw' + else + let g:expected_wrapper = 'mvnw.cmd' + endif + +After: + Restore + + unlet! g:expected_wrapper + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should use 'mvnw' in classpath command if available): + call ale#test#SetFilename('test-files/maven/maven-java-project/module1/src/main/java/dummy1.java') + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir . '/test-files/maven/maven-java-project/module1'), + \ ale#Escape(ale#path#Simplify(g:dir . '/test-files/maven/maven-java-project/module1/' . g:expected_wrapper)) + \ . ' dependency:build-classpath', + \ ], + \ ale#maven#BuildClasspathCommand(bufnr('')) + +Execute(Should use 'mvn' in classpath command if it is executable and 'mvnw' is unavailable): + call ale#test#SetFilename('test-files/maven/maven-java-project/module2/src/main/java/dummy2.java') + let $PATH .= (has('win32') ? ';' : ':') + \ . ale#path#Simplify(g:dir . '/test-files/maven') + + AssertEqual + \ [ + \ ale#path#Simplify(g:dir . '/test-files/maven/maven-java-project/module2'), + \ ale#Escape('mvn') + \ . ' dependency:build-classpath', + \ ], + \ ale#maven#BuildClasspathCommand(bufnr('')) + +Execute(Should return empty string if maven cannot be executed): + call ale#test#SetFilename('test-files/maven/non-maven-project/src/main/java/dummy.java') + + AssertEqual + \ ['', ''], + \ ale#maven#BuildClasspathCommand(bufnr('')) diff --git a/test/test_maven_find_executable.vader b/test/test_maven_find_executable.vader new file mode 100644 index 00000000..f0f06b12 --- /dev/null +++ b/test/test_maven_find_executable.vader @@ -0,0 +1,46 @@ +Before: + Save $PATH + Save $PATHEXT + + " Count the maven executable without .exe as executable on Windows + let $PATHEXT = '.' + + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/java/javac.vim + let g:expected_wrapper = '' + if has('unix') + let g:expected_wrapper = 'mvnw' + else + let g:expected_wrapper = 'mvnw.cmd' + endif + +After: + Restore + + unlet! g:expected_wrapper + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should return 'mvnw' if found in parent directory): + call ale#test#SetFilename('test-files/maven/maven-java-project/module1/src/main/java/dummy1.java') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/maven/maven-java-project/module1/' . g:expected_wrapper), + \ ale#maven#FindExecutable(bufnr('')) + +Execute(Should return 'mvn' if 'mvnw' not found in parent directory): + call ale#test#SetFilename('test-files/maven/maven-java-project/module2/src/main/java/dummy2.java') + let $PATH .= (has('win32') ? ';' : ':') + \ . ale#path#Simplify(g:dir . '/test-files/maven') + + AssertEqual + \ 'mvn', + \ ale#maven#FindExecutable(bufnr('')) + +Execute(Should return empty string if 'mvnw' not in parent directory and mvn not in path): + call ale#test#SetFilename('mvn-test-files/java-maven-project/module2/src/main/java/dummy2.java') + + AssertEqual + \ '', + \ ale#gradle#FindExecutable(bufnr('')) diff --git a/test/test_maven_find_project_root.vader b/test/test_maven_find_project_root.vader new file mode 100644 index 00000000..f761b2ef --- /dev/null +++ b/test/test_maven_find_project_root.vader @@ -0,0 +1,28 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/kotlin/javac.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(Should return directory for 'mvnw' if found in parent directory): + call ale#test#SetFilename('test-files/maven/maven-java-project/module1/src/main/java/dummy1.java') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/maven/maven-java-project/module1'), + \ ale#maven#FindProjectRoot(bufnr('')) + +Execute(Should return directory for 'pom.xml' if found in parent directory): + call ale#test#SetFilename('test-files/maven/maven-java-project/module2/src/main/java/dummy2.java') + + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/maven/maven-java-project/module2'), + \ ale#maven#FindProjectRoot(bufnr('')) + +Execute(Should return empty string if maven files are not found in parent directory): + call ale#test#SetFilename('test-files/maven/non-maven-project/src/main/java/dummy.java') + + AssertEqual + \ '', + \ ale#maven#FindProjectRoot(bufnr('')) diff --git a/test/test_nearest_file_search.vader b/test/test_nearest_file_search.vader new file mode 100644 index 00000000..f5c12de4 --- /dev/null +++ b/test/test_nearest_file_search.vader @@ -0,0 +1,15 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(We should be able to find a configuration file further up): + call ale#test#SetFilename('test-files/top/middle/bottom/dummy.txt') + + AssertEqual + \ ale#path#Simplify(expand('%:p:h:h:h:h:h') . '/test-files/top/example.ini'), + \ ale#path#FindNearestFile(bufnr('%'), 'example.ini') + +Execute(We shouldn't find anything for files which don't match): + AssertEqual '', ale#path#FindNearestFile(bufnr('%'), 'cantfindthis') diff --git a/test/test_neovim_diagnostics.vader b/test/test_neovim_diagnostics.vader new file mode 100644 index 00000000..4cef8d35 --- /dev/null +++ b/test/test_neovim_diagnostics.vader @@ -0,0 +1,71 @@ +Before: + Save g:ale_use_neovim_diagnostics_api + + function! CollectMessages(buffer) + let l:messages = [] + for l:diag in v:lua.vim.diagnostic.get(a:buffer) + call add(l:messages, l:diag.message) + endfor + + return l:messages + endfunction + + +After: + unlet! b:other_bufnr + delfunction CollectMessages + Restore + +Execute(Should only set diagnostics belonging to the given buffer): + if has('nvim-0.6') + let b:other_bufnr = bufnr('/foo/bar/baz', 1) + " Make sure we actually get another buffer number, or the test is invalid. + AssertNotEqual -1, b:other_bufnr + + let g:ale_use_neovim_diagnostics_api = 1 + + call ale#engine#SetResults(bufnr('%'), [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'A', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 10, + \ 'bufnr': b:other_bufnr, + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'B', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'C', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'bufnr': b:other_bufnr, + \ 'vcol': 0, + \ 'linter_name': 'bettercode', + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'D', + \ }, + \]) + + AssertEqual ["A", "C"], CollectMessages(bufnr('%')) + endif diff --git a/test/test_no_linting_on_write_quit.vader b/test/test_no_linting_on_write_quit.vader new file mode 100644 index 00000000..161e6165 --- /dev/null +++ b/test/test_no_linting_on_write_quit.vader @@ -0,0 +1,118 @@ +Before: + Save g:ale_echo_cursor + Save g:ale_fix_on_save + Save g:ale_fixers + Save g:ale_lint_on_save + Save g:ale_set_highlights + Save g:ale_set_lists_synchronously + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_set_signs + + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + " Disable the things we don't need, but leave enabled what we do. + let g:ale_echo_cursor = 0 + let g:ale_set_signs = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 1 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + + function! TestCallback(buffer, output) + return [{'lnum': 1, 'col': 1, 'text': 'xxx'}] + endfunction + + function AddLine(buffer, lines) abort + return a:lines + ['x'] + endfunction + + let g:ale_fixers = { + \ 'testft': ['AddLine'], + \} + + call ale#linter#PreventLoading('testft') + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'true', + \}) + +Given testft (An empty file): + +After: + Restore + + unlet! g:ale_run_synchronously + unlet! b:ale_quitting + delfunction TestCallback + delfunction AddLine + + call ale#linter#Reset() + call setloclist(0, []) + +Execute(No linting should be done on :wq or :x): + let g:ale_lint_on_save = 1 + let g:ale_fix_on_save = 0 + + " 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#GetLoclistWithoutNewerKeys()) + + " Now try doing it again, but where we run the quit event first. + call setloclist(0, []) + call ale#events#QuitEvent(bufnr('')) + call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + +Execute(No linting should be for :w after :q fails): + let g:ale_lint_on_save = 1 + 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#GetLoclistWithoutNewerKeys()) + +Execute(No linting should be done on :wq or :x after fixing files): + let g:ale_lint_on_save = 1 + let g:ale_fix_on_save = 1 + + call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() + + AssertEqual 1, len(ale#test#GetLoclistWithoutNewerKeys()) + + " Now try doing it again, but where we run the quit event first. + call setloclist(0, []) + call ale#events#QuitEvent(bufnr('')) + call ale#events#SaveEvent(bufnr('')) + call ale#test#FlushJobs() + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + +Execute(Linting should be done after :q fails and fixing files): + let g:ale_lint_on_save = 1 + 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#GetLoclistWithoutNewerKeys()) diff --git a/test/test_organize_imports.vader b/test/test_organize_imports.vader new file mode 100644 index 00000000..64307029 --- /dev/null +++ b/test/test_organize_imports.vader @@ -0,0 +1,172 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:old_filename = expand('%:p') + let g:Callback = '' + let g:expr_list = [] + let g:message_list = [] + let g:handle_code_action_called = 0 + let g:code_actions = [] + let g:options = {} + let g:capability_checked = '' + let g:conn_id = 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/organize_imports.vim + runtime autoload/ale/code_action.vim + + 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) + + 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', + \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + function! ale#code_action#HandleCodeAction(code_action, options) abort + let g:handle_code_action_called = 1 + AssertEqual g:ale_save_hidden || !&hidden, get(a:options, 'should_save') + call add(g:code_actions, a:code_action) + endfunction + +After: + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#references#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + 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:code_actions + unlet! g:handle_code_action_called + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/organize_imports.vim + runtime autoload/ale/code_action.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#organize_imports#HandleTSServerResponse(1, {'command': 'foo'}) + AssertEqual g:handle_code_action_called, 0 + +Execute(Failed organizeImports responses should be handled correctly): + call ale#organize_imports#HandleTSServerResponse( + \ 1, + \ {'command': 'organizeImports', 'request_seq': 3} + \) + AssertEqual g:handle_code_action_called, 0 + +Execute(Code actions from tsserver should be handled): + call ale#organize_imports#HandleTSServerResponse(1, { + \ 'command': 'organizeImports', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [], + \}) + AssertEqual g:handle_code_action_called, 1 + AssertEqual [{ + \ 'description': 'Organize Imports', + \ 'changes': [], + \}], g:code_actions + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(tsserver organize imports requests should be sent): + call ale#linter#Reset() + runtime ale_linters/typescript/tsserver.vim + + ALEOrganizeImports + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual + \ 'function(''ale#organize_imports#HandleTSServerResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@organizeImports', { + \ 'scope': { + \ 'type': 'file', + \ 'args': { + \ 'file': expand('%:p'), + \ }, + \ }, + \ }] + \ ], + \ g:message_list + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Should result in error message for LSP): + call ale#linter#Reset() + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + + ALEOrganizeImports + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual [ + \ 'echom ''OrganizeImports currently only works with tsserver''', + \], g:expr_list diff --git a/test/test_other_sources.vader b/test/test_other_sources.vader new file mode 100644 index 00000000..45a7d306 --- /dev/null +++ b/test/test_other_sources.vader @@ -0,0 +1,153 @@ +Before: + Save g:ale_buffer_info + Save g:ale_set_signs + Save g:ale_set_quickfix + Save g:ale_set_loclist + Save g:ale_set_highlights + Save g:ale_echo_cursor + + let g:ale_buffer_info = {} + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + let g:ale_set_signs = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 1 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + + function! TestCallback(buffer, output) + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \}) + +After: + Restore + + unlet! b:ale_linters + unlet! g:want_results_signaled + unlet! g:want_results_buffer_value + unlet! g:lint_pre_signaled + unlet! g:ale_run_synchronously + unlet! g:ale_set_lists_synchronously + + delfunction TestCallback + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + + call ale#linter#Reset() + call setloclist(0, []) + +Given foobar (Some imaginary filetype): +Execute(StartChecking should mark a buffer as being actively checked): + Assert !ale#engine#IsCheckingBuffer(bufnr('')) + + call ale#other_source#StartChecking(bufnr(''), 'other-source-linter') + + Assert ale#engine#IsCheckingBuffer(bufnr('')) + +Execute(ShowResults should make a buffer inactive): + call ale#other_source#StartChecking(bufnr(''), 'other-source-linter') + call ale#other_source#StartChecking(bufnr(''), 'second-other-source-linter') + + call ale#other_source#ShowResults(bufnr(''), 'other-source-linter', []) + + Assert ale#engine#IsCheckingBuffer(bufnr('')) + + call ale#other_source#ShowResults(bufnr(''), 'second-other-source-linter', []) + + Assert !ale#engine#IsCheckingBuffer(bufnr('')) + +Execute(ShowResults should show results at any time): + call ale#other_source#ShowResults(bufnr(''), 'other-source-linter', [ + \ {'text': 'xyz', 'lnum': 1}, + \]) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': 'xyz', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + + call ale#other_source#ShowResults(bufnr(''), 'other-source-linter', []) + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + +Execute(A regular lint cycle shouldn't clear results from other sources): + call ale#other_source#ShowResults(bufnr(''), 'other-source-linter', [ + \ {'text': 'xyz', 'lnum': 1}, + \]) + ALELint + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'E', + \ 'pattern': '', + \ 'text': 'xyz', + \ }, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() + +Execute(ALEWantResults should be signaled when a buffer is checked): + augroup VaderTest + autocmd! + autocmd User ALEWantResults let g:want_results_signaled = 1 + autocmd User ALELintPre let g:lint_pre_signaled = 1 + augroup END + + " Even when all linters are disabled, we should send the signal. + let b:ale_linters = [] + ALELint + + Assert get(g:, 'want_results_signaled') + Assert !get(g:, 'lint_pre_signaled') + +Execute(ALEWantResults should set a variable indicating which buffer is being checked): + augroup VaderTest + autocmd! + autocmd User ALEWantResults let g:want_results_buffer_value = g:ale_want_results_buffer + augroup END + + let b:ale_linters = [] + ALELint + + AssertEqual bufnr(''), g:want_results_buffer_value + +Execute(ALEWantResults should lead to an ALELintPre signal if another source responds): + augroup VaderTest + autocmd! + autocmd User ALEWantResults call ale#other_source#StartChecking(bufnr(''), 'other-source-linter') + autocmd User ALELintPre let g:lint_pre_signaled = 1 + augroup END + + " Even when all linters are disabled, we should send the signal. + let b:ale_linters = [] + ALELint + + Assert get(g:, 'lint_pre_signaled') diff --git a/test/test_parse_command_args.vader b/test/test_parse_command_args.vader new file mode 100644 index 00000000..6a647610 --- /dev/null +++ b/test/test_parse_command_args.vader @@ -0,0 +1,31 @@ +After: + unlet! b:parse_result + + if exists(':ParseTest') + delcommand ParseTest + endif + +Execute(ale#args#Parse should work for an example command): + command! -nargs=* ParseTest let b:parse_result = ale#args#Parse(['foo', 'bar'], ) + + ParseTest + AssertEqual [{}, ''], b:parse_result + + ParseTest -- + AssertEqual [{}, ''], b:parse_result + + ParseTest -foo + AssertEqual [{'foo': ''}, ''], 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 + +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 diff --git a/test/test_path_dirname.vader b/test/test_path_dirname.vader new file mode 100644 index 00000000..818a62a8 --- /dev/null +++ b/test/test_path_dirname.vader @@ -0,0 +1,13 @@ +Execute(ale#path#Dirname should return empty strings should be returned for empty values): + AssertEqual '', ale#path#Dirname('') + AssertEqual '', ale#path#Dirname(0) + AssertEqual '', ale#path#Dirname(v:null) + +Execute(ale#path#Dirname should return the dirname of paths): + AssertEqual '/foo', ale#path#Dirname('/foo/bar') + AssertEqual '/foo', ale#path#Dirname('/foo/bar/') + + if has('win32') + AssertEqual 'C:\foo', ale#path#Dirname('C:\foo\bar') + AssertEqual 'C:\foo', ale#path#Dirname('C:\foo\bar\') + endif diff --git a/test/test_path_equality.vader b/test/test_path_equality.vader new file mode 100644 index 00000000..ee6ae2c5 --- /dev/null +++ b/test/test_path_equality.vader @@ -0,0 +1,82 @@ +Before: + function! CheckPath(path) abort + return ale#path#IsBufferPath(bufnr(''), ale#path#Simplify(a:path)) + endfunction + +After: + delfunction CheckPath + +Execute(ale#path#Simplify should adjust paths correctly): + if has('unix') + " Multiple slashes should be removed correctly. + AssertEqual '/foo/bar/baz', ale#path#Simplify('////foo///bar///baz') + " Back slashes should be converted to forward slashes. + " This means some valid filenames are adjusted incorrectly, but in practice + " no filenames for code should contain back slashes, and adjusting slashes + " like this makes ALE work in MSYS. + AssertEqual 'foo/bar/baz', ale#path#Simplify('foo\bar\baz') + else + " Multiple slashes should be removed correctly. + AssertEqual '\foo\bar\baz', ale#path#Simplify('\\\foo\bar\baz') + " Forward slashes should be replaced with back slashes. + AssertEqual 'foo\bar\baz', ale#path#Simplify('foo/bar/baz') + endif + +Execute(ale#path#IsBufferPath should match simple relative paths): + call ale#test#SetFilename('app/foo.txt') + + Assert CheckPath('app/foo.txt'), 'No match for foo.txt' + Assert !CheckPath('app/bar.txt'), 'Bad match for bar.txt' + +Execute(ale#path#IsBufferPath should match relative paths with dots): + call ale#test#SetFilename('app/foo.txt') + + " Skip these checks on Windows. + if !has('win32') + Assert CheckPath('../../app/foo.txt'), 'No match for ../../app/foo.txt' + endif + +Execute(ale#path#IsBufferPath should match absolute paths): + silent file! foo.txt + + Assert CheckPath(getcwd() . '/foo.txt'), 'No match for foo.txt' + Assert !CheckPath(getcwd() . '/bar.txt'), 'Bad match for bar.txt' + +Execute(ale#path#IsBufferPath should match paths beginning with ./): + silent file! foo.txt + + if !has('win32') + Assert ale#path#IsBufferPath(bufnr(''), './foo.txt'), 'No match for ./foo.txt' + endif + +Execute(ale#path#IsBufferPath should match if our path ends with the test path): + silent file! foo/bar/baz.txt + + Assert CheckPath('bar/baz.txt'), 'No match for bar/baz.txt' + +Execute(ale#path#IsBufferPath should match paths with redundant slashes): + silent file! foo.txt + + Assert CheckPath(getcwd() . '////foo.txt'), 'No match for foo.txt' + +Execute(ale#path#IsBufferPath should accept various names for stdin): + Assert ale#path#IsBufferPath(bufnr(''), '-') + Assert ale#path#IsBufferPath(bufnr(''), 'stdin') + Assert ale#path#IsBufferPath(bufnr(''), '') + Assert ale#path#IsBufferPath(bufnr(''), '') + +Execute(ale#path#IsBufferPath should match files in /tmp): + call ale#test#SetFilename('app/test.ts') + + Assert ale#path#IsBufferPath(bufnr(''), tempname() . '/test.ts') + +Execute(ale#path#IsBufferPath should match Windows paths on Unix): + " This test should pass Unix. + " + " Back slashes in paths should be replaced with forward slashes, even though + " back slashes are valid in filenames on Unix. + if has('unix') + call ale#test#SetFilename('app/foo/test.ts') + + Assert ale#path#IsBufferPath(bufnr(''), 'foo\test.ts') + endif diff --git a/test/test_path_upwards.vader b/test/test_path_upwards.vader new file mode 100644 index 00000000..cd461a28 --- /dev/null +++ b/test/test_path_upwards.vader @@ -0,0 +1,48 @@ +Execute(ale#path#Upwards should return the correct path components): + if has('unix') + " Absolute paths should include / on the end. + AssertEqual + \ ['/foo/bar/baz', '/foo/bar', '/foo', '/'], + \ ale#path#Upwards('/foo/bar/baz') + AssertEqual + \ ['/foo/bar/baz', '/foo/bar', '/foo', '/'], + \ ale#path#Upwards('/foo/bar/baz///') + " Relative paths do not. + AssertEqual + \ ['foo/bar/baz', 'foo/bar', 'foo'], + \ ale#path#Upwards('foo/bar/baz') + AssertEqual + \ ['foo2/bar', 'foo2'], + \ ale#path#Upwards('foo//..////foo2////bar') + " Expect an empty List for empty strings. + AssertEqual [], ale#path#Upwards('') + endif + + if has('win32') + AssertEqual + \ ['C:\foo\bar\baz', 'C:\foo\bar', 'C:\foo', 'C:\'], + \ ale#path#Upwards('C:\foo\bar\baz') + AssertEqual + \ ['C:\foo\bar\baz', 'C:\foo\bar', 'C:\foo', 'C:\'], + \ ale#path#Upwards('C:\foo\bar\baz\\\') + AssertEqual + \ ['/foo\bar\baz', '/foo\bar', '/foo', '/'], + \ ale#path#Upwards('/foo/bar/baz') + AssertEqual + \ ['foo\bar\baz', 'foo\bar', 'foo'], + \ ale#path#Upwards('foo/bar/baz') + AssertEqual + \ ['foo\bar\baz', 'foo\bar', 'foo'], + \ ale#path#Upwards('foo\bar\baz') + " simplify() is used internally, and should sort out \ paths when actually + " running Windows, which we can't test here. + AssertEqual + \ ['foo2\bar', 'foo2'], + \ ale#path#Upwards('foo//..///foo2////bar') + " Expect an empty List for empty strings. + AssertEqual [], ale#path#Upwards('') + " Paths starting with // return / + AssertEqual + \ ['/foo2\bar', '/foo2', '/'], + \ ale#path#Upwards('//foo//..///foo2////bar') + endif diff --git a/test/test_path_uri.vader b/test/test_path_uri.vader new file mode 100644 index 00000000..e2daccf4 --- /dev/null +++ b/test/test_path_uri.vader @@ -0,0 +1,73 @@ +Before: + scriptencoding utf-8 + +Execute(ale#path#ToFileURI should work for Windows paths): + AssertEqual 'file:///C:/foo/bar/baz.tst', ale#path#ToFileURI('C:\foo\bar\baz.tst') + AssertEqual 'foo/bar/baz.tst', ale#path#ToFileURI('foo\bar\baz.tst') + +Execute(ale#path#FromFileURI should work for Unix paths): + AssertEqual '/foo/bar/baz.tst', ale#path#FromFileURI('file:///foo/bar/baz.tst') + AssertEqual '/foo/bar/baz.tst', ale#path#FromFileURI('file:/foo/bar/baz.tst') + AssertEqual '/foo/bar/baz.tst', ale#path#FromFileURI('FILE:///foo/bar/baz.tst') + AssertEqual '/foo/bar/baz.tst', ale#path#FromFileURI('FILE:/foo/bar/baz.tst') + +Execute(ale#path#FromFileURI should work for Windows paths): + if has('win32') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('file:///C:/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('file:/C:/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromFileURI('file:///c:/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromFileURI('file:/c:/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('FILE:///C:/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('FILE:/C:/foo/bar/baz.tst') + else + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromFileURI('file:///C:/foo/bar/baz.tst') + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromFileURI('file:/C:/foo/bar/baz.tst') + AssertEqual '/c:/foo/bar/baz.tst', ale#path#FromFileURI('file:///c:/foo/bar/baz.tst') + AssertEqual '/c:/foo/bar/baz.tst', ale#path#FromFileURI('file:/c:/foo/bar/baz.tst') + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromFileURI('FILE:///C:/foo/bar/baz.tst') + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromFileURI('FILE:/C:/foo/bar/baz.tst') + endif + +Execute(ale#path#FromFileURI parse Windows paths with a pipe): + if has('win32') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('file:///C|/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('file:/C|/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromFileURI('file:///c|/foo/bar/baz.tst') + AssertEqual 'c:\foo\bar\baz.tst', ale#path#FromFileURI('file:/c|/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('FILE:///C|/foo/bar/baz.tst') + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromFileURI('FILE:/C|/foo/bar/baz.tst') + else + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromFileURI('file:///C|/foo/bar/baz.tst') + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromFileURI('file:/C|/foo/bar/baz.tst') + AssertEqual '/c|/foo/bar/baz.tst', ale#path#FromFileURI('file:///c|/foo/bar/baz.tst') + AssertEqual '/c|/foo/bar/baz.tst', ale#path#FromFileURI('file:/c|/foo/bar/baz.tst') + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromFileURI('FILE:///C|/foo/bar/baz.tst') + AssertEqual '/C|/foo/bar/baz.tst', ale#path#FromFileURI('FILE:/C|/foo/bar/baz.tst') + endif + +Execute(ale#path#FromFileURI 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#FromFileURI('file:///C%3A/foo/bar/baz.tst') + else + AssertEqual '/C:/foo/bar/baz.tst', ale#path#FromFileURI('file:///C%3A/foo/bar/baz.tst') + endif + +Execute(ale#path#ToFileURI should work for Unix paths): + AssertEqual 'file:///foo/bar/baz.tst', ale#path#ToFileURI('/foo/bar/baz.tst') + AssertEqual 'foo/bar/baz.tst', ale#path#ToFileURI('foo/bar/baz.tst') + +Execute(ale#path#ToFileURI should keep safe characters): + AssertEqual '//a-zA-Z0-9$-_.!*''(),', ale#path#ToFileURI('\/a-zA-Z0-9$-_.!*''(),') + +Execute(ale#path#ToFileURI should percent encode unsafe characters): + AssertEqual '%20%2b%3a%3f%26%3d', ale#path#ToFileURI(' +:?&=') + +Execute(ale#path#FromFileURI should decode percent encodings): + AssertEqual ' +:?&=', ale#path#FromFileURI('%20%2b%3a%3f%26%3d') + +Execute(ale#path#ToFileURI should handle UTF-8): + AssertEqual 'file:///T%c3%a9l%c3%a9chargement', ale#path#ToFileURI('/Téléchargement') + +Execute(ale#path#FromFileURI should handle UTF-8): + AssertEqual '/Téléchargement', ale#path#FromFileURI('file:///T%C3%A9l%C3%A9chargement') diff --git a/test/test_pattern_options.vader b/test/test_pattern_options.vader new file mode 100644 index 00000000..3b6500e5 --- /dev/null +++ b/test/test_pattern_options.vader @@ -0,0 +1,107 @@ +Before: + Save g:ale_pattern_options + Save g:ale_pattern_options_enabled + Save b:ale_quitting + Save b:ale_original_filetype + Save &filetype + + unlet! b:ale_file_changed + + let g:ale_pattern_options_enabled = 1 + let g:ale_pattern_options = {} + + let b:ale_enabled = 0 + let b:some_option = 0 + + call ale#test#SetDirectory('/testplugin/test') + +After: + Restore + + unlet! b:ale_enabled + unlet! b:some_option + + call ale#test#RestoreDirectory() + +Execute(The pattern options function should work when there are no patterns): + call ale#test#SetFilename('foobar.js') + call ale#events#ReadOrEnterEvent(bufnr('')) + +Execute(Buffer variables should be set when filename patterns match): + let g:ale_pattern_options = { + \ 'baz.*\.js': { + \ 'ale_enabled': 1, + \ 'some_option': 347, + \ '&filetype': 'pattern_option_set_filetype', + \ }, + \} + + call ale#test#SetFilename('foobar.js') + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 0, b:ale_enabled + AssertEqual 0, b:some_option + + call ale#test#SetFilename('bazboz.js') + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 1, b:ale_enabled + AssertEqual 347, b:some_option + AssertEqual 'pattern_option_set_filetype', &filetype + +Execute(Multiple pattern matches should be applied): + let g:ale_pattern_options = { + \ 'foo': { + \ 'some_option': 666, + \ }, + \ 'bar': { + \ 'ale_enabled': 1, + \ 'some_option': 123, + \ }, + \ 'notmatched': { + \ 'some_option': 489, + \ 'ale_enabled': 0, + \ }, + \} + + call ale#test#SetFilename('foobar.js') + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 1, b:ale_enabled + AssertEqual 666, b:some_option + +Execute(Patterns should not be applied when the setting is disabled): + let g:ale_pattern_options_enabled = 0 + let g:ale_pattern_options = {'foo': {'some_option': 666}} + + call ale#test#SetFilename('foobar.js') + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 0, b:some_option + +" This test is important for making sure we update the sorted items. +Execute(Patterns should be applied after the Dictionary changes): + call ale#test#SetFilename('foobar.js') + + let g:ale_pattern_options = {} + + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 0, b:some_option + + let g:ale_pattern_options['foo'] = {'some_option': 666} + + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 666, b:some_option + +Execute(SetOptions should tolerate settings being unset): + " This might happen if ALE is loaded in a weird way, so tolerate it. + unlet! g:ale_pattern_options + unlet! g:ale_pattern_options_enabled + + call ale#events#ReadOrEnterEvent(bufnr('')) + + let g:ale_pattern_options_enabled = 1 + + call ale#events#ReadOrEnterEvent(bufnr('')) diff --git a/test/test_prepare_command.vader b/test/test_prepare_command.vader new file mode 100644 index 00000000..4e963b82 --- /dev/null +++ b/test/test_prepare_command.vader @@ -0,0 +1,96 @@ +Before: + Save &shell + Save &shellcmdflag + Save g:ale_shell + Save g:ale_shell_arguments + + Save b:ale_shell + Save b:ale_shell_arguments + + unlet! b:ale_shell + unlet! b:ale_shell_arguments + + unlet! g:ale_shell + unlet! g:ale_shell_arguments + +After: + Restore + +Execute(sh should be used when the shell is fish): + if !has('win32') + " Set something else, so we will replace that too. + let &shellcmdflag = '-f' + let &shell = 'fish' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + + let &shell = '/usr/bin/fish' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + + let &shell = '/usr/local/bin/fish' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + endif + +Execute(sh should be used when the shell is powershell): + if !has('win32') + " Set something else, so we will replace that too. + let &shellcmdflag = '-f' + let &shell = 'pwsh' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + + let &shell = '/usr/bin/pwsh' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + + let &shell = '/usr/local/bin/pwsh' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + endif + +Execute(Other shells should be used when set): + if !has('win32') + let &shell = '/bin/bash' + let &shellcmdflag = '-c' + let g:ale_shell = &shell + + AssertEqual ['/bin/bash', '-c', 'foobar'], ale#job#PrepareCommand(bufnr(''), 'foobar') + endif + +Execute(cmd /s/c as a string should be used on Windows): + if has('win32') + let &shell = 'who cares' + let &shellcmdflag = 'whatever' + + AssertEqual 'cmd /s/c "foobar"', ale#job#PrepareCommand(bufnr(''), 'foobar') + endif + +Execute(Setting g: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") + +Execute(Setting b:ale_shell should cause ale#job#PrepareCommand to use set shell): + let g:ale_shell = '/wrong/foo/bar' + let b: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 = '--verbose -x' + let b:ale_shell_arguments = '-x' + + AssertEqual ['/foo/bar', '-x', 'foobar'], ale#job#PrepareCommand(bufnr(''), "foobar") diff --git a/test/test_python_pipenv.vader b/test/test_python_pipenv.vader new file mode 100644 index 00000000..041e2874 --- /dev/null +++ b/test/test_python_pipenv.vader @@ -0,0 +1,19 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(ale#python#PipenvPresent is true when a pipenv environment is present): + call ale#test#SetFilename('test-files/python/pipenv/whatever.py') + + AssertEqual + \ ale#python#PipenvPresent(bufnr('%')), + \ 1 + +Execute(ale#python#PipenvPresent is false when no pipenv environment is present): + call ale#test#SetFilename('test-files/python/no_pipenv/whatever.py') + + AssertEqual + \ ale#python#PipenvPresent(bufnr('%')), + \ 0 diff --git a/test/test_python_poetry.vader b/test/test_python_poetry.vader new file mode 100644 index 00000000..8197b786 --- /dev/null +++ b/test/test_python_poetry.vader @@ -0,0 +1,19 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(ale#python#poetryPresent is true when a poetry environment is present): + call ale#test#SetFilename('test-files/python/poetry/whatever.py') + + AssertEqual + \ ale#python#PoetryPresent(bufnr('%')), + \ 1 + +Execute(ale#python#poetryPresent is false when no poetry environment is present): + call ale#test#SetFilename('test-files/python/no_poetry/whatever.py') + + AssertEqual + \ ale#python#PoetryPresent(bufnr('%')), + \ 0 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 ', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ ], "\n"), + \ }], + \ ale#python#HandleTraceback([ + \ 'Traceback (most recent call last):', + \ ' File "./example.py", line 5, in ', + \ ' 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 "", line 1, in ', + \ ' File "example.py", line 5', + \ ' +', + \ ' ^', + \ 'SyntaxError: invalid syntax', + \ ], "\n"), + \ }], + \ ale#python#HandleTraceback([ + \ 'Traceback (most recent call last):', + \ ' File "", line 1, in ', + \ ' 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 ', + \ ' 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 ', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ ], "\n"), + \ }], + \ ale#python#HandleTraceback([ + \ 'Traceback (most recent call last):', + \ ' File "./example.py", line 5, in ', + \ ' raise Exception(''Example message'')', + \ 'Exception: Example error', + \ 'file:1:2: Style issue', + \ 'file:3:4: Non-style issue', + \ ], 1) diff --git a/test/test_python_uv.vader b/test/test_python_uv.vader new file mode 100644 index 00000000..11d202fd --- /dev/null +++ b/test/test_python_uv.vader @@ -0,0 +1,19 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(ale#python#UvPresent is true when a uv environment is present): + call ale#test#SetFilename('test-files/python/uv/whatever.py') + + AssertEqual + \ ale#python#UvPresent(bufnr('%')), + \ 1 + +Execute(ale#python#UvPresent is false when no uv environment is present): + call ale#test#SetFilename('test-files/python/no_uv/whatever.py') + + AssertEqual + \ ale#python#UvPresent(bufnr('%')), + \ 0 diff --git a/test/test_python_virtualenv.vader b/test/test_python_virtualenv.vader new file mode 100644 index 00000000..25699873 --- /dev/null +++ b/test/test_python_virtualenv.vader @@ -0,0 +1,23 @@ +Before: + Save $VIRTUAL_ENV + let $VIRTUAL_ENV = "/opt/example/" + +After: + Restore + +Execute(ale#python#FindVirtualenv falls back to $VIRTUAL_ENV when no directories match): + AssertEqual + \ ale#python#FindVirtualenv(bufnr('%')), + \ '/opt/example/', + \ 'Expected VIRTUAL_ENV environment variable to be used, but it was not' + +Execute(ale#python#AutoVirtualenvEnvString should return the correct values): + if has('win32') + AssertEqual + \ 'set PATH=/opt/example/\Scripts;%PATH% && set VIRTUAL_ENV=/opt/example/ && ', + \ ale#python#AutoVirtualenvEnvString(bufnr('')) + else + AssertEqual + \ 'PATH=''/opt/example//bin''":$PATH" VIRTUAL_ENV=''/opt/example/'' ', + \ ale#python#AutoVirtualenvEnvString(bufnr('')) + endif diff --git a/test/test_quickfix_deduplication.vader b/test/test_quickfix_deduplication.vader new file mode 100644 index 00000000..9cb8b931 --- /dev/null +++ b/test/test_quickfix_deduplication.vader @@ -0,0 +1,50 @@ +Before: + Save g:ale_buffer_info + +After: + Restore + +Execute: + " Results from multiple buffers should be gathered together. + " Equal problems should be de-duplicated. + let g:ale_buffer_info = { + \ '1': {'loclist': [ + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 3, 'lnum': 1, 'col': 1, 'text': 'foo'}, + \ ]}, + \ '2': {'loclist': [ + \ {'type': 'E', 'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'another error'}, + \ ]}, + \} + + AssertEqual + \ [ + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'another error'}, + \ {'type': 'E', 'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'type': 'E', 'bufnr': 3, 'lnum': 1, 'col': 1, 'text': 'foo'}, + \ ], + \ ale#list#GetCombinedList() diff --git a/test/test_quitting_variable.vader b/test/test_quitting_variable.vader new file mode 100644 index 00000000..32eb0c36 --- /dev/null +++ b/test/test_quitting_variable.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_enabled + + unlet! b:ale_quitting + let g:ale_enabled = 0 + +After: + Restore + + unlet! b:ale_quitting + unlet! b:time_before + +Execute(QuitEvent should set b:ale_quitting some time from the clock): + let b:time_before = ale#events#ClockMilliseconds() + + call ale#events#QuitEvent(bufnr('')) + + Assert b:ale_quitting >= b:time_before + Assert b:ale_quitting <= ale#events#ClockMilliseconds() + +Execute(ReadOrEnterEvent should set b:ale_quitting to 0): + let b:ale_quitting = 1 + + call ale#events#ReadOrEnterEvent(bufnr('')) + + AssertEqual 0, b:ale_quitting + +Execute(The QuitRecently function should work when the variable isn't set): + AssertEqual 0, ale#events#QuitRecently(bufnr('')) + +Execute(The QuitRecently function should return 1 when ALE quit recently): + let b:ale_quitting = ale#events#ClockMilliseconds() + + AssertEqual 1, ale#events#QuitRecently(bufnr('')) + +Execute(The QuitRecently function should return 0 when a second has passed): + let b:ale_quitting = ale#events#ClockMilliseconds() - 1001 + + AssertEqual 0, ale#events#QuitRecently(bufnr('')) diff --git a/test/test_redundant_tsserver_rendering_avoided.vader b/test/test_redundant_tsserver_rendering_avoided.vader new file mode 100644 index 00000000..05660d4e --- /dev/null +++ b/test/test_redundant_tsserver_rendering_avoided.vader @@ -0,0 +1,178 @@ +Before: + Save g:ale_buffer_info + Save g:ale_disable_lsp + Save g:ale_lsp_suggestions + + let g:ale_disable_lsp = 0 + let g:ale_lsp_suggestions = 1 + unlet! b:ale_disable_lsp + + function! CreateError(type, message) abort + let l:diagnostics = [] + + if !empty(a:message) + let l:diagnostics = [{ + \ 'start': {'line': 1, 'offset': 1}, + \ 'end': {'line': 1, 'offset':1}, + \ 'text': a:message, + \ 'code': 1005, + \}] + endif + + return { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': a:type, + \ 'body': {'file': expand('%:p'), 'diagnostics': l:diagnostics}, + \} + endfunction + + function! CreateLoclist(message) abort + let l:list = [] + + if !empty(a:message) + let l:list = [{ + \ 'lnum': 1, + \ 'col': 1, + \ 'end_lnum': 1, + \ 'end_col': 1, + \ 'text': a:message, + \}] + endif + + return l:list + endfunction + + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('filename.ts') + + runtime autoload/ale/engine.vim + + 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 + let g:ale_handle_loclist_called = 1 + endfunction + +After: + Restore + + delfunction CreateError + delfunction CreateLoclist + + call ale#test#RestoreDirectory() + + runtime autoload/ale/engine.vim + +Execute(An initial empty list of syntax errors should be ignored): + call ale#lsp_linter#HandleLSPResponse(1, CreateError('syntaxDiag', '')) + + Assert !g:ale_handle_loclist_called + +Execute(An initial list of syntax errors should be handled): + call ale#lsp_linter#HandleLSPResponse(1, CreateError('syntaxDiag', 'x')) + + Assert g:ale_handle_loclist_called + +Execute(Subsequent empty lists should be ignored): + let g:ale_buffer_info[bufnr('')].syntax_loclist = [] + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('syntaxDiag', '')) + + Assert !g:ale_handle_loclist_called + +Execute(Empty then non-empty syntax errors should be handled): + let g:ale_buffer_info[bufnr('')].syntax_loclist = [] + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('syntaxDiag', 'x')) + + Assert g:ale_handle_loclist_called + +Execute(Non-empty then empty syntax errors should be handled): + let g:ale_buffer_info[bufnr('')].syntax_loclist = CreateLoclist('x') + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('syntaxDiag', '')) + + Assert g:ale_handle_loclist_called + +Execute(Non-empty then non-empty syntax errors should be handled): + let g:ale_buffer_info[bufnr('')].syntax_loclist = CreateLoclist('x') + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('syntaxDiag', 'x')) + + Assert g:ale_handle_loclist_called + +Execute(An initial empty list of semantic errors should be ignored): + call ale#lsp_linter#HandleLSPResponse(1, CreateError('semanticDiag', '')) + + Assert !g:ale_handle_loclist_called + +Execute(An initial list of semantic errors should be handled): + call ale#lsp_linter#HandleLSPResponse(1, CreateError('semanticDiag', 'x')) + + Assert g:ale_handle_loclist_called + +Execute(Subsequent empty lists should be ignored - semantic): + let g:ale_buffer_info[bufnr('')].semantic_loclist = [] + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('semanticDiag', '')) + + Assert !g:ale_handle_loclist_called + +Execute(Empty then non-empty semantic errors should be handled): + let g:ale_buffer_info[bufnr('')].semantic_loclist = [] + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('semanticDiag', 'x')) + + Assert g:ale_handle_loclist_called + +Execute(Non-empty then empty semantic errors should be handled): + let g:ale_buffer_info[bufnr('')].semantic_loclist = CreateLoclist('x') + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('semanticDiag', '')) + + Assert g:ale_handle_loclist_called + +Execute(Non-empty then non-empty semantic errors should be handled): + let g:ale_buffer_info[bufnr('')].semantic_loclist = CreateLoclist('x') + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('semanticDiag', 'x')) + + Assert g:ale_handle_loclist_called + +Execute(Subsequent empty lists should be ignored - suggestion): + let g:ale_buffer_info[bufnr('')].suggestion_loclist = [] + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('suggestionDiag', '')) + + Assert !g:ale_handle_loclist_called + +Execute(You should be able to disable suggestions): + let g:ale_lsp_suggestions = 0 + let g:ale_buffer_info[bufnr('')].suggestion_loclist = [] + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('suggestionDiag', 'x')) + + Assert !g:ale_handle_loclist_called + +Execute(Empty then non-empty suggestion messages should be handled): + let g:ale_buffer_info[bufnr('')].suggestion_loclist = [] + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('suggestionDiag', 'x')) + + Assert g:ale_handle_loclist_called + +Execute(Non-empty then empt suggestion messages should be handled): + let g:ale_buffer_info[bufnr('')].suggestion_loclist = CreateLoclist('x') + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('suggestionDiag', '')) + + Assert g:ale_handle_loclist_called + +Execute(Non-empty then non-empty suggestion messages should be handled): + let g:ale_buffer_info[bufnr('')].suggestion_loclist = CreateLoclist('x') + + call ale#lsp_linter#HandleLSPResponse(1, CreateError('suggestionDiag', 'x')) + + Assert g:ale_handle_loclist_called diff --git a/test/test_regex_escaping.vader b/test/test_regex_escaping.vader new file mode 100644 index 00000000..b79b8c56 --- /dev/null +++ b/test/test_regex_escaping.vader @@ -0,0 +1,4 @@ +Execute(ale#util#EscapePCRE should escape strings for PCRE or RE2 appropriately): + AssertEqual '\\\^\$\*\+\?\.\(\)\|\{\}\[\]', ale#util#EscapePCRE('\^$*+?.()|{}[]') + AssertEqual 'abcABC09', ale#util#EscapePCRE('abcABC09') + AssertEqual '/', ale#util#EscapePCRE('/') diff --git a/test/test_rename.vader b/test/test_rename.vader new file mode 100644 index 00000000..ec255bb3 --- /dev/null +++ b/test/test_rename.vader @@ -0,0 +1,494 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:old_filename = expand('%:p') + let g:Callback = '' + let g:expr_list = [] + let g:message_list = [] + let g:handle_code_action_called = 0 + let g:code_actions = [] + let g:options = {} + let g:capability_checked = '' + let g:conn_id = 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/rename.vim + runtime autoload/ale/code_action.vim + + 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) + + 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', + \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + function! ale#code_action#HandleCodeAction(code_action, options) abort + let g:handle_code_action_called = 1 + AssertEqual g:ale_save_hidden || !&hidden, get(a:options, 'should_save', 0) + call add(g:code_actions, a:code_action) + endfunction + + function! ale#util#Input(message, value) abort + return 'a-new-name' + endfunction + + call ale#rename#SetMap({ + \ 3: { + \ 'old_name': 'oldName', + \ 'new_name': 'aNewName', + \ }, + \}) + +After: + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#rename#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + 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:code_actions + unlet! g:handle_code_action_called + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/rename.vim + runtime autoload/ale/code_action.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#rename#HandleTSServerResponse(1, {'command': 'foo'}) + AssertEqual g:handle_code_action_called, 0 + +Execute(Failed rename responses should be handled correctly): + call ale#rename#SetMap({3: {'old_name': 'oldName', 'new_name': 'a-test'}}) + call ale#rename#HandleTSServerResponse( + \ 1, + \ {'command': 'rename', 'request_seq': 3} + \) + AssertEqual g:handle_code_action_called, 0 + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Code actions from tsserver should be handled): + call ale#rename#HandleTSServerResponse(1, { + \ 'command': 'rename', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'locs': [ + \ { + \ 'file': '/foo/bar/file1.ts', + \ 'locs': [ + \ { + \ 'start': { + \ 'line': 1, + \ 'offset': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'offset': 4, + \ }, + \ }, + \ ], + \ }, + \ { + \ 'file': '/foo/bar/file2.ts', + \ 'locs': [ + \ { + \ 'start': { + \ 'line': 10, + \ 'offset': 20, + \ }, + \ 'end': { + \ 'line': 30, + \ 'offset': 40, + \ }, + \ }, + \ ], + \ }, + \ ] + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'rename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 1, + \ 'offset': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'offset': 4, + \ }, + \ 'newText': 'aNewName', + \ }, + \ ], + \ }, + \ { + \ 'fileName': '/foo/bar/file2.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 10, + \ 'offset': 20, + \ }, + \ 'end': { + \ 'line': 30, + \ 'offset': 40, + \ }, + \ 'newText': 'aNewName', + \ }, + \ ], + \ }, + \ ], + \ } + \ ], + \ g:code_actions + +Execute(Prints a tsserver error message when unsuccessful): + call ale#rename#HandleTSServerResponse(1, { + \ 'command': 'rename', + \ 'request_seq': 3, + \ 'success': v:false, + \ 'message': 'This symbol cannot be renamed', + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''Error renaming "oldName" to: "aNewName". ' . + \ 'Reason: This symbol cannot be renamed'''], g:expr_list + +Execute(HandleTSServerResponse does nothing when no changes): + call ale#rename#HandleTSServerResponse(1, { + \ 'command': 'rename', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'locs': [] + \ } + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''Error renaming "oldName" to: "aNewName"'''], g:expr_list + +Execute(tsserver rename requests should be sent): + call ale#rename#SetMap({}) + call ale#linter#Reset() + + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALERename + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'rename', g:capability_checked + AssertEqual + \ 'function(''ale#rename#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@rename', { + \ 'file': expand('%:p'), + \ 'line': 2, + \ 'offset': 5, + \ 'arguments': { + \ 'findInComments': g:ale_rename_tsserver_find_in_comments, + \ 'findInStrings': g:ale_rename_tsserver_find_in_strings, + \ }, + \ }] + \ ], + \ g:message_list + AssertEqual {'42': {'old_name': 'somelongerline', 'new_name': 'a-new-name'}}, + \ ale#rename#GetMap() + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Code actions from LSP should be handled): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'changes': { + \ 'file:///foo/bar/file1.ts': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 1, + \ 'character': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'character': 4, + \ }, + \ }, + \ 'newText': 'bla123' + \ }, + \ ], + \ }, + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'rename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 2, + \ 'offset': 3, + \ }, + \ 'end': { + \ 'line': 4, + \ 'offset': 5, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ ], + \ } + \ ], + \ g:code_actions + +Execute(DocumentChanges from LSP should be handled): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'documentChanges': [ + \ { + \ 'textDocument': { + \ 'version': 1.0, + \ 'uri': 'file:///foo/bar/file1.ts', + \ }, + \ 'edits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 1, + \ 'character': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'character': 4, + \ }, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'rename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 2, + \ 'offset': 3, + \ }, + \ 'end': { + \ 'line': 4, + \ 'offset': 5, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ ], + \ } + \ ], + \ g:code_actions + +Execute(Single DocumentChange from LSP should be handled): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'documentChanges': { + \ 'textDocument': { + \ 'version': 1.0, + \ 'uri': 'file:///foo/bar/file1.ts', + \ }, + \ 'edits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 1, + \ 'character': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'character': 4, + \ }, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'rename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 2, + \ 'offset': 3, + \ }, + \ 'end': { + \ 'line': 4, + \ 'offset': 5, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ ], + \ } + \ ], + \ g:code_actions +Execute(LSP should perform no action when no result): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No rename result received from server'''], g:expr_list + +Execute(LSP should perform no action when no changes): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': {}, + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No changes received from server'''], g:expr_list + +Execute(LSP should perform no action when changes is empty): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'changes': [], + \ }, + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No changes received from server'''], g:expr_list + +Execute(LSP rename requests should be sent): + call ale#rename#SetMap({}) + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALERename + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'rename', g:capability_checked + AssertEqual + \ 'function(''ale#rename#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToFileURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/rename', { + \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ 'newName': 'a-new-name', + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'old_name': 'foo', 'new_name': 'a-new-name'}}, + \ ale#rename#GetMap() diff --git a/test/test_resolve_local_path.vader b/test/test_resolve_local_path.vader new file mode 100644 index 00000000..d8a8ec52 --- /dev/null +++ b/test/test_resolve_local_path.vader @@ -0,0 +1,17 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(We should be able to find the local version of a file): + call ale#test#SetFilename('test-files/top/middle/bottom/dummy.txt') + + AssertEqual + \ ale#path#Simplify(expand('%:p:h:h:h:h:h') . '/test-files/top/example.ini'), + \ ale#path#ResolveLocalPath(bufnr('%'), 'example.ini', '/global/config.ini') + +Execute(We shouldn't find anything for files which don't match): + AssertEqual + \ '/global/config.ini', + \ ale#path#ResolveLocalPath(bufnr('%'), 'missing.ini', '/global/config.ini') diff --git a/test/test_results_not_cleared_when_opening_loclist.vader b/test/test_results_not_cleared_when_opening_loclist.vader new file mode 100644 index 00000000..c586345a --- /dev/null +++ b/test/test_results_not_cleared_when_opening_loclist.vader @@ -0,0 +1,30 @@ +Before: + Save g:ale_buffer_info + + call ale#linter#Reset() + +After: + Restore + + call setloclist(0, []) + call clearmatches() + call ale#sign#Clear() + +Given foobar (Some file): + abc + +Execute(The loclist shouldn't be cleared when opening the loclist): + call ale#engine#InitBufferInfo(bufnr('')) + let g:ale_buffer_info[bufnr('')].loclist = [ + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 2}, + \] + call setloclist(0, g:ale_buffer_info[bufnr('')].loclist) + + " The cleanup function is called when the loclist window is closed. + " If some cleanup is done for this buffer, for which nothing is wrong, + " then the loclist for the window, which is the same window as the window + " we are checking, will be cleared. + :lopen + :q + + AssertEqual 1, len(ale#test#GetLoclistWithoutNewerKeys()), 'The loclist was cleared' diff --git a/test/test_sandbox_execution.vader b/test/test_sandbox_execution.vader new file mode 100644 index 00000000..cf994ce8 --- /dev/null +++ b/test/test_sandbox_execution.vader @@ -0,0 +1,103 @@ +Before: + function! TestCallback(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'bufnr': 1, + \ 'vcol': 0, + \ 'linter_name': 'testlinter', + \ 'nr': -1, + \ 'type': 'E', + \ 'col': 1, + \ 'text': 'Test Error', + \ }, + \] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + + let g:ale_buffer_info = {} + +After: + unlet! b:in_sandbox + unlet! b:result + + delfunction TestCallback + call ale#linter#Reset() + let g:ale_buffer_info = {} + + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ale#util#InSandbox should return 1 when in a sandbox): + sandbox let b:in_sandbox = ale#util#InSandbox() + + Assert b:in_sandbox, 'ale#util#InSandbox() returned 0 for a sandbox command' + +Execute(ALE shouldn't blow up when run from a sandbox): + AssertEqual 'foobar', &filetype + + sandbox call ale#Queue(0) + sandbox call ale#Queue(1) + +Execute(ALE shouldn't blow up if file cleanup happens in a sandbox): + " Make a call to an engine function first, so the function will be defined + " before we make the sandbox call. + " + " You are not allowed to define any functions in the sandbox. + call ale#engine#InitBufferInfo(3) + + let g:ale_buffer_info[3] = { + \ 'temporary_file_list': ['/tmp/foo'], + \ 'temporary_directory_list': ['/tmp/bar'], + \} + 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 + +Execute(You shouldn't be able to define linters from the sandbox): + call ale#linter#Reset() + call ale#linter#PreventLoading('testft') + + AssertThrows sandbox call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'output_stream': 'stdout', + \ 'executable': 'testlinter', + \ 'command': 'testlinter', + \ 'callback': 'testCB', + \}) + AssertEqual 'Vim(let):E48: Not allowed in sandbox', g:vader_exception + AssertEqual [], ale#linter#GetAll(['testft']) + +Execute(You shouldn't be able to register fixers from the sandbox): + call ale#fix#registry#Clear() + AssertThrows sandbox call ale#fix#registry#Add('prettier', '', ['javascript'], 'prettier') + AssertEqual 'Vim(let):E48: Not allowed in sandbox', g:vader_exception + AssertEqual [], ale#fix#registry#CompleteFixers('', 'ALEFix ', 7) + +Execute(You shouldn't be able to get linters from the sandbox, to prevent tampering): + AssertThrows sandbox call ale#linter#GetLintersLoaded() + AssertEqual 'Vim(let):E48: Not allowed in sandbox', g:vader_exception + + call ale#linter#Reset() + + sandbox let b:result = ale#linter#GetAll(['testft']) + + AssertEqual 0, len(b:result) + + let b:result = ale#linter#GetAll(['testft']) + + AssertEqual 1, len(b:result) + + sandbox let b:result = ale#linter#GetAll(['testft']) + + AssertEqual 0, len(b:result) diff --git a/test/test_semver_utils.vader b/test/test_semver_utils.vader new file mode 100644 index 00000000..c1ed30aa --- /dev/null +++ b/test/test_semver_utils.vader @@ -0,0 +1,41 @@ +After: + call ale#semver#ResetVersionCache() + +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#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(ParseVersion should return an empty list when no version can be found): + AssertEqual [], ale#semver#ParseVersion(['x']) + AssertEqual [], ale#semver#ParseVersion([]) + +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, 0], [2, 0, 0]) + Assert ale#semver#GTE([3, 1, 0], [3, 1, 0]) + Assert ale#semver#GTE([3, 2, 0], [3, 1, 0]) + Assert ale#semver#GTE([3, 2, 2], [3, 1, 6]) + Assert ale#semver#GTE([3, 2, 5], [3, 2, 5]) + Assert ale#semver#GTE([3, 2, 6], [3, 2, 5]) + Assert !ale#semver#GTE([2, 9, 1], [3, 0, 0]) + Assert !ale#semver#GTE([3, 2, 3], [3, 3, 3]) + Assert !ale#semver#GTE([3, 3, 2], [3, 3, 3]) + +Execute(GTE should compare pairs correctly): + Assert ale#semver#GTE([3, 0], [3, 0, 0]) + Assert ale#semver#GTE([3, 0], [3, 0]) + Assert ale#semver#GTE([3, 1], [3, 0]) + Assert ale#semver#GTE([3, 1], [3, 0, 0]) + Assert ale#semver#GTE([3, 0, 1], [3, 0]) + Assert !ale#semver#GTE([3, 0], [3, 0, 1]) + Assert !ale#semver#GTE([3, 0], [3, 1]) + Assert !ale#semver#GTE([2, 9, 11], [3, 0]) + +Execute(GTE should permit the LHS to be an empty List): + Assert !ale#semver#GTE([], [0, 0, 0]) diff --git a/test/test_set_list_timers.vader b/test/test_set_list_timers.vader new file mode 100644 index 00000000..07e0202d --- /dev/null +++ b/test/test_set_list_timers.vader @@ -0,0 +1,29 @@ +Before: + Save g:ale_set_lists_synchronously + Save g:ale_open_list + + let g:ale_set_lists_synchronously = 0 + +After: + Restore + + sleep 1ms + call setloclist(0, []) + lclose + +Execute(The SetLists function should work when run in a timer): + call ale#list#SetLists(bufnr(''), [ + \ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x', 'type': 'E'}, + \]) + sleep 1ms + AssertEqual [{ + \ 'lnum': 5, + \ 'bufnr': bufnr(''), + \ 'col': 5, + \ 'text': 'x', + \ 'valid': 1, + \ 'vcol': 0, + \ 'nr': 0, + \ 'type': 'E', + \ 'pattern': '', + \}], ale#test#GetLoclistWithoutNewerKeys() diff --git a/test/test_setting_loclist_from_another_buffer.vader b/test/test_setting_loclist_from_another_buffer.vader new file mode 100644 index 00000000..d33fa075 --- /dev/null +++ b/test/test_setting_loclist_from_another_buffer.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = { + \ bufnr(''): { + \ 'loclist': [{'bufnr': bufnr(''), 'lnum': 4, 'col': 1, 'text': 'foo'}] + \ }, + \} + + let g:original_buffer = bufnr('%') + noautocmd new + +After: + Restore + + unlet! g:original_buffer + +Execute(Errors should be set in the loclist for the original buffer, not the new one): + call ale#list#SetLists( + \ g:original_buffer, + \ g:ale_buffer_info[(g:original_buffer)].loclist, + \ ) + + AssertEqual [], ale#test#GetLoclistWithoutNewerKeys() + AssertEqual 1, len(getloclist(bufwinid(g:original_buffer))) + AssertEqual 'foo', getloclist(bufwinid(g:original_buffer))[0].text diff --git a/test/test_setting_problems_found_in_previous_buffers.vader b/test/test_setting_problems_found_in_previous_buffers.vader new file mode 100644 index 00000000..f1a31fc1 --- /dev/null +++ b/test/test_setting_problems_found_in_previous_buffers.vader @@ -0,0 +1,98 @@ +Before: + Save g:ale_buffer_info + Save &filetype + Save g:ale_set_lists_synchronously + + let g:ale_set_lists_synchronously = 1 + + " Set up items in other buffers which should set in this one. + let g:ale_buffer_info = {} + call ale#engine#InitBufferInfo(bufnr('') + 1) + let g:ale_buffer_info[bufnr('') + 1].loclist = + \ ale#engine#FixLocList(bufnr('') + 1, 'linter_one', 0, [ + \ {'lnum': 1, 'filename': expand('%:p'), 'text': 'foo'}, + \ {'lnum': 2, 'filename': expand('%:p'), 'text': 'bar'}, + \ {'lnum': 2, 'text': 'ignore this one'}, + \ ]) + call ale#engine#InitBufferInfo(bufnr('') + 2) + let g:ale_buffer_info[bufnr('') + 2].loclist = + \ ale#engine#FixLocList(bufnr('') + 2, 'linter_one', 0, [ + \ {'lnum': 1, 'filename': expand('%:p'), 'text': 'foo'}, + \ {'lnum': 3, 'filename': expand('%:p'), 'text': 'baz'}, + \ {'lnum': 5, 'text': 'ignore this one'}, + \ ]) + + call ale#linter#Define('foobar', { + \ 'name': 'linter_one', + \ 'callback': 'WhoCares', + \ 'executable': 'echo', + \ 'command': 'sleep 1000', + \ 'lint_file': 1, + \}) + +After: + call ale#engine#Cleanup(bufnr('')) + Restore + call ale#linter#Reset() + + " Items and markers, etc. + call setloclist(0, []) + call clearmatches() + call ale#sign#Clear() + +Given foobar(A file with some lines): + foo + bar + baz + +Execute(Problems found from previously opened buffers should be set when linting for the first time): + call ale#engine#RunLinters(bufnr(''), ale#linter#Get(&filetype), 0) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'foo', + \ 'sign_id': 1000001, + \ }, + \ { + \ 'lnum': 2, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'bar', + \ 'sign_id': 1000002, + \ }, + \ { + \ 'lnum': 3, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'baz', + \ 'sign_id': 1000003, + \ }, + \ ], + \ g:ale_buffer_info[bufnr('')].loclist + + AssertEqual + \ [ + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'foo'}, + \ {'lnum': 2, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'bar'}, + \ {'lnum': 3, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'baz'}, + \ ], + \ ale#test#GetLoclistWithoutNewerKeys() diff --git a/test/test_shell_detection.vader b/test/test_shell_detection.vader new file mode 100644 index 00000000..11d801c3 --- /dev/null +++ b/test/test_shell_detection.vader @@ -0,0 +1,177 @@ +Before: + runtime ale_linters/sh/shell.vim + runtime ale_linters/sh/shellcheck.vim + +After: + call ale#linter#Reset() + + unlet! b:is_bash + unlet! b:is_sh + unlet! b:is_kornshell + +Given(A file with a Bash hashbang): + #!/bin/bash + +Execute(/bin/bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with /bin/sh): + #!/usr/bin/env sh -eu --foobar + +Execute(/bin/sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with bash as an argument to env): + #!/usr/bin/env bash + +Execute(/usr/bin/env bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a tcsh hash bang and arguments): + #!/usr/bin/env tcsh -eu --foobar + +Execute(tcsh should be detected appropriately): + AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'tcsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'tcsh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a zsh hash bang and arguments): + #!/usr/bin/env zsh -eu --foobar + +Execute(zsh should be detected appropriately): + AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'zsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'zsh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a csh hash bang and arguments): + #!/usr/bin/env csh -eu --foobar + +Execute(csh should be detected appropriately): + AssertEqual 'csh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'csh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'csh', ale#handlers#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#handlers#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#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a sh hash bang and arguments): + #!/usr/bin/env sh -eu --foobar + +Execute(sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file without a hashbang): + +Execute(The bash dialect should be used for shellcheck if b:is_bash is 1): + let b:is_bash = 1 + + AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The sh dialect should be used for shellcheck if b:is_sh is 1): + let b:is_sh = 1 + + AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The ksh dialect should be used for shellcheck if b:is_kornshell is 1): + let b:is_kornshell = 1 + + AssertEqual 'ksh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The filetype should be used as the default shell type when there is no hashbang line): + set filetype=zsh + AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr('')) + + set filetype=tcsh + AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr('')) + + set filetype=python + AssertEqual '', ale#handlers#sh#GetShellType(bufnr('')) + +Given(A file with /bin/ash): + #!/bin/ash + +Execute(The ash dialect should be used for the shell and the base function): + AssertEqual 'ash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'ash', ale_linters#sh#shell#GetExecutable(bufnr('')) + +Execute(dash should be used for shellcheck, which has no ash dialect): + AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with /bin/dash): + #!/bin/dash + +Execute(The dash dialect should be used for the shell and the base function): + AssertEqual 'dash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'dash', ale_linters#sh#shell#GetExecutable(bufnr('')) + +Execute(dash should be used for shellcheck): + AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a Bash shellcheck shell directive): + # shellcheck shell=bash + +Execute(bash dialect should be detected appropriately): + AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a sh shellcheck shell directive): + #shellcheck shell=sh + +Execute(sh dialect should be detected appropriately): + AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a tcsh shellcheck shell directive): + # shellcheck shell=tcsh + +Execute(tcsh dialect should be detected appropriately): + AssertEqual 'tcsh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a zsh shellcheck shell directive): + # shellcheck shell=zsh + +Execute(zsh dialect should be detected appropriately): + AssertEqual 'zsh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a csh shellcheck shell directive): + # shellcheck shell=csh + +Execute(zsh dialect should be detected appropriately): + AssertEqual 'csh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a ksh shellcheck shell directive): + # shellcheck shell=ksh + +Execute(ksh dialect should be detected appropriately): + AssertEqual 'ksh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a dash shellcheck shell directive): + # shellcheck shell=dash + +Execute(dash dialect should be detected appropriately): + AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a ash shellcheck shell directive): + # shellcheck shell=ash + +Execute(dash dialect should be detected for ash that shellcheck does not support): + AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) diff --git a/test/test_should_do_nothing_conditions.vader b/test/test_should_do_nothing_conditions.vader new file mode 100644 index 00000000..6dfed555 --- /dev/null +++ b/test/test_should_do_nothing_conditions.vader @@ -0,0 +1,88 @@ +Before: + Save g:ale_filetype_blacklist + Save g:ale_maximum_file_size + Save g:ale_enabled + Save &l:statusline + + let b:fake_mode = 'n' + + call ale#test#SetDirectory('/testplugin/test') + + let b:funky_command_created = 0 + + runtime autoload/ale/util.vim + + function! ale#util#Mode(...) abort + return b:fake_mode + endfunction + + " We will test for the existence of this command, so create one if needed. + if !exists(':CtrlPFunky') + command CtrlPFunky echo + let b:funky_command_created = 1 + endif + +After: + Restore + + call ale#test#RestoreDirectory() + + if b:funky_command_created + delcommand CtrlPFunky + let b:funky_command_created = 0 + endif + + 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): +Execute(ALE shouldn't do much of anything for ctrlp-funky buffers): + Assert !ale#ShouldDoNothing(bufnr('')), 'The preliminary check failed' + + let &l:statusline = '%#CtrlPMode2# prt %*%#CtrlPMode1# line %* ={%#CtrlPMode1# funky %*}= <-> %=%<%#CtrlPMode2# %{getcwd()} %*' + + Assert ale#ShouldDoNothing(bufnr('')) + +Execute(ALE shouldn't try to check buffers with '.' as the filename): + AssertEqual + \ 0, + \ ale#ShouldDoNothing(bufnr('')), + \ 'ShouldDoNothing() was 1 for some other reason' + + silent! noautocmd file . + + Assert ale#ShouldDoNothing(bufnr('')) + +Execute(DoNothing should return 1 when the filetype is empty): + AssertEqual + \ 0, + \ ale#ShouldDoNothing(bufnr('')), + \ 'ShouldDoNothing() was 1 for some other reason' + + set filetype= + + AssertEqual 1, ale#ShouldDoNothing(bufnr('')) + +Execute(DoNothing should return 1 when an operator is pending): + let b:fake_mode = 'no' + + 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 + unlet! g:ale_enabled + + " This shouldn't throw exceptions. + call ale#ShouldDoNothing(bufnr('')) diff --git a/test/test_sml_command.vader b/test/test_sml_command.vader new file mode 100644 index 00000000..e89486c4 --- /dev/null +++ b/test/test_sml_command.vader @@ -0,0 +1,45 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(smlnj finds CM file if it exists): + call ale#test#SetFilename('test-files/smlnj/cm/foo.sml') + + AssertEqual + \ ale#test#GetFilename('test-files/smlnj/cm/sources.cm'), + \ ale#handlers#sml#GetCmFile(bufnr('%')) + +Execute(smlnj finds CM file by searching upwards): + call ale#test#SetFilename('test-files/smlnj/cm/path/to/bar.sml') + + AssertEqual + \ ale#test#GetFilename('test-files/smlnj/cm/sources.cm'), + \ ale#handlers#sml#GetCmFile(bufnr('%')) + +Execute(smlnj returns '' when no CM file found): + call ale#test#SetFilename('test-files/smlnj/file/qux.sml') + + AssertEqual '', ale#handlers#sml#GetCmFile(bufnr('%')) + +Execute(CM-project mode enabled when CM file found): + call ale#test#SetFilename('test-files/smlnj/cm/foo.sml') + + AssertEqual 'sml', ale#handlers#sml#GetExecutableSmlnjCm(bufnr('%')) + +Execute(single-file mode disabled when CM file found): + call ale#test#SetFilename('test-files/smlnj/cm/foo.sml') + + AssertEqual '', ale#handlers#sml#GetExecutableSmlnjFile(bufnr('%')) + +Execute(CM-project mode disabled when CM file not found): + call ale#test#SetFilename('test-files/smlnj/file/qux.sml') + + AssertEqual '', ale#handlers#sml#GetExecutableSmlnjCm(bufnr('%')) + +Execute(single-file mode enabled when CM file found): + call ale#test#SetFilename('test-files/smlnj/file/qux.sml') + + AssertEqual 'sml', ale#handlers#sml#GetExecutableSmlnjFile(bufnr('%')) diff --git a/test/test_socket_connections.vader b/test/test_socket_connections.vader new file mode 100644 index 00000000..c59b942d --- /dev/null +++ b/test/test_socket_connections.vader @@ -0,0 +1,139 @@ +Before: + let g:can_run_socket_tests = !has('win32') + \ && (exists('*ch_close') || exists('*chanclose')) + + if g:can_run_socket_tests + call ale#test#SetDirectory('/testplugin/test') + + let g:channel_id_received = 0 + let g:data_received = '' + + function! WaitForData(expected_data, timeout) abort + let l:ticks = 0 + + while l:ticks < a:timeout + " Sleep first, so we can switch to the callback. + let l:ticks += 10 + sleep 10ms + + if g:data_received is# a:expected_data + break + endif + endwhile + endfunction + + function! TestCallback(channel_id, data) abort + let g:channel_id_received = a:channel_id + let g:data_received .= a:data + endfunction + + let g:port = 10347 + let g:pid_tcp = str2nr(system( + \ 'python' + \ . ' ' . ale#Escape(g:dir . '/script/dumb_tcp_server.py') + \ . ' ' . g:port + \)) + let g:pipe_path = tempname() + let g:pid_pipe = str2nr(system( + \ 'python' + \ . ' ' . ale#Escape(g:dir . '/script/dumb_named_pipe_server.py') + \ . ' ' . g:pipe_path + \)) + endif + +After: + if g:can_run_socket_tests + call ale#test#RestoreDirectory() + + unlet! g:channel_id_received + unlet! g:data_received + unlet! g:channel_id + + delfunction WaitForData + delfunction TestCallback + + if has_key(g:, 'pid_tcp') + call system('kill ' . g:pid_tcp) + endif + + if has_key(g:, 'pid_pipe') + call system('kill ' . g:pid_pipe) + endif + + unlet! g:pid_tcp + unlet! g:port + unlet! g:pid_pipe + unlet! g:pipe_path + endif + + unlet! g:can_run_socket_tests + +Execute(Sending and receiving connections to tcp sockets should work): + if g:can_run_socket_tests + let g:channel_id = ale#socket#Open( + \ '127.0.0.1:' . g:port, + \ {'callback': function('TestCallback')} + \) + + Assert g:channel_id >= 0, 'The socket was not opened!' + + call ale#socket#Send(g:channel_id, 'hello') + call ale#socket#Send(g:channel_id, ' world') + + AssertEqual 1, ale#socket#IsOpen(g:channel_id) + + " Wait up to 1 second for the expected data to arrive. + call WaitForData('hello world', 1000) + + AssertEqual g:channel_id, g:channel_id_received + AssertEqual 'hello world', g:data_received + AssertEqual '127.0.0.1:' . g:port, ale#socket#GetAddress(g:channel_id) + + call ale#socket#Close(g:channel_id) + + AssertEqual 0, ale#socket#IsOpen(g:channel_id) + AssertEqual '', ale#socket#GetAddress(g:channel_id) + endif + + " NeoVim versions which can't connect to sockets should just fail. + if has('nvim') && !exists('*chanclose') + AssertEqual -1, ale#socket#Open( + \ '127.0.0.1:1111', + \ {'callback': function('function')} + \) + endif + +Execute(Sending and receiving connections to named pipe sockets should work): + if g:can_run_socket_tests && has('nvim-0.4') + let g:channel_id = ale#socket#Open( + \ g:pipe_path, + \ {'callback': function('TestCallback')} + \) + + Assert g:channel_id >= 0, 'The socket was not opened!' + + call ale#socket#Send(g:channel_id, 'hello') + call ale#socket#Send(g:channel_id, ' world') + + AssertEqual 1, ale#socket#IsOpen(g:channel_id) + + " Wait up to 1 second for the expected data to arrive. + call WaitForData('hello world', 1000) + + AssertEqual g:channel_id, g:channel_id_received + AssertEqual 'hello world', g:data_received + AssertEqual g:pipe_path, ale#socket#GetAddress(g:channel_id) + + call ale#socket#Close(g:channel_id) + + AssertEqual 0, ale#socket#IsOpen(g:channel_id) + AssertEqual '', ale#socket#GetAddress(g:channel_id) + endif + + " NeoVim versions which can't connect to sockets should just fail. + if has('nvim-0.4') && !exists('*chanclose') + AssertEqual -1, ale#socket#Open( + \ g:pipe_path, + \ {'callback': function('function')} + \) + endif diff --git a/test/test_statusline.vader b/test/test_statusline.vader new file mode 100644 index 00000000..4deb9f66 --- /dev/null +++ b/test/test_statusline.vader @@ -0,0 +1,144 @@ +Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + + " A function for conveniently creating expected count objects. + function! Counts(data) abort + let l:res = { + \ '0': 0, + \ '1': 0, + \ 'error': 0, + \ 'warning': 0, + \ 'info': 0, + \ 'style_error': 0, + \ 'style_warning': 0, + \ 'total': 0, + \} + + for l:key in keys(a:data) + let l:res[l:key] = a:data[l:key] + endfor + + let l:res[0] = l:res.error + l:res.style_error + let l:res[1] = l:res.warning + l:res.style_warning + l:res.info + let l:res.total = l:res[0] + l:res[1] + + return l:res + endfunction + + " 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'}, + \ {'bufnr': bufnr('') - 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') - 1, 'type': 'W'}, + \ {'bufnr': bufnr('') - 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') - 1, 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr('') + 1, 'type': 'E'}, + \ {'bufnr': bufnr('') + 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') + 1, 'type': 'W'}, + \ {'bufnr': bufnr('') + 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') + 1, 'type': 'I'}, + \ ], + \ }, + \} +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, + \ 'warning': 3, + \ 'style_warning': 4, + \ 'info': 5, + \ '0': 3, + \ '1': 12, + \ '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') diff --git a/test/test_swift_find_project_root.vader b/test/test_swift_find_project_root.vader new file mode 100644 index 00000000..88a26021 --- /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('test-files/swift/swift-package-project/src/folder/dummy.swift') + AssertEqual + \ ale#path#Simplify(g:dir . '/test-files/swift/swift-package-project'), + \ ale#swift#FindProjectRoot(bufnr('')) + +Execute(Detect no root in case of non-Package.swift project): + call ale#test#SetFilename('test-files/swift/non-swift-package-project/src/folder/dummy.swift') + AssertEqual + \ '', + \ ale#swift#FindProjectRoot(bufnr('')) + diff --git a/test/test_symbol_search.vader b/test/test_symbol_search.vader new file mode 100644 index 00000000..6dc2fb3e --- /dev/null +++ b/test/test_symbol_search.vader @@ -0,0 +1,189 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:Callback = '' + let g:expr_list = [] + 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: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, Callback) abort + let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {}) + call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) + 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#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + function! ale#preview#ShowSelection(item_list, options) abort + let g:preview_called = 1 + let g:item_list = a:item_list + let g:options = a:options + endfunction + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + 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 + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/preview.vim + +Execute(Other messages for the LSP handler should be ignored): + call ale#symbol#HandleLSPResponse(1, {'command': 'foo'}) + +Execute(Failed symbol responses should be handled correctly): + call ale#symbol#SetMap({3: {}}) + call ale#symbol#HandleLSPResponse(1, {'id': 3}) + AssertEqual {}, ale#symbol#GetMap() + +Execute(LSP symbol responses should be handled): + call ale#symbol#SetMap({3: {}}) + call ale#symbol#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ { + \ 'name': 'foo', + \ 'location': { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ }, + \ { + \ 'name': 'foobar', + \ 'location': { + \ 'uri': ale#path#ToFileURI(ale#path#Simplify(g:dir . '/other_file')), + \ 'range': { + \ 'start': {'line': 7, 'character': 15}, + \ }, + \ }, + \ }, + \ ], + \ } + \) + + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(g:dir . '/completion_dummy_file'), + \ 'line': 3, + \ 'column': 8, + \ 'match': 'foo', + \ }, + \ { + \ 'filename': ale#path#Simplify(g:dir . '/other_file'), + \ 'line': 8, + \ 'column': 16, + \ 'match': 'foobar', + \ }, + \ ], + \ g:item_list + AssertEqual {}, ale#symbol#GetMap() + +Execute(Preview windows should not be opened for empty LSP symbol responses): + call ale#symbol#SetMap({3: {}}) + call ale#symbol#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ ], + \ } + \) + + Assert !g:preview_called + AssertEqual {}, ale#symbol#GetMap() + AssertEqual ['echom ''No symbols found.'''], g:expr_list + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(LSP symbol requests should be sent): + runtime ale_linters/python/pylsp.vim + let b:ale_linters = ['pylsp'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALESymbolSearch foo bar + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'symbol_search', g:capability_checked + AssertEqual + \ 'function(''ale#symbol#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [0, 'workspace/symbol', {'query': 'foo bar'}], + \ ], + \ g:message_list + + 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/pylsp.vim + let b:ale_linters = ['pylsp'] + 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 new file mode 100644 index 00000000..bb735886 --- /dev/null +++ b/test/test_temporary_file_management.vader @@ -0,0 +1,146 @@ +Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + let g:ale_run_synchronously = 1 + + let g:command = 'echo test' + let g:filename = '' + let g:directory = '' + let g:preserved_directory = '' + + function! TestCommandCallback(buffer) abort + " We are registering a temporary file, so we should delete it. + let g:filename = tempname() + call writefile(['foo'], 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#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#command#ManageFile(a:buffer, g:preserved_directory) + + return g:command + endfunction + + function! TestCallback(buffer, output) abort + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'callback': 'TestCallback', + \ 'command': function('TestCommandCallback'), + \}) + call ale#command#ClearData() + +After: + Restore + + if !empty(g:preserved_directory) + call delete(g:preserved_directory, 'rf') + endif + + unlet! g:ale_run_synchronously + unlet! g:command + unlet! g:filename + unlet! g:directory + unlet! g:preserved_directory + delfunction TestCommandCallback + delfunction TestCallback + call ale#linter#Reset() + call ale#command#ClearData() + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALE should delete managed files/directories appropriately after linting): + AssertEqual 'foobar', &filetype + + call ale#Queue(0) + call ale#test#FlushJobs() + + Assert !filereadable(g:filename), 'The temporary file was not deleted' + Assert !isdirectory(g:directory), 'The temporary directory was not deleted' + Assert isdirectory(g:preserved_directory), 'The temporary directory was not kept' + +Execute(ALE should delete managed files even if no command is run): + AssertEqual 'foobar', &filetype + + let g:command = '' + + call ale#Queue(0) + 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' + Assert isdirectory(g:preserved_directory), 'The temporary directory was not kept' + +Execute(ALE should delete managed files when the buffer is removed): + call ale#engine#InitBufferInfo(bufnr('%')) + call TestCommandCallback(bufnr('%')) + call ale#engine#Cleanup(bufnr('%')) + + Assert !filereadable(g:filename), 'The temporary file was not deleted' + 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#command#CreateDirectory()): + call ale#engine#InitBufferInfo(bufnr('%')) + + let b:dir = ale#command#CreateDirectory(bufnr('%')) + let b:dir2 = ale#command#CreateDirectory(bufnr('%')) + + Assert isdirectory(b:dir), 'The directory was not created' + + " We should get the correct file permissions. + " We want to ensure that the directory is not readable by 'other' + if has('unix') + AssertEqual 'rwxr-x---', getfperm(b:dir) + endif + + " The two directories shouldn't be the same. + AssertNotEqual b:dir2, b:dir + + call ale#engine#Cleanup(bufnr('%')) + + Assert !isdirectory(b:dir), 'The directory was not deleted' + Assert !isdirectory(b:dir2), 'The second directory was not deleted' + +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 + \ { + \ 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') + + AssertEqual + \ { + \ bufnr(''): { + \ 'jobs': {}, + \ 'file_list': [], + \ 'directory_list': ['/foo/bar'], + \ }, + \ }, + \ ale#command#GetData() diff --git a/test/test_tmpdir_wrapper.vader b/test/test_tmpdir_wrapper.vader new file mode 100644 index 00000000..906f8b7d --- /dev/null +++ b/test/test_tmpdir_wrapper.vader @@ -0,0 +1,32 @@ +Before: + let g:exists = exists('$TMPDIR') + let g:old_value = $TMPDIR + +After: + if g:exists + let $TMPDIR = g:old_value + else + silent! unlet! $TMPDIR + endif + + unlet! g:exists + unlet! g:old_value + +Execute(ale#util#Tempname shouldn't set $TMPDIR to an empty string if it isn't set): + " You can't run this test twice on old Vim versions. + if has('unix') + Assert ale#util#Tempname() =~# '^/tmp' + Assert !exists('$TMPDIR'), '$TMPDIR exists where it shouldn''t' + endif + +Execute(ale#util#Tempname shouldn't replace $TMPDIR and reset them to an empty string): + if has('unix') + let $TMPDIR = '' + Assert ale#util#Tempname() =~# '^/tmp' + + if !has('nvim') + Assert exists('$TMPDIR'), '$TMPDIR doesn''t exist where it should' + endif + + AssertEqual '', $TMPDIR + endif diff --git a/test/test_vim8_processid_parsing.vader b/test/test_vim8_processid_parsing.vader new file mode 100644 index 00000000..26416b15 --- /dev/null +++ b/test/test_vim8_processid_parsing.vader @@ -0,0 +1,5 @@ +Execute(Vim8 Process ID parsing should work): + AssertEqual 123, ale#job#ParseVim8ProcessID('process 123 run') + AssertEqual 347, ale#job#ParseVim8ProcessID('process 347 failed') + AssertEqual 789, ale#job#ParseVim8ProcessID('process 789 dead') + AssertEqual 0, ale#job#ParseVim8ProcessID('no process') diff --git a/test/test_virtualtext.vader b/test/test_virtualtext.vader new file mode 100644 index 00000000..da58c9f0 --- /dev/null +++ b/test/test_virtualtext.vader @@ -0,0 +1,221 @@ +Before: + Save g:ale_buffer_info + Save g:ale_virtualtext_cursor + Save g:ale_virtualtext_delay + Save g:ale_virtualtext_single + Save g:ale_virtualtext_prefix + Save b:ale_virtualtext_prefix + Save g:ale_use_neovim_diagnostics_api + + call ale#virtualtext#ResetDataForTests() + + let g:setting = '' + let g:ale_virtualtext_prefix = '%comment% %type%: ' + let g:ale_virtualtext_delay = 0 + let g:ale_virtualtext_single = 0 + let g:ale_buffer_info = { + \ bufnr(''): { + \ 'loclist': [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 1, + \ 'col': 5, + \ 'text': 'Line 1 error', + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'W', + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'Line 2 warning 1', + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'W', + \ 'lnum': 2, + \ 'col': 5, + \ 'text': 'Line 2 warning 2', + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'W', + \ 'lnum': 3, + \ 'col': 3, + \ 'text': 'Line 3 warning 1', + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 3, + \ 'col': 5, + \ 'text': 'Line 3 error 1', + \ }, + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 3, + \ 'col': 6, + \ 'text': 'Line 3 error 2', + \ }, + \ ], + \ }, + \} + let g:ale_use_neovim_diagnostics_api = 0 + +After: + Restore + + unlet! g:setting + unlet! g:ns_id + +Execute(The correct highlight groups should be loaded for virtual-text): + AssertEqual 'ALEVirtualTextError', ale#virtualtext#GetGroup({}) + AssertEqual 'ALEVirtualTextError', ale#virtualtext#GetGroup({'type': 'E'}) + AssertEqual 'ALEVirtualTextStyleError', + \ ale#virtualtext#GetGroup({'type': 'E', 'sub_type': 'style'}) + AssertEqual 'ALEVirtualTextWarning', ale#virtualtext#GetGroup({'type': 'W'}) + AssertEqual 'ALEVirtualTextStyleWarning', + \ ale#virtualtext#GetGroup({'type': 'W', 'sub_type': 'style'}) + AssertEqual 'ALEVirtualTextInfo', ale#virtualtext#GetGroup({'type': 'I'}) + +Given python (An empty Python file): +Execute(Comment text should be detected correctly for Python files): + if has('patch-9.0.0297') || has('nvim-0.8.0') + AssertEqual '#', ale#virtualtext#GetComment(bufnr('')) + endif + +Given java (An empty Java file): +Execute(Comment text should be detected correctly for Java files): + if has('patch-9.0.0297') || has('nvim-0.8.0') + AssertEqual '//', ale#virtualtext#GetComment(bufnr('')) + endif + +Given html (An empty HTML file): +Execute(Comment text should be detected correctly for HTML files): + if has('patch-9.0.0297') || has('nvim-0.8.0') + AssertEqual "\