diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..5411395 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,40 @@ +--- +# 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 + +# 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 + +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 0000000..d9b95d6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# 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 diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index adcb251..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - parserOptions: { - ecmaVersion: 6, - sourceType: "module", - }, - rules: { - semi: 'error', - 'space-infix-ops': 'warn', - radix: 'error', - } -} diff --git a/.gitattributes b/.gitattributes index 394e02d..05b1f3f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,12 @@ .* export-ignore +/CODE_OF_CONDUCT.md export-ignore /CONTRIBUTING.md export-ignore /Dockerfile 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/.gitignore b/.gitignore index 3be1b02..2a99180 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /init.vim /doc/tags .* +!.editorconfig *.obj +tags diff --git a/.travis.yml b/.travis.yml index d4e6cf3..2423732 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,6 @@ sudo: required services: - docker -branches: - only: - - master language: python script: | - make test + ./run-tests diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..587bb37 --- /dev/null +++ b/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/CONTRIBUTING.md b/CONTRIBUTING.md index ec9239c..2612956 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,15 +3,26 @@ 1. [Guidelines](#guidelines) 2. [Creating Issues](#issues) 3. [Creating Pull Requests](#pull-requests) - 1. [Adding a New Linter](#adding-a-new-linter) - 2. [Adding New Options](#adding-new-options) + 1. [Adding a New Linter](#adding-a-new-linter) + 2. [Adding New Options](#adding-new-options) +4. [Writing Documentation](#writing-documentation) + 1. [Documenting New Linters](#documenting-new-linters) + 2. [Editing the Online Documentation](#editing-online-documentation) + 3. [Documenting Linter Options](#documenting-linter-options) +5. [In Case of Busses](#in-case-of-busses) -# 1. Guidelines +## 1. Guidelines Have fun, and work on whatever floats your boat. Take It Easy :tm:. +Don't forget to **write documentation** for whatever it is you are doing. +See the ["Writing Documentation"](#writing-documentation) section. + +Remember to write Vader tests for most of the code you write. You can look at +existing Vader tests in the `test` directory for examples. + When writing code, follow the [Google Vimscript Style Guide](https://google.github.io/styleguide/vimscriptguide.xml), and run `vint -s` on your files to check for most of what the guide mentions and more. If you @@ -20,7 +31,7 @@ will check your code while you type. -# 2. Creating Issues +## 2. 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 @@ -41,7 +52,7 @@ can understand you. -# 3. Creating Pull Requests +## 3. Creating Pull Requests For code you write, make sure to credit yourself at the top of files you add, and probably those you modify. You can write some comments at the top of your @@ -60,13 +71,13 @@ If you want to credit multiple authors, you can comma separate them. -# 3.i. Adding a New Linter +### 3.i. Adding a New Linter If you add a new linter, look for existing handlers first in the -[handlers.vim](autoload/ale/handlers.vim) file. One of the handlers there may +[handlers](autoload/ale/handlers) directory. One of the handlers there may already be able to handle your lines of output. If you find that your new linter replicates an existing error handler, consider pulling it up into the -[handlers.vim](autoload/ale/handlers.vim) file, and use the generic handler in +[handlers](autoload/ale/handlers) directory, and use the generic handler in both places. When you add a linter, make sure the language for the linter and the linter @@ -76,7 +87,7 @@ alphabetically in the table and list. -# 3.ii. Adding New Options +### 3.ii. Adding New Options If you add new options to the plugin, make sure to document those new options in the [README.md](README.md) file, and also in the [help file](doc/ale.txt). @@ -90,3 +101,73 @@ global variable names. Any options for linters should be set to some default value so it is always easy to see what the default is with `:echo g:ale...`. + + + +## 4. Writing Documentation + +If you are adding new linters, changing the API, adding new options, etc., you +_must_ write some documentation describing it in the `doc/ale.txt` file. New +linters _must_ be added to the `README.md` file too, so other users can get a +quick overview of the supported tools. + + + +### 4.i Documenting New Linters + +If you add a new linter to the project, edit the table in the `README.md` file, +and edit the list of linters at the top of the `doc/ale.txt` file. The linters +should be sorted vertically in lexicographic (alphabetical) order by the +programming language name or filetype, and the tools for each language should +be sorted in lexicographic order horizontally. Sorting in this manner is a fair +manner of presenting all of the information in an easy to scan way, without +giving some unfair preference to any particular tool or language. + + + +### 4.ii Editing the Online Documentation + +The "online documentation" file used for this project lives in `doc/ale.txt`. +This is the file used for generating `:help` text inside Vim itself. There are +some guidelines to follow for this file. + +1. Keep all text within a column size of 79 characters, inclusive. +2. Open a section with 79 `=` or `-` characters, for headings and subheadings. +3. Sections should have a _single_ blank line before or after. +4. Between descriptions of variables/functions/commands, use _two_ blank lines. +5. Up-indent the description of a variable/function/command by two spaces. +6. Place tags at the ends of lines, with the final characters on column 79. + All of the tags should line up perfectly on the same column as you scan + down through the document. +7. Keep the table of contents balanced so the longest tag link ends on column + 79, and so all links line up perfectly on their first character, on the + left. + + + +### 4.iii Documenting Linter Options + +For documenting new linter options, please add a new sub-section under the +"Linter Specific Options" section describing all of the global options added +for each linter, and what the default values of the options are. All global +options for linters should be set to some default value. This will allow users +to look up the default value easily by typing `:echo g:ale_...`. + + + +## 5. In Case of Busses + +Should the principal author of the ALE project and all collaborators with the +required access needed to properly administrate the project on GitHub or any +other website either perish or disappear, whether by tragic traffic accident +or government abduction, etc., action should be taken to ensure that the +project continues. If no one is left to administer the project where it is +hosted, please fork the project and nominate someone capable to administer it. +Preferably, in such an event, a single fork of the project will replace the +original, and life will go on, except the life of whoever vanished, because +then they will probably be dead. + +Should w0rp suddenly disappear, then he was probably killed in a traffic +accident, or the government finally decided to kill him and make it look like +suicide. In the latter event, please subvert said government and restore +order to the universe, and ensure peace for mankind. diff --git a/Dockerfile b/Dockerfile index 8c28d95..eba9a1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,18 @@ FROM tweekmonster/vim-testbed:latest -RUN install_vim -tag v8.0.0000 -build \ - -tag v8.0.0027 -build +RUN install_vim -tag v8.0.0027 -build \ + -tag neovim:v0.1.7 -build ENV PACKAGES="\ + bash \ git \ - python=2.7.12-r0 \ - py-pip=8.1.2-r0 \ - nodejs \ + python \ + py-pip \ " RUN apk --update add $PACKAGES && \ rm -rf /var/cache/apk/* /tmp/* /var/tmp/* RUN pip install vim-vint==0.3.9 -RUN npm install -g eslint@3.7.1 - RUN git clone https://github.com/junegunn/vader.vim vader && \ cd vader && git checkout c6243dd81c98350df4dec608fa972df98fa2a3af diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..0276a65 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ + + +## Information + +**VIM version** + +PASTE JUST THE FIRST TWO LINES OF `:version` HERE. + +Operating System: WHAT OS WERE YOU USING? + +### :ALEInfo + +PASTE OUTPUT OF `:ALEInfo` HERE. YOU CAN TRY `:ALEInfoToClipboard`. + +## What went wrong + +WRITE WHAT WENT WRONG HERE. + +## Reproducing the bug + +Steps for repeating the bug: + +1. Write a list of steps. +2. Otherwise nobody will fix the bug. diff --git a/LICENSE b/LICENSE index a8162f2..739ccae 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016, w0rp +Copyright (c) 2016-2018, w0rp All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile deleted file mode 100644 index a8c1da4..0000000 --- a/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -SHELL := /usr/bin/env bash -IMAGE ?= w0rp/ale -CURRENT_IMAGE_ID = 107e4efc4267 -DOCKER_FLAGS = --rm -v $(PWD):/testplugin -v $(PWD)/test:/home "$(IMAGE)" - -test-setup: - docker images -q w0rp/ale | grep ^$(CURRENT_IMAGE_ID) > /dev/null || \ - docker pull $(IMAGE) - -test: test-setup - @:; \ - vims=$$(docker run --rm $(IMAGE) ls /vim-build/bin | grep -E '^n?vim'); \ - if [ -z "$$vims" ]; then echo "No Vims found!"; exit 1; fi; \ - EXIT=0; \ - for vim in $$vims; do \ - echo; \ - echo '========================================'; \ - echo "Running tests for $$vim"; \ - echo '========================================'; \ - echo; \ - docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! test/*' || EXIT=$$?; \ - done; \ - echo; \ - 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 /testplugin | sed s:^/testplugin/:: || EXIT=$$?; \ - set +o pipefail; \ - echo; \ - echo '========================================'; \ - echo 'Running custom checks'; \ - echo '========================================'; \ - echo 'Custom warnings/errors follow:'; \ - echo; \ - set -o pipefail; \ - docker run -a stdout $(DOCKER_FLAGS) /testplugin/custom-checks /testplugin | sed s:^/testplugin/:: || EXIT=$$?; \ - set +o pipefail; \ - echo; \ - exit $$EXIT; - -.DEFAULT_GOAL := test diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..9411653 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ + diff --git a/README.md b/README.md index 8996c98..d5335e2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# Asynchronous Lint Engine [![Build Status](https://travis-ci.org/w0rp/ale.svg?branch=master)](https://travis-ci.org/w0rp/ale) +# Asynchronous Lint Engine [![Travis CI Build Status](https://travis-ci.org/w0rp/ale.svg?branch=master)](https://travis-ci.org/w0rp/ale) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/r0ef1xu8xjmik58d/branch/master?svg=true)](https://ci.appveyor.com/project/w0rp/ale) + ![ALE Logo by Mark Grealish - https://www.bhalash.com/](img/logo.jpg?raw=true) @@ -15,24 +16,38 @@ back to a filesystem. In other words, this plugin allows you to lint while you type. +In addition to linting support, ALE offers some support for fixing code with +formatting tools, and some Language Server Protocol and `tsserver` features. + ## Table of Contents 1. [Supported Languages and Tools](#supported-languages) 2. [Usage](#usage) + 1. [Linting](#usage-linting) + 2. [Fixing](#usage-fixing) + 3. [Completion](#usage-completion) + 4. [Go To Definition](#usage-go-to-definition) 3. [Installation](#installation) - 1. [Installation with Pathogen](#installation-with-pathogen) - 2. [Installation with Vundle](#installation-with-vundle) - 3. [Manual Installation](#manual-installation) -4. [FAQ](#faq) - 1. [How do I disable particular linters?](#faq-disable-linters) - 2. [How can I keep the sign gutter open?](#faq-disable-linters) - 3. [How can I change the signs ALE uses?](#faq-change-signs) - 4. [How can I show errors or warnings in my statusline?](#faq-statusline) - 5. [How can I change the format for echo messages?](#faq-echo-format) - 6. [How can I execute some code when ALE stops linting?](#faq-autocmd) - 7. [How can I navigate between errors quickly?](#faq-navigation) - 8. [How can I run linters only when I save files?](#faq-lint-on-save) - 9. [How can I use the quickfix list instead of the loclist?](#faq-quickfix) + 1. [Installation with Vim package management](#standard-installation) + 2. [Installation with Pathogen](#installation-with-pathogen) + 3. [Installation with Vundle](#installation-with-vundle) +4. [Contributing](#contributing) +5. [FAQ](#faq) + 1. [How do I disable particular linters?](#faq-disable-linters) + 2. [How can I keep the sign gutter open?](#faq-keep-signs) + 3. [How can I change the signs ALE uses?](#faq-change-signs) + 4. [How can I change or disable the highlights ALE uses?](#faq-change-highlights) + 5. [How can I show errors or warnings in my statusline?](#faq-statusline) + 6. [How can I show errors or warnings in my lightline?](#faq-lightline) + 7. [How can I change the format for echo messages?](#faq-echo-format) + 8. [How can I execute some code when ALE starts or stops linting?](#faq-autocmd) + 9. [How can I navigate between errors quickly?](#faq-navigation) + 10. [How can I run linters only when I save files?](#faq-lint-on-save) + 11. [How can I use the quickfix list instead of the loclist?](#faq-quickfix) + 12. [How can I check JSX files with both stylelint and eslint?](#faq-jsx-stylelint-eslint) + 13. [Will this plugin eat all of my laptop battery power?](#faq-my-battery-is-sad) + 14. [How can I configure my C or C++ project?](#faq-c-configuration) + 15. [How can I configure ALE differently for different buffers?](#faq-buffer-configuration) @@ -45,66 +60,122 @@ tools will be run in combination, so they can be complementary. Keep the table rows sorted alphabetically by the language name, and the tools in the tools column sorted alphabetically by the tool name. That seems to be the fairest way to arrange this table. + +Remember to also update doc/ale.txt, which has a similar list with different +formatting. --> +**Notes:** + +* *^ No linters for text or Vim help filetypes are enabled by default.* +* *!! These linters check only files on disk. See `:help ale-lint-file-linters`* + | Language | Tools | | -------- | ----- | +| ASM | [gcc](https://gcc.gnu.org) | | Ansible | [ansible-lint](https://github.com/willthames/ansible-lint) | -| Bash | [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/) | -| Bourne Shell | [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/) | -| C | [cppcheck](http://cppcheck.sourceforge.net), [gcc](https://gcc.gnu.org/), [clang](http://clang.llvm.org/)| -| C++ (filetype cpp) | [cppcheck] (http://cppcheck.sourceforge.net), [gcc](https://gcc.gnu.org/)| -| C# | [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) | +| API Blueprint | [drafter](https://github.com/apiaryio/drafter) | +| AsciiDoc | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [write-good](https://github.com/btford/write-good) | +| Awk | [gawk](https://www.gnu.org/software/gawk/)| +| Bash | shell [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | +| Bourne Shell | shell [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | +| C | [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [clang](http://clang.llvm.org/), [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | +| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) !!, [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) !!, [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | +| CUDA | [nvcc](http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html) | +| C# | [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) see:`help ale-cs-mcs` for details, [mcsc](http://www.mono-project.com/docs/about-mono/languages/csharp/) !! see:`help ale-cs-mcsc` for details and configuration| | Chef | [foodcritic](http://www.foodcritic.io/) | +| Clojure | [joker](https://github.com/candid82/joker) | +| CMake | [cmakelint](https://github.com/richq/cmake-lint) | | CoffeeScript | [coffee](http://coffeescript.org/), [coffeelint](https://www.npmjs.com/package/coffeelint) | -| CSS | [csslint](http://csslint.net/), [stylelint](https://github.com/stylelint/stylelint) | +| Crystal | [crystal](https://crystal-lang.org/) !! | +| CSS | [csslint](http://csslint.net/), [prettier](https://github.com/prettier/prettier), [stylelint](https://github.com/stylelint/stylelint) | | Cython (pyrex filetype) | [cython](http://cython.org/) | | D | [dmd](https://dlang.org/dmd-linux.html) | -| Dockerfile | [hadolint](https://github.com/lukasmartinelli/hadolint) | -| Elixir | [credo](https://github.com/rrrene/credo) | -| Elm | [elm-make](https://github.com/elm-lang/elm-make) | -| Erlang | [erlc](http://erlang.org/doc/man/erlc.html) | +| Dafny | [dafny](https://rise4fun.com/Dafny) !! | +| Dart | [dartanalyzer](https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli) !!, [language_server](https://github.com/natebosch/dart_language_server) | +| Dockerfile | [hadolint](https://github.com/hadolint/hadolint) | +| Elixir | [credo](https://github.com/rrrene/credo), [dialyxir](https://github.com/jeremyjh/dialyxir), [dogma](https://github.com/lpil/dogma) !!| +| Elm | [elm-format](https://github.com/avh4/elm-format), [elm-make](https://github.com/elm-lang/elm-make) | +| Erb | [erb](https://apidock.com/ruby/ERB), [erubi](https://github.com/jeremyevans/erubi), [erubis](https://github.com/kwatch/erubis) | +| Erlang | [erlc](http://erlang.org/doc/man/erlc.html), [SyntaxErl](https://github.com/ten0s/syntaxerl) | +| Fish | fish [-n flag](https://linux.die.net/man/1/fish) | Fortran | [gcc](https://gcc.gnu.org/) | -| Go | [gofmt -e](https://golang.org/cmd/gofmt/), [go vet](https://golang.org/cmd/vet/), [golint](https://godoc.org/github.com/golang/lint), [go build](https://golang.org/cmd/go/) | -| Haskell | [ghc](https://www.haskell.org/ghc/), [hlint](https://hackage.haskell.org/package/hlint) | -| HTML | [HTMLHint](http://htmlhint.com/), [tidy](http://www.html-tidy.org/) | -| JavaScript | [eslint](http://eslint.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [flow](https://flowtype.org/) | -| JSON | [jsonlint](http://zaa.ch/jsonlint/) | -| LaTeX | [chktex](http://www.nongnu.org/chktex/), [lacheck](https://www.ctan.org/pkg/lacheck) | -| Lua | [luacheck](https://github.com/mpeterv/luacheck) | -| Markdown | [mdl](https://github.com/mivok/markdownlint), [proselint](http://proselint.com/)| +| Fountain | [proselint](http://proselint.com/) | +| FusionScript | [fusion-lint](https://github.com/RyanSquared/fusionscript) | +| Git Commit Messages | [gitlint](https://github.com/jorisroovers/gitlint) | +| GLSL | [glslang](https://github.com/KhronosGroup/glslang), [glslls](https://github.com/svenstaro/glsl-language-server) | +| Go | [gofmt](https://golang.org/cmd/gofmt/), [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports), [go vet](https://golang.org/cmd/vet/) !!, [golint](https://godoc.org/github.com/golang/lint), [gotype](https://godoc.org/golang.org/x/tools/cmd/gotype), [gometalinter](https://github.com/alecthomas/gometalinter) !!, [go build](https://golang.org/cmd/go/) !!, [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple) !!, [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) !! | +| GraphQL | [eslint](http://eslint.org/), [gqlint](https://github.com/happylinks/gqlint), [prettier](https://github.com/prettier/prettier) | +| Haml | [haml-lint](https://github.com/brigade/haml-lint) | +| Handlebars | [ember-template-lint](https://github.com/rwjblue/ember-template-lint) | +| Haskell | [brittany](https://github.com/lspitzner/brittany), [ghc](https://www.haskell.org/ghc/), [stack-ghc](https://haskellstack.org/), [stack-build](https://haskellstack.org/) !!, [ghc-mod](https://github.com/DanielG/ghc-mod), [stack-ghc-mod](https://github.com/DanielG/ghc-mod), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools), [hfmt](https://github.com/danstiner/hfmt) | +| HTML | [alex](https://github.com/wooorm/alex) !!, [HTMLHint](http://htmlhint.com/), [proselint](http://proselint.com/), [tidy](http://www.html-tidy.org/), [write-good](https://github.com/btford/write-good) | +| Idris | [idris](http://www.idris-lang.org/) | +| Java | [checkstyle](http://checkstyle.sourceforge.net), [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html), [google-java-format](https://github.com/google/google-java-format) | +| JavaScript | [eslint](http://eslint.org/), [flow](https://flowtype.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [prettier](https://github.com/prettier/prettier), [prettier-eslint](https://github.com/prettier/prettier-eslint), [prettier-standard](https://github.com/sheerun/prettier-standard), [standard](http://standardjs.com/), [xo](https://github.com/sindresorhus/xo) +| JSON | [fixjson](https://github.com/rhysd/fixjson), [jsonlint](http://zaa.ch/jsonlint/), [jq](https://stedolan.github.io/jq/), [prettier](https://github.com/prettier/prettier) | +| Kotlin | [kotlinc](https://kotlinlang.org) !!, [ktlint](https://ktlint.github.io) !! see `:help ale-integration-kotlin` for configuration instructions | +| LaTeX | [alex](https://github.com/wooorm/alex) !!, [chktex](http://www.nongnu.org/chktex/), [lacheck](https://www.ctan.org/pkg/lacheck), [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | +| Less | [lessc](https://www.npmjs.com/package/less), [prettier](https://github.com/prettier/prettier), [stylelint](https://github.com/stylelint/stylelint) | +| LLVM | [llc](https://llvm.org/docs/CommandGuide/llc.html) | +| Lua | [luac](https://www.lua.org/manual/5.1/luac.html), [luacheck](https://github.com/mpeterv/luacheck) | +| Mail | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale) | +| Make | [checkmake](https://github.com/mrtazz/checkmake) | +| Markdown | [alex](https://github.com/wooorm/alex) !!, [mdl](https://github.com/mivok/markdownlint), [prettier](https://github.com/prettier/prettier), [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [remark-lint](https://github.com/wooorm/remark-lint) !!, [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | | MATLAB | [mlint](https://www.mathworks.com/help/matlab/ref/mlint.html) | -| OCaml | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-integration-ocaml-merlin` for configuration instructions +| Nim | [nim check](https://nim-lang.org/docs/nimc.html) !! | +| nix | [nix-instantiate](http://nixos.org/nix/manual/#sec-nix-instantiate) | +| nroff | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good)| +| Objective-C | [clang](http://clang.llvm.org/) | +| Objective-C++ | [clang](http://clang.llvm.org/) | +| OCaml | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-ocaml-merlin` for configuration instructions, [ols](https://github.com/freebroccolo/ocaml-language-server) | | Perl | [perl -c](https://perl.org/), [perl-critic](https://metacpan.org/pod/Perl::Critic) | -| PHP | [hack](http://hacklang.org/), [php -l](https://secure.php.net/), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer) | +| PHP | [hack](http://hacklang.org/), [hackfmt](https://github.com/facebook/flow/tree/master/hack/hackfmt), [langserver](https://github.com/felixfbecker/php-language-server), [phan](https://github.com/phan/phan) see `:help ale-php-phan` to instructions, [php -l](https://secure.php.net/), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer), [phpmd](https://phpmd.org), [phpstan](https://github.com/phpstan/phpstan), [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer), [php-cs-fixer](http://cs.sensiolabs.org/) | +| PO | [alex](https://github.com/wooorm/alex) !!, [msgfmt](https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html), [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | +| Pod | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | +| Pony | [ponyc](https://github.com/ponylang/ponyc) | +| proto | [protoc-gen-lint](https://github.com/ckaznocha/protoc-gen-lint) | | Pug | [pug-lint](https://github.com/pugjs/pug-lint) | | Puppet | [puppet](https://puppet.com), [puppet-lint](https://puppet-lint.com) | -| Python | [flake8](http://flake8.pycqa.org/en/latest/), [mypy](http://mypy-lang.org/), [pylint](https://www.pylint.org/) | -| Ruby | [rubocop](https://github.com/bbatsov/rubocop) | -| Rust | [rustc](https://www.rust-lang.org/), cargo (see `:help ale-integration-rust` for configuration instructions) | +| Python | [autopep8](https://github.com/hhatto/autopep8), [flake8](http://flake8.pycqa.org/en/latest/), [isort](https://github.com/timothycrosley/isort), [mypy](http://mypy-lang.org/), [prospector](http://github.com/landscapeio/prospector), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pyls](https://github.com/palantir/python-language-server), [pylint](https://www.pylint.org/) !!, [yapf](https://github.com/google/yapf) | +| R | [lintr](https://github.com/jimhester/lintr) | +| ReasonML | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-reasonml-ols` for configuration instructions, [ols](https://github.com/freebroccolo/ocaml-language-server), [refmt](https://github.com/reasonml/reason-cli) | +| reStructuredText | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [redpen](http://redpen.cc/), [rstcheck](https://github.com/myint/rstcheck), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good) | +| Re:VIEW | [redpen](http://redpen.cc/) | +| RPM spec | [rpmlint](https://github.com/rpm-software-management/rpmlint) (disabled by default; see `:help ale-integration-spec`) | +| Ruby | [brakeman](http://brakemanscanner.org/) !!, [rails_best_practices](https://github.com/flyerhzm/rails_best_practices) !!, [reek](https://github.com/troessner/reek), [rubocop](https://github.com/bbatsov/rubocop), [ruby](https://www.ruby-lang.org) | +| Rust | cargo !! (see `:help ale-integration-rust` for configuration instructions), [rls](https://github.com/rust-lang-nursery/rls), [rustc](https://www.rust-lang.org/), [rustfmt](https://github.com/rust-lang-nursery/rustfmt) | | SASS | [sass-lint](https://www.npmjs.com/package/sass-lint), [stylelint](https://github.com/stylelint/stylelint) | -| SCSS | [sass-lint](https://www.npmjs.com/package/sass-lint), [scss-lint](https://github.com/brigade/scss-lint), [stylelint](https://github.com/stylelint/stylelint) | -| Scala | [scalac](http://scala-lang.org) | -| Swift | [swiftlint](https://swift.org/) | -| Tex | [proselint](http://proselint.com/) | -| Text^^ | [proselint](http://proselint.com/) | -| TypeScript | [tslint](https://github.com/palantir/tslint), typecheck | +| SCSS | [prettier](https://github.com/prettier/prettier), [sass-lint](https://www.npmjs.com/package/sass-lint), [scss-lint](https://github.com/brigade/scss-lint), [stylelint](https://github.com/stylelint/stylelint) | +| Scala | [scalac](http://scala-lang.org), [scalastyle](http://www.scalastyle.org) | +| Slim | [slim-lint](https://github.com/sds/slim-lint) | +| SML | [smlnj](http://www.smlnj.org/) | +| Solidity | [solhint](https://github.com/protofire/solhint), [solium](https://github.com/duaraghav8/Solium) | +| Stylus | [stylelint](https://github.com/stylelint/stylelint) | +| SQL | [sqlint](https://github.com/purcell/sqlint) | +| Swift | [swiftlint](https://github.com/realm/SwiftLint), [swiftformat](https://github.com/nicklockwood/SwiftFormat) | +| Tcl | [nagelfar](http://nagelfar.sourceforge.net) !! | +| Terraform | [tflint](https://github.com/wata727/tflint) | +| Texinfo | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good)| +| Text^ | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale), [write-good](https://github.com/btford/write-good), [redpen](http://redpen.cc/) | +| Thrift | [thrift](http://thrift.apache.org/) | +| TypeScript | [eslint](http://eslint.org/), [prettier](https://github.com/prettier/prettier), [tslint](https://github.com/palantir/tslint), tsserver, typecheck | | Verilog | [iverilog](https://github.com/steveicarus/iverilog), [verilator](http://www.veripool.org/projects/verilator/wiki/Intro) | | Vim | [vint](https://github.com/Kuniwak/vint) | -| YAML | [yamllint](https://yamllint.readthedocs.io/) | - -* *^^ No text linters are enabled by default.* - -If you would like to see support for more languages and tools, please -[create an issue](https://github.com/w0rp/ale/issues) -or [create a pull request](https://github.com/w0rp/ale/pulls). -If your tool can read from stdin or you have code to suggest which is good, -support can be happily added for it. +| Vim help^ | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | +| Vue | [prettier](https://github.com/prettier/prettier) | +| XHTML | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) | +| XML | [xmllint](http://xmlsoft.org/xmllint.html) | +| YAML | [swaglint](https://github.com/byCedric/swaglint), [yamllint](https://yamllint.readthedocs.io/) | ## 2. Usage + + +### 2.i Linting + Once this plugin is installed, while editing your files in supported languages and tools which have been correctly installed, this plugin will send the contents of your text buffers to a variety of @@ -117,19 +188,115 @@ documented in [the Vim help file](doc/ale.txt). For more information on the options ALE offers, consult `:help ale-options` for global options and `:help ale-linter-options` for options specified to particular linters. + + +### 2.ii Fixing + +ALE can fix files with the `ALEFix` command. Functions need to be configured +for different filetypes with the `g:ale_fixers` variable. For example, the +following code can be used to fix JavaScript code with ESLint: + +```vim +" Put this in vimrc or a plugin file of your own. +" After this is configured, :ALEFix will try and fix your JS code with ESLint. +let g:ale_fixers = { +\ 'javascript': ['eslint'], +\} + +" Set this setting in vimrc if you want to fix files automatically on save. +" This is off by default. +let g:ale_fix_on_save = 1 +``` + +The `:ALEFixSuggest` command will suggest some supported tools for fixing code, +but fixers can be also implemented with functions, including lambda functions +too. See `:help ale-fix` for detailed information. + + + +### 2.iii Completion + +ALE offers some support for completion via hijacking of omnicompletion while you +type. All of ALE's completion information must come from Language Server +Protocol linters, or similar protocols. At the moment, completion is only +supported for TypeScript code with `tsserver`, when `tsserver` is enabled. You +can enable completion like so: + +```vim +" Enable completion where available. +let g:ale_completion_enabled = 1 +``` + +See `:help ale-completion` for more information. + + + +### 2.iv Go To Definition + +ALE supports jumping to the definition of words under your cursor with the +`:ALEGoToDefinition` command using any enabled LSP linters and `tsserver`. + +See `:help ale-go-to-definition` for more information. + ## 3. Installation To install this plugin, you should use one of the following methods. For Windows users, replace usage of the Unix `~/.vim` directory with -`%USERPROFILE%\_vim`, or another directory if you have configured +`%USERPROFILE%\vimfiles`, or another directory if you have configured Vim differently. On Windows, your `~/.vimrc` file will be similarly stored in `%USERPROFILE%\_vimrc`. + + +### 3.i. Installation with Vim package management + +In Vim 8 and NeoVim, you can install plugins easily without needing to use +any other tools. Simply clone the plugin into your `pack` directory. + +#### Vim 8 on Unix + +```bash +mkdir -p ~/.vim/pack/git-plugins/start +git clone https://github.com/w0rp/ale.git ~/.vim/pack/git-plugins/start/ale +``` + +#### NeoVim on Unix + +```bash +mkdir -p ~/.local/share/nvim/site/pack/git-plugins/start +git clone https://github.com/w0rp/ale.git ~/.local/share/nvim/site/pack/git-plugins/start/ale +``` + +#### Vim 8 on Windows + +```bash +# Run these commands in the "Git for Windows" Bash terminal +mkdir -p ~/vimfiles/pack/git-plugins/start +git clone https://github.com/w0rp/ale.git ~/vimfiles/pack/git-plugins/start/ale +``` + +#### Generating Vim help files + +You can add the following line to your vimrc files to generate documentation +tags automatically, if you don't have something similar already, so you can use +the `:help` command to consult ALE's online documentation: + +```vim +" Put these lines at the very end of your vimrc file. + +" Load all plugins now. +" Plugins need to be added to runtimepath before helptags can be generated. +packloadall +" Load all of the helptags now, after plugins have been loaded. +" All messages and errors will be ignored. +silent! helptags ALL +``` + -### 3.i. Installation with Pathogen +### 3.ii. Installation with Pathogen To install this module with [Pathogen](https://github.com/tpope/vim-pathogen), you should clone this repository to your bundle directory, and ensure @@ -143,7 +310,7 @@ git clone https://github.com/w0rp/ale.git -### 3.ii. Installation with Vundle +### 3.iii. Installation with Vundle You can install this plugin using [Vundle](https://github.com/VundleVim/Vundle.vim) by using the path on GitHub for this repository. @@ -154,48 +321,30 @@ Plugin 'w0rp/ale' See the Vundle documentation for more information. - + -### 3.iii. Manual Installation +## 4. Contributing -For installation without a package manager, you can clone this git repository -into a bundle directory as with pathogen, and add the repository to your -runtime path yourself. First clone the repository. +If you would like to see support for more languages and tools, please +[create an issue](https://github.com/w0rp/ale/issues) +or [create a pull request](https://github.com/w0rp/ale/pulls). +If your tool can read from stdin or you have code to suggest which is good, +support can be happily added for it. -```bash -cd ~/.vim/bundle -git clone https://github.com/w0rp/ale.git -``` +If you are interested in the general direction of the project, check out the +[wiki home page](https://github.com/w0rp/ale/wiki). The wiki includes a +Roadmap for the future, and more. -Then, modify your `~/.vimrc` file to add this plugin to your runtime path. - -```vim -set nocompatible -filetype off - -let &runtimepath.=',~/.vim/bundle/ale' - -filetype plugin on -``` - -You can add the following line to generate documentation tags automatically, -if you don't have something similar already, so you can use the `:help` command -to consult ALE's online documentation: - -```vim -silent! helptags ALL -``` - -Because the author of this plugin is a weird nerd, this is his preferred -installation method. +If you'd liked to discuss the project more directly, check out the `#vim-ale` channel +on Freenode. Web chat is available [here](https://webchat.freenode.net/?channels=vim-ale). -## 4. FAQ +## 5. FAQ -### 4.i. How do I disable particular linters? +### 5.i. How do I disable particular linters? By default, all available tools for all supported languages will be run. If you want to only select a subset of the tools, simply create a @@ -219,7 +368,7 @@ in each directory corresponds to the name of a particular linter. -### 4.ii. How can I keep the sign gutter open? +### 5.ii. How can I keep the sign gutter open? You can keep the sign gutter open at all times by setting the `g:ale_sign_column_always` to 1 @@ -230,7 +379,7 @@ let g:ale_sign_column_always = 1 -### 4.iii. How can I change the signs ALE uses? +### 5.iii. How can I change the signs ALE uses? Use these options to specify what text should be used for signs: @@ -248,43 +397,91 @@ highlight clear ALEErrorSign highlight clear ALEWarningSign ``` + + +### 5.iv. 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. + -### 4.iv. How can I show errors or warnings in my statusline? +### 5.v. How can I show errors or warnings in my statusline? -You can use `ALEGetStatusLine()` to integrate ALE into vim statusline. -To enable it, you should have in your `statusline` settings +[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 -%{ALEGetStatusLine()} +" Set this. Airline will handle the rest. +let g:airline#extensions#ale#enabled = 1 ``` -When errors are detected a string showing the number of errors will be shown. -You can customize the output format using the global list `g:ale_statusline_format` where: +If you don't want to use vim-airline, you can implement your own statusline +function without adding any other plugins. ALE provides a function for counting +the number of problems for this purpose, named `ale#statusline#Count`. -- The 1st element is for errors -- The 2nd element is for warnings -- The 3rd element is for when no errors are detected - -e.g +Say you want to display all errors as one figure, and all non-errors as another +figure. You can do the following: ```vim -let g:ale_statusline_format = ['⨉ %d', '⚠ %d', '⬥ ok'] +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()} ``` -![Statusline with issues](img/issues.png) -![Statusline with no issues](img/no_issues.png) +See `:help ale#statusline#Count()` for more information. + + + +### 5.vi. How can I show errors or warnings in my 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). -### 4.v. How can I change the format for echo messages? +### 5.vii. 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 - * `%linter%` is the linter name - * `%severity` is the severity type + * `%s` is the error message itself + * `%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. @@ -302,22 +499,24 @@ Will give you: -### 4.vi. How can I execute some code when ALE stops linting? +### 5.viii. How can I execute some code when ALE starts or stops linting? ALE runs its own [autocmd](http://vimdoc.sourceforge.net/htmldoc/autocmd.html) -event whenever has a linter has been successfully executed and processed. This -autocmd event can be used to call arbitrary functions after ALE stops linting. +events whenever has a linter is started and has been successfully executed and +processed. These events can be used to call arbitrary functions before and after +ALE stops linting. ```vim augroup YourGroup autocmd! - autocmd User ALELint call YourFunction() + autocmd User ALELintPre call YourFunction() + autocmd User ALELintPost call YourFunction() augroup END ``` -### 4.vii. How can I navigate between errors quickly? +### 5.ix. 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 @@ -333,25 +532,27 @@ For more information, consult the online documentation with -### 4.viii. How can I run linters only when I save files? +### 5.x. 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. If you wish to run linters when files are saved, not -as you are editing files, then you can turn the option for linting -when text is changed off too. +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_save = 1 -let g:ale_lint_on_text_changed = 0 +let g:ale_lint_on_text_changed = 'never' " 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`. + -### 4.ix. How can I use the quickfix list instead of the loclist? +### 5.xi. 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 @@ -375,3 +576,125 @@ let g:ale_open_list = 1 " 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. + + + +### 5.xii. How can I check JSX files with both stylelint and eslint? + +If you configure ALE options correctly in your vimrc file, and install +the right tools, you can check JSX files with stylelint and eslint. + +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 your vimrc file: + +```vim +let g:ale_linters = {'jsx': ['stylelint', 'eslint']} +let g:ale_linter_aliases = {'jsx': 'css'} +``` + +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. + + + +### 5.xiii. 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 +behaviour. Set `g:ale_lint_on_text_changed` to `never` or `normal`. 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`. + + + +### 5.xiv. 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` and `clangtidy` 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. + +You may also configure buffer-local settings for linters with project-specific +vimrc files. [local_vimrc](https://github.com/LucHermitte/local_vimrc) can be +used for executing local vimrc files which can be shared in your project. + + + +### 5.xv. How can I configure ALE differently for different buffers? + +ALE offers various ways to configure which linters or fixers are run, and +other settings. For the majority of ALE's settings, they can either be +configured globally with a `g:` variable prefix, or for a specific buffer +with a `b:` variable prefix. For example, you can configure a Python ftplugin +file like so. + +```vim +" In ~/.vim/ftplugin/python.vim + +" Check Python files with flake8 and pylint. +let b:ale_linters = ['flake8', 'pylint'] +" Fix Python files with autopep8 and yapf. +let b:ale_fixers = ['autopep8', 'yapf'] +" Disable warnings about trailing whitespace for Python files. +let b:ale_warn_about_trailing_whitespace = 0 +``` + +For configuring files based on regular expression patterns matched against the +absolute path to a file, you can use `g:ale_pattern_options`. + +```vim +" Do not lint or fix minified files. +let g:ale_pattern_options = { +\ '\.min\.js$': {'ale_linters': [], 'ale_fixers': []}, +\ '\.min\.css$': {'ale_linters': [], 'ale_fixers': []}, +\} +" If you configure g:ale_pattern_options outside of vimrc, you need this. +let g:ale_pattern_options_enabled = 1 +``` + +Buffer-local variables for settings always override the global settings. diff --git a/after/plugin/ale.vim b/after/plugin/ale.vim new file mode 100644 index 0000000..d738dbd --- /dev/null +++ b/after/plugin/ale.vim @@ -0,0 +1,37 @@ +" Author: w0rp +" Description: Follow-up checks for the plugin: warn about conflicting plugins. + +" A flag for ensuring that this is not run more than one time. +if exists('g:loaded_ale_after') + finish +endif + +" Set the flag so this file is not run more than one time. +let g:loaded_ale_after = 1 + +" Check if the flag is available and set to 0 to disable checking for and +" emitting conflicting plugin warnings. +if exists('g:ale_emit_conflict_warnings') && !g:ale_emit_conflict_warnings + finish +endif + +" Conflicting Plugins Checks + +function! s:GetConflictingPluginWarning(plugin_name) abort + return 'ALE conflicts with ' . a:plugin_name + \ . '. Uninstall it, or disable this warning with ' + \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' + \ . '*before* plugins are loaded.' +endfunction + +if exists('g:loaded_syntastic_plugin') + throw s:GetConflictingPluginWarning('Syntastic') +endif + +if exists('g:loaded_neomake') + throw s:GetConflictingPluginWarning('Neomake') +endif + +if exists('g:loaded_validator_plugin') + throw s:GetConflictingPluginWarning('Validator') +endif diff --git a/ale_linters/ansible/ansible-lint.vim b/ale_linters/ansible/ansible-lint.vim deleted file mode 100644 index af1e28b..0000000 --- a/ale_linters/ansible/ansible-lint.vim +++ /dev/null @@ -1,9 +0,0 @@ -" Author: Bjorn Neergaard -" Description: ansible-lint for ansible-yaml files - -call ale#linter#Define('ansible', { -\ 'name': 'ansible', -\ 'executable': 'ansible', -\ 'command': g:ale#util#stdin_wrapper . ' .yml ansible-lint -p', -\ 'callback': 'ale#handlers#HandlePEP8Format', -\}) diff --git a/ale_linters/ansible/ansible_lint.vim b/ale_linters/ansible/ansible_lint.vim new file mode 100644 index 0000000..0b3b39c --- /dev/null +++ b/ale_linters/ansible/ansible_lint.vim @@ -0,0 +1,49 @@ +" Author: Bjorn Neergaard +" Description: ansible-lint for ansible-yaml files + +function! ale_linters#ansible#ansible_lint#Handle(buffer, lines) abort + for l:line in a:lines[:10] + if match(l:line, '^Traceback') >= 0 + return [{ + \ 'lnum': 1, + \ 'text': 'An exception was thrown. See :ALEDetail', + \ 'detail': join(a:lines, "\n"), + \}] + endif + endfor + + " Matches patterns line the following: + " + " test.yml:35: [EANSIBLE0002] Trailing whitespace + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?: \[?([[:alnum:]]+)\]? (.*)$' + let l:output = [] + + 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 + + return l:output +endfunction + +call ale#linter#Define('ansible', { +\ 'name': 'ansible', +\ 'executable': 'ansible', +\ 'command': 'ansible-lint -p %t', +\ 'callback': 'ale_linters#ansible#ansible_lint#Handle', +\}) diff --git a/ale_linters/apiblueprint/drafter.vim b/ale_linters/apiblueprint/drafter.vim new file mode 100644 index 0000000..9cded35 --- /dev/null +++ b/ale_linters/apiblueprint/drafter.vim @@ -0,0 +1,36 @@ +" 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 %t', +\ 'callback': 'ale_linters#apiblueprint#drafter#HandleErrors', +\}) diff --git a/ale_linters/asciidoc/alex.vim b/ale_linters/asciidoc/alex.vim new file mode 100644 index 0000000..79b04fc --- /dev/null +++ b/ale_linters/asciidoc/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for asciidoc files + +call ale#linter#Define('help', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/asciidoc/proselint.vim b/ale_linters/asciidoc/proselint.vim new file mode 100644 index 0000000..b636c06 --- /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 0000000..819e385 --- /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/write-good.vim b/ale_linters/asciidoc/write-good.vim new file mode 100644 index 0000000..c986cc6 --- /dev/null +++ b/ale_linters/asciidoc/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for AsciiDoc files + +call ale#linter#Define('asciidoc', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/asm/gcc.vim b/ale_linters/asm/gcc.vim new file mode 100644 index 0000000..4ac876f --- /dev/null +++ b/ale_linters/asm/gcc.vim @@ -0,0 +1,39 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'asm_gcc_executable') +endfunction + +function! ale_linters#asm#gcc#GetCommand(buffer) abort + return ale#Escape(ale_linters#asm#gcc#GetExecutable(a:buffer)) + \ . ' -x assembler -fsyntax-only ' + \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p: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_callback': 'ale_linters#asm#gcc#GetExecutable', +\ 'command_callback': 'ale_linters#asm#gcc#GetCommand', +\ 'callback': 'ale_linters#asm#gcc#Handle', +\}) diff --git a/ale_linters/awk/gawk.vim b/ale_linters/awk/gawk.vim new file mode 100644 index 0000000..ac6e915 --- /dev/null +++ b/ale_linters/awk/gawk.vim @@ -0,0 +1,26 @@ +" Author: kmarc +" Description: This file adds support for using GNU awk with sripts. + +let g:ale_awk_gawk_executable = +\ get(g:, 'ale_awk_gawk_executable', 'gawk') + +let g:ale_awk_gawk_options = +\ get(g:, 'ale_awk_gawk_options', '') + +function! ale_linters#awk#gawk#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'awk_gawk_executable') +endfunction + +function! ale_linters#awk#gawk#GetCommand(buffer) abort + return ale_linters#awk#gawk#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'awk_gawk_options') + \ . ' ' . '-f %t --lint /dev/null' +endfunction + +call ale#linter#Define('awk', { +\ 'name': 'gawk', +\ 'executable_callback': 'ale_linters#awk#gawk#GetExecutable', +\ 'command_callback': 'ale_linters#awk#gawk#GetCommand', +\ 'callback': 'ale#handlers#cpplint#HandleCppLintFormat', +\ 'output_stream': 'both' +\}) diff --git a/ale_linters/c/clang.vim b/ale_linters/c/clang.vim index 205dd92..7680305 100644 --- a/ale_linters/c/clang.vim +++ b/ale_linters/c/clang.vim @@ -1,20 +1,29 @@ " Author: Masahiro H https://github.com/mshr-h " Description: clang linter for c files -" Set this option to change the Clang options for warnings for C. -if !exists('g:ale_c_clang_options') - " let g:ale_c_clang_options = '-Wall' - " let g:ale_c_clang_options = '-std=c99 -Wall' - " c11 compatible - let g:ale_c_clang_options = '-std=c11 -Wall' -endif +call ale#Set('c_clang_executable', 'clang') +call ale#Set('c_clang_options', '-std=c11 -Wall') + +function! ale_linters#c#clang#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'c_clang_executable') +endfunction + +function! ale_linters#c#clang#GetCommand(buffer) abort + let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) + + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + return ale#Escape(ale_linters#c#clang#GetExecutable(a:buffer)) + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' + \ . ale#c#IncludeOptions(l:paths) + \ . ale#Var(a:buffer, 'c_clang_options') . ' -' +endfunction call ale#linter#Define('c', { \ 'name': 'clang', \ 'output_stream': 'stderr', -\ 'executable': 'clang', -\ 'command': 'clang -S -x c -fsyntax-only ' -\ . g:ale_c_clang_options -\ . ' -', -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'executable_callback': 'ale_linters#c#clang#GetExecutable', +\ 'command_callback': 'ale_linters#c#clang#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/c/clangtidy.vim b/ale_linters/c/clangtidy.vim new file mode 100644 index 0000000..47faa1e --- /dev/null +++ b/ale_linters/c/clangtidy.vim @@ -0,0 +1,64 @@ +" 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. +" This will disable compile_commands.json detection. +call ale#Set('c_clangtidy_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#c#clangtidy#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'c_clangtidy_executable') +endfunction + +function! s:GetBuildDirectory(buffer) abort + " Don't include build directory for header files, as compile_commands.json + " files don't consider headers to be translation units, and provide no + " commands for compiling header files. + if expand('#' . a:buffer) =~# '\v\.(h|hpp)$' + return '' + endif + + 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 + + return ale#c#FindCompileCommands(a:buffer) +endfunction + +function! ale_linters#c#clangtidy#GetCommand(buffer) abort + let l:checks = join(ale#Var(a:buffer, 'c_clangtidy_checks'), ',') + let l:build_dir = s:GetBuildDirectory(a:buffer) + + " Get the extra options if we couldn't find a build directory. + let l:options = empty(l:build_dir) + \ ? ale#Var(a:buffer, 'c_clangtidy_options') + \ : '' + + return ale#Escape(ale_linters#c#clangtidy#GetExecutable(a:buffer)) + \ . (!empty(l:checks) ? ' -checks=' . ale#Escape(l:checks) : '') + \ . ' %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_callback': 'ale_linters#c#clangtidy#GetExecutable', +\ 'command_callback': 'ale_linters#c#clangtidy#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/c/cppcheck.vim b/ale_linters/c/cppcheck.vim index d4362cc..4db93f7 100644 --- a/ale_linters/c/cppcheck.vim +++ b/ale_linters/c/cppcheck.vim @@ -1,16 +1,39 @@ " Author: Bart Libert " Description: cppcheck linter for c files -" Set this option to change the cppcheck options -if !exists('g:ale_c_cppcheck_options') - let g:ale_c_cppcheck_options = '--enable=style' -endif +call ale#Set('c_cppcheck_executable', 'cppcheck') +call ale#Set('c_cppcheck_options', '--enable=style') + +function! ale_linters#c#cppcheck#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'c_cppcheck_executable') +endfunction + +function! ale_linters#c#cppcheck#GetCommand(buffer) abort + " 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:compile_commmands_path = ale#path#FindNearestFile(a:buffer, 'compile_commands.json') + + let l:cd_command = !empty(l:compile_commmands_path) + \ ? ale#path#CdString(fnamemodify(l:compile_commmands_path, ':h')) + \ : '' + let l:compile_commands_option = !empty(l:compile_commmands_path) + \ ? '--project=compile_commands.json ' + \ : '' + + return l:cd_command + \ . ale#Escape(ale_linters#c#cppcheck#GetExecutable(a:buffer)) + \ . ' -q --language=c ' + \ . l:compile_commands_option + \ . ale#Var(a:buffer, 'c_cppcheck_options') + \ . ' %t' +endfunction call ale#linter#Define('c', { \ 'name': 'cppcheck', \ 'output_stream': 'both', -\ 'executable': 'cppcheck', -\ 'command': g:ale#util#stdin_wrapper . ' .c cppcheck -q --language=c ' -\ . g:ale_c_cppcheck_options, -\ 'callback': 'ale#handlers#HandleCppCheckFormat', +\ 'executable_callback': 'ale_linters#c#cppcheck#GetExecutable', +\ 'command_callback': 'ale_linters#c#cppcheck#GetCommand', +\ 'callback': 'ale#handlers#cppcheck#HandleCppCheckFormat', \}) diff --git a/ale_linters/c/flawfinder.vim b/ale_linters/c/flawfinder.vim new file mode 100644 index 0000000..27f269f --- /dev/null +++ b/ale_linters/c/flawfinder.vim @@ -0,0 +1,30 @@ +" 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) + +function! ale_linters#c#flawfinder#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'c_flawfinder_executable') +endfunction + +function! ale_linters#c#flawfinder#GetCommand(buffer) abort + + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'c_flawfinder_minlevel') + + return ale#Escape(ale_linters#c#flawfinder#GetExecutable(a:buffer)) + \ . ' -CDQS' + \ . ale#Var(a:buffer, 'c_flawfinder_options') + \ . l:minlevel + \ . ' %t' +endfunction + +call ale#linter#Define('c', { +\ 'name': 'flawfinder', +\ 'output_stream': 'stdout', +\ 'executable_callback': 'ale_linters#c#flawfinder#GetExecutable', +\ 'command_callback': 'ale_linters#c#flawfinder#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/c/gcc.vim b/ale_linters/c/gcc.vim index 594c5c8..4b241e3 100644 --- a/ale_linters/c/gcc.vim +++ b/ale_linters/c/gcc.vim @@ -1,24 +1,29 @@ " Author: w0rp " Description: gcc linter for c files -" Set this option to change the GCC options for warnings for C. -if !exists('g:ale_c_gcc_options') - " let g:ale_c_gcc_options = '-Wall' - " let g:ale_c_gcc_options = '-std=c99 -Wall' - " c11 compatible - let g:ale_c_gcc_options = '-std=c11 -Wall' -endif +call ale#Set('c_gcc_executable', 'gcc') +call ale#Set('c_gcc_options', '-std=c11 -Wall') + +function! ale_linters#c#gcc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'c_gcc_executable') +endfunction function! ale_linters#c#gcc#GetCommand(buffer) abort - return 'gcc -S -x c -fsyntax-only ' - \ . g:ale_c_gcc_options . ' -' + let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + return ale#Escape(ale_linters#c#gcc#GetExecutable(a:buffer)) + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' + \ . ale#c#IncludeOptions(l:paths) + \ . ale#Var(a:buffer, 'c_gcc_options') . ' -' endfunction call ale#linter#Define('c', { \ 'name': 'gcc', \ 'output_stream': 'stderr', -\ 'executable': 'gcc', +\ 'executable_callback': 'ale_linters#c#gcc#GetExecutable', \ 'command_callback': 'ale_linters#c#gcc#GetCommand', -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/chef/foodcritic.vim b/ale_linters/chef/foodcritic.vim index bfe73c3..2c28246 100644 --- a/ale_linters/chef/foodcritic.vim +++ b/ale_linters/chef/foodcritic.vim @@ -1,31 +1,38 @@ " 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'chef_foodcritic_executable') +endfunction + +function! ale_linters#chef#foodcritic#GetCommand(buffer) abort + let l:executable = ale_linters#chef#foodcritic#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'chef_foodcritic_options') + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . 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 = '^\(.\+:\s.\+\):\s\(.\+\):\(\d\+\)$' + let l:pattern = '\v([^:]+): (.+): ([a-zA-Z]?:?[^:]+):(\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 l:text = l:match[1] - - " vcol is Needed to indicate that the column is a character. + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[3] + 0, - \ 'vcol': 0, - \ 'col': 0, - \ 'text': l:text, + \ 'code': l:match[1], + \ 'text': l:match[2], + \ 'filename': l:match[3], + \ 'lnum': l:match[4] + 0, \ 'type': 'W', - \ 'nr': -1, \}) endfor @@ -34,8 +41,8 @@ endfunction call ale#linter#Define('chef', { \ 'name': 'foodcritic', -\ 'executable': 'foodcritic', -\ 'command': g:ale#util#stdin_wrapper . ' .rb foodcritic', +\ 'executable_callback': 'ale_linters#chef#foodcritic#GetExecutable', +\ 'command_callback': 'ale_linters#chef#foodcritic#GetCommand', \ 'callback': 'ale_linters#chef#foodcritic#Handle', +\ 'lint_file': 1, \}) - diff --git a/ale_linters/clojure/joker.vim b/ale_linters/clojure/joker.vim new file mode 100644 index 0000000..e78066f --- /dev/null +++ b/ale_linters/clojure/joker.vim @@ -0,0 +1,32 @@ +" 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 --lint %t', +\ 'callback': 'ale_linters#clojure#joker#HandleJokerFormat', +\}) diff --git a/ale_linters/cmake/cmakelint.vim b/ale_linters/cmake/cmakelint.vim new file mode 100644 index 0000000..7867651 --- /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_callback': 'ale_linters#cmake#cmakelint#Executable', +\ 'command_callback': 'ale_linters#cmake#cmakelint#Command', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/coffee/coffee.vim b/ale_linters/coffee/coffee.vim index 243fec6..f253928 100644 --- a/ale_linters/coffee/coffee.vim +++ b/ale_linters/coffee/coffee.vim @@ -1,10 +1,23 @@ " Author: KabbAmine - https://github.com/KabbAmine " Description: Coffee for checking coffee files +function! ale_linters#coffee#coffee#GetExecutable(buffer) abort + return ale#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': 'coffee', -\ 'command': 'coffee -cp -s', +\ 'executable_callback': 'ale_linters#coffee#coffee#GetExecutable', +\ 'command_callback': 'ale_linters#coffee#coffee#GetCommand', \ 'output_stream': 'stderr', -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/coffee/coffeelint.vim b/ale_linters/coffee/coffeelint.vim index 96b8c59..6d3df35 100644 --- a/ale_linters/coffee/coffeelint.vim +++ b/ale_linters/coffee/coffeelint.vim @@ -1,6 +1,19 @@ " Author: Prashanth Chandra https://github.com/prashcr " Description: coffeelint linter for coffeescript files +function! ale_linters#coffee#coffeelint#GetExecutable(buffer) abort + return ale#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: " @@ -8,30 +21,14 @@ function! ale_linters#coffee#coffeelint#Handle(buffer, lines) abort " stdin,14,,error,Throwing strings is forbidden " " Note that we currently ignore lineNumberEnd for multiline errors - let l:pattern = 'stdin,\(\d\+\),\(\d*\),\(.\+\),\(.\+\)' + let l:pattern = 'stdin,\(\d\+\),\(\d*\),\(.\{-1,}\),\(.\+\)' 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 l:line = l:match[1] + 0 - let l:column = 1 - let l:type = l:match[3] ==# 'error' ? 'E' : 'W' - let l:text = l:match[4] - - " vcol is needed to indicate that the column is a character + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': l:column, - \ 'text': l:text, - \ 'type': l:type, - \ 'nr': -1, + \ 'lnum': str2nr(l:match[1]), + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \ 'text': l:match[4], \}) endfor @@ -40,7 +37,7 @@ endfunction call ale#linter#Define('coffee', { \ 'name': 'coffeelint', -\ 'executable': 'coffeelint', -\ 'command': 'coffeelint --stdin --reporter csv', +\ 'executable_callback': 'ale_linters#coffee#coffeelint#GetExecutable', +\ 'command_callback': 'ale_linters#coffee#coffeelint#GetCommand', \ 'callback': 'ale_linters#coffee#coffeelint#Handle', \}) diff --git a/ale_linters/cpp/clang.vim b/ale_linters/cpp/clang.vim new file mode 100644 index 0000000..105df82 --- /dev/null +++ b/ale_linters/cpp/clang.vim @@ -0,0 +1,29 @@ +" Author: Tomota Nakamura +" Description: clang linter for cpp files + +call ale#Set('cpp_clang_executable', 'clang++') +call ale#Set('cpp_clang_options', '-std=c++14 -Wall') + +function! ale_linters#cpp#clang#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_clang_executable') +endfunction + +function! ale_linters#cpp#clang#GetCommand(buffer) abort + let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) + + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + return ale#Escape(ale_linters#cpp#clang#GetExecutable(a:buffer)) + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' + \ . ale#c#IncludeOptions(l:paths) + \ . ale#Var(a:buffer, 'cpp_clang_options') . ' -' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'clang', +\ 'output_stream': 'stderr', +\ 'executable_callback': 'ale_linters#cpp#clang#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#clang#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/cpp/clangcheck.vim b/ale_linters/cpp/clangcheck.vim new file mode 100644 index 0000000..a109d5d --- /dev/null +++ b/ale_linters/cpp/clangcheck.vim @@ -0,0 +1,39 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_clangcheck_executable') +endfunction + +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:build_dir = ale#c#FindCompileCommands(a:buffer) + 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 ale#Escape(ale_linters#cpp#clangcheck#GetExecutable(a:buffer)) + \ . ' -analyze %s' + \ . (empty(l:build_dir) ? ' -extra-arg -Xclang -extra-arg -analyzer-output=text' : '') + \ . (!empty(l:user_options) ? ' ' . 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_callback': 'ale_linters#cpp#clangcheck#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#clangcheck#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/cpp/clangtidy.vim b/ale_linters/cpp/clangtidy.vim new file mode 100644 index 0000000..1d5fb77 --- /dev/null +++ b/ale_linters/cpp/clangtidy.vim @@ -0,0 +1,58 @@ +" 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. +" This will disable compile_commands.json detection. +call ale#Set('cpp_clangtidy_options', '') +call ale#Set('c_build_dir', '') + +function! ale_linters#cpp#clangtidy#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_clangtidy_executable') +endfunction + +function! s:GetBuildDirectory(buffer) abort + " Don't include build directory for header files, as compile_commands.json + " files don't consider headers to be translation units, and provide no + " commands for compiling header files. + if expand('#' . a:buffer) =~# '\v\.(h|hpp)$' + return '' + endif + + 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 + + return ale#c#FindCompileCommands(a:buffer) +endfunction + +function! ale_linters#cpp#clangtidy#GetCommand(buffer) abort + let l:checks = join(ale#Var(a:buffer, 'cpp_clangtidy_checks'), ',') + let l:build_dir = s:GetBuildDirectory(a:buffer) + + " Get the extra options if we couldn't find a build directory. + let l:options = empty(l:build_dir) + \ ? ale#Var(a:buffer, 'cpp_clangtidy_options') + \ : '' + + return ale#Escape(ale_linters#cpp#clangtidy#GetExecutable(a:buffer)) + \ . (!empty(l:checks) ? ' -checks=' . ale#Escape(l:checks) : '') + \ . ' %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_callback': 'ale_linters#cpp#clangtidy#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#clangtidy#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/cpp/cppcheck.vim b/ale_linters/cpp/cppcheck.vim index 9576b09..8b2aa80 100644 --- a/ale_linters/cpp/cppcheck.vim +++ b/ale_linters/cpp/cppcheck.vim @@ -1,16 +1,39 @@ " Author: Bart Libert " Description: cppcheck linter for cpp files -" Set this option to change the cppcheck options -if !exists('g:ale_cpp_cppcheck_options') - let g:ale_cpp_cppcheck_options = '--enable=style' -endif +call ale#Set('cpp_cppcheck_executable', 'cppcheck') +call ale#Set('cpp_cppcheck_options', '--enable=style') + +function! ale_linters#cpp#cppcheck#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_cppcheck_executable') +endfunction + +function! ale_linters#cpp#cppcheck#GetCommand(buffer) abort + " 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:compile_commmands_path = ale#path#FindNearestFile(a:buffer, 'compile_commands.json') + + let l:cd_command = !empty(l:compile_commmands_path) + \ ? ale#path#CdString(fnamemodify(l:compile_commmands_path, ':h')) + \ : '' + let l:compile_commands_option = !empty(l:compile_commmands_path) + \ ? '--project=compile_commands.json ' + \ : '' + + return l:cd_command + \ . ale#Escape(ale_linters#cpp#cppcheck#GetExecutable(a:buffer)) + \ . ' -q --language=c++ ' + \ . l:compile_commands_option + \ . ale#Var(a:buffer, 'cpp_cppcheck_options') + \ . ' %t' +endfunction call ale#linter#Define('cpp', { \ 'name': 'cppcheck', \ 'output_stream': 'both', -\ 'executable': 'cppcheck', -\ 'command': g:ale#util#stdin_wrapper . ' .cpp cppcheck -q --language=c++ ' -\ . g:ale_cpp_cppcheck_options, -\ 'callback': 'ale#handlers#HandleCppCheckFormat', +\ 'executable_callback': 'ale_linters#cpp#cppcheck#GetExecutable', +\ 'command_callback': '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 0000000..346ac81 --- /dev/null +++ b/ale_linters/cpp/cpplint.vim @@ -0,0 +1,26 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_cpplint_executable') +endfunction + +function! ale_linters#cpp#cpplint#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'cpp_cpplint_options') + + return ale#Escape(ale_linters#cpp#cpplint#GetExecutable(a:buffer)) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %s' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cpplint', +\ 'output_stream': 'stderr', +\ 'executable_callback': 'ale_linters#cpp#cpplint#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#cpplint#GetCommand', +\ 'callback': 'ale#handlers#cpplint#HandleCppLintFormat', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/cpp/flawfinder.vim b/ale_linters/cpp/flawfinder.vim new file mode 100644 index 0000000..a19f596 --- /dev/null +++ b/ale_linters/cpp/flawfinder.vim @@ -0,0 +1,30 @@ +" 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) + +function! ale_linters#cpp#flawfinder#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_flawfinder_executable') +endfunction + +function! ale_linters#cpp#flawfinder#GetCommand(buffer) abort + + " Set the minimum vulnerability level for flawfinder to bother with + let l:minlevel = ' --minlevel=' . ale#Var(a:buffer, 'cpp_flawfinder_minlevel') + + return ale#Escape(ale_linters#cpp#flawfinder#GetExecutable(a:buffer)) + \ . ' -CDQS' + \ . ale#Var(a:buffer, 'cpp_flawfinder_options') + \ . l:minlevel + \ . ' %t' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'flawfinder', +\ 'output_stream': 'stdout', +\ 'executable_callback': 'ale_linters#cpp#flawfinder#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#flawfinder#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/cpp/gcc.vim b/ale_linters/cpp/gcc.vim index 200a25c..40dffc9 100644 --- a/ale_linters/cpp/gcc.vim +++ b/ale_linters/cpp/gcc.vim @@ -1,30 +1,29 @@ " Author: geam " Description: gcc linter for cpp files +" +call ale#Set('cpp_gcc_executable', 'gcc') +call ale#Set('cpp_gcc_options', '-std=c++14 -Wall') -" Set this option to change the GCC options for warnings for C. -if !exists('g:ale_cpp_gcc_options') - " added c++14 standard support - " POSIX thread and standard c++ thread and atomic library Linker - " let g:ale_cpp_gcc_options = '-std=c++1z' for c++17 - " for previous version and default, you can just use - " let g:ale_cpp_gcc_options = '-Wall' - " for more see man pages of gcc - " $ man g++ - " make sure g++ in your $PATH - " Add flags according to your requirements - let g:ale_cpp_gcc_options = '-std=c++14 -Wall' -endif +function! ale_linters#cpp#gcc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_gcc_executable') +endfunction function! ale_linters#cpp#gcc#GetCommand(buffer) abort - return 'gcc -S -x c++ -fsyntax-only ' - \ . g:ale_cpp_gcc_options . ' -' + let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + return ale#Escape(ale_linters#cpp#gcc#GetExecutable(a:buffer)) + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) . ' ' + \ . ale#c#IncludeOptions(l:paths) + \ . ale#Var(a:buffer, 'cpp_gcc_options') . ' -' endfunction call ale#linter#Define('cpp', { \ 'name': 'g++', \ 'output_stream': 'stderr', -\ 'executable': 'g++', +\ 'executable_callback': 'ale_linters#cpp#gcc#GetExecutable', \ 'command_callback': 'ale_linters#cpp#gcc#GetCommand', -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/crystal/crystal.vim b/ale_linters/crystal/crystal.vim new file mode 100644 index 0000000..81579d6 --- /dev/null +++ b/ale_linters/crystal/crystal.vim @@ -0,0 +1,31 @@ +" 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, []) + 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_callback': 'ale_linters#crystal#crystal#GetCommand', +\ 'callback': 'ale_linters#crystal#crystal#Handle', +\}) diff --git a/ale_linters/cs/mcs.vim b/ale_linters/cs/mcs.vim index edf3bd2..b5c4054 100644 --- a/ale_linters/cs/mcs.vim +++ b/ale_linters/cs/mcs.vim @@ -1,29 +1,23 @@ -if !exists('g:ale_cs_mcs_options') - let g:ale_cs_mcs_options = '' -endif +let g:ale_cs_mcs_options = get(g:, 'ale_cs_mcs_options', '') + +function! ale_linters#cs#mcs#GetCommand(buffer) abort + return 'mcs -unsafe --parse ' . ale#Var(a:buffer, 'cs_mcs_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 = '^.\+.cs(\(\d\+\),\(\d\+\)): \(.\+\): \(.\+\)' + let l:pattern = '^\v(.+\.cs)\((\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 - + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:match[3] . ': ' . l:match[4], - \ 'type': l:match[3] =~# '^error' ? 'E' : 'W', - \ 'nr': -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], \}) endfor @@ -31,10 +25,9 @@ function! ale_linters#cs#mcs#Handle(buffer, lines) abort endfunction call ale#linter#Define('cs',{ -\ 'name': 'mcs', -\ 'output_stream': 'stderr', -\ 'executable': 'mcs', -\ 'command': g:ale#util#stdin_wrapper . ' .cs mcs -unsafe --parse' . g:ale_cs_mcs_options, -\ 'callback': 'ale_linters#cs#mcs#Handle', -\ }) - +\ 'name': 'mcs', +\ 'output_stream': 'stderr', +\ 'executable': 'mcs', +\ 'command_callback': '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 0000000..8a78d3b --- /dev/null +++ b/ale_linters/cs/mcsc.vim @@ -0,0 +1,81 @@ +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! s:GetWorkingDirectory(buffer) abort + let l:working_directory = ale#Var(a:buffer, 'cs_mcsc_source') + + if !empty(l:working_directory) + return l:working_directory + endif + + return 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 + let l:out = tempname() + call ale#engine#ManageFile(a:buffer, l:out) + + " The code is compiled as a module and the output is redirected to a + " temporary file. + return ale#path#CdString(s:GetWorkingDirectory(a:buffer)) + \ . 'mcs -unsafe' + \ . ' ' . ale#Var(a:buffer, 'cs_mcsc_options') + \ . ' ' . l:lib_option + \ . ' ' . 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:pattern = '^\v(.+\.cs)\((\d+),(\d+)\)\: ([^ ]+) ([^ ]+): (.+)$' + let l:output = [] + + let l:dir = s:GetWorkingDirectory(a:buffer) + + 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, + \ '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': 'mcsc', +\ 'output_stream': 'stderr', +\ 'executable': 'mcs', +\ 'command_callback': 'ale_linters#cs#mcsc#GetCommand', +\ 'callback': 'ale_linters#cs#mcsc#Handle', +\ 'lint_file': 1 +\}) diff --git a/ale_linters/css/csslint.vim b/ale_linters/css/csslint.vim index d967f83..98b7fdd 100644 --- a/ale_linters/css/csslint.vim +++ b/ale_linters/css/csslint.vim @@ -1,9 +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': g:ale#util#stdin_wrapper . ' .css csslint --format=compact', -\ 'callback': 'ale#handlers#HandleCSSLintFormat', +\ 'command_callback': 'ale_linters#css#csslint#GetCommand', +\ 'callback': 'ale#handlers#css#HandleCSSLintFormat', \}) diff --git a/ale_linters/css/stylelint.vim b/ale_linters/css/stylelint.vim index e138d6d..9f68319 100644 --- a/ale_linters/css/stylelint.vim +++ b/ale_linters/css/stylelint.vim @@ -1,25 +1,18 @@ " Author: diartyz -let g:ale_css_stylelint_executable = -\ get(g:, 'ale_css_stylelint_executable', 'stylelint') - -let g:ale_css_stylelint_use_global = -\ get(g:, 'ale_css_stylelint_use_global', 0) +call ale#Set('css_stylelint_executable', 'stylelint') +call ale#Set('css_stylelint_options', '') +call ale#Set('css_stylelint_use_global', 0) function! ale_linters#css#stylelint#GetExecutable(buffer) abort - if g:ale_css_stylelint_use_global - return g:ale_css_stylelint_executable - endif - - return ale#util#ResolveLocalPath( - \ a:buffer, + return ale#node#FindExecutable(a:buffer, 'css_stylelint', [ \ 'node_modules/.bin/stylelint', - \ g:ale_css_stylelint_executable - \) + \]) endfunction function! ale_linters#css#stylelint#GetCommand(buffer) abort return ale_linters#css#stylelint#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'css_stylelint_options') \ . ' --stdin-filename %s' endfunction @@ -27,5 +20,5 @@ call ale#linter#Define('css', { \ 'name': 'stylelint', \ 'executable_callback': 'ale_linters#css#stylelint#GetExecutable', \ 'command_callback': 'ale_linters#css#stylelint#GetCommand', -\ 'callback': 'ale#handlers#HandleStyleLintFormat', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/cuda/nvcc.vim b/ale_linters/cuda/nvcc.vim new file mode 100644 index 0000000..7aaa5cc --- /dev/null +++ b/ale_linters/cuda/nvcc.vim @@ -0,0 +1,56 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cuda_nvcc_executable') +endfunction + +function! ale_linters#cuda#nvcc#GetCommand(buffer) abort + " Unused: use ale#util#nul_file + " let l:output_file = tempname() . '.ii' + " call ale#engine#ManageFile(a:buffer, l:output_file) + + return ale#Escape(ale_linters#cuda#nvcc#GetExecutable(a:buffer)) + \ . ' -cuda ' + \ . ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer)) + \ . 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_callback': 'ale_linters#cuda#nvcc#GetExecutable', +\ 'command_callback': 'ale_linters#cuda#nvcc#GetCommand', +\ 'callback': 'ale_linters#cuda#nvcc#HandleNVCCFormat', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/d/dmd.vim b/ale_linters/d/dmd.vim index 0c665d7..b91238a 100644 --- a/ale_linters/d/dmd.vim +++ b/ale_linters/d/dmd.vim @@ -5,7 +5,7 @@ function! s: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#util#FindNearestFile(a:buffer, l:possible_filename) + let l:dub_file = ale#path#FindNearestFile(a:buffer, l:possible_filename) if !empty(l:dub_file) return l:dub_file @@ -31,7 +31,7 @@ function! ale_linters#d#dmd#DUBCommand(buffer) abort " 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. - return 'cd ' . fnameescape(fnamemodify(l:dub_file, ':h')) + return 'cd ' . ale#Escape(fnamemodify(l:dub_file, ':h')) \ . ' && dub describe --import-paths' endfunction @@ -42,13 +42,11 @@ function! ale_linters#d#dmd#DMDCommand(buffer, dub_output) abort for l:line in a:dub_output if !empty(l:line) " The arguments must be '-Ifilename', not '-I filename' - call add(l:import_list, '-I' . fnameescape(l:line)) + call add(l:import_list, '-I' . ale#Escape(l:line)) endif endfor - return g:ale#util#stdin_wrapper . ' .d dmd ' - \ . join(l:import_list) - \ . ' -o- -vcolumns -c' + return 'dmd '. join(l:import_list) . ' -o- -vcolumns -c %t' endfunction function! ale_linters#d#dmd#Handle(buffer, lines) abort @@ -58,27 +56,12 @@ function! ale_linters#d#dmd#Handle(buffer, lines) abort let l:pattern = '^[^(]\+(\([0-9]\+\)\,\?\([0-9]*\)): \([^:]\+\): \(.\+\)' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - break - endif - - let l:line = l:match[1] + 0 - let l:column = l:match[2] + 0 - let l:type = l:match[3] - let l:text = l:match[4] - - " vcol is Needed to indicate that the column is a character. + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': bufnr('%'), - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': l:column, - \ 'text': l:text, - \ 'type': l:type ==# 'Warning' ? 'W' : 'E', - \ 'nr': -1, + \ 'lnum': l:match[1], + \ 'col': l:match[2], + \ 'type': l:match[3] is# 'Warning' ? 'W' : 'E', + \ 'text': l:match[4], \}) endfor diff --git a/ale_linters/dafny/dafny.vim b/ale_linters/dafny/dafny.vim new file mode 100644 index 0000000..8bbf1b1 --- /dev/null +++ b/ale_linters/dafny/dafny.vim @@ -0,0 +1,25 @@ +" Author: Taylor Blau + +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, { + \ 'bufnr': a:buffer, + \ 'col': l:match[3] + 0, + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[5], + \ 'type': l:match[4] =~# '^Error' ? 'E' : 'W' + \ }) + endfor + return l:output +endfunction + +call ale#linter#Define('dafny', { +\ 'name': 'dafny', +\ 'executable': 'dafny', +\ 'command': 'dafny %s /compile:0', +\ 'callback': 'ale_linters#dafny#dafny#Handle', +\ 'lint_file': 1, +\ }) diff --git a/ale_linters/dart/dartanalyzer.vim b/ale_linters/dart/dartanalyzer.vim new file mode 100644 index 0000000..ef33c9d --- /dev/null +++ b/ale_linters/dart/dartanalyzer.vim @@ -0,0 +1,41 @@ +" Author: w0rp +" Description: Check Dart files with dartanalyzer + +call ale#Set('dart_dartanalyzer_executable', 'dartanalyzer') + +function! ale_linters#dart#dartanalyzer#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'dart_dartanalyzer_executable') +endfunction + +function! ale_linters#dart#dartanalyzer#GetCommand(buffer) abort + let l:executable = ale_linters#dart#dartanalyzer#GetExecutable(a:buffer) + let l:path = ale#path#FindNearestFile(a:buffer, '.packages') + + return ale#Escape(l:executable) + \ . (!empty(l:path) ? ' --packages ' . ale#Escape(l:path) : '') + \ . ' %s' +endfunction + +function! ale_linters#dart#dartanalyzer#Handle(buffer, lines) abort + let l:pattern = '\v^ ([a-z]+) . (.+) at (.+):(\d+):(\d+) . (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'type': l:match[1] is# 'error' ? 'E' : 'W', + \ 'text': l:match[6] . ': ' . l:match[2], + \ 'lnum': str2nr(l:match[4]), + \ 'col': str2nr(l:match[5]), + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('dart', { +\ 'name': 'dartanalyzer', +\ 'executable_callback': 'ale_linters#dart#dartanalyzer#GetExecutable', +\ 'command_callback': 'ale_linters#dart#dartanalyzer#GetCommand', +\ 'callback': 'ale_linters#dart#dartanalyzer#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/dart/language_server.vim b/ale_linters/dart/language_server.vim new file mode 100644 index 0000000..15c7701 --- /dev/null +++ b/ale_linters/dart/language_server.vim @@ -0,0 +1,30 @@ +" Author: aurieh +" Description: A language server for dart + +call ale#Set('dart_language_server_executable', 'dart_language_server') + +function! ale_linters#dart#language_server#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'dart_language_server_executable') +endfunction + +function! ale_linters#dart#language_server#GetLanguage(buffer) abort + return 'dart' +endfunction + +function! ale_linters#dart#language_server#GetProjectRoot(buffer) abort + " Note: pub only looks for pubspec.yaml, there's no point in adding + " support for pubspec.yml + 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_callback': 'ale_linters#dart#language_server#GetExecutable', +\ 'command_callback': 'ale_linters#dart#language_server#GetExecutable', +\ 'language_callback': 'ale_linters#dart#language_server#GetLanguage', +\ 'project_root_callback': 'ale_linters#dart#language_server#GetProjectRoot', +\}) + diff --git a/ale_linters/dockerfile/hadolint.vim b/ale_linters/dockerfile/hadolint.vim index dc8e642..7772afb 100644 --- a/ale_linters/dockerfile/hadolint.vim +++ b/ale_linters/dockerfile/hadolint.vim @@ -1,45 +1,97 @@ " 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') + function! ale_linters#dockerfile#hadolint#Handle(buffer, lines) abort - " Matches patterns line the following: - " - " stdin:19: F: Pipe chain should start with a raw value. - let l:pattern = '\v^/dev/stdin:?(\d+)? (\S+) (.+)$' - let l:output = [] + " Matches patterns line the following: + " + " /dev/stdin:19 DL3001 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:line in a:lines - let l:match = matchlist(l:line, l:pattern) + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:lnum = 0 + let l:colnum = 0 - if len(l:match) == 0 - continue - endif + if l:match[1] isnot# '' + let l:lnum = l:match[1] + 0 + endif - let l:lnum = 0 + if l:match[2] isnot# '' + let l:colnum = l:match[2] + 0 + endif - if l:match[1] !=# '' - let l:lnum = l:match[1] + 0 - endif + let l:type = 'W' + let l:text = l:match[6] + let l:detail = l:match[6] + let l:domain = 'https://github.com/hadolint/hadolint/wiki/' - let l:type = 'W' - let l:text = l:match[3] + if l:match[4] is# 'SC' + let l:domain = 'https://github.com/koalaman/shellcheck/wiki/' + endif - " vcol is Needed to indicate that the column is a character. - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:lnum, - \ 'vcol': 0, - \ 'col': 0, - \ 'type': l:type, - \ 'text': l:text, - \ 'nr': l:match[2], - \}) - endfor + 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' + endif - return l:output + call add(l:output, { + \ 'lnum': l:lnum, + \ 'col': l:colnum, + \ 'type': l:type, + \ 'text': l:text, + \ 'detail': l:detail + \}) + 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) + if l:command is# 'docker' + return 'docker run --rm -i ' . ale#Var(a:buffer, 'dockerfile_hadolint_docker_image') + endif + return 'hadolint -' +endfunction + + call ale#linter#Define('dockerfile', { - \ 'name': 'hadolint', - \ 'executable': 'hadolint', - \ 'command': 'hadolint -', - \ 'callback': 'ale_linters#dockerfile#hadolint#Handle' }) +\ 'name': 'hadolint', +\ 'executable_callback': 'ale_linters#dockerfile#hadolint#GetExecutable', +\ 'command_callback': 'ale_linters#dockerfile#hadolint#GetCommand', +\ 'callback': 'ale_linters#dockerfile#hadolint#Handle', +\}) diff --git a/ale_linters/elixir/credo.vim b/ale_linters/elixir/credo.vim index f79106d..af2ff48 100644 --- a/ale_linters/elixir/credo.vim +++ b/ale_linters/elixir/credo.vim @@ -1,45 +1,37 @@ " Author: hauleth - https://github.com/hauleth function! ale_linters#elixir#credo#Handle(buffer, lines) abort - " Matches patterns line the following: - " - " stdin:19: F: Pipe chain should start with a raw value. - let l:pattern = '\v^stdin:(\d+):?(\d+)?: (.): (.+)$' - let l:output = [] + " 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:line in a:lines - let l:match = matchlist(l:line, l:pattern) + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = l:match[3] + let l:text = l:match[4] - if len(l:match) == 0 - continue - endif + if l:type is# 'C' + let l:type = 'E' + elseif l:type is# 'R' + let l:type = 'W' + endif - let l:type = l:match[3] - let l:text = l:match[4] + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + endfor - if l:type ==# 'C' - let l:type = 'E' - elseif l:type ==# 'R' - let l:type = 'W' - 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, - \ 'type': l:type, - \ 'text': l:text, - \ 'nr': -1, - \}) - endfor - - return l:output + return l:output endfunction call ale#linter#Define('elixir', { - \ 'name': 'credo', - \ 'executable': 'mix', - \ 'command': 'mix credo suggest --format=flycheck --read-from-stdin', - \ 'callback': 'ale_linters#elixir#credo#Handle' }) +\ 'name': 'credo', +\ 'executable': 'mix', +\ 'command': 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s', +\ 'callback': 'ale_linters#elixir#credo#Handle', +\}) diff --git a/ale_linters/elixir/dialyxir.vim b/ale_linters/elixir/dialyxir.vim new file mode 100644 index 0000000..5ef3a04 --- /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', +\ 'command': '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 0000000..71cf4f4 --- /dev/null +++ b/ale_linters/elixir/dogma.vim @@ -0,0 +1,38 @@ +" 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', +\ 'command': 'mix help dogma && mix dogma %s --format=flycheck', +\ 'lint_file': 1, +\ 'callback': 'ale_linters#elixir#dogma#Handle', +\}) diff --git a/ale_linters/elm/make.vim b/ale_linters/elm/make.vim index 35522f1..3783b5e 100644 --- a/ale_linters/elm/make.vim +++ b/ale_linters/elm/make.vim @@ -1,67 +1,89 @@ -" Author: buffalocoder - https://github.com/buffalocoder +" Author: buffalocoder - https://github.com/buffalocoder, soywod - https://github.com/soywod " Description: Elm linting in Ale. Closely follows the Syntastic checker in https://github.com/ElmCast/elm-vim. +call ale#Set('elm_make_executable', 'elm-make') +call ale#Set('elm_make_use_global', 0) + +function! ale_linters#elm#make#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'elm_make', [ + \ 'node_modules/.bin/elm-make', + \]) +endfunction + function! ale_linters#elm#make#Handle(buffer, lines) abort let l:output = [] let l:is_windows = has('win32') let l:temp_dir = l:is_windows ? $TMP : $TMPDIR + let l:unparsed_lines = [] for l:line in a:lines - if l:line[0] ==# '[' + if l:line[0] is# '[' let l:errors = json_decode(l:line) for l:error in l:errors " Check if file is from the temp directory. " Filters out any errors not related to the buffer. if l:is_windows - let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] ==? l:temp_dir + let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] is? l:temp_dir else - let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] ==# l:temp_dir + let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] is# l:temp_dir endif if l:file_is_buffer call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:error.region.start.line, - \ 'vcol': 0, \ 'col': l:error.region.start.column, - \ 'type': (l:error.type ==? 'error') ? 'E' : 'W', + \ '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, - \ 'nr': -1, + \ 'detail': l:error.overview . "\n\n" . l:error.details \}) endif endfor + 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 " Return the command to execute the linter in the projects directory. " If it doesn't, then this will fail when imports are needed. function! ale_linters#elm#make#GetCommand(buffer) abort - let l:elm_package = ale#util#FindNearestFile(a:buffer, 'elm-package.json') + let l:elm_package = ale#path#FindNearestFile(a:buffer, 'elm-package.json') + let l:elm_exe = ale_linters#elm#make#GetExecutable(a:buffer) if empty(l:elm_package) let l:dir_set_cmd = '' else let l:root_dir = fnamemodify(l:elm_package, ':p:h') - let l:dir_set_cmd = 'cd ' . fnameescape(l:root_dir) . ' && ' + let l:dir_set_cmd = 'cd ' . ale#Escape(l:root_dir) . ' && ' endif " The elm-make compiler, at the time of this writing, uses '/dev/null' as " a sort of flag to tell the compiler not to generate an output file, - " which is why this is hard coded here. + " which is why this is hard coded here. It does not use NUL on Windows. " Source: https://github.com/elm-lang/elm-make/blob/master/src/Flags.hs - let l:elm_cmd = 'elm-make --report=json --output='.shellescape('/dev/null') - let l:stdin_wrapper = g:ale#util#stdin_wrapper . ' .elm' + let l:elm_cmd = ale#Escape(l:elm_exe) + \ . ' --report=json' + \ . ' --output=/dev/null' - return l:dir_set_cmd . ' ' . l:stdin_wrapper . ' ' . l:elm_cmd + return l:dir_set_cmd . ' ' . l:elm_cmd . ' %t' endfunction call ale#linter#Define('elm', { \ 'name': 'make', -\ 'executable': 'elm-make', +\ 'executable_callback': 'ale_linters#elm#make#GetExecutable', \ 'output_stream': 'both', \ 'command_callback': 'ale_linters#elm#make#GetCommand', \ 'callback': 'ale_linters#elm#make#Handle' \}) - diff --git a/ale_linters/erlang/erlc.vim b/ale_linters/erlang/erlc.vim index 1cd35d0..bddb175 100644 --- a/ale_linters/erlang/erlc.vim +++ b/ale_linters/erlang/erlc.vim @@ -3,7 +3,12 @@ let g:ale_erlang_erlc_options = get(g:, 'ale_erlang_erlc_options', '') function! ale_linters#erlang#erlc#GetCommand(buffer) abort - return g:ale#util#stdin_wrapper . ' .erl erlc ' . g:ale_erlang_erlc_options + let l:output_file = tempname() + call ale#engine#ManageFile(a:buffer, l:output_file) + + return 'erlc -o ' . ale#Escape(l:output_file) + \ . ' ' . ale#Var(a:buffer, 'erlang_erlc_options') + \ . ' %t' endfunction function! ale_linters#erlang#erlc#Handle(buffer, lines) abort @@ -12,7 +17,7 @@ function! ale_linters#erlang#erlc#Handle(buffer, lines) abort " 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^([^:]+):(\d+): (Warning: )?(.+)$' + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+): (Warning: )?(.+)$' " parse_transforms are a special case. The error message does not indicate a location: " error.erl: undefined parse transform 'some_parse_transform' @@ -22,7 +27,7 @@ function! ale_linters#erlang#erlc#Handle(buffer, lines) abort let l:pattern_no_module_definition = '\v(no module definition)$' let l:pattern_unused = '\v(.* is unused)$' - let l:is_hrl = fnamemodify(bufname(a:buffer), ':e') ==# 'hrl' + let l:is_hrl = fnamemodify(bufname(a:buffer), ':e') is# 'hrl' for l:line in a:lines let l:match = matchlist(l:line, l:pattern) @@ -43,11 +48,9 @@ function! ale_linters#erlang#erlc#Handle(buffer, lines) abort call add(l:output, { \ 'bufnr': a:buffer, \ 'lnum': 0, - \ 'vcol': 0, \ 'col': 0, \ 'type': 'E', \ 'text': l:match_parse_transform[0], - \ 'nr': -1, \}) continue @@ -73,15 +76,12 @@ function! ale_linters#erlang#erlc#Handle(buffer, lines) abort let l:type = 'E' endif - " vcol is Needed to indicate that the column is a character. call add(l:output, { \ 'bufnr': a:buffer, \ 'lnum': l:line, - \ 'vcol': 0, \ 'col': 0, \ 'type': l:type, \ 'text': l:text, - \ 'nr': -1, \}) endfor diff --git a/ale_linters/erlang/syntaxerl.vim b/ale_linters/erlang/syntaxerl.vim new file mode 100644 index 0000000..46ecdcb --- /dev/null +++ b/ale_linters/erlang/syntaxerl.vim @@ -0,0 +1,53 @@ +" Author: Dmitri Vereshchagin +" Description: SyntaxErl linter for Erlang files + +call ale#Set('erlang_syntaxerl_executable', 'syntaxerl') + + +function! ale_linters#erlang#syntaxerl#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'erlang_syntaxerl_executable') +endfunction + + +function! ale_linters#erlang#syntaxerl#FeatureCheck(buffer) abort + return s:GetEscapedExecutable(a:buffer) . ' -h' +endfunction + + +function! ale_linters#erlang#syntaxerl#GetCommand(buffer, output) abort + let l:use_b_option = match(a:output, '\C\V-b, --base\>') > -1 + + return s:GetEscapedExecutable(a:buffer) . (l:use_b_option ? ' -b %s %t' : ' %t') +endfunction + + +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': l:match[1] + 0, + \ 'text': l:match[3], + \ 'type': empty(l:match[2]) ? 'E' : 'W', + \}) + endfor + + return l:loclist +endfunction + + +function! s:GetEscapedExecutable(buffer) abort + return ale#Escape(ale_linters#erlang#syntaxerl#GetExecutable(a:buffer)) +endfunction + + +call ale#linter#Define('erlang', { +\ 'name': 'syntaxerl', +\ 'executable_callback': 'ale_linters#erlang#syntaxerl#GetExecutable', +\ 'command_chain': [ +\ {'callback': 'ale_linters#erlang#syntaxerl#FeatureCheck'}, +\ {'callback': 'ale_linters#erlang#syntaxerl#GetCommand'}, +\ ], +\ 'callback': 'ale_linters#erlang#syntaxerl#Handle', +\}) diff --git a/ale_linters/eruby/erb.vim b/ale_linters/eruby/erb.vim new file mode 100644 index 0000000..61d9703 --- /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(%{<%=},%{<%}), nil, %{-}).src') . '< %t | ruby -c' +endfunction + +call ale#linter#Define('eruby', { +\ 'name': 'erb', +\ 'aliases': ['erubylint'], +\ 'executable': 'erb', +\ 'output_stream': 'stderr', +\ 'command_callback': 'ale_linters#eruby#erb#GetCommand', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) + diff --git a/ale_linters/eruby/erubi.vim b/ale_linters/eruby/erubi.vim new file mode 100644 index 0000000..6f2d3ac --- /dev/null +++ b/ale_linters/eruby/erubi.vim @@ -0,0 +1,35 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: eruby checker using `erubi` + +function! ale_linters#eruby#erubi#CheckErubi(buffer) abort + return 'ruby -r erubi/capture_end -e ' . ale#Escape('""') +endfunction + +function! ale_linters#eruby#erubi#GetCommand(buffer, check_erubi_output) abort + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if (!empty(a:check_erubi_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_chain': [ +\ {'callback': 'ale_linters#eruby#erubi#CheckErubi'}, +\ {'callback': 'ale_linters#eruby#erubi#GetCommand', 'output_stream': 'stderr'}, +\ ], +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) diff --git a/ale_linters/eruby/erubis.vim b/ale_linters/eruby/erubis.vim new file mode 100644 index 0000000..1ebd4a0 --- /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_callback': 'ale_linters#eruby#erubis#GetCommand', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) diff --git a/ale_linters/fish/fish.vim b/ale_linters/fish/fish.vim new file mode 100644 index 0000000..19158cb --- /dev/null +++ b/ale_linters/fish/fish.vim @@ -0,0 +1,36 @@ +" 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 + " ^ + " fish: Error while reading file .config/fish/functions/foo.fish + let l:pattern = '^.* (line \(\d\+\)): \(.*\)$' + let l:output = [] + + let l:i = 0 + while l:i < len(a:lines) + let l:match = matchlist(a:lines[l:i], l:pattern) + if len(l:match) && len(l:match[2]) + call add(l:output, { + \ 'col': len(a:lines[l:i + 2]), + \ 'lnum': str2nr(l:match[1]), + \ 'text': l:match[2], + \}) + endif + let l:i += 1 + endwhile + + 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 index 73ec788..5f2ac01 100644 --- a/ale_linters/fortran/gcc.vim +++ b/ale_linters/fortran/gcc.vim @@ -1,6 +1,15 @@ " Author: w0rp " Description: gcc for Fortran files +" This option can be set to 0 to use -ffixed-form +if !exists('g:ale_fortran_gcc_use_free_form') + let g:ale_fortran_gcc_use_free_form = 1 +endif + +if !exists('g:ale_fortran_gcc_executable') + let g:ale_fortran_gcc_executable = 'gcc' +endif + " Set this option to change the GCC options for warnings for Fortran. if !exists('g:ale_fortran_gcc_options') let g:ale_fortran_gcc_options = '-Wall' @@ -12,7 +21,7 @@ function! ale_linters#fortran#gcc#Handle(buffer, lines) abort " " :21.34: " Error: Expected comma in I/O list at (1) - let l:line_marker_pattern = '^:\(\d\+\)\.\(\d\+\):$' + let l:line_marker_pattern = ':\(\d\+\)[.:]\=\(\d\+\)\=:\=$' let l:message_pattern = '^\(Error\|Warning\): \(.\+\)$' let l:looking_for_message = 0 let l:last_loclist_obj = {} @@ -35,15 +44,13 @@ function! ale_linters#fortran#gcc#Handle(buffer, lines) abort " Now we have the text, we can set it and add the error. let l:last_loclist_obj.text = l:match[2] - let l:last_loclist_obj.type = l:match[1] ==# 'Warning' ? 'W' : 'E' + let l:last_loclist_obj.type = l:match[1] is# 'Warning' ? 'W' : 'E' call add(l:output, l:last_loclist_obj) else let l:last_loclist_obj = { \ 'bufnr': a:buffer, \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, \ 'col': l:match[2] + 0, - \ 'nr': -1, \} " Start looking for the message and error type. @@ -54,12 +61,26 @@ function! ale_linters#fortran#gcc#Handle(buffer, lines) abort return l:output endfunction +function! ale_linters#fortran#gcc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'fortran_gcc_executable') +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 ale_linters#fortran#gcc#GetExecutable(a:buffer) + \ . ' -S -x f95 -fsyntax-only ' + \ . l:layout_option . ' ' + \ . ale#Var(a:buffer, 'fortran_gcc_options') . ' ' + \ . '-' +endfunction + call ale#linter#Define('fortran', { \ 'name': 'gcc', \ 'output_stream': 'stderr', -\ 'executable': 'gcc', -\ 'command': 'gcc -S -x f95 -fsyntax-only -ffree-form ' -\ . g:ale_fortran_gcc_options -\ . ' -', +\ 'executable_callback': 'ale_linters#fortran#gcc#GetExecutable', +\ 'command_callback': 'ale_linters#fortran#gcc#GetCommand', \ 'callback': 'ale_linters#fortran#gcc#Handle', \}) diff --git a/ale_linters/fountain/proselint.vim b/ale_linters/fountain/proselint.vim new file mode 100644 index 0000000..353a2e5 --- /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 0000000..968e801 --- /dev/null +++ b/ale_linters/fuse/fusionlint.vim @@ -0,0 +1,41 @@ +" Author: RyanSquared +" Description: `fusion-lint` linter for FusionScript files + +let g:ale_fuse_fusionlint_executable = +\ get(g:, 'ale_fuse_fusionlint_executable', 'fusion-lint') + +let g:ale_fuse_fusionlint_options = +\ get(g:, 'ale_fuse_fusionlint_options', '') + +function! ale_linters#fuse#fusionlint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'fuse_fusionlint_executable') +endfunction + +function! ale_linters#fuse#fusionlint#GetCommand(buffer) abort + return ale#Escape(ale_linters#fuse#fusionlint#GetExecutable(a:buffer)) + \ . ' ' . 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_callback': 'ale_linters#fuse#fusionlint#GetExecutable', +\ 'command_callback': '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 0000000..49aeda7 --- /dev/null +++ b/ale_linters/gitcommit/gitlint.vim @@ -0,0 +1,52 @@ +" Author: Nick Yamane +" Description: gitlint for git commit message files + +let g:ale_gitcommit_gitlint_executable = +\ get(g:, 'ale_gitcommit_gitlint_executable', 'gitlint') +let g:ale_gitcommit_gitlint_options = get(g:, 'ale_gitcommit_gitlint_options', '') +let g:ale_gitcommit_gitlint_use_global = get(g:, 'ale_gitcommit_gitlint_use_global', 0) + + +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') + let l:executable = ale_linters#gitcommit#gitlint#GetExecutable(a:buffer) + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . 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] + + 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_callback': 'ale_linters#gitcommit#gitlint#GetExecutable', +\ 'command_callback': 'ale_linters#gitcommit#gitlint#GetCommand', +\ 'callback': 'ale_linters#gitcommit#gitlint#Handle', +\}) + diff --git a/ale_linters/glsl/glslang.vim b/ale_linters/glsl/glslang.vim new file mode 100644 index 0000000..21a03ee --- /dev/null +++ b/ale_linters/glsl/glslang.vim @@ -0,0 +1,46 @@ +" 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. + +let g:ale_glsl_glslang_executable = +\ get(g:, 'ale_glsl_glslang_executable', 'glslangValidator') + +let g:ale_glsl_glslang_options = get(g:, 'ale_glsl_glslang_options', '') + +function! ale_linters#glsl#glslang#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'glsl_glslang_executable') +endfunction + +function! ale_linters#glsl#glslang#GetCommand(buffer) abort + return ale_linters#glsl#glslang#GetExecutable(a:buffer) + \ . ' ' . 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 + 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[3]), + \ 'col': str2nr(l:match[2]), + \ '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_callback': 'ale_linters#glsl#glslang#GetExecutable', +\ 'command_callback': '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 0000000..67ea379 --- /dev/null +++ b/ale_linters/glsl/glslls.vim @@ -0,0 +1,38 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'glsl_glslls_executable') +endfunction + +function! ale_linters#glsl#glslls#GetCommand(buffer) abort + let l:executable = ale_linters#glsl#glslls#GetExecutable(a:buffer) + 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 ale#Escape(l:executable) . l:logfile_args . ' --stdin' +endfunction + +function! ale_linters#glsl#glslls#GetLanguage(buffer) abort + return 'glsl' +endfunction + +function! ale_linters#glsl#glslls#GetProjectRoot(buffer) abort + let l:project_root = ale#c#FindProjectRoot(a:buffer) + + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h:h') : '' +endfunction + +call ale#linter#Define('glsl', { +\ 'name': 'glslls', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#glsl#glslls#GetExecutable', +\ 'command_callback': 'ale_linters#glsl#glslls#GetCommand', +\ 'language_callback': 'ale_linters#glsl#glslls#GetLanguage', +\ 'project_root_callback': 'ale_linters#glsl#glslls#GetProjectRoot', +\}) diff --git a/ale_linters/go/gobuild.vim b/ale_linters/go/gobuild.vim index 832669f..068877a 100644 --- a/ale_linters/go/gobuild.vim +++ b/ale_linters/go/gobuild.vim @@ -1,204 +1,72 @@ -" Author: Joshua Rubin +" Author: Joshua Rubin , Ben Reedy , +" Jeff Willette " Description: go build for Go files - " inspired by work from dzhou121 +call ale#Set('go_gobuild_options', '') + +function! ale_linters#go#gobuild#ResetEnv() abort + unlet! s:go_env +endfunction + function! ale_linters#go#gobuild#GoEnv(buffer) abort - if exists('s:go_env') - return '' - endif - - return 'go env GOPATH GOROOT' -endfunction - -let s:SplitChar = has('unix') ? ':' : ':' - -" get a list of all source directories from $GOPATH and $GOROOT -function! s:SrcDirs() abort - let l:paths = split(s:go_env.GOPATH, s:SplitChar) - call add(l:paths, s:go_env.GOROOT) - - return l:paths -endfunction - -" figure out from a directory like `/home/user/go/src/some/package` that the -" import for that path is simply `some/package` -function! s:PackageImportPath(buffer) abort - let l:bufname = resolve(bufname(a:buffer)) - let l:pkgdir = fnamemodify(l:bufname, ':p:h') - - for l:path in s:SrcDirs() - let l:path = l:path . '/src/' - - if stridx(l:pkgdir, l:path) == 0 - return l:pkgdir[strlen(l:path):] - endif - endfor - - return '' -endfunction - -" get the package info data structure using `go list` -function! ale_linters#go#gobuild#GoList(buffer, goenv_output) abort - if !empty(a:goenv_output) - let s:go_env = { - \ 'GOPATH': a:goenv_output[0], - \ 'GOROOT': a:goenv_output[1], - \} - endif - - return 'go list -json ' . shellescape(s:PackageImportPath(a:buffer)) -endfunction - -let s:filekeys = [ -\ 'GoFiles', -\ 'CgoFiles', -\ 'CFiles', -\ 'CXXFiles', -\ 'MFiles', -\ 'HFiles', -\ 'FFiles', -\ 'SFiles', -\ 'SwigFiles', -\ 'SwigCXXFiles', -\ 'SysoFiles', -\ 'TestGoFiles', -\ 'XTestGoFiles', -\] - -" get the go and test go files from the package -" will return empty list if the package has any cgo or other invalid files -function! s:PkgFiles(pkginfo) abort - let l:files = [] - - for l:key in s:filekeys - if has_key(a:pkginfo, l:key) - call extend(l:files, a:pkginfo[l:key]) - endif - endfor - - " resolve the path of the file relative to the window directory - return map(l:files, 'shellescape(fnamemodify(resolve(a:pkginfo.Dir . ''/'' . v:val), '':p''))') -endfunction - -function! ale_linters#go#gobuild#CopyFiles(buffer, golist_output) abort - let l:tempdir = tempname() - let l:temppkgdir = l:tempdir . '/src/' . s:PackageImportPath(a:buffer) - call mkdir(l:temppkgdir, 'p', 0700) - - if empty(a:golist_output) - return 'echo ' . shellescape(l:tempdir) - endif - - " parse the output - let l:pkginfo = json_decode(join(a:golist_output, "\n")) - - " get all files for the package - let l:files = s:PkgFiles(l:pkginfo) - - " copy the files to a temp directory with $GOPATH structure - return 'cp ' . join(l:files, ' ') . ' ' . shellescape(l:temppkgdir) . ' && echo ' . shellescape(l:tempdir) -endfunction - -function! ale_linters#go#gobuild#GetCommand(buffer, copy_output) abort - let l:tempdir = a:copy_output[0] - let l:importpath = s:PackageImportPath(a:buffer) - - " write the a:buffer and any modified buffers from the package to the tempdir - for l:bufnum in range(1, bufnr('$')) - " ignore unloaded buffers (can't be a:buffer or a modified buffer) - if !bufloaded(l:bufnum) - continue + if exists('s:go_env') + return '' endif - " ignore non-Go buffers - if getbufvar(l:bufnum, '&ft') !=# 'go' - continue - endif - - " only consider buffers other than a:buffer if they have the same import - " path as a:buffer and are modified - if l:bufnum != a:buffer - if s:PackageImportPath(l:bufnum) !=# l:importpath - continue - endif - - if !getbufvar(l:bufnum, '&mod') - continue - endif - endif - - call writefile(getbufline(l:bufnum, 1, '$'), l:tempdir . '/src/' . s:PkgFile(l:bufnum)) - endfor - - let l:gopaths = [ l:tempdir ] - call extend(l:gopaths, split(s:go_env.GOPATH, s:SplitChar)) - - return 'GOPATH=' . shellescape(join(l:gopaths, s:SplitChar)) . ' go test -c -o /dev/null ' . shellescape(l:importpath) + return 'go env GOPATH GOROOT' endfunction -function! s:PkgFile(buffer) abort - let l:bufname = resolve(bufname(a:buffer)) - let l:importpath = s:PackageImportPath(a:buffer) - let l:fname = fnamemodify(l:bufname, ':t') +function! ale_linters#go#gobuild#GetCommand(buffer, goenv_output) abort + let l:options = ale#Var(a:buffer, 'go_gobuild_options') - return l:importpath . '/' . l:fname -endfunction - -function! s:FindBuffer(file) abort - for l:buffer in range(1, bufnr('$')) - if !buflisted(l:buffer) - continue + if !exists('s:go_env') + let s:go_env = { + \ 'GOPATH': a:goenv_output[0], + \ 'GOROOT': a:goenv_output[1], + \} endif - let l:pkgfile = s:PkgFile(l:buffer) + let l:gopath_env_command = has('win32') + \ ? 'set GOPATH=' . ale#Escape(s:go_env.GOPATH) . ' && ' + \ : 'GOPATH=' . ale#Escape(s:go_env.GOPATH) . ' ' - if a:file =~ '/' . l:pkgfile . '$' - return l:buffer - endif - endfor - - return -1 + " Run go test in local directory with relative path + return l:gopath_env_command + \ . ale#path#BufferCdString(a:buffer) + \ . 'go test' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -c -o /dev/null ./' endfunction -let s:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+' -let s:handler_pattern = '^\(' . s:path_pattern . '\):\(\d\+\):\?\(\d\+\)\?: \(.\+\)$' +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" -let s:multibuffer = 0 + " 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:output = [] + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, s:handler_pattern) + 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 - if len(l:match) == 0 - continue - endif - - let l:buffer = s:FindBuffer(l:match[1]) - - if l:buffer == -1 - continue - endif - - if !s:multibuffer && l:buffer != a:buffer - " strip lines from other buffers - continue - endif - - call add(l:output, { - \ 'bufnr': l:buffer, - \ 'lnum': l:match[2] + 0, - \ 'vcol': 0, - \ 'col': l:match[3] + 0, - \ 'text': l:match[4], - \ 'type': 'E', - \ 'nr': -1, - \}) - endfor - - return l:output + return l:output endfunction call ale#linter#Define('go', { @@ -206,9 +74,8 @@ call ale#linter#Define('go', { \ 'executable': 'go', \ 'command_chain': [ \ {'callback': 'ale_linters#go#gobuild#GoEnv', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#go#gobuild#GoList', 'output_stream': 'stdout'}, -\ {'callback': 'ale_linters#go#gobuild#CopyFiles', 'output_stream': 'stdout'}, \ {'callback': 'ale_linters#go#gobuild#GetCommand', 'output_stream': 'stderr'}, \ ], \ 'callback': 'ale_linters#go#gobuild#Handler', +\ 'lint_file': 1, \}) diff --git a/ale_linters/go/gofmt.vim b/ale_linters/go/gofmt.vim index 50d2508..337deef 100644 --- a/ale_linters/go/gofmt.vim +++ b/ale_linters/go/gofmt.vim @@ -5,6 +5,6 @@ call ale#linter#Define('go', { \ 'name': 'gofmt', \ 'output_stream': 'stderr', \ 'executable': 'gofmt', -\ 'command': g:ale#util#stdin_wrapper . ' .go gofmt -e', -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'command': 'gofmt -e %t', +\ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/go/golint.vim b/ale_linters/go/golint.vim index 5329e33..d580fda 100644 --- a/ale_linters/go/golint.vim +++ b/ale_linters/go/golint.vim @@ -3,7 +3,8 @@ call ale#linter#Define('go', { \ 'name': 'golint', +\ 'output_stream': 'both', \ 'executable': 'golint', -\ 'command': g:ale#util#stdin_wrapper . ' .go golint', -\ 'callback': 'ale#handlers#HandleUnixFormatAsWarning', +\ 'command': 'golint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', \}) diff --git a/ale_linters/go/gometalinter.vim b/ale_linters/go/gometalinter.vim new file mode 100644 index 0000000..375a8b0 --- /dev/null +++ b/ale_linters/go/gometalinter.vim @@ -0,0 +1,62 @@ +" Author: Ben Reedy , Jeff Willette +" Description: Adds support for the gometalinter suite for Go files + +call ale#Set('go_gometalinter_options', '') +call ale#Set('go_gometalinter_executable', 'gometalinter') +call ale#Set('go_gometalinter_lint_package', 0) + +function! ale_linters#go#gometalinter#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'go_gometalinter_executable') +endfunction + +function! ale_linters#go#gometalinter#GetCommand(buffer) abort + let l:executable = ale_linters#go#gometalinter#GetExecutable(a:buffer) + let l:filename = expand('#' . a:buffer . ':t') + let l:options = ale#Var(a:buffer, 'go_gometalinter_options') + let l:lint_package = ale#Var(a:buffer, 'go_gometalinter_lint_package') + + " BufferCdString is used so that we can be sure the paths output from gometalinter can + " be calculated to absolute paths in the Handler + if l:lint_package + return ale#path#BufferCdString(a:buffer) + \ . ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') . ' .' + endif + + return ale#path#BufferCdString(a:buffer) + \ . ale#Escape(l:executable) + \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(l:filename)) + \ . (!empty(l:options) ? ' ' . l:options : '') . ' .' +endfunction + +function! ale_linters#go#gometalinter#GetMatches(lines) abort + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:?:?(warning|error):?\s\*?(.+)$' + + return ale#util#GetMatches(a:lines, l:pattern) +endfunction + +function! ale_linters#go#gometalinter#Handler(buffer, lines) abort + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] + + for l:match in ale_linters#go#gometalinter#GetMatches(a:lines) + " l:match[1] will already be an absolute path, output from gometalinter + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': tolower(l:match[4]) is# 'warning' ? 'W' : 'E', + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('go', { +\ 'name': 'gometalinter', +\ 'executable_callback': 'ale_linters#go#gometalinter#GetExecutable', +\ 'command_callback': 'ale_linters#go#gometalinter#GetCommand', +\ 'callback': 'ale_linters#go#gometalinter#Handler', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/go/gosimple.vim b/ale_linters/go/gosimple.vim new file mode 100644 index 0000000..8a4c01e --- /dev/null +++ b/ale_linters/go/gosimple.vim @@ -0,0 +1,11 @@ +" Author: Ben Reedy +" Description: gosimple for Go files + +call ale#linter#Define('go', { +\ 'name': 'gosimple', +\ 'executable': 'gosimple', +\ 'command': 'gosimple %s', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\ '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 0000000..731f4c9 --- /dev/null +++ b/ale_linters/go/gotype.vim @@ -0,0 +1,23 @@ +" Author: Jelte Fennema +" Description: gotype for Go files + +call ale#linter#Define('go', { +\ 'name': 'gotype', +\ 'output_stream': 'stderr', +\ 'executable': 'gotype', +\ 'command_callback': 'ale_linters#go#gotype#GetCommand', +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) + +"\ 'command': +function! ale_linters#go#gotype#GetCommand(buffer) abort + let l:cur_file = expand('#' . a:buffer . ':p') + if l:cur_file =~# '_test\.go$' + return + endif + + let l:module_files = globpath(expand('#' . a:buffer . ':p:h'), '*.go', 0, 1) + let l:other_module_files = filter(l:module_files, 'v:val isnot# ' . ale#util#EscapeVim(l:cur_file) . ' && v:val !~# "_test\.go$"') + return 'gotype %t ' . join(map(l:other_module_files, 'ale#Escape(v:val)')) + +endfunction diff --git a/ale_linters/go/govet.vim b/ale_linters/go/govet.vim index 6153caa..aae5969 100644 --- a/ale_linters/go/govet.vim +++ b/ale_linters/go/govet.vim @@ -1,10 +1,35 @@ " Author: neersighted " Description: go vet for Go files +" +" Author: John Eikenberry +" Description: updated to work with go1.10 + +function! ale_linters#go#govet#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) . ' go vet .' +endfunction + +function! ale_linters#go#govet#Handler(buffer, lines) abort + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? ?(.+)$' + let l:output = [] + let l:dir = expand('#' . a:buffer . ':p:h') + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \}) + endfor + return l:output +endfunction call ale#linter#Define('go', { \ 'name': 'go vet', \ 'output_stream': 'stderr', \ 'executable': 'go', -\ 'command': g:ale#util#stdin_wrapper . ' .go go vet', -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'command_callback': 'ale_linters#go#govet#GetCommand', +\ 'callback': 'ale_linters#go#govet#Handler', +\ 'lint_file': 1, \}) diff --git a/ale_linters/go/staticcheck.vim b/ale_linters/go/staticcheck.vim new file mode 100644 index 0000000..ce9e6e3 --- /dev/null +++ b/ale_linters/go/staticcheck.vim @@ -0,0 +1,33 @@ +" Author: Ben Reedy +" Description: staticcheck for Go files + +call ale#Set('go_staticcheck_options', '') +call ale#Set('go_staticcheck_lint_package', 0) + +function! ale_linters#go#staticcheck#GetCommand(buffer) abort + let l:filename = expand('#' . a:buffer . ':t') + let l:options = ale#Var(a:buffer, 'go_staticcheck_options') + let l:lint_package = ale#Var(a:buffer, 'go_staticcheck_lint_package') + + " BufferCdString is used so that we can be sure the paths output from + " staticcheck can be calculated to absolute paths in the Handler + if l:lint_package + return ale#path#BufferCdString(a:buffer) + \ . 'staticcheck' + \ . (!empty(l:options) ? ' ' . l:options : '') . ' .' + endif + + return ale#path#BufferCdString(a:buffer) + \ . 'staticcheck' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' ' . ale#Escape(l:filename) +endfunction + +call ale#linter#Define('go', { +\ 'name': 'staticcheck', +\ 'executable': 'staticcheck', +\ 'command_callback': 'ale_linters#go#staticcheck#GetCommand', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\ 'output_stream': 'both', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/graphql/eslint.vim b/ale_linters/graphql/eslint.vim new file mode 100644 index 0000000..dfcbf9d --- /dev/null +++ b/ale_linters/graphql/eslint.vim @@ -0,0 +1,9 @@ +" Author: Benjie Gillam +" Description: eslint for GraphQL files + +call ale#linter#Define('graphql', { +\ 'name': 'eslint', +\ 'executable_callback': 'ale#handlers#eslint#GetExecutable', +\ 'command_callback': 'ale#handlers#eslint#GetCommand', +\ 'callback': 'ale#handlers#eslint#Handle', +\}) diff --git a/ale_linters/graphql/gqlint.vim b/ale_linters/graphql/gqlint.vim new file mode 100644 index 0000000..882cc69 --- /dev/null +++ b/ale_linters/graphql/gqlint.vim @@ -0,0 +1,9 @@ +" Author: Michiel Westerbeek +" Description: Linter for GraphQL Schemas + +call ale#linter#Define('graphql', { +\ 'name': 'gqlint', +\ 'executable': 'gqlint', +\ 'command': 'gqlint --reporter=simple %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/haml/hamllint.vim b/ale_linters/haml/hamllint.vim new file mode 100644 index 0000000..d663359 --- /dev/null +++ b/ale_linters/haml/hamllint.vim @@ -0,0 +1,51 @@ +" Author: Patrick Lewis - https://github.com/patricklewis, thenoseman - https://github.com/thenoseman +" Description: haml-lint for Haml files + +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 ale#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 . ' ' : '') + \ . 'haml-lint' + \ . (!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': 'haml-lint', +\ 'command_callback': 'ale_linters#haml#hamllint#GetCommand', +\ 'callback': 'ale_linters#haml#hamllint#Handle' +\}) diff --git a/ale_linters/handlebars/embertemplatelint.vim b/ale_linters/handlebars/embertemplatelint.vim new file mode 100644 index 0000000..68ea715 --- /dev/null +++ b/ale_linters/handlebars/embertemplatelint.vim @@ -0,0 +1,48 @@ +" Author: Adrian Zalewski +" Description: Ember-template-lint for checking Handlebars files + +call ale#Set('handlebars_embertemplatelint_executable', 'ember-template-lint') +call ale#Set('handlebars_embertemplatelint_use_global', 0) + +function! ale_linters#handlebars#embertemplatelint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'handlebars_embertemplatelint', [ + \ 'node_modules/.bin/ember-template-lint', + \]) +endfunction + +function! ale_linters#handlebars#embertemplatelint#GetCommand(buffer) abort + return ale_linters#handlebars#embertemplatelint#GetExecutable(a:buffer) + \ . ' --json %t' +endfunction + +function! ale_linters#handlebars#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 + +call ale#linter#Define('handlebars', { +\ 'name': 'ember-template-lint', +\ 'executable_callback': 'ale_linters#handlebars#embertemplatelint#GetExecutable', +\ 'command_callback': 'ale_linters#handlebars#embertemplatelint#GetCommand', +\ 'callback': 'ale_linters#handlebars#embertemplatelint#Handle', +\}) diff --git a/ale_linters/haskell/ghc-mod.vim b/ale_linters/haskell/ghc-mod.vim new file mode 100644 index 0000000..1b15d8c --- /dev/null +++ b/ale_linters/haskell/ghc-mod.vim @@ -0,0 +1,16 @@ +" Author: wizzup +" Description: ghc-mod for Haskell files + +call ale#linter#Define('haskell', { +\ 'name': 'ghc-mod', +\ 'executable': 'ghc-mod', +\ 'command': 'ghc-mod --map-file %s=%t check %s', +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) + +call ale#linter#Define('haskell', { +\ 'name': 'stack-ghc-mod', +\ 'executable': 'stack', +\ 'command': 'stack exec ghc-mod -- --map-file %s=%t check %s', +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) diff --git a/ale_linters/haskell/ghc.vim b/ale_linters/haskell/ghc.vim index 5ddc15f..daf91c8 100644 --- a/ale_linters/haskell/ghc.vim +++ b/ale_linters/haskell/ghc.vim @@ -1,73 +1,18 @@ " Author: w0rp " Description: ghc for Haskell files -if exists('g:loaded_ale_linters_haskell_ghc') - finish -endif +call ale#Set('haskell_ghc_options', '-fno-code -v0') -let g:loaded_ale_linters_haskell_ghc = 1 - -function! ale_linters#haskell#ghc#Handle(buffer, lines) abort - " Look for lines like the following. - " - " /dev/stdin:28:26: Not in scope: `>>>>>' - let l:pattern = '^[^:]\+:\(\d\+\):\(\d\+\): \(.\+\)$' - let l:output = [] - - " For some reason the output coming out of the GHC through the wrapper - " script breaks the lines up in strange ways. So we have to join some - " lines back together again. - 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) - if l:line !~# ': error:$' - call add(l:corrected_lines, '') - endif - elseif l:line ==# '' - call add(l:corrected_lines, l:line) - else - if len(l:corrected_lines) > 0 - let l:line = substitute(l:line, '\v\s+', ' ', '') - let l:corrected_lines[-1] .= l:line - endif - endif - endfor - - for l:line in l:corrected_lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:match[3], - \ 'type': 'E', - \ 'nr': -1, - \}) - endfor - - return l:output +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': g:ale#util#stdin_wrapper . ' .hs ghc -fno-code -v0', -\ 'callback': 'ale_linters#haskell#ghc#Handle', -\}) - -call ale#linter#Define('haskell', { -\ 'name': 'stack-ghc', -\ 'output_stream': 'stderr', -\ 'executable': 'stack', -\ 'command': g:ale#util#stdin_wrapper . ' .hs stack ghc -- -fno-code -v0', -\ 'callback': 'ale_linters#haskell#ghc#Handle', +\ 'command_callback': 'ale_linters#haskell#ghc#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 0000000..93c7ddd --- /dev/null +++ b/ale_linters/haskell/hdevtools.vim @@ -0,0 +1,22 @@ +" Author: rob-b, Takano Akio +" Description: hdevtools for Haskell files + +call ale#Set('haskell_hdevtools_executable', 'hdevtools') +call ale#Set('haskell_hdevtools_options', '-g -Wall') + +function! ale_linters#haskell#hdevtools#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'haskell_hdevtools_executable') +endfunction + +function! ale_linters#haskell#hdevtools#GetCommand(buffer) abort + return ale#Escape(ale_linters#haskell#hdevtools#GetExecutable(a:buffer)) + \ . ' check ' . ale#Var(a:buffer, 'haskell_hdevtools_options') + \ . ' -p %s %t' +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'hdevtools', +\ 'executable_callback': 'ale_linters#haskell#hdevtools#GetExecutable', +\ 'command_callback': 'ale_linters#haskell#hdevtools#GetCommand', +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) diff --git a/ale_linters/haskell/hlint.vim b/ale_linters/haskell/hlint.vim index 5b870e3..be40d92 100644 --- a/ale_linters/haskell/hlint.vim +++ b/ale_linters/haskell/hlint.vim @@ -2,19 +2,24 @@ " Description: hlint for Haskell files function! ale_linters#haskell#hlint#Handle(buffer, lines) abort - let l:errors = json_decode(join(a:lines, '')) - let l:output = [] - for l:error in l:errors - " vcol is Needed to indicate that the column is a character. + 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, { - \ 'bufnr': a:buffer, - \ 'lnum': l:error.startLine + 0, - \ 'vcol': 0, - \ 'col': l:error.startColumn + 0, + \ '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:error.severity ==# 'Error' ? 'E' : 'W', + \ 'type': l:type, \}) endfor diff --git a/ale_linters/haskell/stack_build.vim b/ale_linters/haskell/stack_build.vim new file mode 100644 index 0000000..44e2e86 --- /dev/null +++ b/ale_linters/haskell/stack_build.vim @@ -0,0 +1,22 @@ +" 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', +\ 'output_stream': 'stderr', +\ 'executable': 'stack', +\ 'command_callback': '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 0000000..0367dc2 --- /dev/null +++ b/ale_linters/haskell/stack_ghc.vim @@ -0,0 +1,10 @@ +" Author: w0rp +" Description: ghc for Haskell files, using Stack + +call ale#linter#Define('haskell', { +\ 'name': 'stack-ghc', +\ 'output_stream': 'stderr', +\ 'executable': 'stack', +\ 'command': 'stack ghc -- -fno-code -v0 %t', +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) diff --git a/ale_linters/help/alex.vim b/ale_linters/help/alex.vim new file mode 100644 index 0000000..21b23b4 --- /dev/null +++ b/ale_linters/help/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for help files + +call ale#linter#Define('help', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/help/proselint.vim b/ale_linters/help/proselint.vim new file mode 100644 index 0000000..6212450 --- /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/write-good.vim b/ale_linters/help/write-good.vim new file mode 100644 index 0000000..11254cd --- /dev/null +++ b/ale_linters/help/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for vim Help files + +call ale#linter#Define('help', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/html/alex.vim b/ale_linters/html/alex.vim new file mode 100644 index 0000000..5a1f61e --- /dev/null +++ b/ale_linters/html/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for HTML files + +call ale#linter#Define('html', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/html/htmlhint.vim b/ale_linters/html/htmlhint.vim index 910f3e6..88a83f1 100644 --- a/ale_linters/html/htmlhint.vim +++ b/ale_linters/html/htmlhint.vim @@ -1,36 +1,38 @@ " Author: KabbAmine , deathmaz <00maz1987@gmail.com>, diartyz " Description: HTMLHint for checking html files -" CLI options -let g:ale_html_htmlhint_options = get(g:, 'ale_html_htmlhint_options', '--format=unix') - -let g:ale_html_htmlhint_executable = -\ get(g:, 'ale_html_htmlhint_executable', 'htmlhint') - -let g:ale_html_htmlhint_use_global = -\ get(g:, 'ale_html_htmlhint_use_global', 0) +call ale#Set('html_htmlhint_options', '') +call ale#Set('html_htmlhint_executable', 'htmlhint') +call ale#Set('html_htmlhint_use_global', 0) function! ale_linters#html#htmlhint#GetExecutable(buffer) abort - if g:ale_html_htmlhint_use_global - return g:ale_html_htmlhint_executable - endif - - return ale#util#ResolveLocalPath( - \ a:buffer, + return ale#node#FindExecutable(a:buffer, 'html_htmlhint', [ \ 'node_modules/.bin/htmlhint', - \ g:ale_html_htmlhint_executable - \) + \]) endfunction function! ale_linters#html#htmlhint#GetCommand(buffer) abort - return g:ale#util#stdin_wrapper . ' .html ' - \ . ale_linters#html#htmlhint#GetExecutable(a:buffer) - \ . ' ' . g:ale_html_htmlhint_options + 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 ale#Escape(ale_linters#html#htmlhint#GetExecutable(a:buffer)) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --format=unix %t' endfunction call ale#linter#Define('html', { \ 'name': 'htmlhint', \ 'executable_callback': 'ale_linters#html#htmlhint#GetExecutable', \ 'command_callback': 'ale_linters#html#htmlhint#GetCommand', -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/html/proselint.vim b/ale_linters/html/proselint.vim new file mode 100644 index 0000000..9fd7d67 --- /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/tidy.vim b/ale_linters/html/tidy.vim index 5352034..34152c6 100644 --- a/ale_linters/html/tidy.vim +++ b/ale_linters/html/tidy.vim @@ -3,7 +3,9 @@ " CLI options let g:ale_html_tidy_executable = get(g:, 'ale_html_tidy_executable', 'tidy') -let g:ale_html_tidy_args = get(g:, 'ale_html_tidy_args', '-q -e -language en') +" Look for the old _args variable first. +let s:default_options = get(g:, 'ale_html_tidy_args', '-q -e -language en') +let g:ale_html_tidy_options = get(g:, 'ale_html_tidy_options', s:default_options) function! ale_linters#html#tidy#GetCommand(buffer) abort " Specify file encoding in options @@ -23,11 +25,23 @@ function! ale_linters#html#tidy#GetCommand(buffer) abort \ '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 -', - \ g:ale_html_tidy_executable, - \ g:ale_html_tidy_args, + \ l:executable, + \ ale#Var(a:buffer, 'html_tidy_options'), \ l:file_encoding - \ ) + \) +endfunction + +function! ale_linters#html#tidy#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'html_tidy_executable') endfunction function! ale_linters#html#tidy#Handle(buffer, lines) abort @@ -37,27 +51,17 @@ function! ale_linters#html#tidy#Handle(buffer, lines) abort let l:pattern = '^line \(\d\+\) column \(\d\+\) - \(Warning\|Error\): \(.\+\)$' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - + for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[1] + 0 let l:col = l:match[2] + 0 - let l:type = l:match[3] ==# 'Error' ? 'E' : 'W' + let l:type = l:match[3] is# 'Error' ? 'E' : 'W' let l:text = l:match[4] - " vcol is Needed to indicate that the column is a character. call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:line, - \ 'vcol': 0, \ 'col': l:col, \ 'text': l:text, \ 'type': l:type, - \ 'nr': -1, \}) endfor @@ -66,7 +70,7 @@ endfunction call ale#linter#Define('html', { \ 'name': 'tidy', -\ 'executable': g:ale_html_tidy_executable, +\ 'executable_callback': 'ale_linters#html#tidy#GetExecutable', \ 'output_stream': 'stderr', \ 'command_callback': 'ale_linters#html#tidy#GetCommand', \ 'callback': 'ale_linters#html#tidy#Handle', diff --git a/ale_linters/html/write-good.vim b/ale_linters/html/write-good.vim new file mode 100644 index 0000000..9fae882 --- /dev/null +++ b/ale_linters/html/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for nroff files + +call ale#linter#Define('html', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/idris/idris.vim b/ale_linters/idris/idris.vim new file mode 100644 index 0000000..115d04f --- /dev/null +++ b/ale_linters/idris/idris.vim @@ -0,0 +1,87 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'idris_idris_executable') +endfunction + +function! ale_linters#idris#idris#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'idris_idris_options') + + return ale#Escape(ale_linters#idris#idris#GetExecutable(a:buffer)) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --check %s' +endfunction + +function! ale_linters#idris#idris#Handle(buffer, lines) abort + " This was copied almost verbatim from ale#handlers#haskell#HandleGHCFormat + + " Look for lines like the following: + " foo.idr:2:6:When checking right hand side of main with expected type + " bar.idr:11:11-13: + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)(-\d+)?:(.*)?$' + let l:output = [] + + let l:corrected_lines = [] + + for l:line in a:lines + if len(matchlist(l:line, l:pattern)) > 0 + call add(l:corrected_lines, l:line) + elseif len(l:corrected_lines) > 0 + if l:line is# '' + let l:corrected_lines[-1] .= ' ' " turn a blank line into a space + else + let l:corrected_lines[-1] .= l:line + endif + let l:corrected_lines[-1] = substitute(l:corrected_lines[-1], '\s\+', ' ', 'g') + endif + endfor + + for l:line in l:corrected_lines + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + if !ale#path#IsBufferPath(a:buffer, l:match[1]) + continue + endif + + let l:errors = matchlist(l:match[5], '\v([wW]arning|[eE]rror) - ?(.*)') + + if len(l:errors) > 0 + let l:ghc_type = l:errors[1] + let l:text = l:errors[2] + else + let l:ghc_type = '' + let l:text = l:match[5][:0] is# ' ' ? l:match[5][1:] : l:match[5] + endif + + if l:ghc_type is? 'Warning' + let l:type = 'W' + else + let l:type = 'E' + endif + + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('idris', { +\ 'name': 'idris', +\ 'executable_callback': 'ale_linters#idris#idris#GetExecutable', +\ 'command_callback': 'ale_linters#idris#idris#GetCommand', +\ 'callback': 'ale_linters#idris#idris#Handle', +\}) + diff --git a/ale_linters/java/checkstyle.vim b/ale_linters/java/checkstyle.vim new file mode 100644 index 0000000..8155170 --- /dev/null +++ b/ale_linters/java/checkstyle.vim @@ -0,0 +1,36 @@ +" Author: Devon Meunier +" Description: checkstyle for Java files + +function! ale_linters#java#checkstyle#Handle(buffer, lines) abort + let l:pattern = '\v\[(WARN|ERROR)\] [a-zA-Z]?:?[^:]+:(\d+):(\d+)?:? (.*) \[(.+)\]$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'type': l:match[1] is? 'WARN' ? 'W' : 'E', + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'code': l:match[5], + \}) + endfor + + return l:output +endfunction + +function! ale_linters#java#checkstyle#GetCommand(buffer) abort + return 'checkstyle ' + \ . ale#Var(a:buffer, 'java_checkstyle_options') + \ . ' %t' +endfunction + +if !exists('g:ale_java_checkstyle_options') + let g:ale_java_checkstyle_options = '-c /google_checks.xml' +endif + +call ale#linter#Define('java', { +\ 'name': 'checkstyle', +\ 'executable': 'checkstyle', +\ 'command_callback': 'ale_linters#java#checkstyle#GetCommand', +\ 'callback': 'ale_linters#java#checkstyle#Handle', +\}) diff --git a/ale_linters/java/javac.vim b/ale_linters/java/javac.vim new file mode 100644 index 0000000..73e8414 --- /dev/null +++ b/ale_linters/java/javac.vim @@ -0,0 +1,128 @@ +" Author: farenjihn , w0rp +" Description: Lints java files using javac + +let s:classpath_sep = has('unix') ? ':' : ';' + +let g:ale_java_javac_options = get(g:, 'ale_java_javac_options', '') +let g:ale_java_javac_classpath = get(g:, 'ale_java_javac_classpath', '') + +function! ale_linters#java#javac#GetImportPaths(buffer) abort + let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') + + if !empty(l:pom_path) && executable('mvn') + return ale#path#CdString(fnamemodify(l:pom_path, ':h')) + \ . 'mvn dependency:build-classpath' + endif + + let l:classpath_command = ale#gradle#BuildClasspathCommand(a:buffer) + if !empty(l:classpath_command) + return l:classpath_command + endif + + return '' +endfunction + +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, 'java_javac_classpath'), s:classpath_sep), + \) + + 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) 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 + + " Automatically include the test directory, but only for test code. + if expand('#' . a:buffer . ':p') =~? '\vsrc[/\\]test[/\\]java' + let l:test_dir = fnamemodify(l:src_dir, ':h:h:h') + \ . (has('win32') ? '\test\java\' : '/test/java/') + + if isdirectory(l:test_dir) + call add(l:sp_dirs, l:test_dir) + endif + endif + 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#engine#CreateDirectory(a:buffer) + + " Always run javac from the directory the file is in, so we can resolve + " relative paths correctly. + return ale#path#BufferCdString(a:buffer) + \ . 'javac -Xlint' + \ . ' ' . l:cp_option + \ . ' ' . l:sp_option + \ . ' -d ' . ale#Escape(l:class_file_directory) + \ . ' ' . 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+): (.+):(.+)$' + 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]) + let l:output[-1].col = len(l:match[1]) + elseif empty(l:match[3]) + " Add symbols to 'cannot find symbol' errors. + if l:output[-1].text 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': 'javac', +\ 'command_chain': [ +\ {'callback': 'ale_linters#java#javac#GetImportPaths', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#java#javac#GetCommand', 'output_stream': 'stderr'}, +\ ], +\ 'callback': 'ale_linters#java#javac#Handle', +\}) diff --git a/ale_linters/javascript/eslint.vim b/ale_linters/javascript/eslint.vim index c2794ec..23e1694 100644 --- a/ale_linters/javascript/eslint.vim +++ b/ale_linters/javascript/eslint.vim @@ -1,84 +1,10 @@ " Author: w0rp " Description: eslint for JavaScript files -let g:ale_javascript_eslint_executable = -\ get(g:, 'ale_javascript_eslint_executable', 'eslint') - -let g:ale_javascript_eslint_options = -\ get(g:, 'ale_javascript_eslint_options', '') - -let g:ale_javascript_eslint_use_global = -\ get(g:, 'ale_javascript_eslint_use_global', 0) - -function! ale_linters#javascript#eslint#GetExecutable(buffer) abort - if g:ale_javascript_eslint_use_global - return g:ale_javascript_eslint_executable - endif - - return ale#util#ResolveLocalPath( - \ a:buffer, - \ 'node_modules/.bin/eslint', - \ g:ale_javascript_eslint_executable - \) -endfunction - -function! ale_linters#javascript#eslint#GetCommand(buffer) abort - return ale_linters#javascript#eslint#GetExecutable(a:buffer) - \ . ' ' . g:ale_javascript_eslint_options - \ . ' -f unix --stdin --stdin-filename %s' -endfunction - -function! ale_linters#javascript#eslint#Handle(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:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - " Try the parsing pattern for parsing errors. - let l:match = matchlist(l:line, l:parsing_pattern) - endif - - if len(l:match) == 0 - continue - endif - - let l:type = 'Error' - let l:text = l:match[3] - - " Take the error type from the output if available. - if !empty(l:match[4]) - let l:type = split(l:match[4], '/')[0] - let l:text .= ' [' . l:match[4] . ']' - 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:text, - \ 'type': l:type ==# 'Warning' ? 'W' : 'E', - \ 'nr': -1, - \}) - endfor - - return l:output -endfunction - call ale#linter#Define('javascript', { \ 'name': 'eslint', -\ 'executable_callback': 'ale_linters#javascript#eslint#GetExecutable', -\ 'command_callback': 'ale_linters#javascript#eslint#GetCommand', -\ 'callback': 'ale_linters#javascript#eslint#Handle', +\ 'output_stream': 'both', +\ 'executable_callback': 'ale#handlers#eslint#GetExecutable', +\ 'command_callback': 'ale#handlers#eslint#GetCommand', +\ 'callback': 'ale#handlers#eslint#Handle', \}) diff --git a/ale_linters/javascript/flow.vim b/ale_linters/javascript/flow.vim old mode 100644 new mode 100755 index f0b81b1..643ea19 --- a/ale_linters/javascript/flow.vim +++ b/ale_linters/javascript/flow.vim @@ -1,80 +1,178 @@ " Author: Zach Perrault -- @zperrault +" Author: Florian Beeres " Description: FlowType checking for JavaScript files -let g:ale_javascript_flow_executable = -\ get(g:, 'ale_javascript_flow_executable', 'flow') - -let g:ale_javascript_flow_use_global = -\ get(g:, 'ale_javascript_flow_use_global', 0) +call ale#Set('javascript_flow_executable', 'flow') +call ale#Set('javascript_flow_use_home_config', 0) +call ale#Set('javascript_flow_use_global', 0) function! ale_linters#javascript#flow#GetExecutable(buffer) abort - if g:ale_javascript_flow_use_global - return g:ale_javascript_flow_executable - endif + let l:flow_config = ale#path#FindNearestFile(a:buffer, '.flowconfig') - return ale#util#ResolveLocalPath( - \ a:buffer, - \ 'node_modules/.bin/flow', - \ g:ale_javascript_flow_executable - \) + 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#node#FindExecutable(a:buffer, 'javascript_flow', [ + \ 'node_modules/.bin/flow', + \]) endfunction -function! ale_linters#javascript#flow#GetCommand(buffer) abort - return ale_linters#javascript#flow#GetExecutable(a:buffer) - \ . ' check-contents --respect-pragma --json --from ale %s' +function! ale_linters#javascript#flow#VersionCheck(buffer) abort + let l:executable = ale_linters#javascript#flow#GetExecutable(a:buffer) + + if empty(l:executable) + return '' + endif + + return ale#Escape(l:executable) . ' --version' +endfunction + +function! ale_linters#javascript#flow#GetCommand(buffer, version_lines) abort + let l:executable = ale_linters#javascript#flow#GetExecutable(a:buffer) + + if empty(l:executable) + return '' + endif + + let l:version = ale#semver#GetVersion(l:executable, a:version_lines) + + " If we can parse the version number, then only use --respect-pragma + " if the version is >= 0.36.0, which added the argument. + let l:use_respect_pragma = empty(l:version) + \ || ale#semver#GTE(l:version, [0, 36]) + + return ale#Escape(l:executable) + \ . ' check-contents' + \ . (l:use_respect_pragma ? ' --respect-pragma': '') + \ . ' --json --from ale %s' +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(a:lines, '') - if l:str ==# '' - return [] - endif - let l:flow_output = json_decode(l:str) + let l:str = join(s:GetJSONLines(a:lines), '') - if has_key(l:flow_output, 'errors') + if empty(l:str) + return [] + endif + + let l:flow_output = json_decode(l:str) let l:output = [] - for l:error in 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 - if has_key(l:message, 'loc') && l:line ==# 0 - let l:line = l:message.loc.start.line + 0 - let l:col = l:message.loc.start.column + 0 - endif - if l:text ==# '' - let l:text = l:message.descr . ':' - else - let l:text = l:text . ' ' . l:message.descr - endif - endfor + 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 - if has_key(l:error, 'operation') - let l:text = l:text . ' See also: ' . l:error.operation.descr - endif + 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 = s:GetDetails(l:error) + endif + + call add(l:output, l:errorToAdd) - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': l:col, - \ 'text': l:text, - \ 'type': l:error.level ==# 'error' ? 'E' : 'W', - \}) endfor return l:output - else - return [] - endif endfunction call ale#linter#Define('javascript', { \ 'name': 'flow', \ 'executable_callback': 'ale_linters#javascript#flow#GetExecutable', -\ 'command_callback': 'ale_linters#javascript#flow#GetCommand', +\ 'command_chain': [ +\ {'callback': 'ale_linters#javascript#flow#VersionCheck'}, +\ {'callback': 'ale_linters#javascript#flow#GetCommand'}, +\ ], \ 'callback': 'ale_linters#javascript#flow#Handle', +\ 'add_newline': !has('win32'), \}) diff --git a/ale_linters/javascript/jscs.vim b/ale_linters/javascript/jscs.vim index 52710ba..bcf3ee3 100644 --- a/ale_linters/javascript/jscs.vim +++ b/ale_linters/javascript/jscs.vim @@ -1,9 +1,67 @@ " 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', 0) + +function! ale_linters#javascript#jscs#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_jscs', [ + \ 'node_modules/.bin/jscs', + \]) +endfunction + +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 = ale#Escape(ale_linters#javascript#jscs#GetExecutable(a:buffer)) + let l:command .= ' --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': 'jscs', -\ 'command': 'jscs -r unix -n -', -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'executable_callback': 'ale_linters#javascript#jscs#GetExecutable', +\ 'command_callback': 'ale_linters#javascript#jscs#GetCommand', +\ 'callback': 'ale_linters#javascript#jscs#Handle', \}) + diff --git a/ale_linters/javascript/jshint.vim b/ale_linters/javascript/jshint.vim index f4b6651..93b16a8 100644 --- a/ale_linters/javascript/jshint.vim +++ b/ale_linters/javascript/jshint.vim @@ -1,37 +1,28 @@ " Author: Chris Kyrouac - https://github.com/fijshion " Description: JSHint for Javascript files -let g:ale_javascript_jshint_executable = -\ get(g:, 'ale_javascript_jshint_executable', 'jshint') - -let g:ale_javascript_jshint_use_global = -\ get(g:, 'ale_javascript_jshint_use_global', 0) +call ale#Set('javascript_jshint_executable', 'jshint') +call ale#Set('javascript_jshint_use_global', 0) function! ale_linters#javascript#jshint#GetExecutable(buffer) abort - if g:ale_javascript_jshint_use_global - return g:ale_javascript_jshint_executable - endif - - return ale#util#ResolveLocalPath( - \ a:buffer, + return ale#node#FindExecutable(a:buffer, 'javascript_jshint', [ \ 'node_modules/.bin/jshint', - \ g:ale_javascript_jshint_executable - \) + \]) endfunction 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#util#ResolveLocalPath( + let l:jshint_config = ale#path#ResolveLocalPath( \ a:buffer, \ '.jshintrc', \ get(g:, 'ale_jshint_config_loc', '') \) - let l:command = ale_linters#javascript#jshint#GetExecutable(a:buffer) - let l:command .= ' --reporter unix' + let l:command = ale#Escape(ale_linters#javascript#jshint#GetExecutable(a:buffer)) + let l:command .= ' --reporter unix --extract auto' if !empty(l:jshint_config) - let l:command .= ' --config ' . fnameescape(l:jshint_config) + let l:command .= ' --config ' . ale#Escape(l:jshint_config) endif let l:command .= ' -' @@ -43,5 +34,5 @@ call ale#linter#Define('javascript', { \ 'name': 'jshint', \ 'executable_callback': 'ale_linters#javascript#jshint#GetExecutable', \ 'command_callback': 'ale_linters#javascript#jshint#GetCommand', -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/javascript/standard.vim b/ale_linters/javascript/standard.vim new file mode 100644 index 0000000..aa6a3a7 --- /dev/null +++ b/ale_linters/javascript/standard.vim @@ -0,0 +1,30 @@ +" Author: Ahmed El Gabri <@ahmedelgabri> +" Description: standardjs for JavaScript files + +call ale#Set('javascript_standard_executable', 'standard') +call ale#Set('javascript_standard_use_global', 0) +call ale#Set('javascript_standard_options', '') + +function! ale_linters#javascript#standard#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_standard', [ + \ 'node_modules/standard/bin/cmd.js', + \ 'node_modules/.bin/standard', + \]) +endfunction + +function! ale_linters#javascript#standard#GetCommand(buffer) abort + 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_callback': 'ale_linters#javascript#standard#GetExecutable', +\ 'command_callback': 'ale_linters#javascript#standard#GetCommand', +\ 'callback': 'ale#handlers#eslint#Handle', +\}) diff --git a/ale_linters/javascript/xo.vim b/ale_linters/javascript/xo.vim new file mode 100644 index 0000000..cf305eb --- /dev/null +++ b/ale_linters/javascript/xo.vim @@ -0,0 +1,26 @@ +" Author: Daniel Lupu +" Description: xo for JavaScript files + +call ale#Set('javascript_xo_executable', 'xo') +call ale#Set('javascript_xo_use_global', 0) +call ale#Set('javascript_xo_options', '') + +function! ale_linters#javascript#xo#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_xo', [ + \ 'node_modules/.bin/xo', + \]) +endfunction + +function! ale_linters#javascript#xo#GetCommand(buffer) abort + return ale#Escape(ale_linters#javascript#xo#GetExecutable(a:buffer)) + \ . ' ' . ale#Var(a:buffer, 'javascript_xo_options') + \ . ' --reporter unix --stdin --stdin-filename %s' +endfunction + +" xo uses eslint and the output format is the same +call ale#linter#Define('javascript', { +\ 'name': 'xo', +\ 'executable_callback': 'ale_linters#javascript#xo#GetExecutable', +\ 'command_callback': 'ale_linters#javascript#xo#GetCommand', +\ 'callback': 'ale#handlers#eslint#Handle', +\}) diff --git a/ale_linters/json/jsonlint.vim b/ale_linters/json/jsonlint.vim index 5af8f62..75f4708 100644 --- a/ale_linters/json/jsonlint.vim +++ b/ale_linters/json/jsonlint.vim @@ -7,22 +7,11 @@ function! ale_linters#json#jsonlint#Handle(buffer, lines) abort let l:pattern = '^line \(\d\+\), col \(\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 + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, \ 'col': l:match[2] + 0, \ 'text': l:match[3], - \ 'type': 'E', - \ 'nr': -1, \}) endfor diff --git a/ale_linters/kotlin/kotlinc.vim b/ale_linters/kotlin/kotlinc.vim new file mode 100644 index 0000000..00f94be --- /dev/null +++ b/ale_linters/kotlin/kotlinc.vim @@ -0,0 +1,170 @@ +" 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#GetImportPaths(buffer) abort + " exec maven/gradle only if classpath is not set + if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') isnot# '' + return '' + else + let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml') + if !empty(l:pom_path) && executable('mvn') + return ale#path#CdString(fnamemodify(l:pom_path, ':h')) + \ . 'mvn dependency:build-classpath' + endif + + let l:classpath_command = ale#gradle#BuildClasspathCommand(a:buffer) + if !empty(l:classpath_command) + return l:classpath_command + endif + + return '' + endif +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) 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', +\ 'command_chain': [ +\ {'callback': 'ale_linters#kotlin#kotlinc#GetImportPaths', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#kotlin#kotlinc#GetCommand', 'output_stream': 'stderr'}, +\ ], +\ '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 0000000..f474e84 --- /dev/null +++ b/ale_linters/kotlin/ktlint.vim @@ -0,0 +1,54 @@ +" Author: Francis Agyapong +" Description: Lint kotlin files using ktlint + +call ale#Set('kotlin_ktlint_executable', 'ktlint') +call ale#Set('kotlin_ktlint_rulesets', []) +call ale#Set('kotlin_ktlint_format', 0) + + +function! ale_linters#kotlin#ktlint#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'kotlin_ktlint_executable') + let l:file_path = expand('#' . a:buffer . ':p') + let l:options = '' + + " Formmatted content written to original file, not sure how to handle + " if ale#Var(a:buffer, 'kotlin_ktlint_format') + " let l:options = l:options . ' --format' + " endif + + for l:ruleset in ale#Var(a:buffer, 'kotlin_ktlint_rulesets') + let l:options = l:options . ' --ruleset ' . l:ruleset + endfor + + return l:executable . ' ' . l:options . ' ' . l:file_path +endfunction + +function! ale_linters#kotlin#ktlint#Handle(buffer, lines) abort + let l:message_pattern = '^\(.*\):\([0-9]\+\):\([0-9]\+\):\s\+\(.*\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:message_pattern) + let l:line = l:match[2] + 0 + let l:column = l:match[3] + 0 + let l:text = l:match[4] + + let l:type = l:text =~? 'not a valid kotlin file' ? 'E' : 'W' + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:column, + \ 'text': l:text, + \ 'type': l:type + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('kotlin', { +\ 'name': 'ktlint', +\ 'executable': 'ktlint', +\ 'command_callback': 'ale_linters#kotlin#ktlint#GetCommand', +\ 'callback': 'ale_linters#kotlin#ktlint#Handle', +\ 'lint_file': 1 +\}) diff --git a/ale_linters/less/lessc.vim b/ale_linters/less/lessc.vim new file mode 100755 index 0000000..108679d --- /dev/null +++ b/ale_linters/less/lessc.vim @@ -0,0 +1,56 @@ +" 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', 0) + +function! ale_linters#less#lessc#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'less_lessc', [ + \ 'node_modules/.bin/lessc', + \]) +endfunction + +function! ale_linters#less#lessc#GetCommand(buffer) abort + let l:executable = ale_linters#less#lessc#GetExecutable(a:buffer) + let l:dir = expand('#' . a:buffer . ':p:h') + let l:options = ale#Var(a:buffer, 'less_lessc_options') + + return ale#Escape(l:executable) + \ . ' --no-color --lint' + \ . ' --include-path=' . ale#Escape(l:dir) + \ . (!empty(l:options) ? ' ' . l: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_callback': 'ale_linters#less#lessc#GetExecutable', +\ 'command_callback': '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 0000000..690c8c9 --- /dev/null +++ b/ale_linters/less/stylelint.vim @@ -0,0 +1,27 @@ +" Author: diartyz , w0rp + +call ale#Set('less_stylelint_executable', 'stylelint') +call ale#Set('less_stylelint_options', '') +call ale#Set('less_stylelint_use_global', 0) + +function! ale_linters#less#stylelint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'less_stylelint', [ + \ 'node_modules/.bin/stylelint', + \]) +endfunction + +function! ale_linters#less#stylelint#GetCommand(buffer) abort + let l:executable = ale_linters#less#stylelint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'less_stylelint_options') + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('less', { +\ 'name': 'stylelint', +\ 'executable_callback': 'ale_linters#less#stylelint#GetExecutable', +\ 'command_callback': '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 0000000..0a4903e --- /dev/null +++ b/ale_linters/llvm/llc.vim @@ -0,0 +1,35 @@ +" Author: rhysd +" Description: Support for checking LLVM IR with llc + +call ale#Set('llvm_llc_executable', 'llc') + +function! ale_linters#llvm#llc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'llvm_llc_executable') +endfunction + +function! ale_linters#llvm#llc#GetCommand(buffer) abort + return ale#Escape(ale_linters#llvm#llc#GetExecutable(a:buffer)) + \ . ' -filetype=null -o=' + \ . ale#Escape(g:ale#util#nul_file) +endfunction + +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+): (.+)$' + let l:matches = ale#util#GetMatches(a:lines, l:pattern) + + return map(l:matches, "{ + \ '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_callback': 'ale_linters#llvm#llc#GetExecutable', +\ 'output_stream': 'stderr', +\ 'command_callback': 'ale_linters#llvm#llc#GetCommand', +\ 'callback': 'ale_linters#llvm#llc#HandleErrors', +\}) diff --git a/ale_linters/lua/luac.vim b/ale_linters/lua/luac.vim new file mode 100644 index 0000000..4a6bb40 --- /dev/null +++ b/ale_linters/lua/luac.vim @@ -0,0 +1,40 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'lua_luac_executable') +endfunction + +function! ale_linters#lua#luac#GetCommand(buffer) abort + let l:executable = ale_linters#lua#luac#GetExecutable(a:buffer) + return ale#Escape(l:executable) . ' -p - ' +endfunction + +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_callback': 'ale_linters#lua#luac#GetExecutable', +\ 'command_callback': 'ale_linters#lua#luac#GetCommand', +\ 'output_stream': 'stderr', +\ 'callback': 'ale_linters#lua#luac#Handle', +\}) diff --git a/ale_linters/lua/luacheck.vim b/ale_linters/lua/luacheck.vim index ace464c..725153c 100644 --- a/ale_linters/lua/luacheck.vim +++ b/ale_linters/lua/luacheck.vim @@ -4,30 +4,40 @@ let g:ale_lua_luacheck_executable = \ get(g:, 'ale_lua_luacheck_executable', 'luacheck') +let g:ale_lua_luacheck_options = +\ get(g:, 'ale_lua_luacheck_options', '') + +function! ale_linters#lua#luacheck#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'lua_luacheck_executable') +endfunction + +function! ale_linters#lua#luacheck#GetCommand(buffer) abort + return ale#Escape(ale_linters#lua#luacheck#GetExecutable(a:buffer)) + \ . ' ' . ale#Var(a:buffer, 'lua_luacheck_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:pattern = '^.*:\(\d\+\):\(\d\+\): (\([WE]\)\(\d\+\)) \(.\+\)$' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 + 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 - " 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': l:match[3], - \ 'nr': -1, + \ 'code': l:match[3] . l:match[4], + \ 'text': l:match[5], \}) endfor @@ -36,7 +46,7 @@ endfunction call ale#linter#Define('lua', { \ 'name': 'luacheck', -\ 'executable': g:ale_lua_luacheck_executable, -\ 'command': g:ale_lua_luacheck_executable . ' --formatter plain --codes --filename %s -', +\ 'executable_callback': 'ale_linters#lua#luacheck#GetExecutable', +\ 'command_callback': 'ale_linters#lua#luacheck#GetCommand', \ 'callback': 'ale_linters#lua#luacheck#Handle', \}) diff --git a/ale_linters/mail/alex.vim b/ale_linters/mail/alex.vim new file mode 100644 index 0000000..b0651cc --- /dev/null +++ b/ale_linters/mail/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for HTML files + +call ale#linter#Define('mail', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/mail/proselint.vim b/ale_linters/mail/proselint.vim new file mode 100644 index 0000000..82c8d1f --- /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 0000000..e6dfd2e --- /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 0000000..63c35db --- /dev/null +++ b/ale_linters/make/checkmake.vim @@ -0,0 +1,24 @@ +" Author: aurieh - https://github.com/aurieh + +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 + +call ale#linter#Define('make', { +\ 'name': 'checkmake', +\ 'executable': 'checkmake', +\ 'command': 'checkmake %s --format="{{.LineNumber}}:{{.Rule}}:{{.Violation}}"', +\ '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 0000000..2930614 --- /dev/null +++ b/ale_linters/markdown/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for markdown files + +call ale#linter#Define('markdown', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/markdown/mdl.vim b/ale_linters/markdown/mdl.vim index c984252..16b08cc 100644 --- a/ale_linters/markdown/mdl.vim +++ b/ale_linters/markdown/mdl.vim @@ -1,26 +1,31 @@ -" Author: Steve Dignam -" Description: Support for mdl, a markdown linter +" 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:options = ale#Var(a:buffer, 'markdown_mdl_options') + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') +endfunction function! ale_linters#markdown#mdl#Handle(buffer, lines) abort " matches: '(stdin):173: MD004 Unordered list style' let l:pattern = ':\(\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 - + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': 0, \ 'text': l:match[2], \ 'type': 'W', - \ 'nr': -1, \}) endfor @@ -29,7 +34,7 @@ endfunction call ale#linter#Define('markdown', { \ 'name': 'mdl', -\ 'executable': 'mdl', -\ 'command': 'mdl', +\ 'executable_callback': 'ale_linters#markdown#mdl#GetExecutable', +\ 'command_callback': 'ale_linters#markdown#mdl#GetCommand', \ 'callback': 'ale_linters#markdown#mdl#Handle' \}) diff --git a/ale_linters/markdown/proselint.vim b/ale_linters/markdown/proselint.vim index fd2155f..289d881 100644 --- a/ale_linters/markdown/proselint.vim +++ b/ale_linters/markdown/proselint.vim @@ -1,9 +1,9 @@ " Author: poohzrn https://github.com/poohzrn -" Description: proselint for markdown files +" Description: proselint for Markdown files call ale#linter#Define('markdown', { \ 'name': 'proselint', \ 'executable': 'proselint', -\ 'command': g:ale#util#stdin_wrapper . ' .md proselint', -\ 'callback': 'ale#handlers#HandleUnixFormatAsWarning', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', \}) diff --git a/ale_linters/markdown/redpen.vim b/ale_linters/markdown/redpen.vim new file mode 100644 index 0000000..ff2cbaf --- /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 0000000..5b3b3d4 --- /dev/null +++ b/ale_linters/markdown/remark_lint.vim @@ -0,0 +1,28 @@ +" Author rhysd https://rhysd.github.io/ +" Description: remark-lint for Markdown files + +function! ale_linters#markdown#remark_lint#Handle(buffer, lines) abort + " matches: ' 1:4 warning Incorrect list-item indent: add 1 space list-item-indent remark-lint' + let l:pattern = '^ \+\(\d\+\):\(\d\+\) \(warning\|error\) \(.\+\)$' + 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', + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('markdown', { +\ 'name': 'remark-lint', +\ 'executable': 'remark', +\ 'command': 'remark --no-stdout --no-color %s', +\ 'callback': 'ale_linters#markdown#remark_lint#Handle', +\ 'lint_file': 1, +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/markdown/vale.vim b/ale_linters/markdown/vale.vim new file mode 100644 index 0000000..838c4db --- /dev/null +++ b/ale_linters/markdown/vale.vim @@ -0,0 +1,9 @@ +" Author: chew-z https://github.com/chew-z +" Description: vale for Markdown files + +call ale#linter#Define('markdown', { +\ 'name': 'vale', +\ 'executable': 'vale', +\ 'command': 'vale --output=JSON %t', +\ 'callback': 'ale#handlers#vale#Handle', +\}) diff --git a/ale_linters/markdown/write-good.vim b/ale_linters/markdown/write-good.vim new file mode 100644 index 0000000..21dbff1 --- /dev/null +++ b/ale_linters/markdown/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for Markdown files + +call ale#linter#Define('markdown', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/matlab/mlint.vim b/ale_linters/matlab/mlint.vim index 302e75c..3276633 100644 --- a/ale_linters/matlab/mlint.vim +++ b/ale_linters/matlab/mlint.vim @@ -4,6 +4,16 @@ let g:ale_matlab_mlint_executable = \ get(g:, 'ale_matlab_mlint_executable', 'mlint') +function! ale_linters#matlab#mlint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'matlab_mlint_executable') +endfunction + +function! ale_linters#matlab#mlint#GetCommand(buffer) abort + let l:executable = ale_linters#matlab#mlint#GetExecutable(a:buffer) + + return l:executable . ' -id %t' +endfunction + function! ale_linters#matlab#mlint#Handle(buffer, lines) abort " Matches patterns like the following: " @@ -12,13 +22,7 @@ function! ale_linters#matlab#mlint#Handle(buffer, lines) abort let l:pattern = '^L \(\d\+\) (C \([0-9-]\+\)): \([A-Z]\+\): \(.\+\)$' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - + 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] @@ -26,19 +30,16 @@ function! ale_linters#matlab#mlint#Handle(buffer, lines) abort " Suppress erroneous waring about filename " TODO: Enable this error when copying filename is supported - if l:code ==# 'FNDEF' + if l:code is# 'FNDEF' continue endif - " vcol is needed to indicate that the column is a character. call add(l:output, { \ 'bufnr': a:buffer, \ 'lnum': l:lnum, - \ 'vcol': 0, \ 'col': l:col, \ 'text': l:text, \ 'type': 'W', - \ 'nr': -1, \}) endfor @@ -47,9 +48,8 @@ endfunction call ale#linter#Define('matlab', { \ 'name': 'mlint', -\ 'executable': 'mlint', -\ 'command': g:ale#util#stdin_wrapper . -\ ' .m ' . g:ale_matlab_mlint_executable . ' -id', +\ 'executable_callback': 'ale_linters#matlab#mlint#GetExecutable', +\ 'command_callback': 'ale_linters#matlab#mlint#GetCommand', \ 'output_stream': 'stderr', \ 'callback': 'ale_linters#matlab#mlint#Handle', \}) diff --git a/ale_linters/nim/nimcheck.vim b/ale_linters/nim/nimcheck.vim new file mode 100644 index 0000000..bff45f7 --- /dev/null +++ b/ale_linters/nim/nimcheck.vim @@ -0,0 +1,65 @@ +" Author: Baabelfish +" Description: Typechecking for nim files + +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 + + 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_callback': 'ale_linters#nim#nimcheck#GetCommand', +\ 'callback': 'ale_linters#nim#nimcheck#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/nix/nix.vim b/ale_linters/nix/nix.vim new file mode 100644 index 0000000..0a0c5c3 --- /dev/null +++ b/ale_linters/nix/nix.vim @@ -0,0 +1,26 @@ +" Author: Alistair Bill <@alibabzo> +" Description: nix-instantiate linter for nix files + +function! ale_linters#nix#nix#Handle(buffer, lines) abort + let l:pattern = '^\(.\+\): \(.\+\), at .*:\(\d\+\):\(\d\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'col': l:match[4] + 0, + \ 'text': l:match[1] . ': ' . l:match[2], + \ 'type': l:match[1] =~# '^error' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('nix', { +\ 'name': 'nix', +\ 'output_stream': 'stderr', +\ 'executable': 'nix-instantiate', +\ 'command': 'nix-instantiate --parse -', +\ 'callback': 'ale_linters#nix#nix#Handle', +\}) diff --git a/ale_linters/nroff/alex.vim b/ale_linters/nroff/alex.vim new file mode 100644 index 0000000..a10db2d --- /dev/null +++ b/ale_linters/nroff/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for nroff files + +call ale#linter#Define('nroff', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/nroff/proselint.vim b/ale_linters/nroff/proselint.vim new file mode 100644 index 0000000..a23e56b --- /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/write-good.vim b/ale_linters/nroff/write-good.vim new file mode 100644 index 0000000..d318fb2 --- /dev/null +++ b/ale_linters/nroff/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for nroff files + +call ale#linter#Define('nroff', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/objc/clang.vim b/ale_linters/objc/clang.vim new file mode 100644 index 0000000..f4725a0 --- /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 ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) + \ . ' ' . ale#Var(a:buffer, 'objc_clang_options') . ' -' +endfunction + +call ale#linter#Define('objc', { +\ 'name': 'clang', +\ 'output_stream': 'stderr', +\ 'executable': 'clang', +\ 'command_callback': 'ale_linters#objc#clang#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/objcpp/clang.vim b/ale_linters/objcpp/clang.vim new file mode 100644 index 0000000..0e9cefe --- /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 ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) + \ . ' ' . ale#Var(a:buffer, 'objcpp_clang_options') . ' -' +endfunction + +call ale#linter#Define('objcpp', { +\ 'name': 'clang', +\ 'output_stream': 'stderr', +\ 'executable': 'clang++', +\ 'command_callback': 'ale_linters#objcpp#clang#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/ocaml/merlin.vim b/ale_linters/ocaml/merlin.vim index 75cdb12..cfec996 100644 --- a/ale_linters/ocaml/merlin.vim +++ b/ale_linters/ocaml/merlin.vim @@ -2,12 +2,11 @@ " Description: Report errors in OCaml code with Merlin if !exists('g:merlin') - finish + finish endif function! ale_linters#ocaml#merlin#Handle(buffer, lines) abort - let l:errors = merlin#ErrorLocList() - return l:errors + return merlin#ErrorLocList() endfunction call ale#linter#Define('ocaml', { @@ -16,4 +15,3 @@ call ale#linter#Define('ocaml', { \ 'command': 'true', \ 'callback': 'ale_linters#ocaml#merlin#Handle', \}) - diff --git a/ale_linters/ocaml/ols.vim b/ale_linters/ocaml/ols.vim new file mode 100644 index 0000000..c0255a6 --- /dev/null +++ b/ale_linters/ocaml/ols.vim @@ -0,0 +1,14 @@ +" 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', 0) + +call ale#linter#Define('ocaml', { +\ 'name': 'ols', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale#handlers#ols#GetExecutable', +\ 'command_callback': 'ale#handlers#ols#GetCommand', +\ 'language_callback': 'ale#handlers#ols#GetLanguage', +\ 'project_root_callback': 'ale#handlers#ols#GetProjectRoot', +\}) diff --git a/ale_linters/perl/perl.vim b/ale_linters/perl/perl.vim index 3724244..1b9aa95 100644 --- a/ale_linters/perl/perl.vim +++ b/ale_linters/perl/perl.vim @@ -1,32 +1,59 @@ " Author: Vincent Lequertier " Description: This file adds support for checking perl syntax +let g:ale_perl_perl_executable = +\ get(g:, 'ale_perl_perl_executable', 'perl') + +let g:ale_perl_perl_options = +\ get(g:, 'ale_perl_perl_options', '-c -Mwarnings -Ilib') + +function! ale_linters#perl#perl#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'perl_perl_executable') +endfunction + +function! ale_linters#perl#perl#GetCommand(buffer) abort + return ale#Escape(ale_linters#perl#perl#GetExecutable(a:buffer)) + \ . ' ' . 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 let l:pattern = '\(.\+\) at \(.\+\) line \(\d\+\)' let l:output = [] + let l:basename = expand('#' . a:buffer . ':t') - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) + let l:type = 'E' + if a:lines[-1] =~# 'syntax OK' + let l:type = 'W' + endif - if len(l:match) == 0 - continue - endif + let l:seen = {} + for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[3] - let l:column = 1 + let l:file = l:match[2] let l:text = l:match[1] - let l:type = 'E' - " vcol is Needed to indicate that the column is a character. - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': l:column, - \ 'text': l:text, - \ 'type': l:type, - \ 'nr': -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 @@ -34,8 +61,8 @@ endfunction call ale#linter#Define('perl', { \ 'name': 'perl', -\ 'executable': 'perl', +\ 'executable_callback': 'ale_linters#perl#perl#GetExecutable', \ 'output_stream': 'both', -\ 'command': 'perl -X -c -Mwarnings -Ilib', +\ 'command_callback': 'ale_linters#perl#perl#GetCommand', \ 'callback': 'ale_linters#perl#perl#Handle', \}) diff --git a/ale_linters/perl/perlcritic.vim b/ale_linters/perl/perlcritic.vim index dfad2d4..24f7eb8 100644 --- a/ale_linters/perl/perlcritic.vim +++ b/ale_linters/perl/perlcritic.vim @@ -1,31 +1,67 @@ -" Author: Vincent Lequertier +" Author: Vincent Lequertier , Chris Weyl " Description: This file adds support for checking perl with perl critic +let g:ale_perl_perlcritic_executable = +\ get(g:, 'ale_perl_perlcritic_executable', 'perlcritic') + +let g:ale_perl_perlcritic_profile = +\ get(g:, 'ale_perl_perlcritic_profile', '.perlcriticrc') + +let g:ale_perl_perlcritic_options = +\ get(g:, 'ale_perl_perlcritic_options', '') + +let g:ale_perl_perlcritic_showrules = +\ get(g:, 'ale_perl_perlcritic_showrules', 0) + +function! ale_linters#perl#perlcritic#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'perl_perlcritic_executable') +endfunction + +function! ale_linters#perl#perlcritic#GetProfile(buffer) abort + + " first see if we've been overridden + let l:profile = ale#Var(a:buffer, 'perl_perlcritic_profile') + if l:profile is? '' + return '' + endif + + " 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') + + let l:command = ale#Escape(ale_linters#perl#perlcritic#GetExecutable(a:buffer)) + \ . " --verbose '". l:critic_verbosity . "' --nocolor" + + if l:profile isnot? '' + let l:command .= ' --profile ' . ale#Escape(l:profile) + endif + if l:options isnot? '' + let l:command .= ' ' . l:options + endif + + return l:command +endfunction + + function! ale_linters#perl#perlcritic#Handle(buffer, lines) abort - let l:pattern = '\(.\+\) at \(.\+\) line \(\d\+\)' + let l:pattern = '\(\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 l:line = l:match[3] - let l:column = 1 - let l:text = l:match[1] - let l:type = 'E' - - " vcol is Needed to indicate that the column is a character. + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': l:column, - \ 'text': l:text, - \ 'type': l:type, - \ 'nr': -1, + \ 'lnum': l:match[1], + \ 'col': l:match[2], + \ 'text': l:match[3], + \ 'type': 'W' \}) endfor @@ -34,8 +70,8 @@ endfunction call ale#linter#Define('perl', { \ 'name': 'perlcritic', -\ 'executable': 'perlcritic', \ 'output_stream': 'stdout', -\ 'command': 'perlcritic --verbose 3 --nocolor', +\ 'executable_callback': 'ale_linters#perl#perlcritic#GetExecutable', +\ 'command_callback': 'ale_linters#perl#perlcritic#GetCommand', \ 'callback': 'ale_linters#perl#perlcritic#Handle', \}) diff --git a/ale_linters/php/hack.vim b/ale_linters/php/hack.vim index 5c90247..77d3a58 100644 --- a/ale_linters/php/hack.vim +++ b/ale_linters/php/hack.vim @@ -5,24 +5,15 @@ function! ale_linters#php#hack#Handle(buffer, lines) abort let l:pattern = '^\(.*\):\(\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 + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if a:buffer != bufnr(l:match[1]) continue endif - if a:buffer != bufnr(l:match[1]) - continue - endif - call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:match[2] + 0, - \ 'vcol': 0, \ 'col': l:match[3] + 0, \ 'text': l:match[5], - \ 'type': 'E', \}) endfor diff --git a/ale_linters/php/langserver.vim b/ale_linters/php/langserver.vim new file mode 100644 index 0000000..be2d6ef --- /dev/null +++ b/ale_linters/php/langserver.vim @@ -0,0 +1,34 @@ +" 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', 0) + +function! ale_linters#php#langserver#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'php_langserver', [ + \ 'vendor/bin/php-language-server.php', + \]) +endfunction + +function! ale_linters#php#langserver#GetCommand(buffer) abort + return 'php ' . ale#Escape(ale_linters#php#langserver#GetExecutable(a:buffer)) +endfunction + +function! ale_linters#php#langserver#GetLanguage(buffer) abort + return 'php' +endfunction + +function! ale_linters#php#langserver#GetProjectRoot(buffer) abort + let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') + + return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' +endfunction + +call ale#linter#Define('php', { +\ 'name': 'langserver', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#php#langserver#GetExecutable', +\ 'command_callback': 'ale_linters#php#langserver#GetCommand', +\ 'language_callback': 'ale_linters#php#langserver#GetLanguage', +\ 'project_root_callback': 'ale_linters#php#langserver#GetProjectRoot', +\}) diff --git a/ale_linters/php/phan.vim b/ale_linters/php/phan.vim new file mode 100644 index 0000000..f3b3d48 --- /dev/null +++ b/ale_linters/php/phan.vim @@ -0,0 +1,36 @@ +" Author: diegoholiveira +" Description: static analyzer for PHP + +" Define the minimum severity +let g:ale_php_phan_minimum_severity = get(g:, 'ale_php_phan_minimum_severity', 0) + +function! ale_linters#php#phan#GetCommand(buffer) abort + return 'phan -y ' + \ . ale#Var(a:buffer, 'php_phan_minimum_severity') + \ . ' %s' +endfunction + +function! ale_linters#php#phan#Handle(buffer, lines) abort + " Matches against lines like the following: + " + " /path/to/some-filename.php:18 ERRORTYPE message + let l:pattern = '^.*:\(\d\+\)\s\(\w\+\)\s\(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[3], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phan', +\ 'executable': 'phan', +\ 'command_callback': 'ale_linters#php#phan#GetCommand', +\ 'callback': 'ale_linters#php#phan#Handle', +\}) diff --git a/ale_linters/php/php.vim b/ale_linters/php/php.vim index b807fee..6470383 100644 --- a/ale_linters/php/php.vim +++ b/ale_linters/php/php.vim @@ -1,30 +1,28 @@ -" Author: Spencer Wood +" Author: Spencer Wood , Adriaan Zonnenberg " Description: This file adds support for checking PHP with php-cli function! ale_linters#php#php#Handle(buffer, lines) abort " Matches patterns like the following: " - " Parse error: parse error in - on line 7 - let l:pattern = 'Parse error:\s\+\(.\+\) on line \(\d\+\)' + " 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.+)@ +" 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_executable', 'phpcs') +call ale#Set('php_phpcs_use_global', 0) + +function! ale_linters#php#phpcs#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'php_phpcs', [ + \ 'vendor/bin/phpcs', + \ 'phpcs' + \]) +endfunction + function! ale_linters#php#phpcs#GetCommand(buffer) abort - let l:command = 'phpcs -s --report=emacs --stdin-path=%s' + let l:executable = ale_linters#php#phpcs#GetExecutable(a:buffer) - " This option can be set to change the standard used by phpcs - if exists('g:ale_php_phpcs_standard') - let l:command .= ' --standard=' . g:ale_php_phpcs_standard - endif + let l:standard = ale#Var(a:buffer, 'php_phpcs_standard') + let l:standard_option = !empty(l:standard) + \ ? '--standard=' . l:standard + \ : '' - return l:command + return ale#Escape(l:executable) + \ . ' -s --report=emacs --stdin-path=%s ' . l:standard_option 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:pattern = '^.*:\(\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 l:text = l:match[4] + 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] - " 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:text, - \ 'type': l:type ==# 'error' ? 'E' : 'W', - \ 'nr': -1, + \ 'type': l:type is# 'error' ? 'E' : 'W', \}) endfor @@ -46,7 +50,7 @@ endfunction call ale#linter#Define('php', { \ 'name': 'phpcs', -\ 'executable': 'phpcs', +\ 'executable_callback': 'ale_linters#php#phpcs#GetExecutable', \ 'command_callback': '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 0000000..e945075 --- /dev/null +++ b/ale_linters/php/phpmd.vim @@ -0,0 +1,45 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'php_phpmd_executable') +endfunction + +function! ale_linters#php#phpmd#GetCommand(buffer) abort + let l:executable = ale_linters#php#phpmd#GetExecutable(a:buffer) + + return ale#Escape(l:executable) + \ . ' %s text ' + \ . ale#Var(a:buffer, 'php_phpmd_ruleset') + \ . ' --ignore-violations-on-exit %t' +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_callback': 'ale_linters#php#phpmd#GetExecutable', +\ 'command_callback': '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 0000000..2476208 --- /dev/null +++ b/ale_linters/php/phpstan.vim @@ -0,0 +1,53 @@ +" Author: medains , ardis +" 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', '4') +let g:ale_php_phpstan_configuration = get(g:, 'ale_php_phpstan_configuration', '') + +function! ale_linters#php#phpstan#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'php_phpstan_executable') +endfunction + +function! ale_linters#php#phpstan#GetCommand(buffer) abort + let l:executable = ale_linters#php#phpstan#GetExecutable(a:buffer) + + let l:configuration = ale#Var(a:buffer, 'php_phpstan_configuration') + let l:configuration_option = !empty(l:configuration) + \ ? ' -c ' . l:configuration + \ : '' + + return ale#Escape(l:executable) + \ . ' analyze -l' + \ . ale#Var(a:buffer, 'php_phpstan_level') + \ . ' --errorFormat raw' + \ . l:configuration_option + \ . ' %s' +endfunction + +function! ale_linters#php#phpstan#Handle(buffer, lines) abort + " Matches against lines like the following: + " + " filename.php:15:message + " C:\folder\filename.php:15:message + let l:pattern = '^\([a-zA-Z]:\)\?[^:]\+:\(\d\+\):\(.*\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'text': l:match[3], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'phpstan', +\ 'executable_callback': 'ale_linters#php#phpstan#GetExecutable', +\ 'command_callback': 'ale_linters#php#phpstan#GetCommand', +\ 'callback': 'ale_linters#php#phpstan#Handle', +\}) diff --git a/ale_linters/po/alex.vim b/ale_linters/po/alex.vim new file mode 100644 index 0000000..411d835 --- /dev/null +++ b/ale_linters/po/alex.vim @@ -0,0 +1,11 @@ +" Author: Cian Butler https://github.com/butlerx +" Description: alex for PO files + +call ale#linter#Define('po', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/po/msgfmt.vim b/ale_linters/po/msgfmt.vim new file mode 100644 index 0000000..60c25d3 --- /dev/null +++ b/ale_linters/po/msgfmt.vim @@ -0,0 +1,10 @@ +" Author: Cian Butler https://github.com/butlerx +" Description: msgfmt for PO files + +call ale#linter#Define('po', { +\ 'name': 'msgfmt', +\ 'executable': 'msgfmt', +\ 'output_stream': 'stderr', +\ 'command': 'msgfmt --statistics %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/po/proselint.vim b/ale_linters/po/proselint.vim new file mode 100644 index 0000000..ce13250 --- /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/write-good.vim b/ale_linters/po/write-good.vim new file mode 100644 index 0000000..5a01cb6 --- /dev/null +++ b/ale_linters/po/write-good.vim @@ -0,0 +1,9 @@ +" Author: Cian Butler https://github.com/butlerx +" Description: write-good for PO files + +call ale#linter#Define('po', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/pod/alex.vim b/ale_linters/pod/alex.vim new file mode 100644 index 0000000..5c09bef --- /dev/null +++ b/ale_linters/pod/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for pod files + +call ale#linter#Define('pod', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/pod/proselint.vim b/ale_linters/pod/proselint.vim new file mode 100644 index 0000000..2eb83f5 --- /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/write-good.vim b/ale_linters/pod/write-good.vim new file mode 100644 index 0000000..14ed5c0 --- /dev/null +++ b/ale_linters/pod/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for Pod files + +call ale#linter#Define('pod', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/pony/ponyc.vim b/ale_linters/pony/ponyc.vim new file mode 100644 index 0000000..b332905 --- /dev/null +++ b/ale_linters/pony/ponyc.vim @@ -0,0 +1,21 @@ +" Description: ponyc linter for pony files + +call ale#Set('pony_ponyc_executable', 'ponyc') +call ale#Set('pony_ponyc_options', '--pass paint') + +function! ale_linters#pony#ponyc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'pony_ponyc_executable') +endfunction + +function! ale_linters#pony#ponyc#GetCommand(buffer) abort + return ale#Escape(ale_linters#pony#ponyc#GetExecutable(a:buffer)) + \ . ' ' . ale#Var(a:buffer, 'pony_ponyc_options') +endfunction + +call ale#linter#Define('pony', { +\ 'name': 'ponyc', +\ 'output_stream': 'stderr', +\ 'executable_callback': 'ale_linters#pony#ponyc#GetExecutable', +\ 'command_callback': 'ale_linters#pony#ponyc#GetCommand', +\ 'callback': 'ale#handlers#pony#HandlePonycFormat', +\}) diff --git a/ale_linters/proto/protoc_gen_lint.vim b/ale_linters/proto/protoc_gen_lint.vim new file mode 100644 index 0000000..c8b5c33 --- /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_callback': 'ale_linters#proto#protoc_gen_lint#GetCommand', +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) diff --git a/ale_linters/pug/puglint.vim b/ale_linters/pug/puglint.vim index 338cb05..6c29efe 100644 --- a/ale_linters/pug/puglint.vim +++ b/ale_linters/pug/puglint.vim @@ -1,10 +1,48 @@ " 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', 0) + +function! ale_linters#pug#puglint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'pug_puglint', [ + \ 'node_modules/.bin/pug-lint', + \]) +endfunction + +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:executable = ale_linters#pug#puglint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'pug_puglint_options') + let l:config = s:FindConfig(a:buffer) + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (!empty(l:config) ? ' -c ' . ale#Escape(l:config) : '') + \ . ' -r inline %t' +endfunction + call ale#linter#Define('pug', { \ 'name': 'puglint', -\ 'executable': 'pug-lint', +\ 'executable_callback': 'ale_linters#pug#puglint#GetExecutable', \ 'output_stream': 'stderr', -\ 'command': g:ale#util#stdin_wrapper . ' .pug pug-lint -r inline', -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'command_callback': 'ale_linters#pug#puglint#GetCommand', +\ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/puppet/puppet.vim b/ale_linters/puppet/puppet.vim index 534894c..8beeb61 100644 --- a/ale_linters/puppet/puppet.vim +++ b/ale_linters/puppet/puppet.vim @@ -3,26 +3,16 @@ 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" - let l:pattern = '^Error: .*: \(.\+\) at .\+:\(\d\+\):\(\d\+\)$' + let l:pattern = '^Error: .*: \(.\+\) at .\+\.pp:\(\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 + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:match[2] + 0, - \ 'vcol': 0, \ 'col': l:match[3] + 0, \ 'text': l:match[1], - \ 'type': 'E', - \ 'nr': -1, \}) endfor @@ -33,6 +23,6 @@ call ale#linter#Define('puppet', { \ 'name': 'puppet', \ 'executable': 'puppet', \ 'output_stream': 'stderr', -\ 'command': g:ale#util#stdin_wrapper . ' .pp puppet parser validate --color=false', +\ 'command': 'puppet parser validate --color=false %t', \ 'callback': 'ale_linters#puppet#puppet#Handle', \}) diff --git a/ale_linters/puppet/puppetlint.vim b/ale_linters/puppet/puppetlint.vim index 2d93e53..13da511 100644 --- a/ale_linters/puppet/puppetlint.vim +++ b/ale_linters/puppet/puppetlint.vim @@ -1,8 +1,26 @@ -" Author: Alexander Olofsson +" Author: Alexander Olofsson , Robert Flechtner +" Description: puppet-lint for puppet files + +let g:ale_puppet_puppetlint_executable = +\ get(g:, 'ale_puppet_puppetlint_executable', 'puppet-lint') + +let g:ale_puppet_puppetlint_options = +\ get(g:, 'ale_puppet_puppetlint_options', '--no-autoloader_layout-check') + +function! ale_linters#puppet#puppetlint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'puppet_puppetlint_executable') +endfunction + +function! ale_linters#puppet#puppetlint#GetCommand(buffer) abort + return ale_linters#puppet#puppetlint#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'puppet_puppetlint_options') + \ . ' --log-format "-:%{line}:%{column}: %{kind}: [%{check}] %{message}"' + \ . ' %t' +endfunction call ale#linter#Define('puppet', { \ 'name': 'puppetlint', -\ 'executable': 'puppet-lint', -\ 'command': g:ale#util#stdin_wrapper . ' .pp puppet-lint --no-autoloader_layout-check --log-format "-:%{line}:%{column}: %{kind}: [%{check}] %{message}"', -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'executable_callback': 'ale_linters#puppet#puppetlint#GetExecutable', +\ 'command_callback': 'ale_linters#puppet#puppetlint#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \}) diff --git a/ale_linters/pyrex/cython.vim b/ale_linters/pyrex/cython.vim index 5900431..bd5a447 100644 --- a/ale_linters/pyrex/cython.vim +++ b/ale_linters/pyrex/cython.vim @@ -5,8 +5,6 @@ call ale#linter#Define('pyrex', { \ 'name': 'cython', \ 'output_stream': 'stderr', \ 'executable': 'cython', -\ 'command': g:ale#util#stdin_wrapper -\ . ' .pyx cython --warning-extra -o ' -\ . g:ale#util#nul_file, -\ 'callback': 'ale#handlers#HandleUnixFormatAsError', +\ 'command': 'cython --warning-extra -o ' . g:ale#util#nul_file . ' %t', +\ 'callback': 'ale#handlers#unix#HandleAsError', \}) diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index bd136b2..e7bbcfb 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -4,15 +4,22 @@ let g:ale_python_flake8_executable = \ get(g:, 'ale_python_flake8_executable', 'flake8') -let g:ale_python_flake8_args = -\ get(g:, 'ale_python_flake8_args', '') +" Support an old setting as a fallback. +let s:default_options = get(g:, 'ale_python_flake8_args', '') +let g:ale_python_flake8_options = +\ get(g:, 'ale_python_flake8_options', s:default_options) +let g:ale_python_flake8_use_global = get(g:, 'ale_python_flake8_use_global', 0) -" A map from Python executable paths to semver strings parsed for those -" executables, so we don't have to look up the version number constantly. -let s:version_cache = {} +function! s:UsingModule(buffer) abort + return ale#Var(a:buffer, 'python_flake8_options') =~# ' *-m flake8' +endfunction function! ale_linters#python#flake8#GetExecutable(buffer) abort - return g:ale_python_flake8_executable + 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#VersionCheck(buffer) abort @@ -20,49 +27,112 @@ function! ale_linters#python#flake8#VersionCheck(buffer) abort " If we have previously stored the version number in a cache, then " don't look it up again. - if has_key(s:version_cache, l:executable) + if ale#semver#HasVersion(l:executable) " Returning an empty string skips this command. return '' endif - return ale_linters#python#flake8#GetExecutable(a:buffer) . ' --version' -endfunction + let l:executable = ale#Escape(l:executable) + let l:module_string = s:UsingModule(a:buffer) ? ' -m flake8' : '' -" Get the flake8 version from the output, or the cache. -function! s:GetVersion(buffer, version_output) abort - let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) - let l:version = [] - - " Get the version from the cache. - if has_key(s:version_cache, l:executable) - return s:version_cache[l:executable] - endif - - if !empty(a:version_output) - " Parse the version string, and store it in the cache. - let l:version = ale#semver#Parse(a:version_output[0]) - let s:version_cache[l:executable] = l:version - endif - - return l:version -endfunction - -" flake8 versions 3 and up support the --stdin-display-name argument. -function! s:SupportsDisplayName(version) abort - return !empty(a:version) && ale#semver#GreaterOrEqual(a:version, [3, 0, 0]) + return l:executable . l:module_string . ' --version' endfunction function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort - let l:version = s:GetVersion(a:buffer, a:version_output) + let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) + let l:version = ale#semver#GetVersion(l:executable, a:version_output) " Only include the --stdin-display-name argument if we can parse the " flake8 version, and it is recent enough to support it. - let l:display_name_args = s:SupportsDisplayName(l:version) - \ ? '--stdin-display-name %s' + let l:display_name_args = ale#semver#GTE(l:version, [3, 0, 0]) + \ ? ' --stdin-display-name %s' \ : '' - return ale_linters#python#flake8#GetExecutable(a:buffer) - \ . ' ' . g:ale_python_flake8_args . ' ' . l:display_name_args . ' -' + let l:options = ale#Var(a:buffer, 'python_flake8_options') + + return ale#Escape(l:executable) + \ . (!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 + 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 + + " Matches patterns line the following: + " + " Matches patterns line the following: + " + " stdin:6:6: E111 indentation is not a multiple of four + let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):?(\d+)?: ([[:alnum:]]+) (.*)$' + let l:output = [] + + 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, + \ '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', { @@ -70,7 +140,7 @@ call ale#linter#Define('python', { \ 'executable_callback': 'ale_linters#python#flake8#GetExecutable', \ 'command_chain': [ \ {'callback': 'ale_linters#python#flake8#VersionCheck'}, -\ {'callback': 'ale_linters#python#flake8#GetCommand'}, +\ {'callback': 'ale_linters#python#flake8#GetCommand', 'output_stream': 'both'}, \ ], -\ 'callback': 'ale#handlers#HandlePEP8Format', +\ 'callback': 'ale_linters#python#flake8#Handle', \}) diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim index 1509d9e..c1c9174 100644 --- a/ale_linters/python/mypy.vim +++ b/ale_linters/python/mypy.vim @@ -1,24 +1,39 @@ " Author: Keith Smiley , w0rp " Description: mypy support for optional python typechecking -let g:ale_python_mypy_options = get(g:, 'ale_python_mypy_options', '') +call ale#Set('python_mypy_executable', 'mypy') +call ale#Set('python_mypy_ignore_invalid_syntax', 0) +call ale#Set('python_mypy_options', '') +call ale#Set('python_mypy_use_global', 0) -function! g:ale_linters#python#mypy#GetCommand(buffer) abort - let l:automatic_stubs_dir = ale#util#FindNearestDirectory(a:buffer, 'stubs') - " TODO: Add Windows support - let l:automatic_stubs_command = (has('unix') && !empty(l:automatic_stubs_dir)) - \ ? 'MYPYPATH=' . l:automatic_stubs_dir . ' ' - \ : '' - - return l:automatic_stubs_command - \ . g:ale#util#stdin_wrapper - \ . ' .py mypy --show-column-numbers ' - \ . g:ale_python_mypy_options +function! ale_linters#python#mypy#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'python_mypy', ['mypy']) endfunction -let s:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+' +" The directory to change to before running mypy +function! s:GetDir(buffer) abort + let l:project_root = ale#python#FindProjectRoot(a:buffer) -function! g:ale_linters#python#mypy#Handle(buffer, lines) abort + return !empty(l:project_root) + \ ? l:project_root + \ : expand('#' . a:buffer . ':p:h') +endfunction + +function! ale_linters#python#mypy#GetCommand(buffer) abort + let l:dir = s:GetDir(a:buffer) + let l:executable = ale_linters#python#mypy#GetExecutable(a:buffer) + + " We have to always switch to an explicit directory for a command so + " we can know with certainty the base path for the 'filename' keys below. + return ale#path#CdString(l:dir) + \ . ale#Escape(l:executable) + \ . ' --show-column-numbers ' + \ . ale#Var(a:buffer, 'python_mypy_options') + \ . ' --shadow-file %s %t %s' +endfunction + +function! ale_linters#python#mypy#Handle(buffer, lines) abort + let l:dir = s:GetDir(a:buffer) " Look for lines like the following: " " file.py:4: error: No library stub file for module 'django.db' @@ -26,39 +41,31 @@ function! g:ale_linters#python#mypy#Handle(buffer, lines) abort " Lines like these should be ignored below: " " file.py:4: note: (Stub files are from https://github.com/python/typeshed) - let l:pattern = '^' . s:path_pattern . ':\(\d\+\):\?\(\d\+\)\?: \([^:]\+\): \(.\+\)$' + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?: (error|warning): (.+)$' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - - if l:match[4] =~# 'Stub files are from' - " The lines telling us where to get stub files from make it so - " we can't read the actual errors, so exclude them. + 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, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:match[4], - \ 'type': l:match[3] =~# 'error' ? 'E' : 'W', - \ 'nr': -1, + \ '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', + \ 'text': l:match[5], \}) endfor return l:output endfunction -call g:ale#linter#Define('python', { +call ale#linter#Define('python', { \ 'name': 'mypy', -\ 'executable': 'mypy', +\ 'executable_callback': 'ale_linters#python#mypy#GetExecutable', \ 'command_callback': 'ale_linters#python#mypy#GetCommand', \ 'callback': 'ale_linters#python#mypy#Handle', \}) diff --git a/ale_linters/python/prospector.vim b/ale_linters/python/prospector.vim new file mode 100644 index 0000000..66af598 --- /dev/null +++ b/ale_linters/python/prospector.vim @@ -0,0 +1,82 @@ +" Author: chocoelho +" Description: prospector linter python files + +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', 0) + +function! ale_linters#python#prospector#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'python_prospector', ['prospector']) +endfunction + +function! ale_linters#python#prospector#GetCommand(buffer) abort + return ale#Escape(ale_linters#python#prospector#GetExecutable(a:buffer)) + \ . ' ' . 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 = [] + + 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_callback': 'ale_linters#python#prospector#GetExecutable', +\ 'command_callback': 'ale_linters#python#prospector#GetCommand', +\ 'callback': 'ale_linters#python#prospector#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/python/pycodestyle.vim b/ale_linters/python/pycodestyle.vim new file mode 100644 index 0000000..19f05a5 --- /dev/null +++ b/ale_linters/python/pycodestyle.vim @@ -0,0 +1,63 @@ +" 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', 0) + +function! ale_linters#python#pycodestyle#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'python_pycodestyle', ['pycodestyle']) +endfunction + +function! ale_linters#python#pycodestyle#GetCommand(buffer) abort + return ale#Escape(ale_linters#python#pycodestyle#GetExecutable(a:buffer)) + \ . ' ' + \ . ale#Var(a:buffer, 'python_pycodestyle_options') + \ . ' -' +endfunction + +function! ale_linters#python#pycodestyle#Handle(buffer, lines) abort + let l:pattern = '\v^(\S*):(\d*):(\d*): ([EW]\d+) (.*)$' + let l:output = [] + + " lines are formatted as follows: + " file.py:21:26: W291 trailing whitespace + for l:match in ale#util#GetMatches(a:lines, l:pattern) + 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_callback': 'ale_linters#python#pycodestyle#GetExecutable', +\ 'command_callback': 'ale_linters#python#pycodestyle#GetCommand', +\ 'callback': 'ale_linters#python#pycodestyle#Handle', +\}) diff --git a/ale_linters/python/pyflakes.vim b/ale_linters/python/pyflakes.vim new file mode 100644 index 0000000..b4a0b5f --- /dev/null +++ b/ale_linters/python/pyflakes.vim @@ -0,0 +1,38 @@ +" Author: w0rp +" Description: pyflakes for python files + +call ale#Set('python_pyflakes_executable', 'pyflakes') +call ale#Set('python_pyflakes_use_global', 0) + +function! ale_linters#python#pyflakes#GetExecutable(buffer) abort + 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) + + return ale#Escape(l:executable) . ' %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_callback': 'ale_linters#python#pyflakes#GetExecutable', +\ 'command_callback': 'ale_linters#python#pyflakes#GetCommand', +\ 'callback': 'ale_linters#python#pyflakes#Handle', +\ 'output_stream': 'both', +\}) diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim index f0cd5bd..e3e6624 100644 --- a/ale_linters/python/pylint.vim +++ b/ale_linters/python/pylint.vim @@ -7,20 +7,57 @@ let g:ale_python_pylint_executable = let g:ale_python_pylint_options = \ get(g:, 'ale_python_pylint_options', '') +let g:ale_python_pylint_use_global = get(g:, 'ale_python_pylint_use_global', 0) + function! ale_linters#python#pylint#GetExecutable(buffer) abort - return g:ale_python_pylint_executable + return ale#python#FindExecutable(a:buffer, 'python_pylint', ['pylint']) endfunction function! ale_linters#python#pylint#GetCommand(buffer) abort - return g:ale#util#stdin_wrapper . ' .py ' - \ . ale_linters#python#pylint#GetExecutable(a:buffer) - \ . ' ' . g:ale_python_pylint_options - \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} {msg}" --reports n' + return ale#Escape(ale_linters#python#pylint#GetExecutable(a:buffer)) + \ . ' ' . ale#Var(a:buffer, 'python_pylint_options') + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n' + \ . ' %s' +endfunction + +function! ale_linters#python#pylint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " test.py:4:4: W0101 (unreachable) Unreachable code + let l:pattern = '\v^[^:]+:(\d+):(\d+): ([[:alnum:]]+) \(([^(]*)\) (.*)$' + let l:output = [] + + 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 + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 1, + \ 'text': l:match[5], + \ 'code': l:match[4], + \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', + \}) + endfor + + return l:output endfunction call ale#linter#Define('python', { \ 'name': 'pylint', \ 'executable_callback': 'ale_linters#python#pylint#GetExecutable', \ 'command_callback': 'ale_linters#python#pylint#GetCommand', -\ 'callback': 'ale#handlers#HandlePEP8Format', +\ 'callback': 'ale_linters#python#pylint#Handle', +\ 'lint_file': 1, \}) diff --git a/ale_linters/python/pyls.vim b/ale_linters/python/pyls.vim new file mode 100644 index 0000000..9888853 --- /dev/null +++ b/ale_linters/python/pyls.vim @@ -0,0 +1,28 @@ +" Author: aurieh +" Description: A language server for Python + +call ale#Set('python_pyls_executable', 'pyls') +call ale#Set('python_pyls_use_global', 0) + +function! ale_linters#python#pyls#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'python_pyls', ['pyls']) +endfunction + +function! ale_linters#python#pyls#GetCommand(buffer) abort + let l:executable = ale_linters#python#pyls#GetExecutable(a:buffer) + + return ale#Escape(l:executable) +endfunction + +function! ale_linters#python#pyls#GetLanguage(buffer) abort + return 'python' +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pyls', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#python#pyls#GetExecutable', +\ 'command_callback': 'ale_linters#python#pyls#GetCommand', +\ 'language_callback': 'ale_linters#python#pyls#GetLanguage', +\ 'project_root_callback': 'ale#python#FindProjectRoot', +\}) diff --git a/ale_linters/r/lintr.vim b/ale_linters/r/lintr.vim new file mode 100644 index 0000000..51e5c56 --- /dev/null +++ b/ale_linters/r/lintr.vim @@ -0,0 +1,35 @@ +" Author: Michel Lang , w0rp , +" Fenner Macrae +" 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 ale#path#BufferCdString(a:buffer) + \ . 'Rscript -e ' + \ . ale#Escape(l:cmd_string) . ' %t' +endfunction + +call ale#linter#Define('r', { +\ 'name': 'lintr', +\ 'executable': 'Rscript', +\ 'command_callback': 'ale_linters#r#lintr#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\ 'output_stream': 'both', +\}) diff --git a/ale_linters/reason/merlin.vim b/ale_linters/reason/merlin.vim new file mode 100644 index 0000000..7bef7df --- /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 0000000..b2cd5f7 --- /dev/null +++ b/ale_linters/reason/ols.vim @@ -0,0 +1,14 @@ +" 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', 0) + +call ale#linter#Define('reason', { +\ 'name': 'ols', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale#handlers#ols#GetExecutable', +\ 'command_callback': 'ale#handlers#ols#GetCommand', +\ 'language_callback': 'ale#handlers#ols#GetLanguage', +\ 'project_root_callback': 'ale#handlers#ols#GetProjectRoot', +\}) diff --git a/ale_linters/review/redpen.vim b/ale_linters/review/redpen.vim new file mode 100644 index 0000000..0006cab --- /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/rst/alex.vim b/ale_linters/rst/alex.vim new file mode 100644 index 0000000..e637eae --- /dev/null +++ b/ale_linters/rst/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for rst files + +call ale#linter#Define('rst', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/rst/proselint.vim b/ale_linters/rst/proselint.vim new file mode 100644 index 0000000..018347a --- /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 0000000..ac966c5 --- /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 0000000..b660627 --- /dev/null +++ b/ale_linters/rst/rstcheck.vim @@ -0,0 +1,37 @@ +" 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 + +function! ale_linters#rst#rstcheck#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) + \ . 'rstcheck' + \ . ' %t' +endfunction + + +call ale#linter#Define('rst', { +\ 'name': 'rstcheck', +\ 'executable': 'rstcheck', +\ 'command_callback': 'ale_linters#rst#rstcheck#GetCommand', +\ 'callback': 'ale_linters#rst#rstcheck#Handle', +\ 'output_stream': 'both', +\}) diff --git a/ale_linters/rst/vale.vim b/ale_linters/rst/vale.vim new file mode 100644 index 0000000..2e654dc --- /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/write-good.vim b/ale_linters/rst/write-good.vim new file mode 100644 index 0000000..12137db --- /dev/null +++ b/ale_linters/rst/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for reStructuredText files + +call ale#linter#Define('rst', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/ruby/brakeman.vim b/ale_linters/ruby/brakeman.vim new file mode 100644 index 0000000..85cfc18 --- /dev/null +++ b/ale_linters/ruby/brakeman.vim @@ -0,0 +1,47 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Brakeman, a static analyzer for Rails security + +let g:ale_ruby_brakeman_options = +\ get(g:, 'ale_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 + + return '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': 'brakeman', +\ 'command_callback': 'ale_linters#ruby#brakeman#GetCommand', +\ 'callback': 'ale_linters#ruby#brakeman#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/ruby/rails_best_practices.vim b/ale_linters/ruby/rails_best_practices.vim new file mode 100644 index 0000000..107753c --- /dev/null +++ b/ale_linters/ruby/rails_best_practices.vim @@ -0,0 +1,53 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: rails_best_practices, a code metric tool for rails projects + +let g:ale_ruby_rails_best_practices_options = +\ get(g:, 'ale_ruby_rails_best_practices_options', '') + +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:executable = ale#handlers#rails_best_practices#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? 'bundle$' + \ ? ' exec rails_best_practices' + \ : '' + + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + + if l:rails_root is? '' + return '' + endif + + let l:output_file = ale#Has('win32') ? '%t ' : '/dev/stdout ' + let l:cat_file = ale#Has('win32') ? '; type %t' : '' + + return ale#Escape(l:executable) . l:exec_args + \ . ' --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_callback': 'ale#handlers#rails_best_practices#GetExecutable', +\ 'command_callback': '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 0000000..a11b9cf --- /dev/null +++ b/ale_linters/ruby/reek.vim @@ -0,0 +1,45 @@ +" 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) + +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 + +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, '[' . a:error.wiki_link . ']') + endif + + return join(l:parts, ' ') +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'reek', +\ 'executable': 'reek', +\ 'command': 'reek -f json --no-progress --no-color', +\ 'callback': 'ale_linters#ruby#reek#Handle', +\}) diff --git a/ale_linters/ruby/rubocop.vim b/ale_linters/ruby/rubocop.vim index 87fc7b7..777f457 100644 --- a/ale_linters/ruby/rubocop.vim +++ b/ale_linters/ruby/rubocop.vim @@ -1,54 +1,61 @@ -" Author: ynonp - https://github.com/ynonp -" Description: rubocop for Ruby files +" Author: ynonp - https://github.com/ynonp, Eddie Lebow https://github.com/elebow +" Description: RuboCop, a code style analyzer for Ruby files + +function! ale_linters#ruby#rubocop#GetCommand(buffer) abort + let l:executable = ale#handlers#rubocop#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? 'bundle$' + \ ? ' exec rubocop' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . ' --format json --force-exclusion ' + \ . ale#Var(a:buffer, 'ruby_rubocop_options') + \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) +endfunction function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort - " Matches patterns line the following: - " - " :83:29: C: Prefer single-quoted strings when you don't - " need string interpolation or special symbols. - let l:pattern = '\v:(\d+):(\d+): (.): (.+)' + 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:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - - let l:text = l:match[4] - let l:type = l:match[3] - - " vcol is Needed to indicate that the column is a character. + for l:error in l:errors['files'][0]['offenses'] + let l:start_col = l:error['location']['column'] + 0 call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:text, - \ 'type': index(['C', 'E'], l:type) != -1 ? 'E' : 'W', - \ 'nr': -1, + \ 'lnum': l:error['location']['line'] + 0, + \ 'col': l:start_col, + \ 'end_col': l:start_col + l:error['location']['length'] - 1, + \ 'code': l:error['cop_name'], + \ 'text': l:error['message'], + \ 'type': ale_linters#ruby#rubocop#GetType(l:error['severity']), \}) endfor return l:output endfunction -function! ale_linters#ruby#rubocop#GetCommand(buffer) abort - return 'rubocop --format emacs --force-exclusion ' . - \ g:ale_ruby_rubocop_options . - \ ' --stdin ' . bufname(a:buffer) -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 -" Set this option to change Rubocop options. -if !exists('g:ale_ruby_rubocop_options') - " let g:ale_ruby_rubocop_options = '--lint' - let g:ale_ruby_rubocop_options = '' -endif + return 'E' +endfunction call ale#linter#Define('ruby', { \ 'name': 'rubocop', -\ 'executable': 'rubocop', +\ 'executable_callback': 'ale#handlers#rubocop#GetExecutable', \ 'command_callback': 'ale_linters#ruby#rubocop#GetCommand', \ 'callback': 'ale_linters#ruby#rubocop#Handle', \}) diff --git a/ale_linters/ruby/ruby.vim b/ale_linters/ruby/ruby.vim new file mode 100644 index 0000000..1aa8885 --- /dev/null +++ b/ale_linters/ruby/ruby.vim @@ -0,0 +1,22 @@ +" Author: Brandon Roehl - https://github.com/BrandonRoehl +" Description: Ruby MRI for Ruby files + +call ale#Set('ruby_ruby_executable', 'ruby') + +function! ale_linters#ruby#ruby#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'ruby_ruby_executable') +endfunction + +function! ale_linters#ruby#ruby#GetCommand(buffer) abort + let l:executable = ale_linters#ruby#ruby#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' -w -c -T1 %t' +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'ruby', +\ 'executable_callback': 'ale_linters#ruby#ruby#GetExecutable', +\ 'command_callback': 'ale_linters#ruby#ruby#GetCommand', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim index 7f821d2..09f41bb 100644 --- a/ale_linters/rust/cargo.vim +++ b/ale_linters/rust/cargo.vim @@ -1,8 +1,14 @@ -" Author: Daniel Schemala +" 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_default_feature_behavior', 'default') +call ale#Set('rust_cargo_include_features', '') + function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort - if ale#util#FindNearestFile(a:bufnr, 'Cargo.toml') !=# '' + if ale#path#FindNearestFile(a:bufnr, 'Cargo.toml') isnot# '' return 'cargo' else " if there is no Cargo.toml file, we don't use cargo even if it exists, @@ -11,10 +17,52 @@ function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort endif endfunction +function! ale_linters#rust#cargo#VersionCheck(buffer) abort + return !ale#semver#HasVersion('cargo') + \ ? 'cargo --version' + \ : '' +endfunction + +function! ale_linters#rust#cargo#GetCommand(buffer, version_output) abort + let l:version = ale#semver#GetVersion('cargo', a:version_output) + + let l:use_check = ale#Var(a:buffer, 'rust_cargo_use_check') + \ && ale#semver#GTE(l:version, [0, 17, 0]) + let l:use_all_targets = l:use_check + \ && ale#Var(a:buffer, 'rust_cargo_check_all_targets') + \ && ale#semver#GTE(l:version, [0, 22, 0]) + + 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 + + return 'cargo ' + \ . (l:use_check ? 'check' : 'build') + \ . (l:use_all_targets ? ' --all-targets' : '') + \ . ' --frozen --message-format=json -q' + \ . l:default_feature + \ . l:include_features +endfunction + call ale#linter#Define('rust', { \ 'name': 'cargo', \ 'executable_callback': 'ale_linters#rust#cargo#GetCargoExecutable', -\ 'command': 'cargo build --message-format=json -q', +\ 'command_chain': [ +\ {'callback': 'ale_linters#rust#cargo#VersionCheck'}, +\ {'callback': 'ale_linters#rust#cargo#GetCommand'}, +\ ], \ 'callback': 'ale#handlers#rust#HandleRustErrors', -\ 'output_stream': 'stdout', +\ 'output_stream': 'both', +\ 'lint_file': 1, \}) diff --git a/ale_linters/rust/rls.vim b/ale_linters/rust/rls.vim new file mode 100644 index 0000000..832fe3e --- /dev/null +++ b/ale_linters/rust/rls.vim @@ -0,0 +1,36 @@ +" Author: w0rp +" Description: A language server for Rust + +call ale#Set('rust_rls_executable', 'rls') +call ale#Set('rust_rls_toolchain', 'nightly') + +function! ale_linters#rust#rls#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'rust_rls_executable') +endfunction + +function! ale_linters#rust#rls#GetCommand(buffer) abort + let l:executable = ale_linters#rust#rls#GetExecutable(a:buffer) + let l:toolchain = ale#Var(a:buffer, 'rust_rls_toolchain') + + return ale#Escape(l:executable) + \ . ' +' . ale#Escape(l:toolchain) +endfunction + +function! ale_linters#rust#rls#GetLanguage(buffer) abort + return 'rust' +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', +\ 'executable_callback': 'ale_linters#rust#rls#GetExecutable', +\ 'command_callback': 'ale_linters#rust#rls#GetCommand', +\ 'language_callback': 'ale_linters#rust#rls#GetLanguage', +\ 'project_root_callback': 'ale_linters#rust#rls#GetProjectRoot', +\}) diff --git a/ale_linters/rust/rustc.vim b/ale_linters/rust/rustc.vim index 1d080b9..3cd401b 100644 --- a/ale_linters/rust/rustc.vim +++ b/ale_linters/rust/rustc.vim @@ -1,21 +1,27 @@ " Author: Daniel Schemala " Description: rustc for rust files -function! ale_linters#rust#rustc#RustcCommand(buffer_number) abort +call ale#Set('rust_rustc_options', '-Z no-trans') + +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#util#FindNearestFile(a:buffer_number, 'Cargo.toml') + let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') - if l:cargo_file !=# '' - let l:project_root = fnamemodify(l:cargo_file, ':h') - let l:dependencies = '-L ' . l:project_root . '/target/debug/deps -L ' . - \ l:project_root . '/target/release/deps' + 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 - return 'rustc --error-format=json -Z no-trans ' . l:dependencies . ' -' + 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', { diff --git a/ale_linters/sass/sasslint.vim b/ale_linters/sass/sasslint.vim index 8c497ed..bbe7125 100644 --- a/ale_linters/sass/sasslint.vim +++ b/ale_linters/sass/sasslint.vim @@ -3,6 +3,6 @@ call ale#linter#Define('sass', { \ 'name': 'sasslint', \ 'executable': 'sass-lint', -\ 'command': g:ale#util#stdin_wrapper . ' .sass sass-lint -v -q -f compact', -\ 'callback': 'ale#handlers#HandleCSSLintFormat', +\ 'command': 'sass-lint -v -q -f compact %t', +\ 'callback': 'ale#handlers#css#HandleCSSLintFormat', \}) diff --git a/ale_linters/sass/stylelint.vim b/ale_linters/sass/stylelint.vim index 0d85829..98c3725 100644 --- a/ale_linters/sass/stylelint.vim +++ b/ale_linters/sass/stylelint.vim @@ -1,21 +1,12 @@ " Author: diartyz -let g:ale_sass_stylelint_executable = -\ get(g:, 'ale_sass_stylelint_executable', 'stylelint') - -let g:ale_sass_stylelint_use_global = -\ get(g:, 'ale_sass_stylelint_use_global', 0) +call ale#Set('sass_stylelint_executable', 'stylelint') +call ale#Set('sass_stylelint_use_global', 0) function! ale_linters#sass#stylelint#GetExecutable(buffer) abort - if g:ale_sass_stylelint_use_global - return g:ale_sass_stylelint_executable - endif - - return ale#util#ResolveLocalPath( - \ a:buffer, + return ale#node#FindExecutable(a:buffer, 'sass_stylelint', [ \ 'node_modules/.bin/stylelint', - \ g:ale_sass_stylelint_executable - \) + \]) endfunction function! ale_linters#sass#stylelint#GetCommand(buffer) abort @@ -27,5 +18,5 @@ call ale#linter#Define('sass', { \ 'name': 'stylelint', \ 'executable_callback': 'ale_linters#sass#stylelint#GetExecutable', \ 'command_callback': 'ale_linters#sass#stylelint#GetCommand', -\ 'callback': 'ale#handlers#HandleStyleLintFormat', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/scala/scalac.vim b/ale_linters/scala/scalac.vim index c2e7543..584aee7 100644 --- a/ale_linters/scala/scalac.vim +++ b/ale_linters/scala/scalac.vim @@ -1,7 +1,26 @@ -" vim: set et: -" Author: Zoltan Kalmar - https://github.com/kalmiz +" Author: Zoltan Kalmar - https://github.com/kalmiz, +" w0rp " Description: Basic scala support using scalac +function! ale_linters#scala#scalac#GetExecutable(buffer) abort + if index(split(getbufvar(a:buffer, '&filetype'), '\.'), 'sbt') >= 0 + " Don't check sbt files with scalac. + return '' + endif + + return 'scalac' +endfunction + +function! ale_linters#scala#scalac#GetCommand(buffer) abort + let l:executable = ale_linters#scala#scalac#GetExecutable(a:buffer) + + if empty(l:executable) + return '' + endif + + return ale#Escape(l:executable) . ' -Ystop-after:parser %t' +endfunction + function! ale_linters#scala#scalac#Handle(buffer, lines) abort " Matches patterns line the following: " @@ -19,24 +38,18 @@ function! ale_linters#scala#scalac#Handle(buffer, lines) abort endif let l:text = l:match[3] - let l:type = l:match[2] ==# 'error' ? 'E' : 'W' + let l:type = l:match[2] is# 'error' ? 'E' : 'W' let l:col = 0 + if l:ln + 1 < len(a:lines) let l:col = stridx(a:lines[l:ln + 1], '^') - if l:col == -1 - let l:col = 0 - endif 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:col + 1, \ 'text': l:text, \ 'type': l:type, - \ 'nr': -1, \}) endfor @@ -45,8 +58,8 @@ endfunction call ale#linter#Define('scala', { \ 'name': 'scalac', -\ 'executable': 'scalac', -\ 'output_stream': 'stderr', -\ 'command': g:ale#util#stdin_wrapper . ' .scala scalac -Ystop-after:parser', +\ 'executable_callback': 'ale_linters#scala#scalac#GetExecutable', +\ 'command_callback': 'ale_linters#scala#scalac#GetCommand', \ 'callback': 'ale_linters#scala#scalac#Handle', +\ 'output_stream': 'stderr', \}) diff --git a/ale_linters/scala/scalastyle.vim b/ale_linters/scala/scalastyle.vim new file mode 100644 index 0000000..f78fd74 --- /dev/null +++ b/ale_linters/scala/scalastyle.vim @@ -0,0 +1,94 @@ +" Author: Kevin Kays - https://github.com/okkays +" Description: Support for the scalastyle checker. + +let g:ale_scala_scalastyle_options = +\ get(g:, 'ale_scala_scalastyle_options', '') + +let g:ale_scalastyle_config_loc = +\ 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 = get(g:, 'ale_scalastyle_config_loc', '') + endif + + " Build the command using the config file and additional options. + let l:command = 'scalastyle' + + if !empty(l:scalastyle_config) + let l:command .= ' --config ' . ale#Escape(l:scalastyle_config) + endif + + if !empty(g:ale_scala_scalastyle_options) + let l:command .= ' ' . g:ale_scala_scalastyle_options + endif + + let l:command .= ' %t' + + return l:command +endfunction + +call ale#linter#Define('scala', { +\ 'name': 'scalastyle', +\ 'executable': 'scalastyle', +\ 'output_stream': 'stdout', +\ 'command_callback': 'ale_linters#scala#scalastyle#GetCommand', +\ 'callback': 'ale_linters#scala#scalastyle#Handle', +\}) diff --git a/ale_linters/scss/sasslint.vim b/ale_linters/scss/sasslint.vim index 0b78f3b..bd01646 100644 --- a/ale_linters/scss/sasslint.vim +++ b/ale_linters/scss/sasslint.vim @@ -3,6 +3,6 @@ call ale#linter#Define('scss', { \ 'name': 'sasslint', \ 'executable': 'sass-lint', -\ 'command': g:ale#util#stdin_wrapper . ' .scss sass-lint -v -q -f compact', -\ 'callback': 'ale#handlers#HandleCSSLintFormat', +\ 'command': 'sass-lint -v -q -f compact %t', +\ 'callback': 'ale#handlers#css#HandleCSSLintFormat', \}) diff --git a/ale_linters/scss/scsslint.vim b/ale_linters/scss/scsslint.vim index 1ee0662..7ce5724 100644 --- a/ale_linters/scss/scsslint.vim +++ b/ale_linters/scss/scsslint.vim @@ -8,27 +8,18 @@ function! ale_linters#scss#scsslint#Handle(buffer, lines) abort let l:pattern = '^.*:\(\d\+\):\(\d*\) \[\([^\]]\+\)\] \(.\+\)$' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 + 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 - if !g:ale_warn_about_trailing_whitespace && l:match[4] =~# '^TrailingWhitespace' - " Skip trailing whitespace warnings if that option is on. - 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': l:match[3] ==# 'E' ? 'E' : 'W', - \ 'nr': -1, + \ 'type': l:match[3] is# 'E' ? 'E' : 'W', \}) endfor diff --git a/ale_linters/scss/stylelint.vim b/ale_linters/scss/stylelint.vim index 2f5da7c..00189a8 100644 --- a/ale_linters/scss/stylelint.vim +++ b/ale_linters/scss/stylelint.vim @@ -1,21 +1,12 @@ " Author: diartyz -let g:ale_scss_stylelint_executable = -\ get(g:, 'ale_scss_stylelint_executable', 'stylelint') - -let g:ale_scss_stylelint_use_global = -\ get(g:, 'ale_scss_stylelint_use_global', 0) +call ale#Set('scss_stylelint_executable', 'stylelint') +call ale#Set('scss_stylelint_use_global', 0) function! ale_linters#scss#stylelint#GetExecutable(buffer) abort - if g:ale_scss_stylelint_use_global - return g:ale_scss_stylelint_executable - endif - - return ale#util#ResolveLocalPath( - \ a:buffer, + return ale#node#FindExecutable(a:buffer, 'scss_stylelint', [ \ 'node_modules/.bin/stylelint', - \ g:ale_scss_stylelint_executable - \) + \]) endfunction function! ale_linters#scss#stylelint#GetCommand(buffer) abort @@ -27,5 +18,5 @@ call ale#linter#Define('scss', { \ 'name': 'stylelint', \ 'executable_callback': 'ale_linters#scss#stylelint#GetExecutable', \ 'command_callback': 'ale_linters#scss#stylelint#GetCommand', -\ 'callback': 'ale#handlers#HandleStyleLintFormat', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', \}) diff --git a/ale_linters/sh/shell.vim b/ale_linters/sh/shell.vim index 786cc0e..cf5e4e6 100644 --- a/ale_linters/sh/shell.vim +++ b/ale_linters/sh/shell.vim @@ -1,36 +1,33 @@ " Author: w0rp " Description: Lints sh files using bash -n +" Backwards compatibility +if exists('g:ale_linters_sh_shell_default_shell') + let g:ale_sh_shell_default_shell = g:ale_linters_sh_shell_default_shell +endif + " This option can be changed to change the default shell when the shell " cannot be taken from the hashbang line. -if !exists('g:ale_linters_sh_shell_default_shell') - let g:ale_linters_sh_shell_default_shell = fnamemodify($SHELL, ':t') +if !exists('g:ale_sh_shell_default_shell') + let g:ale_sh_shell_default_shell = fnamemodify($SHELL, ':t') - if g:ale_linters_sh_shell_default_shell ==# '' - let g:ale_linters_sh_shell_default_shell = 'bash' + if g:ale_sh_shell_default_shell is# '' || g:ale_sh_shell_default_shell is# 'fish' + let g:ale_sh_shell_default_shell = 'bash' endif endif function! ale_linters#sh#shell#GetExecutable(buffer) abort - let l:banglines = getbufline(a:buffer, 1) + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) - " Take the shell executable from the hashbang, if we can. - if len(l:banglines) == 1 && l:banglines[0] =~# '^#!' - " Remove options like -e, etc. - let l:line = substitute(l:banglines[0], '--\?[a-zA-Z0-9]\+', '', 'g') - - for l:possible_shell in ['bash', 'tcsh', 'csh', 'zsh', 'sh'] - if l:line =~# l:possible_shell . '\s*$' - return l:possible_shell - endif - endfor + if !empty(l:shell_type) + return l:shell_type endif - return g:ale_linters_sh_shell_default_shell + 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' + return ale_linters#sh#shell#GetExecutable(a:buffer) . ' -n %t' endfunction function! ale_linters#sh#shell#Handle(buffer, lines) abort @@ -38,30 +35,13 @@ function! ale_linters#sh#shell#Handle(buffer, lines) abort " " bash: line 13: syntax error near unexpected token `d' " sh: 11: Syntax error: "(" unexpected - let l:pattern = '^[^:]\+: \%(\w\+ \|\)\(\d\+\): \(.\+\)' + let l:pattern = '\v(line |: ?)(\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 l:line = l:match[1] + 0 - let l:column = 1 - let l:text = l:match[2] - let l:type = 'E' - - " vcol is Needed to indicate that the column is a character. + for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': l:column, - \ 'text': l:text, - \ 'type': l:type, - \ 'nr': -1, + \ 'lnum': str2nr(l:match[2]), + \ 'text': l:match[3], \}) endfor diff --git a/ale_linters/sh/shellcheck.vim b/ale_linters/sh/shellcheck.vim index 735a0cd..ac66892 100644 --- a/ale_linters/sh/shellcheck.vim +++ b/ale_linters/sh/shellcheck.vim @@ -2,39 +2,121 @@ " Description: This file adds support for using the shellcheck linter with " shell scripts. -" This global variable can be set with a string of comma-seperated error +" This global variable can be set with a string of comma-separated error " codes to exclude from shellcheck. For example: " -" let g:ale_linters_sh_shellcheck_exclusions = 'SC2002,SC2004' -if !exists('g:ale_linters_sh_shellcheck_exclusions') - let g:ale_linters_sh_shellcheck_exclusions = '' -endif +" let g:ale_sh_shellcheck_exclusions = 'SC2002,SC2004' +let g:ale_sh_shellcheck_exclusions = +\ get(g:, 'ale_sh_shellcheck_exclusions', get(g:, 'ale_linters_sh_shellcheck_exclusions', '')) -if g:ale_linters_sh_shellcheck_exclusions !=# '' - let s:exclude_option = '-e ' . g:ale_linters_sh_shellcheck_exclusions -else - let s:exclude_option = '' -endif +let g:ale_sh_shellcheck_executable = +\ get(g:, 'ale_sh_shellcheck_executable', 'shellcheck') -function! s:GetDialectArgument() abort - if exists('b:is_bash') && b:is_bash - return '-s bash' - elseif exists('b:is_sh') && b:is_sh - return '-s sh' - elseif exists('b:is_kornshell') && b:is_kornshell - return '-s ksh' +let g:ale_sh_shellcheck_use_global = +\ get(g:, 'ale_sh_shellcheck_use_global', 0) + +let g:ale_sh_shellcheck_options = +\ get(g:, 'ale_sh_shellcheck_options', '') + +function! ale_linters#sh#shellcheck#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'sh_shellcheck', [ + \ 'node_modules/.bin/shellcheck', + \]) +endfunction + +function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) + + 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 + + " If there's no hashbang, try using Vim's buffer variables. + if getbufvar(a:buffer, 'is_bash', 0) + return 'bash' + elseif getbufvar(a:buffer, 'is_sh', 0) + return 'sh' + elseif getbufvar(a:buffer, 'is_kornshell', 0) + return 'ksh' endif return '' endfunction -function! ale_linters#sh#shellcheck#GetCommand(buffer) abort - return 'shellcheck ' . s:exclude_option . ' ' . s:GetDialectArgument() . ' -f gcc -' +function! ale_linters#sh#shellcheck#VersionCheck(buffer) abort + let l:executable = ale_linters#sh#shellcheck#GetExecutable(a:buffer) + + " Don't check the version again if we've already cached it. + return !ale#semver#HasVersion(l:executable) + \ ? ale#Escape(l:executable) . ' --version' + \ : '' +endfunction + +function! ale_linters#sh#shellcheck#GetCommand(buffer, version_output) abort + let l:executable = ale_linters#sh#shellcheck#GetExecutable(a:buffer) + let l:version = ale#semver#GetVersion(l:executable, a:version_output) + + let l:options = ale#Var(a:buffer, 'sh_shellcheck_options') + let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions') + let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer) + let l:external_option = ale#semver#GTE(l:version, [0, 4, 0]) ? ' -x' : '' + + return ale#path#BufferCdString(a:buffer) + \ . ale#Escape(l:executable) + \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '') + \ . l:external_option + \ . ' -f gcc -' +endfunction + +function! ale_linters#sh#shellcheck#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) + 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], + \} + + 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 call ale#linter#Define('sh', { \ 'name': 'shellcheck', -\ 'executable': 'shellcheck', -\ 'command_callback': 'ale_linters#sh#shellcheck#GetCommand', -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'executable_callback': 'ale_linters#sh#shellcheck#GetExecutable', +\ 'command_chain': [ +\ {'callback': 'ale_linters#sh#shellcheck#VersionCheck'}, +\ {'callback': 'ale_linters#sh#shellcheck#GetCommand'}, +\ ], +\ 'callback': 'ale_linters#sh#shellcheck#Handle', \}) diff --git a/ale_linters/slim/slimlint.vim b/ale_linters/slim/slimlint.vim new file mode 100644 index 0000000..00c6b26 --- /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 ale#Has('win32') + let l:command = 'set SLIM_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config) . ' && ' . l:command + else + let l:command = 'SLIM_LINT_RUBOCOP_CONF=' . ale#Escape(l:rubocop_config) . ' ' . l:command + endif + 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_callback': '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 0000000..f15579e --- /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_callback': '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 0000000..96cd7bd --- /dev/null +++ b/ale_linters/sml/smlnj_cm.vim @@ -0,0 +1,19 @@ +" 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', +\ 'executable_callback': 'ale#handlers#sml#GetExecutableSmlnjCm', +\ 'lint_file': 1, +\ 'command_callback': 'ale_linters#sml#smlnj_cm#GetCommand', +\ 'callback': 'ale#handlers#sml#Handle', +\}) + +" vim:ts=4:sts=4:sw=4 diff --git a/ale_linters/solidity/solhint.vim b/ale_linters/solidity/solhint.vim new file mode 100644 index 0000000..519fd49 --- /dev/null +++ b/ale_linters/solidity/solhint.vim @@ -0,0 +1,30 @@ +" Author: Franco Victorio - https://github.com/fvictorio +" Description: Report errors in Solidity code with solhint + +function! ale_linters#solidity#solhint#Handle(buffer, lines) abort + " Matches patterns like the following: + " /path/to/file/file.sol: line 1, col 10, Error - 'addOne' is defined but never used. (no-unused-vars) + + 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:isError = l:match[3] is? 'error' + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4], + \ 'code': l:match[5], + \ 'type': l:isError ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('solidity', { +\ 'name': 'solhint', +\ 'executable': 'solhint', +\ 'command': 'solhint --formatter compact %t', +\ '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 0000000..61ab184 --- /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 0000000..f5308af --- /dev/null +++ b/ale_linters/spec/rpmlint.vim @@ -0,0 +1,85 @@ +" 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. + +let g:ale_spec_rpmlint_executable = +\ get(g:, 'ale_spec_rpmlint_executable', 'rpmlint') + +let g:ale_spec_rpmlint_options = +\ get(g:, 'ale_spec_rpmlint_options', '') + +function! ale_linters#spec#rpmlint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'spec_rpmlint_executable') +endfunction + +function! ale_linters#spec#rpmlint#GetCommand(buffer) abort + return ale_linters#spec#rpmlint#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'spec_rpmlint_options') + \ . ' -o "NetworkEnabled False"' + \ . ' -v' + \ . ' %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_callback': 'ale_linters#spec#rpmlint#GetExecutable', +\ 'command_callback': 'ale_linters#spec#rpmlint#GetCommand', +\ 'callback': 'ale_linters#spec#rpmlint#Handle', +\}) diff --git a/ale_linters/sql/sqlint.vim b/ale_linters/sql/sqlint.vim new file mode 100644 index 0000000..ca89372 --- /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/stylus/stylelint.vim b/ale_linters/stylus/stylelint.vim new file mode 100644 index 0000000..2721529 --- /dev/null +++ b/ale_linters/stylus/stylelint.vim @@ -0,0 +1,24 @@ +" Author: diartyz , w0rp + +call ale#Set('stylus_stylelint_executable', 'stylelint') +call ale#Set('stylus_stylelint_options', '') +call ale#Set('stylus_stylelint_use_global', 0) + +function! ale_linters#stylus#stylelint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'stylus_stylelint', [ + \ 'node_modules/.bin/stylelint', + \]) +endfunction + +function! ale_linters#stylus#stylelint#GetCommand(buffer) abort + return ale_linters#stylus#stylelint#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'stylus_stylelint_options') + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('stylus', { +\ 'name': 'stylelint', +\ 'executable_callback': 'ale_linters#stylus#stylelint#GetExecutable', +\ 'command_callback': 'ale_linters#stylus#stylelint#GetCommand', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/swift/swiftlint.vim b/ale_linters/swift/swiftlint.vim index aaa77a9..697d246 100644 --- a/ale_linters/swift/swiftlint.vim +++ b/ale_linters/swift/swiftlint.vim @@ -1,9 +1,51 @@ " Author: David Mohundro " Description: swiftlint for swift files + +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': 'swiftlint', \ 'command': 'swiftlint lint --use-stdin', -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'callback': 'ale_linters#swift#swiftlint#Handle', \}) diff --git a/ale_linters/tcl/nagelfar.vim b/ale_linters/tcl/nagelfar.vim new file mode 100644 index 0000000..13b7a54 --- /dev/null +++ b/ale_linters/tcl/nagelfar.vim @@ -0,0 +1,46 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'tcl_nagelfar_executable') +endfunction + +function! ale_linters#tcl#nagelfar#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'tcl_nagelfar_options') + + return ale#Escape(ale_linters#tcl#nagelfar#GetExecutable(a:buffer)) + \ . (!empty(l:options) ? ' ' . 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_callback': 'ale_linters#tcl#nagelfar#GetExecutable', +\ 'command_callback': 'ale_linters#tcl#nagelfar#GetCommand', +\ 'callback': 'ale_linters#tcl#nagelfar#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/terraform/tflint.vim b/ale_linters/terraform/tflint.vim new file mode 100644 index 0000000..93966ff --- /dev/null +++ b/ale_linters/terraform/tflint.vim @@ -0,0 +1,62 @@ +" 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 = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + 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 + + return l:output +endfunction + +function! ale_linters#terraform#tflint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'terraform_tflint_executable') +endfunction + +function! ale_linters#terraform#tflint#GetCommand(buffer) abort + let l:cmd = ale#Escape(ale#Var(a:buffer, 'terraform_tflint_executable')) + + 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 %t' + + return l:cmd +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'tflint', +\ 'executable_callback': 'ale_linters#terraform#tflint#GetExecutable', +\ 'command_callback': 'ale_linters#terraform#tflint#GetCommand', +\ 'callback': 'ale_linters#terraform#tflint#Handle', +\}) + +" vim:sw=4 diff --git a/ale_linters/tex/alex.vim b/ale_linters/tex/alex.vim new file mode 100644 index 0000000..78c530f --- /dev/null +++ b/ale_linters/tex/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for TeX files + +call ale#linter#Define('tex', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/tex/chktex.vim b/ale_linters/tex/chktex.vim index 2d53c66..7f1b0c7 100644 --- a/ale_linters/tex/chktex.vim +++ b/ale_linters/tex/chktex.vim @@ -8,51 +8,42 @@ let g:ale_tex_chktex_options = \ get(g:, 'ale_tex_chktex_options', '-I') function! ale_linters#tex#chktex#GetCommand(buffer) abort - " Check for optional .chktexrc - let l:chktex_config = ale#util#FindNearestFile( - \ a:buffer, - \ '.chktexrc') + " Check for optional .chktexrc + let l:chktex_config = ale#path#FindNearestFile( + \ a:buffer, + \ '.chktexrc') - let l:command = g:ale_tex_chktex_executable - " Avoid bug when used without -p (last warning has gibberish for a filename) - let l:command .= ' -v0 -p stdin -q' + let l:command = ale#Var(a:buffer, 'tex_chktex_executable') + " Avoid bug when used without -p (last warning has gibberish for a filename) + let l:command .= ' -v0 -p stdin -q' - if !empty(l:chktex_config) - let l:command .= ' -l ' . fnameescape(l:chktex_config) - endif + if !empty(l:chktex_config) + let l:command .= ' -l ' . ale#Escape(l:chktex_config) + endif - let l:command .= ' ' . g:ale_tex_chktex_options + let l:command .= ' ' . ale#Var(a:buffer, 'tex_chktex_options') - return l:command + return l:command 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 = [] + " 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:line in a:lines - let l:match = matchlist(l:line, l:pattern) + 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 - if len(l:match) == 0 - continue - endif - - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:match[4] . ' (' . (l:match[3]+0) . ')', - \ 'type': 'W', - \ 'nr': -1 - \}) - endfor - - return l:output + return l:output endfunction call ale#linter#Define('tex', { diff --git a/ale_linters/tex/lacheck.vim b/ale_linters/tex/lacheck.vim index ffa1daa..e5a9632 100644 --- a/ale_linters/tex/lacheck.vim +++ b/ale_linters/tex/lacheck.vim @@ -4,47 +4,44 @@ let g:ale_tex_lacheck_executable = \ get(g:, 'ale_tex_lacheck_executable', 'lacheck') +function! ale_linters#tex#lacheck#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'tex_lacheck_executable') +endfunction + +function! ale_linters#tex#lacheck#GetCommand(buffer) abort + return ale#Var(a:buffer, 'tex_lacheck_executable') . ' %t' +endfunction + 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." + " 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 = [] + let l:pattern = '^".\+", line \(\d\+\): \(.\+\)$' + let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " lacheck follows `\input{}` commands. If the cwd is not the same as the + " file in the buffer then it will fail to find the inputed items. We do not + " want warnings from those items anyway + if !empty(matchstr(l:match[2], '^Could not open ".\+"$')) + continue + endif - if len(l:match) == 0 - continue - endif + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[2], + \ 'type': 'W', + \}) + endfor - " lacheck follows `\input{}` commands. If the cwd is not the same as the - " file in the buffer then it will fail to find the inputed items. We do not - " want warnings from those items anyway - if !empty(matchstr(l:match[2], '^Could not open ".\+"$')) - continue - endif - - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': 0, - \ 'text': l:match[2], - \ 'type': 'W', - \ 'nr': -1 - \}) - endfor - - return l:output + return l:output endfunction call ale#linter#Define('tex', { \ 'name': 'lacheck', -\ 'executable': 'lacheck', -\ 'command': g:ale#util#stdin_wrapper . ' .tex ' -\ . g:ale_tex_lacheck_executable, +\ 'executable_callback': 'ale_linters#tex#lacheck#GetExecutable', +\ 'command_callback': 'ale_linters#tex#lacheck#GetCommand', \ 'callback': 'ale_linters#tex#lacheck#Handle' \}) diff --git a/ale_linters/tex/proselint.vim b/ale_linters/tex/proselint.vim index d175c88..35e764e 100644 --- a/ale_linters/tex/proselint.vim +++ b/ale_linters/tex/proselint.vim @@ -1,9 +1,9 @@ " Author: poohzrn https://github.com/poohzrn -" Description: proselint for tex files +" Description: proselint for TeX files call ale#linter#Define('tex', { \ 'name': 'proselint', \ 'executable': 'proselint', -\ 'command': g:ale#util#stdin_wrapper . ' .tex proselint', -\ 'callback': 'ale#handlers#HandleUnixFormatAsWarning', +\ '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 0000000..952a600 --- /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/vale.vim b/ale_linters/tex/vale.vim new file mode 100644 index 0000000..f64e72a --- /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/write-good.vim b/ale_linters/tex/write-good.vim new file mode 100644 index 0000000..dc59de2 --- /dev/null +++ b/ale_linters/tex/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for TeX files + +call ale#linter#Define('tex', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/texinfo/alex.vim b/ale_linters/texinfo/alex.vim new file mode 100644 index 0000000..4a88457 --- /dev/null +++ b/ale_linters/texinfo/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for texinfo files + +call ale#linter#Define('texinfo', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/texinfo/proselint.vim b/ale_linters/texinfo/proselint.vim new file mode 100644 index 0000000..003e3a0 --- /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/write-good.vim b/ale_linters/texinfo/write-good.vim new file mode 100644 index 0000000..8104c63 --- /dev/null +++ b/ale_linters/texinfo/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for Texinfo files + +call ale#linter#Define('texinfo', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/text/alex.vim b/ale_linters/text/alex.vim new file mode 100644 index 0000000..c696367 --- /dev/null +++ b/ale_linters/text/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for text files + +call ale#linter#Define('text', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/text/proselint.vim b/ale_linters/text/proselint.vim index f84462d..281b4ff 100644 --- a/ale_linters/text/proselint.vim +++ b/ale_linters/text/proselint.vim @@ -4,6 +4,6 @@ call ale#linter#Define('text', { \ 'name': 'proselint', \ 'executable': 'proselint', -\ 'callback': 'ale#handlers#HandleUnixFormatAsWarning', -\ 'command': g:ale#util#stdin_wrapper . ' .txt 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 0000000..ec4433b --- /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/vale.vim b/ale_linters/text/vale.vim new file mode 100644 index 0000000..cf37c2f --- /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/write-good.vim b/ale_linters/text/write-good.vim new file mode 100644 index 0000000..ff76ce4 --- /dev/null +++ b/ale_linters/text/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for text files + +call ale#linter#Define('text', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/thrift/thrift.vim b/ale_linters/thrift/thrift.vim new file mode 100644 index 0000000..2f62570 --- /dev/null +++ b/ale_linters/thrift/thrift.vim @@ -0,0 +1,91 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'thrift_thrift_executable') +endfunction + +function! ale_linters#thrift#thrift#GetCommand(buffer) abort + let l:generators = ale#Var(a:buffer, 'thrift_thrift_generators') + let l:includes = ale#Var(a:buffer, 'thrift_thrift_includes') + + " The thrift compiler requires at least one generator. If none are set, + " fall back to our default value to avoid silently failing. We could also + " `throw` here, but that seems even less helpful. + if empty(l:generators) + let l:generators = ['cpp'] + endif + + let l:output_dir = tempname() + call mkdir(l:output_dir) + call ale#engine#ManageDirectory(a:buffer, l:output_dir) + + return ale#Escape(ale_linters#thrift#thrift#GetExecutable(a:buffer)) + \ . ' ' . join(map(copy(l:generators), "'--gen ' . v:val")) + \ . ' ' . join(map(copy(l:includes), "'-I ' . v:val")) + \ . ' ' . ale#Var(a:buffer, 'thrift_thrift_options') + \ . ' -out ' . ale#Escape(l:output_dir) + \ . ' %t' +endfunction + +function! ale_linters#thrift#thrift#Handle(buffer, lines) abort + " Matches lines like the following: + " + " [SEVERITY:/path/filename.thrift:31] Message text + " [ERROR:/path/filename.thrift:31] (last token was ';') + let l:pattern = '\v^\[(\u+):(.*):(\d+)\] (.*)$' + + let l:index = 0 + let l:output = [] + + " Roll our own output-matching loop instead of using ale#util#GetMatches + " because we need to support error messages that span multiple lines. + while l:index < len(a:lines) + let l:line = a:lines[l:index] + + let l:match = matchlist(l:line, l:pattern) + if empty(l:match) + let l:index += 1 + continue + endif + + let l:severity = l:match[1] + if l:severity is# 'WARNING' + let l:type = 'W' + else + let l:type = 'E' + endif + + " If our text looks like "(last token was ';')", the *next* line + " should contain a more descriptive error message. + let l:text = l:match[4] + if l:text =~# '\(last token was .*\)' + let l:index += 1 + let l:text = get(a:lines, l:index, 'Unknown error ' . l:text) + endif + + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + + let l:index += 1 + endwhile + + return l:output +endfunction + +call ale#linter#Define('thrift', { +\ 'name': 'thrift', +\ 'executable': 'thrift', +\ 'output_stream': 'both', +\ 'executable_callback': 'ale_linters#thrift#thrift#GetExecutable', +\ 'command_callback': 'ale_linters#thrift#thrift#GetCommand', +\ 'callback': 'ale_linters#thrift#thrift#Handle', +\}) diff --git a/ale_linters/typescript/eslint.vim b/ale_linters/typescript/eslint.vim new file mode 100644 index 0000000..f1ae54e --- /dev/null +++ b/ale_linters/typescript/eslint.vim @@ -0,0 +1,9 @@ +" Author: w0rp +" Description: eslint for JavaScript files + +call ale#linter#Define('typescript', { +\ 'name': 'eslint', +\ 'executable_callback': 'ale#handlers#eslint#GetExecutable', +\ 'command_callback': 'ale#handlers#eslint#GetCommand', +\ 'callback': 'ale#handlers#eslint#Handle', +\}) diff --git a/ale_linters/typescript/tslint.vim b/ale_linters/typescript/tslint.vim index eb5b1a7..f4b4816 100644 --- a/ale_linters/typescript/tslint.vim +++ b/ale_linters/typescript/tslint.vim @@ -1,55 +1,85 @@ -" Author: Prashanth Chandra https://github.com/prashcr +" Author: Prashanth Chandra , Jonathan Clem " Description: tslint for TypeScript files +call ale#Set('typescript_tslint_executable', 'tslint') +call ale#Set('typescript_tslint_config_path', '') +call ale#Set('typescript_tslint_rules_dir', '') +call ale#Set('typescript_tslint_use_global', 0) +call ale#Set('typescript_tslint_ignore_empty_files', 0) + +function! ale_linters#typescript#tslint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'typescript_tslint', [ + \ 'node_modules/.bin/tslint', + \]) +endfunction + function! ale_linters#typescript#tslint#Handle(buffer, lines) abort - " Matches patterns like the following: - " - " hello.ts[7, 41]: trailing whitespace - " hello.ts[5, 1]: Forbidden 'var' keyword, use 'let' or 'const' instead - " - let l:ext = '.' . fnamemodify(bufname(a:buffer), ':e') - let l:pattern = '.\+' . l:ext . '\[\(\d\+\), \(\d\+\)\]: \(.\+\)' + " 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:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + if get(l:error, 'ruleName', '') is# 'no-implicit-dependencies' continue endif - let l:line = l:match[1] + 0 - let l:column = l:match[2] + 0 - let l:type = 'E' - let l:text = l:match[3] + 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, + \} - " vcol is Needed to indicate that the column is a character. - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': l:column, - \ 'text': l:text, - \ 'type': l:type, - \ 'nr': -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#BuildLintCommand(buffer_n) abort - let l:tsconfig_path = ale#util#FindNearestFile(a:buffer_n, 'tslint.json') - let l:tslint_options = empty(l:tsconfig_path) ? '' : '-c ' . l:tsconfig_path +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:ext = '.' . fnamemodify(bufname(a:buffer_n), ':e') + 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 g:ale#util#stdin_wrapper . ' ' . l:ext . ' tslint ' . l:tslint_options + return ale#path#BufferCdString(a:buffer) + \ . ale_linters#typescript#tslint#GetExecutable(a:buffer) + \ . ' --format json' + \ . l:tslint_config_option + \ . l:tslint_rules_option + \ . ' %t' endfunction call ale#linter#Define('typescript', { \ 'name': 'tslint', -\ 'executable': 'tslint', -\ 'command_callback': 'ale_linters#typescript#tslint#BuildLintCommand', +\ 'executable_callback': 'ale_linters#typescript#tslint#GetExecutable', +\ 'command_callback': '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 0000000..7a155bd --- /dev/null +++ b/ale_linters/typescript/tsserver.vim @@ -0,0 +1,30 @@ +" 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', 0) + +" These functions need to be defined just to comply with the API for LSP. +function! ale_linters#typescript#tsserver#GetProjectRoot(buffer) abort + return '' +endfunction + +function! ale_linters#typescript#tsserver#GetLanguage(buffer) abort + return '' +endfunction + +function! ale_linters#typescript#tsserver#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'typescript_tsserver', [ + \ 'node_modules/.bin/tsserver', + \]) +endfunction + +call ale#linter#Define('typescript', { +\ 'name': 'tsserver', +\ 'lsp': 'tsserver', +\ 'executable_callback': 'ale_linters#typescript#tsserver#GetExecutable', +\ 'command_callback': 'ale_linters#typescript#tsserver#GetExecutable', +\ 'project_root_callback': 'ale_linters#typescript#tsserver#GetProjectRoot', +\ 'language_callback': 'ale_linters#typescript#tsserver#GetLanguage', +\}) diff --git a/ale_linters/typescript/typecheck.vim b/ale_linters/typescript/typecheck.vim index 080a238..2f18691 100644 --- a/ale_linters/typescript/typecheck.vim +++ b/ale_linters/typescript/typecheck.vim @@ -7,30 +7,18 @@ function! ale_linters#typescript#typecheck#Handle(buffer, lines) abort " 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:pattern = '.\+\.ts\[\(\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 - + 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:type = 'E' let l:text = l:match[3] - " vcol is Needed to indicate that the column is a character. call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:line, - \ 'vcol': 0, \ 'col': l:column, \ 'text': l:text, - \ 'type': l:type, - \ 'nr': -1, \}) endfor diff --git a/ale_linters/verilog/iverilog.vim b/ale_linters/verilog/iverilog.vim index d10661d..c64a3be 100644 --- a/ale_linters/verilog/iverilog.vim +++ b/ale_linters/verilog/iverilog.vim @@ -1,6 +1,14 @@ " 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 ' + \ . ale#Var(a:buffer, 'verilog_iverilog_options') + \ . ' %t' +endfunction + function! ale_linters#verilog#iverilog#Handle(buffer, lines) abort " Look for lines like the following. " @@ -11,25 +19,15 @@ function! ale_linters#verilog#iverilog#Handle(buffer, lines) abort let l:pattern = '^[^:]\+:\(\d\+\): \(warning\|error\|syntax error\)\(: \(.\+\)\)\?' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - + for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[1] + 0 let l:type = l:match[2] =~# 'error' ? 'E' : 'W' - let l:text = l:match[2] ==# 'syntax error' ? 'syntax error' : l:match[4] + let l:text = l:match[2] is# 'syntax error' ? 'syntax error' : l:match[4] call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': 1, \ 'text': l:text, \ 'type': l:type, - \ 'nr': -1, \}) endfor @@ -40,6 +38,6 @@ call ale#linter#Define('verilog', { \ 'name': 'iverilog', \ 'output_stream': 'stderr', \ 'executable': 'iverilog', -\ 'command': g:ale#util#stdin_wrapper . ' .v iverilog -t null -Wall', +\ 'command_callback': 'ale_linters#verilog#iverilog#GetCommand', \ 'callback': 'ale_linters#verilog#iverilog#Handle', \}) diff --git a/ale_linters/verilog/verilator.vim b/ale_linters/verilog/verilator.vim index 534e1ae..6053da0 100644 --- a/ale_linters/verilog/verilator.vim +++ b/ale_linters/verilog/verilator.vim @@ -1,6 +1,24 @@ " 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 + let l:filename = tempname() . '_verilator_linted.v' + + " Create a special filename, so we can detect it in the handler. + call ale#engine#ManageFile(a:buffer, l:filename) + let l:lines = getbufline(a:buffer, 1, '$') + call ale#util#Writefile(a:buffer, l:lines, l:filename) + + return 'verilator --lint-only -Wall -Wno-DECLFILENAME ' + \ . ale#Var(a:buffer, 'verilog_verilator_options') .' ' + \ . ale#Escape(l:filename) +endfunction + function! ale_linters#verilog#verilator#Handle(buffer, lines) abort " Look for lines like the following. " @@ -13,28 +31,18 @@ function! ale_linters#verilog#verilator#Handle(buffer, lines) abort let l:pattern = '^%\(Warning\|Error\)[^:]*:\([^:]\+\):\(\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 - + for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:line = l:match[3] + 0 - let l:type = l:match[1] ==# 'Error' ? 'E' : 'W' + let l:type = l:match[1] is# 'Error' ? 'E' : 'W' let l:text = l:match[4] let l:file = l:match[2] - if(l:file =~# '_verilator_linted.v') - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:line, - \ 'vcol': 0, - \ 'col': 1, - \ 'text': l:text, - \ 'type': l:type, - \ 'nr': -1, - \}) + if l:file =~# '_verilator_linted.v' + call add(l:output, { + \ 'lnum': l:line, + \ 'text': l:text, + \ 'type': l:type, + \}) endif endfor @@ -45,6 +53,7 @@ call ale#linter#Define('verilog', { \ 'name': 'verilator', \ 'output_stream': 'stderr', \ 'executable': 'verilator', -\ 'command': g:ale#util#stdin_wrapper . ' _verilator_linted.v verilator --lint-only -Wall -Wno-DECLFILENAME', +\ 'command_callback': 'ale_linters#verilog#verilator#GetCommand', \ 'callback': 'ale_linters#verilog#verilator#Handle', +\ 'read_buffer': 0, \}) diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim index bc6ea46..dfa00dc 100644 --- a/ale_linters/vim/vint.vim +++ b/ale_linters/vim/vint.vim @@ -4,19 +4,64 @@ " This flag can be used to change enable/disable style issues. let g:ale_vim_vint_show_style_issues = \ get(g:, 'ale_vim_vint_show_style_issues', 1) - -let s:warning_flag = g:ale_vim_vint_show_style_issues ? '-s' : '-w' let s:enable_neovim = has('nvim') ? ' --enable-neovim ' : '' let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})"' +function! ale_linters#vim#vint#VersionCommand(buffer) abort + " Check the Vint version if we haven't checked it already. + return !ale#semver#HasVersion('vint') + \ ? 'vint --version' + \ : '' +endfunction + +function! ale_linters#vim#vint#GetCommand(buffer, version_output) abort + let l:version = ale#semver#GetVersion('vint', a:version_output) + + let l:can_use_no_color_flag = empty(l:version) + \ || ale#semver#GTE(l:version, [0, 3, 7]) + + let l:warning_flag = ale#Var(a:buffer, 'vim_vint_show_style_issues') ? '-s' : '-w' + + return 'vint ' + \ . l:warning_flag . ' ' + \ . (l:can_use_no_color_flag ? '--no-color ' : '') + \ . s:enable_neovim + \ . s:format + \ . ' %t' +endfunction + +let s:word_regex_list = [ +\ '\v^Undefined variable: ([^ ]+)', +\ '\v^Make the scope explicit like ...([^ ]+). ', +\ '\v^.*start with a capital or contain a colon: ([^ ]+)', +\ '\v.*instead of .(\=[=~]).', +\] + +function! ale_linters#vim#vint#Handle(buffer, lines) abort + let l:loclist = ale#handlers#gcc#HandleGCCFormat(a:buffer, a:lines) + + for l:item in l:loclist + let l:match = [] + + for l:regex in s:word_regex_list + let l:match = matchlist(l:item.text, l:regex) + + if !empty(l:match) + let l:item.end_col = l:item.col + len(l:match[1]) - 1 + break + endif + endfor + endfor + + return l:loclist +endfunction + call ale#linter#Define('vim', { \ 'name': 'vint', \ 'executable': 'vint', -\ 'command': g:ale#util#stdin_wrapper -\ . ' .vim vint ' -\ . s:warning_flag -\ . ' --no-color ' -\ . s:enable_neovim -\ . s:format, -\ 'callback': 'ale#handlers#HandleGCCFormat', +\ 'command_chain': [ +\ {'callback': 'ale_linters#vim#vint#VersionCommand', 'output_stream': 'stderr'}, +\ {'callback': 'ale_linters#vim#vint#GetCommand', 'output_stream': 'stdout'}, +\ ], +\ 'callback': 'ale_linters#vim#vint#Handle', \}) diff --git a/ale_linters/xhtml/alex.vim b/ale_linters/xhtml/alex.vim new file mode 100644 index 0000000..60a9a7c --- /dev/null +++ b/ale_linters/xhtml/alex.vim @@ -0,0 +1,11 @@ +" Author: Johannes Wienke +" Description: alex for XHTML files + +call ale#linter#Define('xhtml', { +\ 'name': 'alex', +\ 'executable': 'alex', +\ 'command': 'alex %s -t', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#alex#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/xhtml/proselint.vim b/ale_linters/xhtml/proselint.vim new file mode 100644 index 0000000..dfad921 --- /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/write-good.vim b/ale_linters/xhtml/write-good.vim new file mode 100644 index 0000000..83d1863 --- /dev/null +++ b/ale_linters/xhtml/write-good.vim @@ -0,0 +1,9 @@ +" Author: Sumner Evans +" Description: write-good for XHTML files + +call ale#linter#Define('xhtml', { +\ 'name': 'write-good', +\ 'executable_callback': 'ale#handlers#writegood#GetExecutable', +\ 'command_callback': 'ale#handlers#writegood#GetCommand', +\ 'callback': 'ale#handlers#writegood#Handle', +\}) diff --git a/ale_linters/xml/xmllint.vim b/ale_linters/xml/xmllint.vim new file mode 100644 index 0000000..63d7f76 --- /dev/null +++ b/ale_linters/xml/xmllint.vim @@ -0,0 +1,69 @@ +" 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#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'xml_xmllint_executable') +endfunction + +function! ale_linters#xml#xmllint#GetCommand(buffer) abort + return ale#Escape(ale_linters#xml#xmllint#GetExecutable(a:buffer)) + \ . ' ' . 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_callback': 'ale_linters#xml#xmllint#GetExecutable', +\ 'command_callback': 'ale_linters#xml#xmllint#GetCommand', +\ 'callback': 'ale_linters#xml#xmllint#Handle', +\ }) diff --git a/ale_linters/yaml/swaglint.vim b/ale_linters/yaml/swaglint.vim new file mode 100644 index 0000000..75a496c --- /dev/null +++ b/ale_linters/yaml/swaglint.vim @@ -0,0 +1,49 @@ +" 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', 0) + +function! ale_linters#yaml#swaglint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'yaml_swaglint', [ + \ 'node_modules/.bin/swaglint', + \]) +endfunction + +function! ale_linters#yaml#swaglint#GetCommand(buffer) abort + return ale_linters#yaml#swaglint#GetExecutable(a:buffer) + \ . ' -r compact --stdin' +endfunction + +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_callback': 'ale_linters#yaml#swaglint#GetExecutable', +\ 'command_callback': 'ale_linters#yaml#swaglint#GetCommand', +\ 'callback': 'ale_linters#yaml#swaglint#Handle', +\}) diff --git a/ale_linters/yaml/yamllint.vim b/ale_linters/yaml/yamllint.vim index 05af709..731f801 100644 --- a/ale_linters/yaml/yamllint.vim +++ b/ale_linters/yaml/yamllint.vim @@ -1,5 +1,21 @@ " Author: KabbAmine +let g:ale_yaml_yamllint_executable = +\ get(g:, 'ale_yaml_yamllint_executable', 'yamllint') + +let g:ale_yaml_yamllint_options = +\ get(g:, 'ale_yaml_yamllint_options', '') + +function! ale_linters#yaml#yamllint#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'yaml_yamllint_executable') +endfunction + +function! ale_linters#yaml#yamllint#GetCommand(buffer) abort + return ale_linters#yaml#yamllint#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'yaml_yamllint_options') + \ . ' -f parsable %t' +endfunction + function! ale_linters#yaml#yamllint#Handle(buffer, lines) abort " Matches patterns line the following: " something.yaml:1:1: [warning] missing document start "---" (document-start) @@ -7,27 +23,17 @@ function! ale_linters#yaml#yamllint#Handle(buffer, lines) abort let l:pattern = '^.*:\(\d\+\):\(\d\+\): \[\(error\|warning\)\] \(.\+\)$' let l:output = [] - for l:line in a:lines - let l:match = matchlist(l:line, l:pattern) - - if len(l:match) == 0 - continue - endif - + 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] let l:text = l:match[4] - " vcol is Needed to indicate that the column is a character. call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:line, - \ 'vcol': 0, \ 'col': l:col, \ 'text': l:text, - \ 'type': l:type ==# 'error' ? 'E' : 'W', - \ 'nr': -1, + \ 'type': l:type is# 'error' ? 'E' : 'W', \}) endfor @@ -36,7 +42,7 @@ endfunction call ale#linter#Define('yaml', { \ 'name': 'yamllint', -\ 'executable': 'yamllint', -\ 'command': g:ale#util#stdin_wrapper . ' .yml yamllint -f parsable', +\ 'executable_callback': 'ale_linters#yaml#yamllint#GetExecutable', +\ 'command_callback': 'ale_linters#yaml#yamllint#GetCommand', \ 'callback': 'ale_linters#yaml#yamllint#Handle', \}) diff --git a/autoload/ale.vim b/autoload/ale.vim index a5251f6..f6c06cf 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -1,61 +1,287 @@ -" Author: w0rp +" Author: w0rp , David Alexander " Description: Primary code path for the plugin " Manages execution of linters when requested by autocommands let s:lint_timer = -1 +let s:queued_buffer_number = -1 +let s:should_lint_file_for_buffer = {} +let s:error_delay_ms = 1000 * 60 * 2 -function! ale#Queue(delay) abort - " Do nothing for blacklisted files. - if index(g:ale_filetype_blacklist, &filetype) >= 0 +let s:timestamp_map = {} + +" Given a key for a script variable for tracking the time to wait until +" a given function should be called, a funcref for a function to call, and +" a List of arguments, call the function and return whatever value it returns. +" +" If the function throws an exception, then the function will not be called +" for a while, and 0 will be returned instead. +function! ale#CallWithCooldown(timestamp_key, func, arglist) abort + let l:now = ale#util#ClockMilliseconds() + + if l:now < get(s:timestamp_map, a:timestamp_key, -1) + return 0 + endif + + let s:timestamp_map[a:timestamp_key] = l:now + s:error_delay_ms + + let l:return_value = call(a:func, a:arglist) + + let s:timestamp_map[a:timestamp_key] = -1 + + return l:return_value +endfunction + +" Return 1 if a file is too large for ALE to handle. +function! ale#FileTooLarge() abort + let l:max = ale#Var(bufnr(''), 'maximum_file_size') + + return l:max > 0 ? (line2byte(line('$') + 1) > l:max) : 0 +endfunction + +let s:getcmdwintype_exists = exists('*getcmdwintype') + +" 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. + + " Don't perform any checks when newer NeoVim versions are exiting. + if get(v:, 'exiting', v:null) isnot v:null + return 1 + endif + + " Do nothing for blacklisted files + if index(g:ale_filetype_blacklist, getbufvar(a:buffer, '&filetype')) >= 0 + 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') + + if l:filename is# '.' + return 1 + endif + + " Do nothing if running in the sandbox + if ale#util#InSandbox() + return 1 + endif + + " Do nothing if ALE is disabled. + if !ale#Var(a:buffer, 'enabled') + return 1 + endif + + " Do nothing if the file is too large. + if ale#FileTooLarge() + 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 + +" (delay, [linting_flag, buffer_number]) +function! ale#Queue(delay, ...) abort + if a:0 > 2 + throw 'too many arguments!' + endif + + " Default linting_flag to '' + let l:linting_flag = get(a:000, 0, '') + let l:buffer = get(a:000, 1, bufnr('')) + + return ale#CallWithCooldown( + \ 'dont_queue_until', + \ function('s:ALEQueueImpl'), + \ [a:delay, l:linting_flag, l:buffer], + \) +endfunction + +function! s:ALEQueueImpl(delay, linting_flag, buffer) abort + if a:linting_flag isnot# '' && a:linting_flag isnot# 'lint_file' + throw "linting_flag must be either '' or 'lint_file'" + endif + + if type(a:buffer) != type(0) + throw 'buffer_number must be a Number' + endif + + if ale#ShouldDoNothing(a:buffer) return endif + " Remember that we want to check files for this buffer. + " We will remember this until we finally run the linters, via any event. + if a:linting_flag is# 'lint_file' + let s:should_lint_file_for_buffer[a:buffer] = 1 + endif + if s:lint_timer != -1 call timer_stop(s:lint_timer) let s:lint_timer = -1 endif - let l:linters = ale#linter#Get(&filetype) - if len(l:linters) == 0 - " There are no linters to lint with, so stop here. + let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype')) + + " Don't set up buffer data and so on if there are no linters to run. + if empty(l:linters) + " If we have some previous buffer data, then stop any jobs currently + " running and clear everything. + if has_key(g:ale_buffer_info, a:buffer) + call ale#engine#RunLinters(a:buffer, [], 1) + endif + return endif if a:delay > 0 + let s:queued_buffer_number = a:buffer let s:lint_timer = timer_start(a:delay, function('ale#Lint')) else - call ale#Lint() + call ale#Lint(-1, a:buffer) endif endfunction function! ale#Lint(...) abort - " Do nothing for blacklisted files. - if index(g:ale_filetype_blacklist, &filetype) >= 0 + if a:0 > 1 + " Use the buffer number given as the optional second argument. + let l:buffer = a:2 + elseif a:0 > 0 && a:1 == s:lint_timer + " Use the buffer number for the buffer linting was queued for. + let l:buffer = s:queued_buffer_number + else + " Use the current buffer number. + let l:buffer = bufnr('') + endif + + return ale#CallWithCooldown( + \ 'dont_lint_until', + \ function('s:ALELintImpl'), + \ [l:buffer], + \) +endfunction + +function! s:ALELintImpl(buffer) abort + if ale#ShouldDoNothing(a:buffer) return endif - let l:buffer = bufnr('%') - let l:linters = ale#linter#Get(&filetype) + " Use the filetype from the buffer + let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype')) + let l:should_lint_file = 0 - " Initialise the buffer information if needed. - call ale#engine#InitBufferInfo(l:buffer) + " Check if we previously requested checking the file. + if has_key(s:should_lint_file_for_buffer, a:buffer) + unlet s:should_lint_file_for_buffer[a:buffer] + " Lint files if they exist. + let l:should_lint_file = filereadable(expand('#' . a:buffer . ':p')) + endif - " Clear the new loclist again, so we will work with all new items. - let g:ale_buffer_info[l:buffer].new_loclist = [] - - for l:linter in l:linters - " Check if a given linter has a program which can be executed. - if has_key(l:linter, 'executable_callback') - let l:executable = ale#util#GetFunction(l:linter.executable_callback)(l:buffer) - else - let l:executable = l:linter.executable - endif - - if !executable(l:executable) - " The linter's program cannot be executed, so skip it. - continue - endif - - call ale#engine#Invoke(l:buffer, l:linter) - endfor + call ale#engine#RunLinters(a:buffer, l:linters, l:should_lint_file) +endfunction + +" Reset flags indicating that files should be checked for all buffers. +function! ale#ResetLintFileMarkers() abort + let s:should_lint_file_for_buffer = {} +endfunction + +function! ale#ResetErrorDelays() abort + let s:timestamp_map = {} +endfunction + +let g:ale_has_override = get(g:, 'ale_has_override', {}) + +" Call has(), but check a global Dictionary so we can force flags on or off +" for testing purposes. +function! ale#Has(feature) abort + return get(g:ale_has_override, a:feature, has(a:feature)) +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:nr = str2nr(a:buffer) + let l:full_name = 'ale_' . a:variable_name + + if bufexists(l:nr) + let l:vars = getbufvar(l:nr, '') + elseif has_key(g:, 'ale_fix_buffer_data') + let l:vars = get(g:ale_fix_buffer_data, l:nr, {'vars': {}}).vars + else + let l:vars = {} + endif + + 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 + let l:value = get(g:, l:full_name, a:default) + let g:[l:full_name] = l:value + + return l: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%severity%', '\=l:severity', 'g') + let l:msg = substitute(l:msg, '\V%linter%', '\=l:linter_name', 'g') + let l:msg = substitute(l:msg, '\v\%([^\%]*)code([^\%]*)\%', l:code_repl, 'g') + " Replace %s with the text. + let l:msg = substitute(l:msg, '\V%s', '\=a:item.text', 'g') + + return l:msg endfunction diff --git a/autoload/ale/balloon.vim b/autoload/ale/balloon.vim new file mode 100644 index 0000000..41fa95f --- /dev/null +++ b/autoload/ale/balloon.vim @@ -0,0 +1,21 @@ +" Author: w0rp +" Description: balloonexpr support for ALE. + +function! ale#balloon#MessageForPos(bufnr, lnum, col) abort + let l:loclist = get(g:ale_buffer_info, a:bufnr, {'loclist': []}).loclist + let l:index = ale#util#BinarySearch(l:loclist, a:bufnr, a:lnum, a:col) + + return l:index >= 0 ? l:loclist[l:index].text : '' +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 + set noballooneval +endfunction + +function! ale#balloon#Enable() abort + set ballooneval balloonexpr=ale#balloon#Expr() +endfunction diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim new file mode 100644 index 0000000..f6ad7de --- /dev/null +++ b/autoload/ale/c.vim @@ -0,0 +1,93 @@ +" Author: gagbo , w0rp +" Description: Functions for integrating with C-family linters. + +let s:sep = has('win32') ? '\' : '/' + +function! ale#c#FindProjectRoot(buffer) abort + for l:project_filename in ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt'] + 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 + + return '' +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 + +let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [ +\ 'build', +\ 'bin', +\]) + +" Given a buffer number, find the build subdirectory with compile commands +" The subdirectory is returned without the trailing / +function! ale#c#FindCompileCommands(buffer) abort + 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 + + if filereadable(l:c_build_dir . '/compile_commands.json') + return l:c_build_dir + endif + endfor + endfor + + return '' +endfunction diff --git a/autoload/ale/cleanup.vim b/autoload/ale/cleanup.vim deleted file mode 100644 index 0a1cd39..0000000 --- a/autoload/ale/cleanup.vim +++ /dev/null @@ -1,13 +0,0 @@ -" Author: w0rp -" Description: Utility functions related to cleaning state. - -function! ale#cleanup#Buffer(buffer) abort - if has_key(g:ale_buffer_info, a:buffer) - " When buffers are removed, clear all of the jobs. - for l:job in get(g:ale_buffer_info[a:buffer], 'job_list', []) - call ale#engine#ClearJob(l:job) - endfor - - call remove(g:ale_buffer_info, a:buffer) - endif -endfunction diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim new file mode 100644 index 0000000..558fe23 --- /dev/null +++ b/autoload/ale/command.vim @@ -0,0 +1,57 @@ +" Author: w0rp +" Description: Special command formatting for creating temporary files and +" passing buffer filenames easily. + +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 tempname() . (has('win32') ? '\' : '/') . l:filename +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, command, pipe_file_if_needed) abort + let l:temporary_file = '' + let l:command = a:command + + " First replace all uses of %%, used for literal percent characters, + " with an ugly string. + let l:command = substitute(l:command, '%%', '<>', 'g') + + " 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', '\=ale#Escape(l:filename)', 'g') + endif + + if 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, '%t', '\=ale#Escape(l:temporary_file)', '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 + + return [l:temporary_file, l:command] +endfunction diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim new file mode 100644 index 0000000..7ad7f9d --- /dev/null +++ b/autoload/ale/completion.vim @@ -0,0 +1,477 @@ +" Author: w0rp +" Description: Completion support for LSP linters + +let s:timer_id = -1 +let s:last_done_pos = [] + +" CompletionItemKind values from the LSP protocol. +let s:LSP_COMPLETION_TEXT_KIND = 1 +let s:LSP_COMPLETION_METHOD_KIND = 2 +let s:LSP_COMPLETION_FUNCTION_KIND = 3 +let s:LSP_COMPLETION_CONSTRUCTOR_KIND = 4 +let s:LSP_COMPLETION_FIELD_KIND = 5 +let s:LSP_COMPLETION_VARIABLE_KIND = 6 +let s:LSP_COMPLETION_CLASS_KIND = 7 +let s:LSP_COMPLETION_INTERFACE_KIND = 8 +let s:LSP_COMPLETION_MODULE_KIND = 9 +let s:LSP_COMPLETION_PROPERTY_KIND = 10 +let s:LSP_COMPLETION_UNIT_KIND = 11 +let s:LSP_COMPLETION_VALUE_KIND = 12 +let s:LSP_COMPLETION_ENUM_KIND = 13 +let s:LSP_COMPLETION_KEYWORD_KIND = 14 +let s:LSP_COMPLETION_SNIPPET_KIND = 15 +let s:LSP_COMPLETION_COLOR_KIND = 16 +let s:LSP_COMPLETION_FILE_KIND = 17 +let s:LSP_COMPLETION_REFERENCE_KIND = 18 + +" 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]*$|\.$', +\} + +" 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]*$', +\} + +" A map of exact characters for triggering LSP completions. +let s:trigger_character_map = { +\ '': ['.'], +\} + +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 + 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(suggestions, prefix) abort + " For completing... + " foo. + " ^ + " We need to include all of the given suggestions. + if a:prefix is# '.' + return a:suggestions + endif + + let l:filtered_suggestions = [] + + " Filter suggestions down to those starting with the prefix we used for + " finding suggestions in the first place. + " + " Some completion tools will include suggestions which don't even start + " with the characters we have already typed. + for l:item in a:suggestions + " A List of String values or a List of completion item Dictionaries + " is accepted here. + let l:word = type(l:item) == type('') ? l:item : l:item.word + + " Add suggestions if the suggestion starts with a case-insensitive + " match for the prefix. + if l:word[: len(a:prefix) - 1] is? a:prefix + call add(l:filtered_suggestions, l:item) + endif + endfor + + return l:filtered_suggestions +endfunction + +function! s:ReplaceCompleteopt() abort + if !exists('b:ale_old_completopt') + let b:ale_old_completopt = &l:completeopt + endif + + let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' +endfunction + +function! ale#completion#OmniFunc(findstart, base) abort + if a:findstart + let l:line = b:ale_completion_info.line + 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 + else + " Parse a new response if there is one. + if exists('b:ale_completion_response') + \&& exists('b:ale_completion_parser') + let l:response = b:ale_completion_response + let l:parser = b:ale_completion_parser + + unlet b:ale_completion_response + unlet b:ale_completion_parser + + let b:ale_completion_result = function(l:parser)(l:response) + endif + + call s:ReplaceCompleteopt() + + return get(b:, 'ale_completion_result', []) + endif +endfunction + +function! ale#completion#Show(response, completion_parser) 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 + + " Set the list in the buffer, temporarily replace omnifunc with our + " function, and then start omni-completion. + let b:ale_completion_response = a:response + let b:ale_completion_parser = a:completion_parser + let &l:omnifunc = 'ale#completion#OmniFunc' + call s:ReplaceCompleteopt() + call ale#util#FeedKeys("\\", 'n') +endfunction + +function! s:CompletionStillValid(request_id) abort + let [l:line, l:column] = getcurpos()[1:2] + + return has_key(b:, 'ale_completion_info') + \&& b:ale_completion_info.request_id == a:request_id + \&& b:ale_completion_info.line == l:line + \&& b:ale_completion_info.column == l:column +endfunction + +function! ale#completion#ParseTSServerCompletions(response) abort + let l:names = [] + + for l:suggestion in a:response.body + call add(l:names, l:suggestion.name) + endfor + + return l:names +endfunction + +function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort + let l:results = [] + + for l:suggestion in a:response.body + let l:displayParts = [] + + for l:part in l:suggestion.displayParts + 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 + + if l:suggestion.kind is# 'clasName' + let l:kind = 'f' + elseif l:suggestion.kind is# 'parameterName' + let l:kind = 'f' + else + let l:kind = 'v' + endif + + " See :help complete-items + call add(l:results, { + \ 'word': l:suggestion.name, + \ 'kind': l:kind, + \ 'icase': 1, + \ 'menu': join(l:displayParts, ''), + \ 'info': join(l:documentationParts, ''), + \}) + endfor + + return l:results +endfunction + +function! ale#completion#ParseLSPCompletions(response) abort + let l:item_list = [] + + if type(get(a:response, 'result')) is type([]) + let l:item_list = a:response.result + elseif type(get(a:response, 'result')) is type({}) + \&& type(get(a:response.result, 'items')) is type([]) + let l:item_list = a:response.result.items + endif + + let l:results = [] + + for l:item in l:item_list + " See :help complete-items for Vim completion kinds + if l:item.kind is s:LSP_COMPLETION_METHOD_KIND + let l:kind = 'm' + elseif l:item.kind is s:LSP_COMPLETION_CONSTRUCTOR_KIND + let l:kind = 'm' + elseif l:item.kind is s:LSP_COMPLETION_FUNCTION_KIND + let l:kind = 'f' + elseif l:item.kind is s:LSP_COMPLETION_CLASS_KIND + let l:kind = 'f' + elseif l:item.kind is s:LSP_COMPLETION_INTERFACE_KIND + let l:kind = 'f' + else + let l:kind = 'v' + endif + + call add(l:results, { + \ 'word': l:item.label, + \ 'kind': l:kind, + \ 'icase': 1, + \ 'menu': l:item.detail, + \ 'info': l:item.documentation, + \}) + endfor + + return l:results +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:command = get(a:response, 'command', '') + + if l:command is# 'completions' + let l:names = ale#completion#Filter( + \ ale#completion#ParseTSServerCompletions(a:response), + \ b:ale_completion_info.prefix, + \)[: g:ale_completion_max_suggestions - 1] + + if !empty(l:names) + let b:ale_completion_info.request_id = ale#lsp#Send( + \ b:ale_completion_info.conn_id, + \ ale#lsp#tsserver_message#CompletionEntryDetails( + \ bufnr(''), + \ b:ale_completion_info.line, + \ b:ale_completion_info.column, + \ l:names, + \ ), + \) + endif + elseif l:command is# 'completionEntryDetails' + call ale#completion#Show( + \ a:response, + \ 'ale#completion#ParseTSServerCompletionEntryDetails', + \) + endif +endfunction + + +function! ale#completion#HandleLSPResponse(conn_id, response) abort + if !s:CompletionStillValid(get(a:response, 'id')) + return + endif + + call ale#completion#Show( + \ a:response, + \ 'ale#completion#ParseLSPCompletions', + \) +endfunction + +function! s:GetLSPCompletions(linter) abort + let l:buffer = bufnr('') + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#completion#HandleTSServerResponse') + \ : function('ale#completion#HandleLSPResponse') + + let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Completions( + \ l:buffer, + \ b:ale_completion_info.line, + \ b:ale_completion_info.column, + \ b:ale_completion_info.prefix, + \) + else + " Send a message saying the buffer has changed first, otherwise + " completions won't know what text is nearby. + call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) + + " 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, + \ min([ + \ b:ale_completion_info.line_length, + \ 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, l:root) + + if l:request_id + let b:ale_completion_info.conn_id = l:id + let b:ale_completion_info.request_id = l:request_id + endif +endfunction + +function! ale#completion#GetCompletions() abort + if !g:ale_completion_enabled + return + endif + + let [l:line, l:column] = getcurpos()[1:2] + + let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) + + if empty(l:prefix) + return + 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, + \} + + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + if l:linter.lsp is# 'tsserver' + \|| get(g:, 'ale_completion_experimental_lsp_support', 0) + call s:GetLSPCompletions(l:linter) + endif + endif + endfor +endfunction + +function! s:TimerHandler(...) abort + let s:timer_id = -1 + + let [l:line, l:column] = getcurpos()[1:2] + + " When running the timer callback, we have to be sure that the cursor + " hasn't moved from where it was when we requested completions by typing. + if s:timer_pos == [l:line, l:column] + call ale#completion#GetCompletions() + endif +endfunction + +" 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 !g:ale_completion_enabled + return + endif + + let s:timer_pos = getcurpos()[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#Done() abort + silent! pclose + + " 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_completopt') + let &l:completeopt = b:ale_old_completopt + unlet b:ale_old_completopt + endif + + let s:last_done_pos = getcurpos()[1:2] +endfunction + +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/cursor.vim b/autoload/ale/cursor.vim index 864f6f2..50b1fb5 100644 --- a/autoload/ale/cursor.vim +++ b/autoload/ale/cursor.vim @@ -1,87 +1,100 @@ " Author: w0rp " Description: Echoes lint message for the current line, if any -" Return a formatted message according to g:ale_echo_msg_format variable -function! s:GetMessage(linter, type, text) abort - let l:msg = g:ale_echo_msg_format - let l:type = a:type ==# 'E' - \ ? g:ale_echo_msg_error_str - \ : g:ale_echo_msg_warning_str - " Capitalize the 1st character - let l:text = toupper(a:text[0]) . a:text[1:-1] +let s:cursor_timer = -1 +let s:last_pos = [0, 0, 0] - " Replace handlers if they exist - for [l:k, l:v] in items({'linter': a:linter, 'severity': l:type}) - let l:msg = substitute(l:msg, '\V%' . l:k . '%', l:v, '') - endfor - - return printf(l:msg, l:text) -endfunction - -function! ale#cursor#TruncatedEcho(message) abort - let l:message = a:message +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') - " We need to turn T for truncated messages on for shortmess, - " and then then we need to reset the option back to what it was. - let l:shortmess_options = getbufvar('%', '&shortmess') + " We need to remember the setting for shortmess and reset it again. + let l:shortmess_options = &l:shortmess try - " Echo the message truncated to fit without creating a prompt. + let l:cursor_position = getcurpos() + + " The message is truncated and saved to the history. setlocal shortmess+=T - exec "norm! :echomsg message\n" + exec "norm! :echomsg l:message\n" + + " Reset the cursor position if we moved off the end of the line. + " Using :norm and :echomsg can move the cursor off the end of the + " line. + if l:cursor_position != getcurpos() + call setpos('.', l:cursor_position) + endif finally - call setbufvar('%', '&shortmess', l:shortmess_options) + let &l:shortmess = l:shortmess_options endtry endfunction -function! ale#cursor#EchoCursorWarning(...) abort - " Only echo the warnings in normal mode, otherwise we will get problems. - if mode() !=# 'n' - return - endif - - let l:buffer = bufnr('%') - - if !has_key(g:ale_buffer_info, l:buffer) - return - endif - +function! s:FindItemAtCursor() abort + let l:buf = bufnr('') + let l:info = get(g:ale_buffer_info, l:buf, {}) + let l:loclist = get(l:info, 'loclist', []) let l:pos = getcurpos() - let l:loclist = g:ale_buffer_info[l:buffer].loclist - let l:index = ale#util#BinarySearch(l:loclist, l:pos[1], l:pos[2]) + let l:index = ale#util#BinarySearch(l:loclist, l:buf, l:pos[1], l:pos[2]) + let l:loc = l:index >= 0 ? l:loclist[l:index] : {} - if l:index >= 0 - let l:loc = l:loclist[l:index] - let l:msg = s:GetMessage(l:loc.linter_name, l:loc.type, l:loc.text) - call ale#cursor#TruncatedEcho(l:msg) - let g:ale_buffer_info[l:buffer].echoed = 1 - else - " We'll only clear the echoed message when moving off errors once, - " so we don't continually clear the echo line. - if get(g:ale_buffer_info[l:buffer], 'echoed') - echo - let g:ale_buffer_info[l:buffer].echoed = 0 - endif - endif + return [l:info, l:loc] endfunction -let s:cursor_timer = -1 -let s:last_pos = [0, 0, 0] - -function! ale#cursor#EchoCursorWarningWithDelay() abort - " Do nothing for blacklisted files. - if index(g:ale_filetype_blacklist, &filetype) >= 0 - return - 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#cursor#EchoCursorWarning(...) abort + return ale#CallWithCooldown('dont_echo_until', function('s:EchoImpl'), []) +endfunction + +function! s:EchoImpl() abort + if !g:ale_echo_cursor + return + endif + + " Only echo the warnings in normal mode, otherwise we will get problems. + if mode() isnot# 'n' + return + endif + + if ale#ShouldDoNothing(bufnr('')) + return + endif + + let l:buffer = bufnr('') + let [l:info, l:loc] = s:FindItemAtCursor() + + 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. + execute 'echo' + let l:info.echoed = 0 + endif +endfunction + +function! ale#cursor#EchoCursorWarningWithDelay() abort + if !g:ale_echo_cursor + return + endif + + " Only echo the warnings in normal mode, otherwise we will get problems. + if mode() isnot# 'n' + return + endif + + call s:StopCursorTimer() let l:pos = getcurpos()[0:2] @@ -90,7 +103,34 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort " we should echo something. Otherwise we can end up doing processing " the echo message far too frequently. if l:pos != s:last_pos + let l:delay = ale#Var(bufnr(''), 'echo_delay') + let s:last_pos = l:pos - let s:cursor_timer = timer_start(10, function('ale#cursor#EchoCursorWarning')) + let s:cursor_timer = timer_start( + \ l:delay, + \ function('ale#cursor#EchoCursorWarning') + \) + endif +endfunction + +function! ale#cursor#ShowCursorDetail() abort + " Only echo the warnings in normal mode, otherwise we will get problems. + if mode() isnot# 'n' + return + endif + + if ale#ShouldDoNothing(bufnr('')) + return + endif + + call s:StopCursorTimer() + + let [l:info, l:loc] = s:FindItemAtCursor() + + if !empty(l:loc) + let l:message = get(l:loc, 'detail', l:loc.text) + + call ale#preview#Show(split(l:message, "\n")) + execute 'echo' endif endfunction diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim new file mode 100644 index 0000000..cb93ec1 --- /dev/null +++ b/autoload/ale/debugging.vim @@ -0,0 +1,213 @@ +" Author: w0rp +" Description: This file implements debugging information for ALE + +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_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_history_log_output', +\ 'ale_keep_list_window_open', +\ 'ale_lint_delay', +\ 'ale_lint_on_enter', +\ 'ale_lint_on_filetype_changed', +\ 'ale_lint_on_save', +\ 'ale_lint_on_text_changed', +\ 'ale_lint_on_insert_leave', +\ 'ale_linter_aliases', +\ 'ale_linters', +\ 'ale_linters_explicit', +\ 'ale_list_window_size', +\ 'ale_list_vertical', +\ '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_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_statusline_format', +\ 'ale_type_map', +\ 'ale_warn_about_trailing_blank_lines', +\ 'ale_warn_about_trailing_whitespace', +\] + +function! s:Echo(message) abort + execute 'echo a:message' +endfunction + +function! s:GetLinterVariables(filetype, 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:linter_names, l:match[2]) >= 0 + 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! ale#debugging#Info() abort + let l:filetype = &filetype + + " We get the list of enabled linters for free by the above function. + 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'']') + + " Load linter variables to display + " This must be done after linters are loaded. + let l:variable_list = s:GetLinterVariables(l:filetype, l:enabled_names) + + 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(' Linter Variables:') + call s:Echo('') + call s:EchoLinterVariables(l:variable_list) + call s:Echo(' Global Variables:') + call s:Echo('') + call s:EchoGlobalVariables() + call s:Echo(' Command History:') + call s:Echo('') + call s:EchoCommandHistory() +endfunction + +function! ale#debugging#InfoToClipboard() abort + redir @+> + silent call ale#debugging#Info() + redir END + + call s:Echo('ALEInfo copied to your clipboard') +endfunction diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim new file mode 100644 index 0000000..521cd0b --- /dev/null +++ b/autoload/ale/definition.vim @@ -0,0 +1,125 @@ +" Author: w0rp +" Description: Go to definition support for LSP linters. + +let s:go_to_definition_map = {} + +" 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 + +" This function is used so we can check the execution of commands without +" running them. +function! ale#definition#Execute(expr) abort + execute a:expr +endfunction + +function! ale#definition#ClearLSPData() abort + let s:go_to_definition_map = {} +endfunction + +function! ale#definition#Open(options, filename, line, column) abort + if a:options.open_in_tab + call ale#definition#Execute('tabedit ' . fnameescape(a:filename)) + else + call ale#definition#Execute('edit ' . fnameescape(a:filename)) + endif + + call cursor(a:line, a:column) +endfunction + +function! ale#definition#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') is# 'definition' + \&& has_key(s:go_to_definition_map, a:response.request_seq) + let l:options = remove(s:go_to_definition_map, a:response.request_seq) + + if get(a:response, 'success', v:false) is v:true + let l:filename = a:response.body[0].file + let l:line = a:response.body[0].start.line + let l:column = a:response.body[0].start.offset + + call ale#definition#Open(l:options, l:filename, l:line, l:column) + endif + 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 type({}) + let l:result = [l:result] + elseif type(l:result) isnot type([]) + let l:result = [] + endif + + for l:item in l:result + let l:filename = ale#path#FromURI(l:item.uri) + let l:line = l:item.range.start.line + 1 + let l:column = l:item.range.start.character + + call ale#definition#Open(l:options, l:filename, l:line, l:column) + break + endfor + endif +endfunction + +function! s:GoToLSPDefinition(linter, options) abort + let l:buffer = bufnr('') + let [l:line, l:column] = getcurpos()[1:2] + + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#definition#HandleTSServerResponse') + \ : function('ale#definition#HandleLSPResponse') + + let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Definition( + \ l:buffer, + \ l:line, + \ l:column + \) + else + " Send a message saying the buffer has changed first, or the + " definition position probably won't make sense. + call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) + + let l:column = min([l:column, len(getline(l:line))]) + + " For LSP completions, we need to clamp the column to the length of + " the line. python-language-server and perhaps others do not implement + " this correctly. + let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + + let s:go_to_definition_map[l:request_id] = { + \ 'open_in_tab': get(a:options, 'open_in_tab', 0), + \} +endfunction + +function! ale#definition#GoTo(options) abort + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + call s:GoToLSPDefinition(l:linter, a:options) + endif + endfor +endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 55633ae..8916987 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -7,332 +7,608 @@ " linter: The linter dictionary for the job. " buffer: The buffer number for the job. " output: The array of lines for the output of the job. -let s:job_info_map = {} +if !has_key(s:, 'job_info_map') + let s:job_info_map = {} +endif -function! s:GetJobID(job) abort - if has('nvim') - "In NeoVim, job values are just IDs. - return a:job +" Associates LSP connection IDs with linter names. +if !has_key(s:, 'lsp_linter_map') + let s:lsp_linter_map = {} +endif + +if !has_key(s:, 'executable_cache_map') + let s:executable_cache_map = {} +endif + +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 - " In Vim 8, the job is a special variable, and we open a channel for each - " job. We'll use the ID of the channel instead as the job ID. - return ch_info(job_getchannel(a:job)).id + " 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 || 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) - " job_list will hold the list of jobs + " job_list will hold the list of job IDs + " active_linter_list will hold the list of active linter names " loclist holds the loclist items after all jobs have completed. - " new_loclist holds loclist items while jobs are being run. + " temporary_file_list holds temporary files to be cleaned up + " temporary_directory_list holds temporary directories to be cleaned up let g:ale_buffer_info[a:buffer] = { \ 'job_list': [], + \ 'active_linter_list': [], \ 'loclist': [], - \ 'new_loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], \} - endif -endfunction -function! ale#engine#ClearJob(job) abort - let l:job_id = s:GetJobID(a:job) - let l:linter = s:job_info_map[l:job_id].linter - - if has('nvim') - call jobstop(a:job) - else - " 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(a:job)) ==# 'open' - call ch_close_in(job_getchannel(a:job)) - endif - - call job_stop(a:job) + return 1 endif - call remove(s:job_info_map, l:job_id) + return 0 endfunction -function! s:StopPreviousJobs(buffer, linter) abort - if !has_key(g:ale_buffer_info, a:buffer) - " Do nothing if we didn't run anything for the buffer. +" Clear LSP linter data for the linting engine. +function! ale#engine#ClearLSPData() abort + let s:lsp_linter_map = {} +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', [])) +endfunction + +" Register a temporary file to be managed with the ALE engine for +" a current job run. +function! ale#engine#ManageFile(buffer, filename) abort + call add(g:ale_buffer_info[a:buffer].temporary_file_list, a:filename) +endfunction + +" Same as the above, but manage an entire directory. +function! ale#engine#ManageDirectory(buffer, directory) abort + call add(g:ale_buffer_info[a:buffer].temporary_directory_list, a:directory) +endfunction + +" Create a new temporary directory and manage it in one go. +function! ale#engine#CreateDirectory(buffer) abort + let l:temporary_directory = tempname() + " Create the temporary directory for the file, unreadable by 'other' + " users. + call mkdir(l:temporary_directory, '', 0750) + call ale#engine#ManageDirectory(a:buffer, l:temporary_directory) + + return l:temporary_directory +endfunction + +function! ale#engine#RemoveManagedFiles(buffer) abort + let l:info = get(g:ale_buffer_info, a:buffer, {}) + + " We can't delete anything in a sandbox, so wait until we escape from + " it to delete temporary files and directories. + if ale#util#InSandbox() return endif - let l:new_job_list = [] + " Delete files with a call akin to a plan `rm` command. + if has_key(l:info, 'temporary_file_list') + for l:filename in l:info.temporary_file_list + call delete(l:filename) + endfor - for l:job in g:ale_buffer_info[a:buffer].job_list - let l:job_id = s:GetJobID(l:job) - - if has_key(s:job_info_map, l:job_id) - \&& s:job_info_map[l:job_id].linter.name ==# a:linter.name - " Stop jobs which match the buffer and linter. - call ale#engine#ClearJob(l:job) - else - " Keep other jobs in the list. - call add(l:new_job_list, l:job) - endif - endfor - - " Update the list, removing the previously run job. - let g:ale_buffer_info[a:buffer].job_list = l:new_job_list -endfunction - -function! s:GatherOutputVim(channel, data) abort - let l:job_id = s:GetJobID(ch_getjob(a:channel)) - - if !has_key(s:job_info_map, l:job_id) - return + let l:info.temporary_file_list = [] endif - call add(s:job_info_map[l:job_id].output, a:data) -endfunction + " Delete directories like `rm -rf`. + " Directories are handled differently from files, so paths that are + " intended to be single files can be set up for automatic deletion without + " accidentally deleting entire directories. + if has_key(l:info, 'temporary_directory_list') + for l:directory in l:info.temporary_directory_list + call delete(l:directory, 'rf') + endfor -function! s:GatherOutputNeoVim(job, data, event) abort - let l:job_id = s:GetJobID(a:job) - - if !has_key(s:job_info_map, l:job_id) - return - endif - - " Join the lines passed to ale, because Neovim splits them up. - " a:data is a list of strings, where every item is a new line, except the - " first one, which is the continuation of the last item passed last time. - call ale#engine#JoinNeovimOutput(s:job_info_map[l:job_id].output, a:data) -endfunction - -function! ale#engine#JoinNeovimOutput(output, data) abort - if empty(a:output) - call extend(a:output, a:data) - else - " Extend the previous line, which can be continued. - let a:output[-1] .= get(a:data, 0, '') - - " Add the new lines. - call extend(a:output, a:data[1:]) + let l:info.temporary_directory_list = [] endif endfunction -function! s:HandleExit(job) abort - if a:job ==# 'no process' - " Stop right away when the job is not valid in Vim 8. +function! s:GatherOutput(job_id, line) abort + if has_key(s:job_info_map, a:job_id) + call add(s:job_info_map[a:job_id].output, a:line) + endif +endfunction + +function! ale#engine#HandleLoclist(linter_name, buffer, loclist) abort + let l:info = get(g:ale_buffer_info, a:buffer, {}) + + if empty(l:info) return endif - let l:job_id = s:GetJobID(a:job) + " Remove this linter from the list of active linters. + " This may have already been done when the job exits. + call filter(l:info.active_linter_list, 'v:val isnot# a:linter_name') - if !has_key(s:job_info_map, l:job_id) + " Make some adjustments to the loclists to fix common problems, and also + " to set default values for loclist items. + let l:linter_loclist = ale#engine#FixLocList(a:buffer, a:linter_name, a:loclist) + + " Remove previous items for this linter. + call filter(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 - let l:job_info = s:job_info_map[l:job_id] + call ale#engine#SetResults(a:buffer, l:info.loclist) +endfunction + +function! s:HandleExit(job_id, exit_code) abort + if !has_key(s:job_info_map, a:job_id) + return + endif + + let l:job_info = s:job_info_map[a:job_id] let l:linter = l:job_info.linter let l:output = l:job_info.output let l:buffer = l:job_info.buffer let l:next_chain_index = l:job_info.next_chain_index - " Call the same function for stopping jobs again to clean up the job - " which just closed. - call s:StopPreviousJobs(l:buffer, l:linter) + if g:ale_history_enabled + call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code) + endif + + " Remove this job from the list. + call ale#job#Stop(a:job_id) + call remove(s:job_info_map, a:job_id) + call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val isnot# a:job_id') + call filter(g:ale_buffer_info[l:buffer].active_linter_list, 'v:val isnot# l:linter.name') + + " 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(l:output) && empty(l:output[-1]) + call remove(l:output, -1) + endif if l:next_chain_index < len(get(l:linter, 'command_chain', [])) call s:InvokeChain(l:buffer, l:linter, l:next_chain_index, l:output) return endif - let l:linter_loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output) + " Log the output of the command for ALEInfo if we should. + if g:ale_history_enabled && g:ale_history_log_output + call ale#history#RememberOutput(l:buffer, a:job_id, l:output[:]) + endif - " Make some adjustments to the loclists to fix common problems. - call s:FixLocList(l:buffer, l:linter_loclist) + let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output) - for l:item in l:linter_loclist - let l:item.linter_name = l:linter.name - endfor + call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist) +endfunction - " Add the loclist items from the linter. - call extend(g:ale_buffer_info[l:buffer].new_loclist, l:linter_loclist) +function! s:HandleLSPDiagnostics(conn_id, response) abort + let l:linter_name = s:lsp_linter_map[a:conn_id] + let l:filename = ale#path#FromURI(a:response.params.uri) + let l:buffer = bufnr(l:filename) - if !empty(g:ale_buffer_info[l:buffer].job_list) - " Wait for all jobs to complete before doing anything else. + if l:buffer <= 0 return endif - " Sort the loclist again. - " We need a sorted list so we can run a binary search against it - " for efficient lookup of the messages in the cursor handler. - call sort(g:ale_buffer_info[l:buffer].new_loclist, 'ale#util#LocItemCompare') + let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) - " Now swap the old and new loclists, after we have collected everything - " and sorted the list again. - let g:ale_buffer_info[l:buffer].loclist = g:ale_buffer_info[l:buffer].new_loclist - let g:ale_buffer_info[l:buffer].new_loclist = [] + call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist) +endfunction - if g:ale_set_quickfix || g:ale_set_loclist - call ale#list#SetLists(g:ale_buffer_info[l:buffer].loclist) +function! s:HandleTSServerDiagnostics(response, error_type) abort + let l:buffer = bufnr(a:response.body.file) + let l:info = get(g:ale_buffer_info, l:buffer, {}) + + if empty(l:info) + return endif + let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) + + " tsserver sends syntax and semantic errors in separate messages, so we + " have to collect the messages separately for each buffer and join them + " back together again. + if a:error_type is# 'syntax' + let l:info.syntax_loclist = l:thislist + else + let l:info.semantic_loclist = l:thislist + endif + + let l:loclist = get(l:info, 'semantic_loclist', []) + \ + get(l:info, 'syntax_loclist', []) + + call ale#engine#HandleLoclist('tsserver', l:buffer, l:loclist) +endfunction + +function! s:HandleLSPErrorMessage(error_message) abort + execute 'echoerr ''Error from LSP:''' + + for l:line in split(a:error_message, "\n") + execute 'echoerr l:line' + endfor +endfunction + +function! ale#engine#HandleLSPResponse(conn_id, response) abort + let l:method = get(a:response, 'method', '') + + if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error') + " Uncomment this line to print LSP error messages. + " call s:HandleLSPErrorMessage(a:response.error.message) + elseif l:method is# 'textDocument/publishDiagnostics' + call s:HandleLSPDiagnostics(a:conn_id, a:response) + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'semanticDiag' + call s:HandleTSServerDiagnostics(a:response, 'semantic') + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'syntaxDiag' + call s:HandleTSServerDiagnostics(a:response, 'syntax') + endif +endfunction + +function! ale#engine#SetResults(buffer, loclist) abort + let l:linting_is_done = !ale#engine#IsCheckingBuffer(a:buffer) + + " Set signs first. This could potentially fix some line numbers. + " The List could be sorted again here by SetSigns. if g:ale_set_signs - call ale#sign#SetSigns(l:buffer, g:ale_buffer_info[l:buffer].loclist) + call ale#sign#SetSigns(a:buffer, a:loclist) + endif + + if 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(l:buffer, g:ale_buffer_info[l:buffer].loclist) + call ale#statusline#Update(a:buffer, a:loclist) endif - " Call user autocommands. This allows users to hook into ALE's lint cycle. - silent doautocmd User ALELint + if g:ale_set_highlights + call ale#highlight#SetHighlights(a:buffer, a:loclist) + endif - " Mark line 200, column 17 with a squiggly line or something - " matchadd('ALEError', '\%200l\%17v') + 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 + + " 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#engine#RemoveManagedFiles(a:buffer) + + " Call user autocommands. This allows users to hook into ALE's lint cycle. + silent doautocmd User ALELintPost + " Old DEPRECATED name; call it for backwards compatibility. + silent doautocmd User ALELint + endif endfunction -function! s:HandleExitNeoVim(job, data, event) abort - call s:HandleExit(a:job) +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! s:HandleExitVim(channel) abort - call s:HandleExit(ch_getjob(a:channel)) -endfunction +" Save the temporary directory so we can figure out if files are in it. +let s:temp_dir = fnamemodify(tempname(), ':h') + +function! ale#engine#FixLocList(buffer, linter_name, loclist) abort + let l:bufnr_map = {} + let l:new_loclist = [] -function! s:FixLocList(buffer, loclist) abort " 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:item in a:loclist - if l:item.lnum == 0 - " When errors appear at line 0, put them at line 1 instead. - let l:item.lnum = 1 - elseif l:item.lnum > l:last_line_number - let l:item.lnum = l:last_line_number - endif - endfor -endfunction - -function! s:RunJob(command, generic_job_options) abort - let l:buffer = a:generic_job_options.buffer - let l:linter = a:generic_job_options.linter - let l:output_stream = a:generic_job_options.output_stream - let l:next_chain_index = a:generic_job_options.next_chain_index - let l:read_buffer = a:generic_job_options.read_buffer - let l:command = a:command - - if l:command =~# '%s' - " If there is a '%s' in the command string, replace it with the name - " of the file. - let l:command = printf(l:command, shellescape(fnamemodify(bufname(l:buffer), ':p'))) - endif - - if has('nvim') - if l:output_stream ==# 'stderr' - " Read from stderr instead of stdout. - let l:job = jobstart(l:command, { - \ 'on_stderr': function('s:GatherOutputNeoVim'), - \ 'on_exit': function('s:HandleExitNeoVim'), - \}) - elseif l:output_stream ==# 'both' - let l:job = jobstart(l:command, { - \ 'on_stdout': function('s:GatherOutputNeoVim'), - \ 'on_stderr': function('s:GatherOutputNeoVim'), - \ 'on_exit': function('s:HandleExitNeoVim'), - \}) - else - let l:job = jobstart(l:command, { - \ 'on_stdout': function('s:GatherOutputNeoVim'), - \ 'on_exit': function('s:HandleExitNeoVim'), - \}) - endif - else - let l:job_options = { - \ 'in_mode': 'nl', - \ 'out_mode': 'nl', - \ 'err_mode': 'nl', - \ 'close_cb': function('s:HandleExitVim'), + 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': get(l:old_item, 'vcol', 0), + \ 'type': get(l:old_item, 'type', 'E'), + \ 'nr': get(l:old_item, 'nr', -1), + \ 'linter_name': a:linter_name, \} - if l:output_stream ==# 'stderr' - " Read from stderr instead of stdout. - let l:job_options.err_cb = function('s:GatherOutputVim') - elseif l:output_stream ==# 'both' - " Read from both streams. - let l:job_options.out_cb = function('s:GatherOutputVim') - let l:job_options.err_cb = function('s:GatherOutputVim') - else - let l:job_options.out_cb = function('s:GatherOutputVim') + if has_key(l:old_item, 'code') + let l:item.code = l:old_item.code endif - if has('win32') - " job_start commands on Windows have to be run with cmd /c, - " othwerwise %PATHTEXT% will not be used to programs ending int - " .cmd, .bat, .exe, etc. - let l:command = 'cmd /c ' . l:command - else - " Execute the command with the shell, to fix escaping issues. - let l:command = split(&shell) + split(&shellcmdflag) + [l:command] + if has_key(l:old_item, 'filename') + \&& !ale#path#IsTempName(l:old_item.filename) + " Use the filename given. + " Temporary files are assumed to be for this buffer, + " and the filename is not included then, because it looks bad + " in the loclist window. + let l:filename = l:old_item.filename + let l:item.filename = l:filename - if l:read_buffer - " On Unix machines, we can send the Vim buffer directly. - " This is faster than reading the lines ourselves. - let l:job_options.in_io = 'buffer' - let l:job_options.in_buf = l:buffer + 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 - " Vim 8 will read the stdin from the file's buffer. - let l:job = job_start(l:command, l:job_options) + 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) + 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 + 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 - " Only proceed if the job is being run. - if has('nvim') || (l:job !=# 'no process' && job_status(l:job) ==# 'run') - " Add the job to the list of jobs, so we can track them. - call add(g:ale_buffer_info[l:buffer].job_list, l:job) + 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 + return substitute(a:command_part, '%', '%%', 'g') +endfunction + +function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort + if empty(a:temporary_file) + " There is no file, so we didn't create anything. + return 0 + endif + + let l:temporary_directory = fnamemodify(a:temporary_file, ':h') + " Create the temporary directory for the file, unreadable by 'other' + " users. + call mkdir(l:temporary_directory, '', 0750) + " Automatically delete the directory later. + call ale#engine#ManageDirectory(a:buffer, l:temporary_directory) + " Write the buffer out to a file. + let l:lines = getbufline(a:buffer, 1, '$') + call ale#util#Writefile(a:buffer, l:lines, a:temporary_file) + + return 1 +endfunction + +" Run a job. +" +" Returns 1 when the job was started successfully. +function! s:RunJob(options) abort + let l:command = a:options.command + let l:buffer = a:options.buffer + let l:linter = a:options.linter + let l:output_stream = a:options.output_stream + let l:next_chain_index = a:options.next_chain_index + let l:read_buffer = a:options.read_buffer + let l:info = g:ale_buffer_info[l:buffer] + + if empty(l:command) + return 0 + endif + + let [l:temporary_file, l:command] = ale#command#FormatCommand(l:buffer, l:command, l:read_buffer) + + if s:CreateTemporaryFileForJob(l:buffer, l:temporary_file) + " If a temporary filename has been formatted in to the command, then + " we do not need to send the Vim buffer to the command. + let l:read_buffer = 0 + endif + + " Add a newline to commands which need it. + " This is only used for Flow for now, and is not documented. + if l:linter.add_newline + if has('win32') + let l:command = l:command . '; echo.' + else + let l:command = l:command . '; echo' + endif + endif + + let l:command = ale#job#PrepareCommand(l:buffer, l:command) + let l:job_options = { + \ 'mode': 'nl', + \ 'exit_cb': function('s:HandleExit'), + \} + + if l:output_stream is# 'stderr' + let l:job_options.err_cb = function('s:GatherOutput') + elseif l:output_stream is# 'both' + let l:job_options.out_cb = function('s:GatherOutput') + let l:job_options.err_cb = function('s:GatherOutput') + else + let l:job_options.out_cb = function('s:GatherOutput') + endif + + if get(g:, 'ale_run_synchronously') == 1 + " Find a unique Job value to use, which will be the same as the ID for + " running commands synchronously. This is only for test code. + let l:job_id = len(s:job_info_map) + 1 + + while has_key(s:job_info_map, l:job_id) + let l:job_id += 1 + endwhile + else + let l:job_id = ale#job#Start(l:command, l:job_options) + endif + + let l:status = 'failed' + + " Only proceed if the job is being run. + if l:job_id + " Add the job to the list of jobs, so we can track them. + call add(l:info.job_list, l:job_id) + + if index(l:info.active_linter_list, l:linter.name) < 0 + call add(l:info.active_linter_list, l:linter.name) + endif + + let l:status = 'started' " Store the ID for the job in the map to read back again. - let s:job_info_map[s:GetJobID(l:job)] = { + let s:job_info_map[l:job_id] = { \ 'linter': l:linter, \ 'buffer': l:buffer, \ 'output': [], \ 'next_chain_index': l:next_chain_index, \} - - if l:read_buffer - if has('nvim') - " In NeoVim, we have to send the buffer lines ourselves. - let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n" - - call jobsend(l:job, l:input) - call jobclose(l:job, 'stdin') - elseif has('win32') - " On some Vim versions, we have to send the buffer data ourselves. - let l:input = join(getbufline(l:buffer, 1, '$'), "\n") . "\n" - let l:channel = job_getchannel(l:job) - - if ch_status(l:channel) ==# 'open' - call ch_sendraw(l:channel, l:input) - call ch_close_in(l:channel) - endif - endif - endif endif + + if g:ale_history_enabled + call ale#history#Add(l:buffer, l:status, l:job_id, l:command) + endif + + if get(g:, 'ale_run_synchronously') == 1 + " Run a command synchronously if this test option is set. + let s:job_info_map[l:job_id].output = systemlist( + \ type(l:command) == type([]) + \ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2]) + \ : l:command + \) + + call l:job_options.exit_cb(l:job_id, v:shell_error) + endif + + return l:job_id != 0 endfunction -function! s:InvokeChain(buffer, linter, chain_index, input) abort +" Determine which commands to run for a link in a command chain, or +" just a regular command. +function! ale#engine#ProcessChain(buffer, linter, chain_index, input) abort let l:output_stream = get(a:linter, 'output_stream', 'stdout') + let l:read_buffer = a:linter.read_buffer let l:chain_index = a:chain_index let l:input = a:input if has_key(a:linter, 'command_chain') while l:chain_index < len(a:linter.command_chain) - " Run a chain of commands, one asychronous command after the other, + " Run a chain of commands, one asynchronous command after the other, " so that many programs can be run in a sequence. let l:chain_item = a:linter.command_chain[l:chain_index] - " The chain item can override the output_stream option. - if has_key(l:chain_item, 'output_stream') - let l:output_stream = l:chain_item.output_stream - endif - if l:chain_index == 0 " The first callback in the chain takes only a buffer number. let l:command = ale#util#GetFunction(l:chain_item.callback)( @@ -348,6 +624,21 @@ function! s:InvokeChain(buffer, linter, chain_index, input) abort if !empty(l:command) " We hit a command to run, so we'll execute that + + " The chain item can override the output_stream option. + if has_key(l:chain_item, 'output_stream') + let l:output_stream = l:chain_item.output_stream + endif + + " The chain item can override the read_buffer option. + if has_key(l:chain_item, 'read_buffer') + let l:read_buffer = l:chain_item.read_buffer + elseif l:chain_index != len(a:linter.command_chain) - 1 + " Don't read the buffer for commands besides the last one + " in the chain by default. + let l:read_buffer = 0 + endif + break endif @@ -357,33 +648,212 @@ function! s:InvokeChain(buffer, linter, chain_index, input) abort let l:input = [] let l:chain_index += 1 endwhile - - if empty(l:command) - " Don't run any jobs if the last command is an empty string. - return - endif - elseif has_key(a:linter, 'command_callback') - " If there is a callback for generating a command, call that instead. - let l:command = ale#util#GetFunction(a:linter.command_callback)(a:buffer) else - let l:command = a:linter.command + let l:command = ale#linter#GetCommand(a:buffer, a:linter) endif - let l:is_last_job = l:chain_index >= len(get(a:linter, 'command_chain', [])) - 1 - - call s:RunJob(l:command, { + return { + \ 'command': l:command, \ 'buffer': a:buffer, \ 'linter': a:linter, \ 'output_stream': l:output_stream, \ 'next_chain_index': l:chain_index + 1, - \ 'read_buffer': l:is_last_job, - \}) + \ 'read_buffer': l:read_buffer, + \} endfunction -function! ale#engine#Invoke(buffer, linter) abort - " Stop previous jobs for the same linter. - call s:StopPreviousJobs(a:buffer, a:linter) - call s:InvokeChain(a:buffer, a:linter, 0, []) +function! s:InvokeChain(buffer, linter, chain_index, input) abort + let l:options = ale#engine#ProcessChain(a:buffer, a:linter, a:chain_index, a:input) + + return s:RunJob(l:options) +endfunction + +function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort + let l:info = get(g:ale_buffer_info, a:buffer, {}) + let l:new_job_list = [] + let l:new_active_linter_list = [] + + for l:job_id in get(l:info, 'job_list', []) + let l:job_info = get(s:job_info_map, l:job_id, {}) + + if !empty(l:job_info) + if a:include_lint_file_jobs || !l:job_info.linter.lint_file + call ale#job#Stop(l:job_id) + call remove(s:job_info_map, l:job_id) + else + call add(l:new_job_list, l:job_id) + " Linters with jobs still running are still active. + call add(l:new_active_linter_list, l:job_info.linter.name) + endif + endif + endfor + + " Remove duplicates from the active linter list. + call uniq(sort(l:new_active_linter_list)) + + " Update the List, so it includes only the jobs we still need. + let l:info.job_list = l:new_job_list + " Update the active linter list, clearing out anything not running. + let l:info.active_linter_list = l:new_active_linter_list +endfunction + +function! s:CheckWithLSP(buffer, linter) abort + let l:info = g:ale_buffer_info[a:buffer] + let l:lsp_details = ale#linter#StartLSP( + \ a:buffer, + \ a:linter, + \ function('ale#engine#HandleLSPResponse'), + \) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root + + " Remember the linter this connection is for. + let s:lsp_linter_map[l:id] = a:linter.name + + let l:change_message = a:linter.lsp is# 'tsserver' + \ ? ale#lsp#tsserver_message#Geterr(a:buffer) + \ : ale#lsp#message#DidChange(a:buffer) + let l:request_id = ale#lsp#Send(l:id, l:change_message, l:root) + + " If this was a file save event, also notify the server of that. + if a:linter.lsp isnot# 'tsserver' + \&& getbufvar(a:buffer, 'ale_save_event_fired', 0) + let l:save_message = ale#lsp#message#DidSave(a:buffer) + let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root) + endif + + if l:request_id != 0 + if index(l:info.active_linter_list, a:linter.name) < 0 + call add(l:info.active_linter_list, a:linter.name) + endif + endif + + return l:request_id != 0 +endfunction + +function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort + " Figure out which linters are still enabled, and remove + " problems for linters which are no longer enabled. + 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(l:name_map, get(v:val, ''linter_name''))', + \) +endfunction + +function! s:AddProblemsFromOtherBuffers(buffer, linters) abort + let l:filename = expand('#' . a:buffer . ':p') + let l:loclist = [] + let l:name_map = {} + + " Build a map of the active linters. + for l:linter in a:linters + let l:name_map[l:linter.name] = 1 + endfor + + " Find the items from other buffers, for the linters that are enabled. + for l:info in values(g:ale_buffer_info) + for l:item in l:info.loclist + if has_key(l:item, 'filename') + \&& l:item.filename is# l:filename + \&& has_key(l:name_map, l:item.linter_name) + " Copy the items and set the buffer numbers to this one. + let l:new_item = copy(l:item) + let l:new_item.bufnr = a:buffer + call add(l:loclist, l:new_item) + endif + endfor + endfor + + if !empty(l:loclist) + call sort(l:loclist, function('ale#util#LocItemCompareWithText')) + call uniq(l:loclist, function('ale#util#LocItemCompareWithText')) + + " Set the loclist variable, used by some parts of ALE. + let g:ale_buffer_info[a:buffer].loclist = l:loclist + call ale#engine#SetResults(a:buffer, l:loclist) + endif +endfunction + +" Run a linter for a buffer. +" +" Returns 1 if the linter was successfully run. +function! s:RunLinter(buffer, linter) abort + if !empty(a:linter.lsp) + return s:CheckWithLSP(a:buffer, a:linter) + else + let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) + + if ale#engine#IsExecutable(a:buffer, l:executable) + return s:InvokeChain(a:buffer, a:linter, 0, []) + endif + endif + + return 0 +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:StopCurrentJobs(a:buffer, a:should_lint_file) + 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:linter in a:linters + " Only run lint_file linters if we should. + if !l:linter.lint_file || a:should_lint_file + if s:RunLinter(a:buffer, l:linter) + " 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 l:new_buffer + call s:AddProblemsFromOtherBuffers(a:buffer, a:linters) + endif +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 !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. @@ -402,7 +872,7 @@ endfunction " The time taken will be a very rough approximation, and more time may be " permitted than is specified. function! ale#engine#WaitForJobs(deadline) abort - let l:start_time = system('date +%s%3N') + 0 + let l:start_time = ale#util#ClockMilliseconds() if l:start_time == 0 throw 'Failed to read milliseconds from the clock!' @@ -412,17 +882,28 @@ function! ale#engine#WaitForJobs(deadline) abort " Gather all of the jobs from every buffer. for l:info in values(g:ale_buffer_info) - call extend(l:job_list, l:info.job_list) + call extend(l:job_list, get(l:info, 'job_list', [])) endfor + " NeoVim has a built-in API for this, so use that. + if has('nvim') + let l:nvim_code_list = jobwait(l:job_list, a:deadline) + + if index(l:nvim_code_list, -1) >= 0 + throw 'Jobs did not complete on time!' + endif + + return + endif + let l:should_wait_more = 1 while l:should_wait_more let l:should_wait_more = 0 - for l:job in l:job_list - if job_status(l:job) ==# 'run' - let l:now = system('date +%s%3N') + 0 + for l:job_id in l:job_list + if ale#job#IsRunning(l:job_id) + let l:now = ale#util#ClockMilliseconds() if l:now - l:start_time > a:deadline " Stop waiting after a timeout, so we don't wait forever. @@ -447,15 +928,19 @@ function! ale#engine#WaitForJobs(deadline) abort " for command_chain linters. let l:has_new_jobs = 0 + " Check again to see if any jobs are running. for l:info in values(g:ale_buffer_info) - if !empty(l:info.job_list) - let l:has_new_jobs = 1 - endif + for l:job_id in get(l:info, 'job_list', []) + if ale#job#IsRunning(l:job_id) + let l:has_new_jobs = 1 + break + endif + endfor endfor if l:has_new_jobs " We have to wait more. Offset the timeout by the time taken so far. - let l:now = system('date +%s%3N') + 0 + let l:now = ale#util#ClockMilliseconds() let l:new_deadline = a:deadline - (l:now - l:start_time) if l:new_deadline <= 0 diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim new file mode 100644 index 0000000..c7d17ea --- /dev/null +++ b/autoload/ale/events.vim @@ -0,0 +1,69 @@ +" Author: w0rp + +function! ale#events#QuitEvent(buffer) abort + " Remember when ALE is quitting for BufWrite, etc. + call setbufvar(a:buffer, 'ale_quitting', ale#util#ClockMilliseconds()) +endfunction + +function! ale#events#QuitRecently(buffer) abort + let l:time = getbufvar(a:buffer, 'ale_quitting', 0) + + return l:time && ale#util#ClockMilliseconds() - l:time < 1000 +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') + let l:will_fix = ale#fix#Fix('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! s:LintOnEnter(buffer) abort + if ale#Var(a:buffer, 'enabled') + \&& g:ale_lint_on_enter + \&& has_key(b:, 'ale_file_changed') + call remove(b:, 'ale_file_changed') + call ale#Queue(0, 'lint_file', a:buffer) + endif +endfunction + +function! ale#events#EnterEvent(buffer) abort + " 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) + + call s:LintOnEnter(a:buffer) +endfunction + +function! ale#events#FileTypeEvent(buffer, new_filetype) abort + let l:filetype = getbufvar(a:buffer, 'ale_original_filetype', '') + + " If we're setting the filetype for the first time after it was blank, + " and the option for linting on enter is off, then we should set this + " filetype as the original filetype. Otherwise ALE will still appear to + " lint files because of the BufEnter event, etc. + if empty(l:filetype) && !ale#Var(a:buffer, 'lint_on_enter') + call setbufvar(a:buffer, 'ale_original_filetype', a:new_filetype) + elseif a:new_filetype isnot# l:filetype + call ale#Queue(300, 'lint_file', a:buffer) + endif +endfunction + +function! ale#events#FileChangedEvent(buffer) abort + call setbufvar(a:buffer, 'ale_file_changed', 1) + + if bufnr('') == a:buffer + call s:LintOnEnter(a:buffer) + endif +endfunction diff --git a/autoload/ale/filetypes.vim b/autoload/ale/filetypes.vim new file mode 100644 index 0000000..6174aa0 --- /dev/null +++ b/autoload/ale/filetypes.vim @@ -0,0 +1,60 @@ +" Author: w0rp +" Description: This file handles guessing file extensions for filetypes, etc. + +function! ale#filetypes#LoadExtensionMap() abort + " Output includes: + " '*.erl setf erlang' + redir => l:output + silent exec 'autocmd' + redir end + + 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 0000000..9111db3 --- /dev/null +++ b/autoload/ale/fix.vim @@ -0,0 +1,480 @@ +" This global Dictionary tracks the ALE fix data for jobs, etc. +" This Dictionary should not be accessed outside of the plugin. It is only +" global so it can be modified in Vader tests. +if !has_key(g:, 'ale_fix_buffer_data') + let g:ale_fix_buffer_data = {} +endif + +if !has_key(s:, 'job_info_map') + let s:job_info_map = {} +endif + +function! s:GatherOutput(job_id, line) abort + if has_key(s:job_info_map, a:job_id) + call add(s:job_info_map[a:job_id].output, a:line) + endif +endfunction + +" Apply fixes queued up for buffers which may be hidden. +" Vim doesn't let you modify hidden buffers. +function! ale#fix#ApplyQueuedFixes() abort + let l:buffer = bufnr('') + let l:data = get(g:ale_fix_buffer_data, l:buffer, {'done': 0}) + + if !l:data.done + return + endif + + call remove(g:ale_fix_buffer_data, l:buffer) + + if l:data.changes_made + call setline(1, l:data.output) + + let l:start_line = len(l:data.output) + 1 + let l:end_line = len(l:data.lines_before) + + if l:end_line >= l:start_line + let l:save = winsaveview() + silent execute l:start_line . ',' . l:end_line . 'd_' + call winrestview(l:save) + endif + + if l:data.should_save + if empty(&buftype) + noautocmd :w! + else + set nomodified + endif + endif + endif + + if l:data.should_save + let l:should_lint = g:ale_fix_on_save + else + let l:should_lint = l:data.changes_made + endif + + " 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(l:buffer) + call ale#Queue(0, l:data.should_save ? 'lint_file' : '') + endif +endfunction + +function! ale#fix#ApplyFixes(buffer, output) abort + call ale#fix#RemoveManagedFiles(a:buffer) + + let l:data = g:ale_fix_buffer_data[a:buffer] + let l:data.output = a:output + let l:data.changes_made = l:data.lines_before != l:data.output + + 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) + execute 'echoerr ''The file was changed before fixing finished''' + return + endif + endif + + if !bufexists(a:buffer) + " Remove the buffer data when it doesn't exist. + call remove(g:ale_fix_buffer_data, a:buffer) + endif + + let l:data.done = 1 + + " We can only change the lines of a buffer which is currently open, + " so try and apply the fixes to the current buffer. + call ale#fix#ApplyQueuedFixes() +endfunction + +function! s:HandleExit(job_id, exit_code) abort + if !has_key(s:job_info_map, a:job_id) + return + endif + + let l:job_info = remove(s:job_info_map, a:job_id) + let l:buffer = l:job_info.buffer + + if g:ale_history_enabled + call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code) + endif + + if has_key(l:job_info, 'file_to_read') + let l:job_info.output = readfile(l:job_info.file_to_read) + endif + + let l:ChainCallback = get(l:job_info, 'chain_with', v:null) + let l:ProcessWith = get(l:job_info, 'process_with', v:null) + + " Post-process the output with a function if we have one. + if l:ProcessWith isnot v:null + let l:job_info.output = call( + \ ale#util#GetFunction(l:ProcessWith), + \ [l:buffer, l:job_info.output] + \) + endif + + " Use the output of the job for changing the file if it isn't empty, + " otherwise skip this job and use the input from before. + " + " We'll use the input from before for chained commands. + if l:ChainCallback is v:null && !empty(split(join(l:job_info.output))) + let l:input = l:job_info.output + else + let l:input = l:job_info.input + endif + + let l:next_index = l:ChainCallback is v:null + \ ? l:job_info.callback_index + 1 + \ : l:job_info.callback_index + + call s:RunFixer({ + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'output': l:job_info.output, + \ 'callback_list': l:job_info.callback_list, + \ 'callback_index': l:next_index, + \ 'chain_callback': l:ChainCallback, + \}) +endfunction + +function! ale#fix#ManageDirectory(buffer, directory) abort + call add(g:ale_fix_buffer_data[a:buffer].temporary_directory_list, a:directory) +endfunction + +function! ale#fix#RemoveManagedFiles(buffer) abort + if !has_key(g:ale_fix_buffer_data, a:buffer) + return + endif + + " We can't delete anything in a sandbox, so wait until we escape from + " it to delete temporary files and directories. + if ale#util#InSandbox() + return + endif + + " Delete directories like `rm -rf`. + " Directories are handled differently from files, so paths that are + " intended to be single files can be set up for automatic deletion without + " accidentally deleting entire directories. + for l:directory in g:ale_fix_buffer_data[a:buffer].temporary_directory_list + call delete(l:directory, 'rf') + endfor + + let g:ale_fix_buffer_data[a:buffer].temporary_directory_list = [] +endfunction + +function! s:CreateTemporaryFileForJob(buffer, temporary_file, input) abort + if empty(a:temporary_file) + " There is no file, so we didn't create anything. + return 0 + endif + + let l:temporary_directory = fnamemodify(a:temporary_file, ':h') + " Create the temporary directory for the file, unreadable by 'other' + " users. + call mkdir(l:temporary_directory, '', 0750) + " Automatically delete the directory later. + call ale#fix#ManageDirectory(a:buffer, l:temporary_directory) + " Write the buffer out to a file. + call ale#util#Writefile(a:buffer, a:input, a:temporary_file) + + return 1 +endfunction + +function! s:RunJob(options) abort + let l:buffer = a:options.buffer + let l:command = a:options.command + let l:input = a:options.input + let l:output_stream = a:options.output_stream + let l:read_temporary_file = a:options.read_temporary_file + let l:ChainWith = a:options.chain_with + let l:read_buffer = a:options.read_buffer + + if empty(l:command) + " If there's nothing further to chain the command with, stop here. + if l:ChainWith is v:null + return 0 + endif + + " If there's another chained callback to run, then run that. + call s:RunFixer({ + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'callback_index': a:options.callback_index, + \ 'callback_list': a:options.callback_list, + \ 'chain_callback': l:ChainWith, + \ 'output': [], + \}) + + return 1 + endif + + let [l:temporary_file, l:command] = ale#command#FormatCommand( + \ l:buffer, + \ l:command, + \ l:read_buffer, + \) + call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input) + + let l:command = ale#job#PrepareCommand(l:buffer, l:command) + let l:job_options = { + \ 'mode': 'nl', + \ 'exit_cb': function('s:HandleExit'), + \} + + let l:job_info = { + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'output': [], + \ 'chain_with': l:ChainWith, + \ 'callback_index': a:options.callback_index, + \ 'callback_list': a:options.callback_list, + \ 'process_with': a:options.process_with, + \} + + if l:read_temporary_file + " TODO: Check that a temporary file is set here. + let l:job_info.file_to_read = l:temporary_file + elseif l:output_stream is# 'stderr' + let l:job_options.err_cb = function('s:GatherOutput') + elseif l:output_stream is# 'both' + let l:job_options.out_cb = function('s:GatherOutput') + let l:job_options.err_cb = function('s:GatherOutput') + else + let l:job_options.out_cb = function('s:GatherOutput') + endif + + if get(g:, 'ale_emulate_job_failure') == 1 + let l:job_id = 0 + elseif get(g:, 'ale_run_synchronously') == 1 + " Find a unique Job value to use, which will be the same as the ID for + " running commands synchronously. This is only for test code. + let l:job_id = len(s:job_info_map) + 1 + + while has_key(s:job_info_map, l:job_id) + let l:job_id += 1 + endwhile + else + let l:job_id = ale#job#Start(l:command, l:job_options) + endif + + let l:status = l:job_id ? 'started' : 'failed' + + if g:ale_history_enabled + call ale#history#Add(l:buffer, l:status, l:job_id, l:command) + endif + + if l:job_id == 0 + return 0 + endif + + let s:job_info_map[l:job_id] = l:job_info + + if get(g:, 'ale_run_synchronously') == 1 + " Run a command synchronously if this test option is set. + let l:output = systemlist( + \ type(l:command) == type([]) + \ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2]) + \ : l:command + \) + + if !l:read_temporary_file + let s:job_info_map[l:job_id].output = l:output + endif + + call l:job_options.exit_cb(l:job_id, v:shell_error) + endif + + return 1 +endfunction + +function! s:RunFixer(options) abort + let l:buffer = a:options.buffer + let l:input = a:options.input + let l:index = a:options.callback_index + let l:ChainCallback = get(a:options, 'chain_callback', v:null) + + while len(a:options.callback_list) > l:index + let l:Function = l:ChainCallback isnot v:null + \ ? ale#util#GetFunction(l:ChainCallback) + \ : a:options.callback_list[l:index] + + if l:ChainCallback isnot v:null + " Chained commands accept (buffer, output, [input]) + let l:result = ale#util#FunctionArgCount(l:Function) == 2 + \ ? call(l:Function, [l:buffer, a:options.output]) + \ : call(l:Function, [l:buffer, a:options.output, copy(l:input)]) + else + " Chained commands accept (buffer, [input]) + let l:result = ale#util#FunctionArgCount(l:Function) == 1 + \ ? call(l:Function, [l:buffer]) + \ : call(l:Function, [l:buffer, copy(l:input)]) + endif + + if type(l:result) == type(0) && l:result == 0 + " When `0` is returned, skip this item. + let l:index += 1 + elseif type(l:result) == type([]) + let l:input = l:result + let l:index += 1 + else + let l:ChainWith = get(l:result, 'chain_with', v:null) + " Default to piping the buffer for the last fixer in the chain. + let l:read_buffer = get(l:result, 'read_buffer', l:ChainWith is v:null) + + let l:job_ran = s:RunJob({ + \ 'buffer': l:buffer, + \ 'command': l:result.command, + \ 'input': l:input, + \ 'output_stream': get(l:result, 'output_stream', 'stdout'), + \ 'read_temporary_file': get(l:result, 'read_temporary_file', 0), + \ 'read_buffer': l:read_buffer, + \ 'chain_with': l:ChainWith, + \ 'callback_list': a:options.callback_list, + \ 'callback_index': l:index, + \ 'process_with': get(l:result, 'process_with', v:null), + \}) + + if !l:job_ran + " The job failed to run, so skip to the next item. + let l:index += 1 + else + " Stop here, we will handle exit later on. + return + endif + endif + endwhile + + call ale#fix#ApplyFixes(l:buffer, l:input) +endfunction + +function! s:GetCallbacks() abort + if type(get(b:, 'ale_fixers')) is type([]) + " Lists can be used for buffer-local variables only + let l:callback_list = b:ale_fixers + else + " buffer and global options can use dictionaries mapping filetypes to + " callbacks to run. + let l:fixers = ale#Var(bufnr(''), 'fixers') + let l:callback_list = [] + + for l:sub_type in split(&filetype, '\.') + let l:sub_type_callacks = get(l:fixers, l:sub_type, []) + + if type(l:sub_type_callacks) == type('') + call add(l:callback_list, l:sub_type_callacks) + else + call extend(l:callback_list, l:sub_type_callacks) + endif + endfor + endif + + if empty(l:callback_list) + return [] + 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 + if type(l:Item) == type('') + let l:Func = ale#fix#registry#GetFunc(l:Item) + + if !empty(l:Func) + let l:Item = l:Func + endif + endif + + try + call add(l:corrected_list, ale#util#GetFunction(l:Item)) + catch /E475/ + " Rethrow exceptions for failing to get a function so we can print + " a friendly message about it. + throw 'BADNAME ' . v:exception + endtry + endfor + + return l:corrected_list +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] = { + \ 'vars': getbufvar(a:buffer, ''), + \ 'lines_before': getbufline(a:buffer, 1, '$'), + \ 'filename': expand('#' . a:buffer . ':p'), + \ 'done': 0, + \ 'should_save': a:fixing_flag is# 'save_file', + \ '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(...) abort + if len(a:0) > 1 + throw 'too many arguments!' + endif + + let l:fixing_flag = get(a:000, 0, '') + + if l:fixing_flag isnot# '' && l:fixing_flag isnot# 'save_file' + throw "fixing_flag must be either '' or 'save_file'" + endif + + try + let l:callback_list = s:GetCallbacks() + catch /E700\|BADNAME/ + let l:function_name = join(split(split(v:exception, ':')[3])) + let l:echo_message = printf( + \ 'There is no fixer named `%s`. Check :ALEFixSuggest', + \ l:function_name, + \) + execute 'echom l:echo_message' + + return 0 + endtry + + if empty(l:callback_list) + if l:fixing_flag is# '' + execute 'echom ''No fixers have been defined. Try :ALEFixSuggest''' + endif + + return 0 + endif + + let l:buffer = bufnr('') + + for l:job_id in keys(s:job_info_map) + call remove(s:job_info_map, l:job_id) + call ale#job#Stop(l:job_id) + endfor + + " Clean up any files we might have left behind from a previous run. + call ale#fix#RemoveManagedFiles(l:buffer) + call ale#fix#InitBufferData(l:buffer, l:fixing_flag) + + call s:RunFixer({ + \ 'buffer': l:buffer, + \ 'input': g:ale_fix_buffer_data[l: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() +augroup END diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim new file mode 100644 index 0000000..3e407c5 --- /dev/null +++ b/autoload/ale/fix/registry.vim @@ -0,0 +1,343 @@ +" 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.', +\ }, +\ 'align_help_tags': { +\ 'function': 'ale#fixers#help#AlignTags', +\ 'suggested_filetypes': ['help'], +\ 'description': 'Align help tags to the right margin', +\ }, +\ 'autopep8': { +\ 'function': 'ale#fixers#autopep8#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix PEP8 issues with autopep8.', +\ }, +\ 'prettier_standard': { +\ 'function': 'ale#fixers#prettier_standard#Fix', +\ 'suggested_filetypes': ['javascript'], +\ 'description': 'Apply prettier-standard to a file.', +\ 'aliases': ['prettier-standard'], +\ }, +\ 'eslint': { +\ 'function': 'ale#fixers#eslint#Fix', +\ 'suggested_filetypes': ['javascript', 'typescript'], +\ '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.', +\ }, +\ 'format': { +\ 'function': 'ale#fixers#format#Fix', +\ 'suggested_filetypes': ['elm'], +\ 'description': 'Apply elm-format to a file.', +\ }, +\ 'isort': { +\ 'function': 'ale#fixers#isort#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Sort Python imports with isort.', +\ }, +\ 'prettier': { +\ 'function': 'ale#fixers#prettier#Fix', +\ 'suggested_filetypes': ['javascript', 'typescript', 'json', 'css', 'scss', 'less', 'markdown', 'graphql', 'vue'], +\ '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'], +\ }, +\ '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.', +\ }, +\ 'yapf': { +\ 'function': 'ale#fixers#yapf#Fix', +\ 'suggested_filetypes': ['python'], +\ 'description': 'Fix Python files with yapf.', +\ }, +\ 'rubocop': { +\ 'function': 'ale#fixers#rubocop#Fix', +\ 'suggested_filetypes': ['ruby'], +\ 'description': 'Fix ruby files with rubocop --auto-correct.', +\ }, +\ 'standard': { +\ 'function': 'ale#fixers#standard#Fix', +\ 'suggested_filetypes': ['javascript'], +\ 'description': 'Fix JavaScript files using standard --fix', +\ }, +\ 'stylelint': { +\ 'function': 'ale#fixers#stylelint#Fix', +\ 'suggested_filetypes': ['css', 'sass', 'scss', 'stylus'], +\ 'description': 'Fix stylesheet files using stylelint --fix.', +\ }, +\ 'swiftformat': { +\ 'function': 'ale#fixers#swiftformat#Fix', +\ 'suggested_filetypes': ['swift'], +\ 'description': 'Apply SwiftFormat to a file.', +\ }, +\ 'phpcbf': { +\ 'function': 'ale#fixers#phpcbf#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix PHP files with phpcbf.', +\ }, +\ 'php_cs_fixer': { +\ 'function': 'ale#fixers#php_cs_fixer#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix PHP files with php-cs-fixer.', +\ }, +\ 'clang-format': { +\ 'function': 'ale#fixers#clangformat#Fix', +\ 'suggested_filetypes': ['c', 'cpp'], +\ 'description': 'Fix C/C++ files with clang-format.', +\ }, +\ 'gofmt': { +\ 'function': 'ale#fixers#gofmt#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go files with go fmt.', +\ }, +\ 'goimports': { +\ 'function': 'ale#fixers#goimports#Fix', +\ 'suggested_filetypes': ['go'], +\ 'description': 'Fix Go files imports with goimports.', +\ }, +\ '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.', +\ }, +\ 'hackfmt': { +\ 'function': 'ale#fixers#hackfmt#Fix', +\ 'suggested_filetypes': ['php'], +\ 'description': 'Fix Hack files with hackfmt.', +\ }, +\ '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.', +\ }, +\ 'refmt': { +\ 'function': 'ale#fixers#refmt#Fix', +\ 'suggested_filetypes': ['reason'], +\ 'description': 'Fix ReasonML files with refmt.', +\ }, +\ 'shfmt': { +\ 'function': 'ale#fixers#shfmt#Fix', +\ 'suggested_filetypes': ['sh'], +\ 'description': 'Fix sh files with shfmt.', +\ }, +\ '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.', +\ }, +\} + +" 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 + if type(a:name) != type('') + throw '''name'' must be a String' + endif + + if type(a:func) != type('') + throw '''func'' must be a String' + endif + + if type(a:filetypes) != type([]) + throw '''filetypes'' must be a List' + endif + + for l:type in a:filetypes + if type(l:type) != type('') + throw 'Each entry of ''filetypes'' must be a String' + endif + endfor + + if type(a:desc) != type('') + throw '''desc'' must be a String' + endif + + let l:aliases = get(a:000, 0, []) + + if type(l:aliases) != type([]) + \|| !empty(filter(copy(l:aliases), 'type(v:val) != type('''')')) + 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: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 + +" Suggest functions to use from the registry. +function! ale#fix#registry#Suggest(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 empty(s:entries[l:key].suggested_filetypes) + call add( + \ l:generic_fixer_list, + \ s:FormatEntry(l:key, s:entries[l:key]), + \) + endif + endfor + + 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/autopep8.vim b/autoload/ale/fixers/autopep8.vim new file mode 100644 index 0000000..e2dd7bf --- /dev/null +++ b/autoload/ale/fixers/autopep8.vim @@ -0,0 +1,26 @@ +" Author: w0rp +" Description: Fixing files with autopep8. + +call ale#Set('python_autopep8_executable', 'autopep8') +call ale#Set('python_autopep8_use_global', 0) +call ale#Set('python_autopep8_options', '') + +function! ale#fixers#autopep8#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'python_autopep8', + \ ['autopep8'], + \) + + if !executable(l:executable) + return 0 + endif + + let l:options = ale#Var(a:buffer, 'python_autopep8_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/brittany.vim b/autoload/ale/fixers/brittany.vim new file mode 100644 index 0000000..fed5eb8 --- /dev/null +++ b/autoload/ale/fixers/brittany.vim @@ -0,0 +1,15 @@ +" Author: eborden +" Description: Integration of brittany with ALE. + +call ale#Set('haskell_brittany_executable', 'brittany') + +function! ale#fixers#brittany#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_brittany_executable') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/clangformat.vim b/autoload/ale/fixers/clangformat.vim new file mode 100644 index 0000000..b50b704 --- /dev/null +++ b/autoload/ale/fixers/clangformat.vim @@ -0,0 +1,22 @@ +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', 0) +call ale#Set('c_clangformat_options', '') + +function! ale#fixers#clangformat#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'c_clangformat', [ + \ 'clang-format', + \]) +endfunction + +function! ale#fixers#clangformat#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'c_clangformat_options') + + return { + \ 'command': ale#Escape(ale#fixers#clangformat#GetExecutable(a:buffer)) + \ . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/eslint.vim b/autoload/ale/fixers/eslint.vim new file mode 100644 index 0000000..36f4751 --- /dev/null +++ b/autoload/ale/fixers/eslint.vim @@ -0,0 +1,70 @@ +" 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#semver#HasVersion(l:executable) + \ ? '' + \ : ale#node#Executable(a:buffer, l:executable) . ' --version' + + return { + \ 'command': l:command, + \ 'chain_with': '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 =~# '^Error:' + return [] + endif + endfor + + return a:output +endfunction + +function! ale#fixers#eslint#ApplyFixForVersion(buffer, version_output) abort + let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) + let l:version = ale#semver#GetVersion(l:executable, a:version_output) + + let l:config = ale#handlers#eslint#FindConfig(a:buffer) + + if empty(l:config) + return 0 + endif + + " Use --fix-to-stdout with eslint_d + if l:executable =~# 'eslint_d$' && ale#semver#GTE(l:version, [3, 19, 0]) + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ' --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(l:version, [4, 9, 0]) + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \} + endif + + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ' -c ' . ale#Escape(l:config) + \ . ' --fix %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 0000000..43eb063 --- /dev/null +++ b/autoload/ale/fixers/fixjson.vim @@ -0,0 +1,27 @@ +" 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', 0) + +function! ale#fixers#fixjson#GetExecutable(buffer) abort + return ale#node#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/format.vim b/autoload/ale/fixers/format.vim new file mode 100644 index 0000000..be130f0 --- /dev/null +++ b/autoload/ale/fixers/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', 0) +call ale#Set('elm_format_options', '--yes') + +function! ale#fixers#format#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'elm_format', [ + \ 'node_modules/.bin/elm-format', + \]) +endfunction + +function! ale#fixers#format#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'elm_format_options') + + return { + \ 'command': ale#Escape(ale#fixers#format#GetExecutable(a:buffer)) + \ . ' %t' + \ . (empty(l:options) ? '' : ' ' . l:options), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/generic.vim b/autoload/ale/fixers/generic.vim new file mode 100644 index 0000000..cb8865b --- /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 0000000..124146b --- /dev/null +++ b/autoload/ale/fixers/generic_python.vim @@ -0,0 +1,60 @@ +" 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 + + for l:line in a:lines + let l:indent_size = len(matchstr(l:line, '^ *')) + + if !l:last_line_is_blank + \&& 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/gofmt.vim b/autoload/ale/fixers/gofmt.vim new file mode 100644 index 0000000..66b67a9 --- /dev/null +++ b/autoload/ale/fixers/gofmt.vim @@ -0,0 +1,18 @@ +" 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') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -l -w' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/goimports.vim b/autoload/ale/fixers/goimports.vim new file mode 100644 index 0000000..783d020 --- /dev/null +++ b/autoload/ale/fixers/goimports.vim @@ -0,0 +1,22 @@ +" 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') + + if !executable(l:executable) + return 0 + endif + + return { + \ 'command': 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/google_java_format.vim b/autoload/ale/fixers/google_java_format.vim new file mode 100644 index 0000000..92632e8 --- /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('google_java_format_executable', 'google-java-format') +call ale#Set('google_java_format_use_global', 0) +call ale#Set('google_java_format_options', '') + +function! ale#fixers#google_java_format#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'google_java_format_options') + let l:executable = ale#Var(a:buffer, '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/hackfmt.vim b/autoload/ale/fixers/hackfmt.vim new file mode 100644 index 0000000..b5bf0dc --- /dev/null +++ b/autoload/ale/fixers/hackfmt.vim @@ -0,0 +1,18 @@ +" Author: Sam Howie +" Description: Integration of hackfmt with ALE. + +call ale#Set('php_hackfmt_executable', 'hackfmt') +call ale#Set('php_hackfmt_options', '') + +function! ale#fixers#hackfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'php_hackfmt_executable') + let l:options = ale#Var(a:buffer, 'php_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 0000000..b20740f --- /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 0000000..ea061da --- /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#Escape(l:executable) + \ . ' -w' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/importjs.vim b/autoload/ale/fixers/importjs.vim new file mode 100644 index 0000000..e8eedb1 --- /dev/null +++ b/autoload/ale/fixers/importjs.vim @@ -0,0 +1,24 @@ +" Author: Jeff Willette +" Description: Integration of importjs with ALE. + +call ale#Set('js_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, 'js_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 0000000..b631822 --- /dev/null +++ b/autoload/ale/fixers/isort.vim @@ -0,0 +1,22 @@ +" Author: w0rp +" Description: Fixing Python imports with isort. + +call ale#Set('python_isort_executable', 'isort') +call ale#Set('python_isort_use_global', 0) + +function! ale#fixers#isort#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'python_isort', + \ ['isort'], + \) + + if !executable(l:executable) + return 0 + endif + + return { + \ 'command': ale#path#BufferCdString(a:buffer) + \ . ale#Escape(l:executable) . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/jq.vim b/autoload/ale/fixers/jq.vim new file mode 100644 index 0000000..b0a43fe --- /dev/null +++ b/autoload/ale/fixers/jq.vim @@ -0,0 +1,15 @@ +call ale#Set('json_jq_executable', 'jq') +call ale#Set('json_jq_options', '') + +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') + + return { + \ 'command': ale#Escape(ale#fixers#jq#GetExecutable(a:buffer)) + \ . ' . ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/mix_format.vim b/autoload/ale/fixers/mix_format.vim new file mode 100644 index 0000000..0486640 --- /dev/null +++ b/autoload/ale/fixers/mix_format.vim @@ -0,0 +1,16 @@ +" Author: carakan +" Description: Fixing files with elixir formatter 'mix format'. + +call ale#Set('elixir_mix_executable', 'mix') + +function! ale#fixers#mix_format#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'elixir_mix_executable') +endfunction + +function! ale#fixers#mix_format#Fix(buffer) abort + return { + \ 'command': ale#Escape(ale#fixers#mix_format#GetExecutable(a:buffer)) + \ . ' format %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/php_cs_fixer.vim b/autoload/ale/fixers/php_cs_fixer.vim new file mode 100644 index 0000000..56aa915 --- /dev/null +++ b/autoload/ale/fixers/php_cs_fixer.vim @@ -0,0 +1,23 @@ +" 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', 0) + +function! ale#fixers#php_cs_fixer#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'php_cs_fixer', [ + \ 'vendor/bin/php-cs-fixer', + \ 'php-cs-fixer' + \]) +endfunction + +function! ale#fixers#php_cs_fixer#Fix(buffer) abort + let l:executable = ale#fixers#php_cs_fixer#GetExecutable(a:buffer) + return { + \ 'command': ale#Escape(l:executable) . ' fix %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 0000000..649e17d --- /dev/null +++ b/autoload/ale/fixers/phpcbf.vim @@ -0,0 +1,24 @@ +" Author: notomo +" Description: Fixing files with phpcbf. + +call ale#Set('php_phpcbf_standard', '') +call ale#Set('php_phpcbf_executable', 'phpcbf') +call ale#Set('php_phpcbf_use_global', 0) + +function! ale#fixers#phpcbf#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'php_phpcbf', [ + \ 'vendor/bin/phpcbf', + \ 'phpcbf' + \]) +endfunction + +function! ale#fixers#phpcbf#Fix(buffer) abort + let l:executable = ale#fixers#phpcbf#GetExecutable(a:buffer) + let l:standard = ale#Var(a:buffer, 'php_phpcbf_standard') + let l:standard_option = !empty(l:standard) + \ ? '--standard=' . l:standard + \ : '' + return { + \ 'command': ale#Escape(l:executable) . ' --stdin-path=%s ' . l:standard_option . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim new file mode 100644 index 0000000..d75299e --- /dev/null +++ b/autoload/ale/fixers/prettier.vim @@ -0,0 +1,53 @@ +" 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', 0) +call ale#Set('javascript_prettier_options', '') + +function! ale#fixers#prettier#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_prettier', [ + \ 'node_modules/.bin/prettier_d', + \ 'node_modules/prettier-cli/index.js', + \ 'node_modules/.bin/prettier', + \]) +endfunction + +function! ale#fixers#prettier#Fix(buffer) abort + let l:executable = ale#fixers#prettier#GetExecutable(a:buffer) + + let l:command = ale#semver#HasVersion(l:executable) + \ ? '' + \ : ale#Escape(l:executable) . ' --version' + + return { + \ 'command': l:command, + \ 'chain_with': 'ale#fixers#prettier#ApplyFixForVersion', + \} +endfunction + +function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort + let l:executable = ale#fixers#prettier#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'javascript_prettier_options') + + let l:version = ale#semver#GetVersion(l:executable, a:version_output) + + " 1.4.0 is the first version with --stdin-filepath + if ale#semver#GTE(l:version, [1, 4, 0]) + return { + \ 'command': ale#path#BufferCdString(a:buffer) + \ . 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 0000000..5dd9102 --- /dev/null +++ b/autoload/ale/fixers/prettier_eslint.vim @@ -0,0 +1,66 @@ +" Author: tunnckoCore (Charlike Mike Reagent) , +" w0rp , morhetz (Pavel Pertsev) +" Description: Integration between Prettier and ESLint. + +function! ale#fixers#prettier_eslint#SetOptionDefaults() abort + call ale#Set('javascript_prettier_eslint_executable', 'prettier-eslint') + call ale#Set('javascript_prettier_eslint_use_global', 0) + call ale#Set('javascript_prettier_eslint_options', '') +endfunction + +call ale#fixers#prettier_eslint#SetOptionDefaults() + +function! ale#fixers#prettier_eslint#GetExecutable(buffer) abort + return ale#node#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 + let l:executable = ale#fixers#prettier_eslint#GetExecutable(a:buffer) + + let l:command = ale#semver#HasVersion(l:executable) + \ ? '' + \ : ale#Escape(l:executable) . ' --version' + + return { + \ 'command': l:command, + \ 'chain_with': 'ale#fixers#prettier_eslint#ApplyFixForVersion', + \} +endfunction + +function! ale#fixers#prettier_eslint#ApplyFixForVersion(buffer, version_output) abort + let l:options = ale#Var(a:buffer, 'javascript_prettier_eslint_options') + let l:executable = ale#fixers#prettier_eslint#GetExecutable(a:buffer) + + let l:version = ale#semver#GetVersion(l:executable, a:version_output) + + " 4.2.0 is the first version with --eslint-config-path + let l:config = ale#semver#GTE(l:version, [4, 2, 0]) + \ ? 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(l:version, [4, 4, 0]) + return { + \ 'command': ale#path#BufferCdString(a:buffer) + \ . 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 0000000..7d938e1 --- /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', 0) +call ale#Set('javascript_prettier_standard_options', '') + +function! ale#fixers#prettier_standard#GetExecutable(buffer) abort + return ale#node#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)) + \ . ' %t' + \ . ' ' . l:options, + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/puppetlint.vim b/autoload/ale/fixers/puppetlint.vim new file mode 100644 index 0000000..81f34e8 --- /dev/null +++ b/autoload/ale/fixers/puppetlint.vim @@ -0,0 +1,21 @@ +" 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/refmt.vim b/autoload/ale/fixers/refmt.vim new file mode 100644 index 0000000..514f950 --- /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/rubocop.vim b/autoload/ale/fixers/rubocop.vim new file mode 100644 index 0000000..35569b1 --- /dev/null +++ b/autoload/ale/fixers/rubocop.vim @@ -0,0 +1,21 @@ +function! ale#fixers#rubocop#GetCommand(buffer) abort + let l:executable = ale#handlers#rubocop#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? 'bundle$' + \ ? ' exec rubocop' + \ : '' + let l:config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml') + let l:options = ale#Var(a:buffer, 'ruby_rubocop_options') + + return ale#Escape(l:executable) . l:exec_args + \ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --auto-correct %t' + +endfunction + +function! ale#fixers#rubocop#Fix(buffer) abort + return { + \ 'command': ale#fixers#rubocop#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 0000000..38882fb --- /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/shfmt.vim b/autoload/ale/fixers/shfmt.vim new file mode 100644 index 0000000..882cf3a --- /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) + \ . (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 0000000..c998cfd --- /dev/null +++ b/autoload/ale/fixers/standard.vim @@ -0,0 +1,23 @@ +" Author: Sumner Evans +" Description: Fixing files with Standard. + +call ale#Set('javascript_standard_executable', 'standard') +call ale#Set('javascript_standard_use_global', 0) +call ale#Set('javascript_standard_options', '') + +function! ale#fixers#standard#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_standard', [ + \ 'node_modules/standard/bin/cmd.js', + \ 'node_modules/.bin/standard', + \]) +endfunction + +function! ale#fixers#standard#Fix(buffer) abort + let l:executable = ale#fixers#standard#GetExecutable(a:buffer) + + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ' --fix %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/stylelint.vim b/autoload/ale/fixers/stylelint.vim new file mode 100644 index 0000000..899fcf4 --- /dev/null +++ b/autoload/ale/fixers/stylelint.vim @@ -0,0 +1,23 @@ +" Author: Mahmoud Mostafa +" Description: Fixing files with stylelint. + +call ale#Set('stylelint_executable', 'stylelint') +call ale#Set('stylelint_use_global', 0) + +function! ale#fixers#stylelint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'stylelint', [ + \ 'node_modules/stylelint/bin/stylelint.js', + \ 'node_modules/.bin/stylelint', + \]) +endfunction + + +function! ale#fixers#stylelint#Fix(buffer) abort + let l:executable = ale#fixers#stylelint#GetExecutable(a:buffer) + + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ' --fix %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/swiftformat.vim b/autoload/ale/fixers/swiftformat.vim new file mode 100644 index 0000000..dcc204b --- /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', 0) +call ale#Set('swift_swiftformat_options', '') + +function! ale#fixers#swiftformat#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'swift_swiftformat', [ + \ 'Pods/SwiftFormat/CommandLineTool/swiftformat', + \ 'ios/Pods/SwiftFormat/CommandLineTool/swiftformat', + \ 'swiftformat', + \]) +endfunction + +function! ale#fixers#swiftformat#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'swift_swiftformat_options') + + return { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(ale#fixers#swiftformat#GetExecutable(a:buffer)) + \ . ' %t' + \ . ' ' . l:options, + \} +endfunction diff --git a/autoload/ale/fixers/tslint.vim b/autoload/ale/fixers/tslint.vim new file mode 100644 index 0000000..4d905a0 --- /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_linters#typescript#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 + \ . ' --fix %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/yapf.vim b/autoload/ale/fixers/yapf.vim new file mode 100644 index 0000000..ba7453b --- /dev/null +++ b/autoload/ale/fixers/yapf.vim @@ -0,0 +1,26 @@ +" Author: w0rp +" Description: Fixing Python files with yapf. + +call ale#Set('python_yapf_executable', 'yapf') +call ale#Set('python_yapf_use_global', 0) + +function! ale#fixers#yapf#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'python_yapf', + \ ['yapf'], + \) + + if !executable(l:executable) + return 0 + endif + + 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:config_options, + \} +endfunction diff --git a/autoload/ale/gradle.vim b/autoload/ale/gradle.vim new file mode 100644 index 0000000..dc377fb --- /dev/null +++ b/autoload/ale/gradle.vim @@ -0,0 +1,67 @@ +" 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, build a command to print the classpath of the root +" project. Returns an empty string if cannot build the command. +function! ale#gradle#BuildClasspathCommand(buffer) abort + let l:executable = ale#gradle#FindExecutable(a:buffer) + let l:project_root = ale#gradle#FindProjectRoot(a:buffer) + + if !empty(l:executable) && !empty(l:project_root) + return ale#path#CdString(l:project_root) + \ . ale#Escape(l:executable) + \ . ' -I ' . ale#Escape(s:init_path) + \ . ' -q printClasspath' + endif + + return '' +endfunction diff --git a/autoload/ale/gradle/init.gradle b/autoload/ale/gradle/init.gradle new file mode 100644 index 0000000..fb1db9e --- /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.vim b/autoload/ale/handlers.vim deleted file mode 100644 index 4f1611f..0000000 --- a/autoload/ale/handlers.vim +++ /dev/null @@ -1,222 +0,0 @@ -scriptencoding utf-8 -" Author: w0rp -" Description: This file defines some standard error format handlers. Any -" linter which outputs warnings and errors in a format accepted by one of -" these functions can simply use one of these pre-defined error handlers. - -let s:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+' - -function! s:HandleUnixFormat(buffer, lines, type) abort - " Matches patterns line 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" - let l:pattern = '^' . s:path_pattern . ':\(\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[3], - \ 'type': a:type, - \ 'nr': -1, - \}) - endfor - - return l:output -endfunction - -function! ale#handlers#HandleUnixFormatAsError(buffer, lines) abort - return s:HandleUnixFormat(a:buffer, a:lines, 'E') -endfunction - -function! ale#handlers#HandleUnixFormatAsWarning(buffer, lines) abort - return s:HandleUnixFormat(a:buffer, a:lines, 'W') -endfunction - -function! ale#handlers#HandleGCCFormat(buffer, lines) abort - " 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 l:pattern = '^.\+:\(\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 - - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:match[4], - \ 'type': l:match[3] =~# 'error' ? 'E' : 'W', - \ 'nr': -1, - \}) - endfor - - return l:output -endfunction - -function! ale#handlers#HandleCppCheckFormat(buffer, lines) abort - " Look for lines like the following. - " - " [test.cpp:5]: (error) Array 'a[10]' accessed at index 10, which is out of bounds - let l:pattern = '^\[.\{-}:\(\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 - - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': 0, - \ 'text': l:match[3] . ' (' . l:match[2] . ')', - \ 'type': l:match[2] ==# 'error' ? 'E' : 'W', - \ 'nr': -1, - \}) - endfor - - return l:output -endfunction - -function! ale#handlers#HandlePEP8Format(buffer, lines) abort - " Matches patterns line the following: - " - " Matches patterns line the following: - " - " stdin:6:6: E111 indentation is not a multiple of four - " test.yml:35: [EANSIBLE0002] Trailing whitespace - let l:pattern = '^' . s:path_pattern . ':\(\d\+\):\?\(\d\+\)\?: \[\?\(\([[:alpha:]]\)[[:alnum:]]\+\)\]\? \(.*\)$' - 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 l:code = l:match[3] - if (l:code ==# 'W291' || l:code ==# 'W293' || l:code ==# 'EANSIBLE002') - \ && !g:ale_warn_about_trailing_whitespace - " Skip warnings for trailing whitespace if the option is off. - continue - endif - - if l:code ==# 'I0011' - " Skip 'Locally disabling' message - continue - endif - - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:code . ': ' . l:match[5], - \ 'type': l:match[4] ==# 'E' ? 'E' : 'W', - \ 'nr': -1, - \}) - endfor - - return l:output -endfunction - -function! ale#handlers#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 = '^.*: line \(\d\+\), col \(\d\+\), \(Error\|Warning\) - \(.\+\) (\([^)]\+\))$' - 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 l:text = l:match[4] - let l:type = l:match[3] - let l:errorGroup = l:match[5] - - " Put the error group at the front, so we can see what kind of error - " it is on small echo lines. - let l:text = '(' . l:errorGroup . ') ' . l:text - - " 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:text, - \ 'type': l:type ==# 'Warning' ? 'W' : 'E', - \ 'nr': -1, - \}) - endfor - - return l:output -endfunction - -function! ale#handlers#HandleStyleLintFormat(buffer, lines) abort - " 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 = '^.* \(\d\+\):\(\d\+\) \s\+\(\S\+\)\s\+ \(\u.\+\) \(.\+\)$' - 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 l:type = l:match[3] ==# '✖' ? 'E' : 'W' - let l:text = l:match[4] . '[' . l:match[5] . ']' - - " 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:text, - \ 'type': l:type, - \ 'nr': -1, - \}) - endfor - - return l:output -endfunction diff --git a/autoload/ale/handlers/alex.vim b/autoload/ale/handlers/alex.vim new file mode 100644 index 0000000..853d313 --- /dev/null +++ b/autoload/ale/handlers/alex.vim @@ -0,0 +1,22 @@ +" Author: Johannes Wienke +" Description: Error handling for errors in alex output format + +function! ale#handlers#alex#Handle(buffer, lines) abort + " Example output: + " 6:256-6:262 warning Be careful with “killed”, it’s profane in some cases killed retext-profanities + let l:pattern = '^ *\(\d\+\):\(\d\+\)-\(\d\+\):\(\d\+\) \+warning \+\(.\{-\}\) \+\(.\{-\}\) \+\(.\{-\}\)$' + let l: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 diff --git a/autoload/ale/handlers/cppcheck.vim b/autoload/ale/handlers/cppcheck.vim new file mode 100644 index 0000000..dc56cd0 --- /dev/null +++ b/autoload/ale/handlers/cppcheck.vim @@ -0,0 +1,21 @@ +" Description: Handle errors for cppcheck. + +function! ale#handlers#cppcheck#HandleCppCheckFormat(buffer, lines) abort + " Look for lines like the following. + " + " [test.cpp:5]: (error) Array 'a[10]' accessed at index 10, which is out of bounds + let l:pattern = '\v^\[(.+):(\d+)\]: \(([a-z]+)\) (.+)$' + 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]), + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \ 'text': l:match[4], + \}) + 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 0000000..5c475a5 --- /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/css.vim b/autoload/ale/handlers/css.vim new file mode 100644 index 0000000..de9eadc --- /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/eslint.vim b/autoload/ale/handlers/eslint.vim new file mode 100644 index 0000000..ff59016 --- /dev/null +++ b/autoload/ale/handlers/eslint.vim @@ -0,0 +1,153 @@ +" Author: w0rp +" Description: Functions for working with eslint, for checking or fixing files. + +let s:sep = has('win32') ? '\' : '/' + +call ale#Set('javascript_eslint_options', '') +call ale#Set('javascript_eslint_executable', 'eslint') +call ale#Set('javascript_eslint_use_global', 0) +call ale#Set('javascript_eslint_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 [ + \ '.eslintrc.js', + \ '.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#node#FindExecutable(a:buffer, 'javascript_eslint', [ + \ 'node_modules/.bin/eslint_d', + \ 'node_modules/eslint/bin/eslint.js', + \ 'node_modules/.bin/eslint', + \]) +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 unix --stdin --stdin-filename %s' +endfunction + +let s:col_end_patterns = [ +\ '\vParsing error: Unexpected token (.+) ?', +\ '\v''(.+)'' is not defined.', +\ '\v%(Unexpected|Redundant use of) [''`](.+)[''`]', +\ '\vUnexpected (console) statement', +\] + +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! ale#handlers#eslint#Handle(buffer, lines) 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 + + " 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] + + if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore') + if l:text is# 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.' + continue + endif + endif + + 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 + + if expand('#' . a:buffer . ':t') =~? '\.tsx\?$' + call s:AddHintsForTypeScriptParsingErrors(l:output) + endif + + return l:output +endfunction diff --git a/autoload/ale/handlers/gcc.vim b/autoload/ale/handlers/gcc.vim new file mode 100644 index 0000000..7f2078a --- /dev/null +++ b/autoload/ale/handlers/gcc.vim @@ -0,0 +1,69 @@ +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' + +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! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort + " 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 l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): ?(.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l: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) + let l:output[-1]['detail'] = + \ get(l:output[-1], 'detail', '') + \ . s:RemoveUnicodeQuotes(l:match[0]) . "\n" + endif + + continue + endif + + 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 + + " 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/haskell.vim b/autoload/ale/handlers/haskell.vim new file mode 100644 index 0000000..8a0d001 --- /dev/null +++ b/autoload/ale/handlers/haskell.vim @@ -0,0 +1,91 @@ +" Author: w0rp +" Description: Error handling for the format GHC outputs. + +" Remember the directory used for temporary files for Vim. +let s:temp_dir = fnamemodify(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! 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 = [] + + " 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/ols.vim b/autoload/ale/handlers/ols.vim new file mode 100644 index 0000000..1dda7f9 --- /dev/null +++ b/autoload/ale/handlers/ols.vim @@ -0,0 +1,25 @@ +" 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#node#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/pony.vim b/autoload/ale/handlers/pony.vim new file mode 100644 index 0000000..0ac18e7 --- /dev/null +++ b/autoload/ale/handlers/pony.vim @@ -0,0 +1,34 @@ +scriptencoding utf-8 +" Description: This file defines a handler function which ought to work for +" any program which outputs errors in the format that ponyc uses. + +function! s:RemoveUnicodeQuotes(text) abort + let l:text = a:text + let l:text = substitute(l:text, '[`´‘’]', '''', 'g') + let l:text = substitute(l:text, '\v\\u2018([^\\]+)\\u2019', '''\1''', 'g') + let l:text = substitute(l:text, '[“”]', '"', 'g') + + return l:text +endfunction + +function! ale#handlers#pony#HandlePonycFormat(buffer, lines) abort + " Look for lines like the following. + " /home/code/pony/classes/Wombat.pony:22:30: can't lookup private fields from outside the type + + let l:pattern = '\v^([^:]+):(\d+):(\d+)?:? (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:item = { + \ 'filename': l:match[1], + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'type': 'E', + \ 'text': s:RemoveUnicodeQuotes(l:match[4]), + \} + + call add(l:output, l:item) + endfor + + return l:output +endfunction diff --git a/autoload/ale/handlers/rails_best_practices.vim b/autoload/ale/handlers/rails_best_practices.vim new file mode 100644 index 0000000..51bafbb --- /dev/null +++ b/autoload/ale/handlers/rails_best_practices.vim @@ -0,0 +1,6 @@ +call ale#Set('ruby_rails_best_practices_options', '') +call ale#Set('ruby_rails_best_practices_executable', 'rails_best_practices') + +function! ale#handlers#rails_best_practices#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'ruby_rails_best_practices_executable') +endfunction diff --git a/autoload/ale/handlers/redpen.vim b/autoload/ale/handlers/redpen.vim new file mode 100644 index 0000000..2fb0568 --- /dev/null +++ b/autoload/ale/handlers/redpen.vim @@ -0,0 +1,32 @@ +" 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 = json_decode(join(a:lines))[0] + let l:output = [] + for l:err in 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 + call add(l:output, l:item) + endfor + return l:output +endfunction + diff --git a/autoload/ale/handlers/rubocop.vim b/autoload/ale/handlers/rubocop.vim new file mode 100644 index 0000000..f6367cf --- /dev/null +++ b/autoload/ale/handlers/rubocop.vim @@ -0,0 +1,6 @@ +call ale#Set('ruby_rubocop_options', '') +call ale#Set('ruby_rubocop_executable', 'rubocop') + +function! ale#handlers#rubocop#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'ruby_rubocop_executable') +endfunction diff --git a/autoload/ale/handlers/ruby.vim b/autoload/ale/handlers/ruby.vim new file mode 100644 index 0000000..555c13b --- /dev/null +++ b/autoload/ale/handlers/ruby.vim @@ -0,0 +1,37 @@ +" 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 index c00c227..537bc73 100644 --- a/autoload/ale/handlers/rust.vim +++ b/autoload/ale/handlers/rust.vim @@ -7,26 +7,25 @@ if !exists('g:ale_rust_ignore_error_codes') let g:ale_rust_ignore_error_codes = [] endif -" returns: a list [lnum, col] with the location of the error or [] -function! s:FindErrorInExpansion(span, file_name) abort - if a:span.file_name ==# a:file_name - return [a:span.line_start, a:span.byte_start] +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 - if !empty(a:span.expansion) - return s:FindErrorInExpansion(a:span.expansion.span, a:file_name) + " 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 [] + return {} endfunction -" A handler function which accepts a file name, to make unit testing easier. -function! ale#handlers#rust#HandleRustErrorsForFile(buffer, full_filename, lines) abort - let l:filename = fnamemodify(a:full_filename, ':t') +function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort let l:output = [] for l:errorline in a:lines - " ignore everything that is not Json + " ignore everything that is not JSON if l:errorline !~# '^{' continue endif @@ -45,46 +44,21 @@ function! ale#handlers#rust#HandleRustErrorsForFile(buffer, full_filename, lines continue endif - for l:span in l:error.spans - let l:span_filename = fnamemodify(l:span.file_name, ':t') + for l:root_span in l:error.spans + let l:span = s:FindSpan(a:buffer, l:root_span) - if ( - \ l:span.is_primary - \ && (l:span_filename ==# l:filename || l:span_filename ==# '') - \) + if !empty(l:span) call add(l:output, { - \ 'bufnr': a:buffer, \ 'lnum': l:span.line_start, - \ 'vcol': 0, - \ 'col': l:span.byte_start, - \ 'nr': -1, - \ 'text': l:error.message, + \ 'end_lnum': l:span.line_end, + \ 'col': l:span.column_start, + \ 'end_col': l:span.column_end, + \ 'text': empty(l:span.label) ? l:error.message : printf('%s: %s', l:error.message, l:span.label), \ 'type': toupper(l:error.level[0]), \}) - else - " when the error is caused in the expansion of a macro, we have - " to bury deeper - let l:root_cause = s:FindErrorInExpansion(l:span, l:filename) - - if !empty(l:root_cause) - call add(l:output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:root_cause[0], - \ 'vcol': 0, - \ 'col': l:root_cause[1], - \ 'nr': -1, - \ 'text': l:error.message, - \ 'type': toupper(l:error.level[0]), - \}) - endif endif endfor endfor return l:output endfunction - -" A handler for output for Rust linters. -function! ale#handlers#rust#HandleRustErrors(buffer, lines) abort - return ale#handlers#rust#HandleRustErrorsForFile(a:buffer, bufname(a:buffer), a:lines) -endfunction diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim new file mode 100644 index 0000000..e96dd3c --- /dev/null +++ b/autoload/ale/handlers/sh.vim @@ -0,0 +1,20 @@ +" Author: w0rp + +" Get the shell type for a buffer, based on the hashbang line. +function! ale#handlers#sh#GetShellType(buffer) abort + let l:bang_line = get(getbufline(a:buffer, 1), 0, '') + + " Take the shell executable from the hashbang, if we can. + if l:bang_line[:1] is# '#!' + " Remove options like -e, etc. + let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g') + + for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'sh'] + if l:command =~# l:possible_shell . '\s*$' + return l:possible_shell + endif + endfor + endif + + return '' +endfunction diff --git a/autoload/ale/handlers/sml.vim b/autoload/ale/handlers/sml.vim new file mode 100644 index 0000000..377eade --- /dev/null +++ b/autoload/ale/handlers/sml.vim @@ -0,0 +1,92 @@ +" 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. +" executable_callback is called before *every* lint attempt +function! s:GetExecutable(buffer, source) abort + if ale#handlers#sml#GetCmFile(a:buffer) is# '' + " No CM file found; only allow single-file mode to be enabled + 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 + call add(l:out, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match2[1] + 0, + \ 'col' : l:match2[2] - 1, + \ 'text': l:match2[3], + \ 'type': l:match2[3] =~# '^Warning' ? 'W' : 'E', + \}) + continue + endif + + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) != 0 + call add(l:out, { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[2] . ': ' . l:match[3], + \ 'type': l:match[2] is# 'error' ? 'E' : 'W', + \}) + continue + endif + + endfor + + return l:out +endfunction + +" vim:ts=4:sts=4:sw=4 diff --git a/autoload/ale/handlers/unix.vim b/autoload/ale/handlers/unix.vim new file mode 100644 index 0000000..f90fd59 --- /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 0000000..9dc0872 --- /dev/null +++ b/autoload/ale/handlers/vale.vim @@ -0,0 +1,38 @@ +" 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 0000000..f9d452e --- /dev/null +++ b/autoload/ale/handlers/writegood.vim @@ -0,0 +1,61 @@ +" 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', 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#node#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 diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim new file mode 100644 index 0000000..ae1f3e7 --- /dev/null +++ b/autoload/ale/highlight.vim @@ -0,0 +1,143 @@ +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) + +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 + for l:match in getmatches() + if l:match.group =~# '^ALE' + call matchdelete(l:match.id) + endif + endfor +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:line = l:item.lnum + let l:col = l:item.col + let l:end_line = get(l:item, 'end_lnum', l:line) + let l:end_col = get(l:item, 'end_col', l:col) + + " Set all of the positions, which are chunked into Lists which + " are as large as will be accepted by matchaddpos. + call map( + \ ale#highlight#CreatePositions(l:line, l:col, l:end_line, l:end_col), + \ 'matchaddpos(l:group, v:val)' + \) + 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 matchaddpos(l:group, [l:item.lnum]) + 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) + + " 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 0000000..a6282ea --- /dev/null +++ b/autoload/ale/history.vim @@ -0,0 +1,59 @@ +" Author: w0rp +" Description: Tools for managing command history + +" Return a shallow copy of the command history for a given buffer number. +function! ale#history#Get(buffer) abort + return copy(getbufvar(a:buffer, 'ale_history', [])) +endfunction + +function! ale#history#Add(buffer, status, job_id, command) abort + if g:ale_max_buffer_history_size <= 0 + " Don't save anything if the history isn't a positive number. + 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/job.vim b/autoload/ale/job.vim new file mode 100644 index 0000000..2909dab --- /dev/null +++ b/autoload/ale/job.vim @@ -0,0 +1,341 @@ +" 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) + +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 + +" Note that jobs and IDs are the same thing on NeoVim. +function! ale#job#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 + +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#job#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#job#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') + call ale#util#GetFunction(l:info.exit_cb)(l:job_id, l:info.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! 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') + call ale#util#GetFunction(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') + + let l:command = !empty(l:wrapper) + \ ? s:PrepareWrappedCommand(l:wrapper, a:command) + \ : a:command + + " The command will be executed in a subshell. This fixes a number of + " issues, including reading the PATH variables correctly, %PATHEXT% + " expansion on Windows, etc. + " + " 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. + if has('win32') + return 'cmd /s/c "' . l:command . '"' + endif + + if &shell =~? 'fish$' + 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') + endif + + if has_key(a:options, 'err_cb') + let l:job_options.err_cb = function('s:VimErrorCallback') + 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 + + " 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 + +" Send raw data to the job. +function! ale#job#SendRaw(job_id, string) abort + if has('nvim') + call jobsend(a:job_id, a:string) + else + call ch_sendraw(job_getchannel(s:job_map[a:job_id].job), a:string) + 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 + +" 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/linter.vim b/autoload/ale/linter.vim index 0a5b9ae..d059a12 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -1,10 +1,11 @@ +call ale#Set('wrap_command_as_one_argument', 0) " Author: w0rp " Description: Linter registration and lazy-loading " Retrieves linters as requested by the engine, loading them if needed. let s:linters = {} -" Default filetype aliaes. +" Default filetype aliases. " The user defined aliases will be merged with this Dictionary. let s:default_ale_linter_aliases = { \ 'Dockerfile': 'dockerfile', @@ -18,10 +19,21 @@ let s:default_ale_linter_aliases = { " The user defined linter selections will be merged with this Dictionary. " " No linters are used for plaintext files by default. +" +" Only cargo is enabled for Rust by default. +" rpmlint is disabled by default because it can result in code execution. +" +" NOTE: Update the g:ale_linters documentation when modifying this. let s:default_ale_linters = { -\ 'zsh': ['shell'], \ 'csh': ['shell'], +\ 'go': ['gofmt', 'golint', 'go vet'], +\ 'help': [], +\ 'perl': ['perlcritic'], +\ 'python': ['flake8', 'mypy', 'pylint'], +\ 'rust': ['cargo'], +\ 'spec': [], \ 'text': [], +\ 'zsh': ['shell'], \} " Testing/debugging helper to unload all linters. @@ -33,25 +45,48 @@ function! s:IsCallback(value) abort return type(a:value) == type('') || type(a:value) == type(function('type')) endfunction +function! s:IsBoolean(value) abort + return type(a:value) == type(0) && (a:value == 0 || a:value == 1) +endfunction + function! ale#linter#PreProcess(linter) abort if type(a:linter) != type({}) throw 'The linter object must be a Dictionary' endif let l:obj = { + \ 'add_newline': get(a:linter, 'add_newline', 0), \ 'name': get(a:linter, 'name'), - \ 'callback': get(a:linter, 'callback'), + \ 'lsp': get(a:linter, 'lsp', ''), \} if type(l:obj.name) != type('') throw '`name` must be defined to name the linter' endif - if !s:IsCallback(l:obj.callback) - throw '`callback` must be defined with a callback to accept output' + 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 has_key(a:linter, 'executable_callback') + if index(['', 'socket', 'stdio', 'tsserver'], l:obj.lsp) < 0 + throw '`lsp` must be either `''lsp''` or `''tsserver''` if defined' + endif + + if !l:needs_executable + if has_key(a:linter, 'executable') + \|| has_key(a:linter, 'executable_callback') + throw '`executable` and `executable_callback` cannot be used when lsp == ''socket''' + endif + elseif has_key(a:linter, 'executable_callback') let l:obj.executable_callback = a:linter.executable_callback if !s:IsCallback(l:obj.executable_callback) @@ -67,7 +102,13 @@ function! ale#linter#PreProcess(linter) abort throw 'Either `executable` or `executable_callback` must be defined' endif - if has_key(a:linter, 'command_chain') + if !l:needs_command + if has_key(a:linter, 'command') + \|| has_key(a:linter, 'command_callback') + \|| has_key(a:linter, 'command_chain') + throw '`command` and `command_callback` and `command_chain` cannot be used when lsp == ''socket''' + endif + elseif has_key(a:linter, 'command_chain') let l:obj.command_chain = a:linter.command_chain if type(l:obj.command_chain) != type([]) @@ -95,6 +136,10 @@ function! ale#linter#PreProcess(linter) abort endif endif + if has_key(l:link, 'read_buffer') && !s:IsBoolean(l:link.read_buffer) + throw l:err_prefix . 'value for `read_buffer` must be `0` or `1`' + endif + let l:link_index += 1 endfor elseif has_key(a:linter, 'command_callback') @@ -114,6 +159,43 @@ function! ale#linter#PreProcess(linter) abort \ . 'must be defined' endif + if ( + \ has_key(a:linter, 'command') + \ + has_key(a:linter, 'command_chain') + \ + has_key(a:linter, 'command_callback') + \) > 1 + throw 'Only one of `command`, `command_callback`, or `command_chain` ' + \ . 'should be set' + endif + + if !l:needs_address + if has_key(a:linter, 'address_callback') + throw '`address_callback` cannot be used when lsp != ''socket''' + endif + elseif has_key(a:linter, 'address_callback') + let l:obj.address_callback = a:linter.address_callback + + if !s:IsCallback(l:obj.address_callback) + throw '`address_callback` must be a callback if defined' + endif + else + throw '`address_callback` must be defined for getting the LSP address' + endif + + if l:needs_lsp_details + let l:obj.language_callback = get(a:linter, 'language_callback') + + if !s:IsCallback(l:obj.language_callback) + throw '`language_callback` must be a callback for LSP linters' + endif + + let l:obj.project_root_callback = get(a:linter, 'project_root_callback') + + if !s:IsCallback(l:obj.project_root_callback) + throw '`project_root_callback` must be a callback for LSP linters' + endif + endif + let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout') if type(l:obj.output_stream) != type('') @@ -121,6 +203,32 @@ function! ale#linter#PreProcess(linter) abort 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) + throw '`lint_file` must be `0` or `1`' + endif + + " An option indicating that the buffer should be read. + let l:obj.read_buffer = get(a:linter, 'read_buffer', !l:obj.lint_file) + + if !s:IsBoolean(l:obj.read_buffer) + throw '`read_buffer` must be `0` or `1`' + endif + + if l:obj.lint_file && l:obj.read_buffer + throw 'Only one of `lint_file` or `read_buffer` can be `1`' + endif + + let l:obj.aliases = get(a:linter, 'aliases', []) + + if type(l:obj.aliases) != type([]) + \|| len(filter(copy(l:obj.aliases), 'type(v:val) != type('''')')) > 0 + throw '`aliases` must be a List of String values' + endif + return l:obj endfunction @@ -134,102 +242,233 @@ function! ale#linter#Define(filetype, linter) abort call add(s:linters[a:filetype], l:new_linter) endfunction -function! ale#linter#GetAll(filetype) abort - if a:filetype ==# '' - " Empty filetype? Nothing to be done about that. - return [] - endif - - if has_key(s:linters, a:filetype) - " We already loaded the linter files for this filetype, so stop here. - return s:linters[a:filetype] - endif - - " Load all linters for a given filetype. - execute 'silent! runtime! ale_linters/' . a:filetype . '/*.vim' - - if !has_key(s:linters, a:filetype) - " If we couldn't load any linters, let everyone know. - let s:linters[a:filetype] = [] - endif - - return s:linters[a:filetype] -endfunction - -function! s:ResolveFiletype(original_filetype) abort - " Try and get an aliased file type either from the user's Dictionary, or - " our default Dictionary, otherwise use the filetype as-is. - let l:filetype = get( - \ g:ale_linter_aliases, - \ a:original_filetype, - \ get( - \ s:default_ale_linter_aliases, - \ a:original_filetype, - \ a:original_filetype - \ ) - \) - - return l:filetype -endfunction - -function! ale#linter#Get(original_filetypes) abort +function! ale#linter#GetAll(filetypes) abort let l:combined_linters = [] - " Handle dot-seperated filetypes. - for l:original_filetype in split(a:original_filetypes, '\.') - let l:filetype = s:ResolveFiletype(l:original_filetype) + for l:filetype in a:filetypes + " Load linter defintions from files if we haven't loaded them yet. + if !has_key(s:linters, l:filetype) + execute 'silent! runtime! ale_linters/' . l:filetype . '/*.vim' - " Try and get a list of linters to run, using the original file type, - " not the aliased filetype. We have some linters to limit by default, - " and users may define their own list of linters to run. - let l:linter_names = get( - \ g:ale_linters, - \ l:original_filetype, - \ get( - \ s:default_ale_linters, - \ l:original_filetype, - \ 'all' - \ ) - \) - - let l:all_linters = ale#linter#GetAll(l:filetype) - let l:filetype_linters = [] - - if type(l:linter_names) == type('') && l:linter_names ==# 'all' - let l:filetype_linters = l:all_linters - elseif type(l:linter_names) == type([]) - " Select only the linters we or the user has specified. - for l:linter in l:all_linters - if index(l:linter_names, l:linter.name) >= 0 - call add(l:filetype_linters, l:linter) - endif - endfor + " Always set an empty List for the loaded linters if we don't find + " any. This will prevent us from executing the runtime command + " many times, redundantly. + if !has_key(s:linters, l:filetype) + let s:linters[l:filetype] = [] + endif endif - call extend(l:combined_linters, l:filetype_linters) + call extend(l:combined_linters, get(s:linters, l:filetype, [])) endfor return l:combined_linters endfunction -function! ale#linter#Info() abort - let l:original_filetypes = &filetype +function! s:GetAliasedFiletype(original_filetype) abort + let l:buffer_aliases = get(b:, 'ale_linter_aliases', {}) - " We get the list of enabled linters for free by the above function. - let l:enabled_linters = deepcopy(ale#linter#Get(l:original_filetypes)) + " b:ale_linter_aliases can be set to a List. + if type(l:buffer_aliases) is type([]) + return l:buffer_aliases + endif - " But have to build the list of available linters ourselves. - let l:all_linters = [] - for l:original_filetype in split(l:original_filetypes, '\.') - let l:filetype = s:ResolveFiletype(l:original_filetype) - let l:filetype_linters = ale#linter#GetAll(l:filetype) - call extend(l:all_linters, l:filetype_linters) + " 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 - let l:all_names = map(l:all_linters, 'v:val[''name'']') - let l:enabled_names = map(l:enabled_linters, 'v:val[''name'']') - - echom ' Current Filetype: ' . l:original_filetypes - echom 'Available Linters: ' . string(l:all_names) - echom ' Enabled Linters: ' . string(l:enabled_names) + return a:original_filetype +endfunction + +function! ale#linter#ResolveFiletype(original_filetype) abort + let l:filetype = s:GetAliasedFiletype(a:original_filetype) + + if type(l:filetype) != type([]) + 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 type([]) + 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) == type('') && l:linter_names is# 'all' + let l:filetype_linters = l:all_linters + elseif type(l:linter_names) == type([]) + " Select only the linters we or the user has specified. + 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 + return has_key(a:linter, 'executable_callback') + \ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer) + \ : a:linter.executable +endfunction + +" Given a buffer and linter, get the command String for the linter. +" The command_chain key is not supported. +function! ale#linter#GetCommand(buffer, linter) abort + return has_key(a:linter, 'command_callback') + \ ? ale#util#GetFunction(a:linter.command_callback)(a:buffer) + \ : a:linter.command +endfunction + +" Given a buffer and linter, get the address for connecting to the server. +function! ale#linter#GetAddress(buffer, linter) abort + return has_key(a:linter, 'address_callback') + \ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer) + \ : a:linter.address +endfunction + +" Given a buffer, an LSP linter, and a callback to register for handling +" messages, start up an LSP linter and get ready to receive errors or +" completions. +function! ale#linter#StartLSP(buffer, linter, callback) abort + let l:command = '' + let l:address = '' + let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) + + if empty(l:root) && a:linter.lsp isnot# 'tsserver' + " If there's no project root, then we can't check files with LSP, + " unless we are using tsserver, which doesn't use project roots. + return {} + endif + + if a:linter.lsp is# 'socket' + let l:address = ale#linter#GetAddress(a:buffer, a:linter) + let l:conn_id = ale#lsp#ConnectToAddress( + \ l:address, + \ l:root, + \ a:callback, + \) + else + let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) + + if !executable(l:executable) + return {} + endif + + let l:command = ale#job#PrepareCommand( + \ a:buffer, + \ ale#linter#GetCommand(a:buffer, a:linter), + \) + let l:conn_id = ale#lsp#StartProgram( + \ l:executable, + \ l:command, + \ l:root, + \ a:callback, + \) + endif + + let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) + + if !l:conn_id + if g:ale_history_enabled && !empty(l:command) + call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command) + endif + + return {} + endif + + if ale#lsp#OpenDocumentIfNeeded(l:conn_id, a:buffer, l:root, l:language_id) + if g:ale_history_enabled && !empty(l:command) + call ale#history#Add(a:buffer, 'started', l:conn_id, l:command) + endif + endif + + " The change message needs to be sent for tsserver before doing anything. + if a:linter.lsp is# 'tsserver' + call ale#lsp#Send(l:conn_id, ale#lsp#tsserver_message#Change(a:buffer)) + endif + + return { + \ 'connection_id': l:conn_id, + \ 'command': l:command, + \ 'project_root': l:root, + \ 'language_id': l:language_id, + \} endfunction diff --git a/autoload/ale/list.vim b/autoload/ale/list.vim index a086b10..30b8f5c 100644 --- a/autoload/ale/list.vim +++ b/autoload/ale/list.vim @@ -1,49 +1,177 @@ " Author: Bjorn Neergaard , modified by Yann fery " Description: Manages the loclist and quickfix lists +if !exists('s:timer_args') + let s:timer_args = {} +endif + " Return 1 if there is a buffer with buftype == 'quickfix' in bufffer list function! ale#list#IsQuickfixOpen() abort for l:buf in range(1, bufnr('$')) - if getbufvar(l:buf, '&buftype') ==# 'quickfix' + if getbufvar(l:buf, '&buftype') is# 'quickfix' return 1 endif endfor return 0 endfunction -function! ale#list#SetLists(loclist) abort +" Check if we should open the list, based on the save event being fired, and +" that setting being on, or the setting just being set to `1`. +function! s:ShouldOpen(buffer) abort + let l:val = ale#Var(a:buffer, 'open_list') + let l:saved = getbufvar(a:buffer, 'ale_save_event_fired', 0) + + return l:val is 1 || (l:val is# 'on_save' && l:saved) +endfunction + +function! ale#list#GetCombinedList() abort + let l:list = [] + + for l:info in values(g:ale_buffer_info) + call extend(l:list, l:info.loclist) + endfor + + call sort(l:list, function('ale#util#LocItemCompareWithText')) + call uniq(l:list, function('ale#util#LocItemCompareWithText')) + + return l:list +endfunction + +function! s:FixList(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:BufWinId(buffer) abort + return exists('*bufwinid') ? bufwinid(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 - call setqflist(a:loclist) + 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 - call setloclist(0, a:loclist) + " If windows support is off, bufwinid() may not exist. + " We'll set result in the current window, which might not be correct, + " but it's better than nothing. + let l:id = s:BufWinId(a:buffer) + + if has('nvim') + call setloclist(l:id, s:FixList(a:buffer, a:loclist), ' ', l:title) + else + call setloclist(l:id, s:FixList(a:buffer, a:loclist)) + call setloclist(l:id, [], 'r', {'title': l:title}) + endif endif - " If we don't auto-open lists, bail out here. - if !g:ale_open_list && !g:ale_keep_list_window_open - return - endif - - " If we have errors in our list, open the list. Only if it isn't already open - if len(a:loclist) > 0 || g:ale_keep_list_window_open + " Open a window to show the problems if we need to. + " + " We'll check if the current buffer's List is not empty here, so the + " window will only be opened if the current buffer has problems. + if s:ShouldOpen(a:buffer) && !empty(a:loclist) let l:winnr = winnr() + let l:mode = mode() + let l:reset_visual_selection = l:mode is? 'v' || l:mode is# "\" + let l:reset_character_selection = l:mode is? 's' || l:mode is# "\" + " open windows vertically instead of default horizontally + let l:open_type = '' + if ale#Var(a:buffer, 'list_vertical') == 1 + let l:open_type = 'vert ' + endif if g:ale_set_quickfix - copen + if !ale#list#IsQuickfixOpen() + silent! execute l:open_type . 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) + endif elseif g:ale_set_loclist - lopen + 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 !=# winnr() + if l:winnr isnot# winnr() wincmd p endif - " Only close if the list is totally empty (relying on Vim's state, not our - " own). This keeps us from closing the window when other plugins have - " populated it. - elseif !g:ale_keep_list_window_open && g:ale_set_quickfix && len(getqflist()) == 0 - cclose - elseif !g:ale_keep_list_window_open && len(getloclist(0)) == 0 - lclose + if l:reset_visual_selection || l:reset_character_selection + " If we were in a selection mode before, select the last selection. + normal! gv + + if l:reset_character_selection + " Switch back to Select mode, if we were in that. + normal! "\" + endif + endif + 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 + +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! s:CloseWindowIfNeeded(buffer) abort + if ale#Var(a:buffer, 'keep_list_window_open') || !s:ShouldOpen(a:buffer) + return + endif + + 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 + endif + else + let l:win_id = s:BufWinId(a:buffer) + + if g:ale_set_loclist && empty(getloclist(l:win_id)) + lclose + endif + endif + " Ignore 'Cannot close last window' errors. + catch /E444/ + endtry +endfunction diff --git a/autoload/ale/loclist_jumping.vim b/autoload/ale/loclist_jumping.vim index b4d1993..7ed9e6b 100644 --- a/autoload/ale/loclist_jumping.vim +++ b/autoload/ale/loclist_jumping.vim @@ -1,37 +1,6 @@ " Author: w0rp " Description: This file implements functions for jumping around in a file -" based on errors and warnings in the loclist or quickfix list. - -function! s:GetCurrentList() abort - if g:ale_set_loclist - return getloclist(winnr()) - elseif g:ale_set_quickfix - let l:buffer = bufnr('%') - - return filter(getqflist(), 'get(v:val, ''bufnr'', -1) == ' . l:buffer) - endif - - return [] -endfunction - -function! s:GetSortedLoclist() abort - let l:loclist = [] - - for l:item in s:GetCurrentList() - if l:item.lnum < 1 - " Remove items we can't even jump to. - continue - endif - - call add(l:loclist, l:item) - endfor - - " We must sort the list again, as the loclist could contain items set - " by other plugins. - call sort(l:loclist, 'ale#util#LocItemCompare') - - return l:loclist -endfunction +" 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 @@ -41,17 +10,15 @@ endfunction " 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:loclist = s:GetSortedLoclist() - - if empty(l:loclist) - " We couldn't find anything, so stop here. - return [] - endif - - let l:search_item = {'lnum': getcurpos()[1], 'col': getcurpos()[2]} + let l:buffer = bufnr('') + let l:pos = getcurpos() + 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]} " When searching backwards, so we can find the next smallest match. - if a:direction ==# 'before' + if a:direction is# 'before' call reverse(l:loclist) endif @@ -64,25 +31,29 @@ function! ale#loclist_jumping#FindNearest(direction, wrap) abort " cursor to a line without changing the column, in some cases. let l:cmp_value = ale#util#LocItemCompare( \ { + \ 'bufnr': l:buffer, \ 'lnum': l:item.lnum, - \ 'col': min([max([l:item.col, 1]), len(getline(l:item.lnum))]), + \ 'col': min([ + \ max([l:item.col, 1]), + \ max([len(getline(l:item.lnum)), 1]), + \ ]), \ }, \ l:search_item \) - if a:direction ==# 'before' && l:cmp_value < 0 + if a:direction is# 'before' && l:cmp_value < 0 return [l:item.lnum, l:item.col] endif - if a:direction ==# 'after' && l:cmp_value > 0 + if a:direction is# 'after' && l:cmp_value > 0 return [l:item.lnum, l:item.col] endif endfor " 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 - let l:item = get(l:loclist, 0) + if a:wrap && !empty(l:loclist) + let l:item = l:loclist[0] return [l:item.lnum, l:item.col] endif @@ -98,3 +69,19 @@ function! ale#loclist_jumping#Jump(direction, wrap) abort call cursor(l:nearest) endif 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) + 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 0000000..8db9348 --- /dev/null +++ b/autoload/ale/lsp.vim @@ -0,0 +1,420 @@ +" Author: w0rp +" Description: Language Server Protocol client code + +" A List of connections, used for tracking servers which have been connected +" to, and programs which are run. +let s:connections = [] +let g:ale_lsp_next_message_id = 1 + +function! s:NewConnection() abort + " id: The job ID as a Number, or the server address as a string. + " data: The message data received so far. + " executable: An executable only set for program connections. + " open_documents: A list of buffers we told the server we opened. + " callback_list: A list of callbacks for handling LSP responses. + let l:conn = { + \ 'id': '', + \ 'data': '', + \ 'projects': {}, + \ 'open_documents': [], + \ 'callback_list': [], + \} + + call add(s:connections, l:conn) + + return l:conn +endfunction + +function! s:FindConnection(key, value) abort + for l:conn in s:connections + if has_key(l:conn, a:key) && get(l:conn, a:key) == a:value + return l:conn + endif + endfor + + return {} +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] =~# '^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 + +function! s:FindProjectWithInitRequestID(conn, init_request_id) abort + for l:project_root in keys(a:conn.projects) + let l:project = a:conn.projects[l:project_root] + + if l:project.init_request_id == a:init_request_id + return l:project + endif + endfor + + return {} +endfunction + +function! s:MarkProjectAsInitialized(conn, project) abort + let a:project.initialized = 1 + + " After the server starts, send messages we had queued previously. + for l:message_data in a:project.message_queue + call s:SendMessageData(a:conn, l:message_data) + endfor + + " Remove the messages now. + let a:conn.message_queue = [] +endfunction + +function! s:HandleInitializeResponse(conn, response) abort + let l:request_id = a:response.request_id + let l:project = s:FindProjectWithInitRequestID(a:conn, l:request_id) + + if !empty(l:project) + call s:MarkProjectAsInitialized(a:conn, l:project) + endif +endfunction + +function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort + let l:uninitialized_projects = [] + + for [l:key, l:value] in items(a:conn.projects) + if l:value.initialized == 0 + call add(l:uninitialized_projects, [l:key, l:value]) + endif + endfor + + if empty(l:uninitialized_projects) + return + endif + + if get(a:response, 'method', '') is# '' + if has_key(get(a:response, 'result', {}), 'capabilities') + for [l:dir, l:project] in l:uninitialized_projects + call s:MarkProjectAsInitialized(a:conn, l:project) + endfor + endif + elseif get(a:response, 'method', '') is# 'textDocument/publishDiagnostics' + let l:filename = ale#path#FromURI(a:response.params.uri) + + for [l:dir, l:project] in l:uninitialized_projects + if l:filename[:len(l:dir) - 1] is# l:dir + call s:MarkProjectAsInitialized(a:conn, l:project) + endif + endfor + endif +endfunction + +function! ale#lsp#HandleMessage(conn, message) abort + let a:conn.data .= a:message + + " Parse the objects now if we can, and keep the remaining text. + let [a:conn.data, l:response_list] = ale#lsp#ReadMessageData(a:conn.data) + + " Call our callbacks. + for l:response in l:response_list + if get(l:response, 'method', '') is# 'initialize' + call s:HandleInitializeResponse(a:conn, l:response) + else + call ale#lsp#HandleOtherInitializeResponses(a:conn, l:response) + + " Call all of the registered handlers with the response. + for l:Callback in a:conn.callback_list + call ale#util#GetFunction(l:Callback)(a:conn.id, l:response) + endfor + endif + endfor +endfunction + +function! s:HandleChannelMessage(channel, message) abort + let l:info = ch_info(a:channel) + let l:address = l:info.hostname . l:info.address + let l:conn = s:FindConnection('id', l:address) + + call ale#lsp#HandleMessage(l:conn, a:message) +endfunction + +function! s:HandleCommandMessage(job_id, message) abort + let l:conn = s:FindConnection('id', a:job_id) + + call ale#lsp#HandleMessage(l:conn, a:message) +endfunction + +function! ale#lsp#RegisterProject(conn, project_root) abort + " Empty strings can't be used for Dictionary keys in NeoVim, due to E713. + " This appears to be a nonsensical bug in NeoVim. + let l:key = empty(a:project_root) ? '<>' : a:project_root + + if !has_key(a:conn.projects, l:key) + " Tools without project roots are ready right away, like tsserver. + let a:conn.projects[l:key] = { + \ 'initialized': empty(a:project_root), + \ 'init_request_id': 0, + \ 'message_queue': [], + \} + endif +endfunction + +function! ale#lsp#GetProject(conn, project_root) abort + let l:key = empty(a:project_root) ? '<>' : a:project_root + + return get(a:conn.projects, l:key, {}) +endfunction + +" Start a program for LSP servers which run with executables. +" +" The job ID will be returned for for the program if it ran, otherwise +" 0 will be returned. +function! ale#lsp#StartProgram(executable, command, project_root, callback) abort + if !executable(a:executable) + return 0 + endif + + let l:conn = s:FindConnection('executable', a:executable) + + " Get the current connection or a new one. + let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() + let l:conn.executable = a:executable + + if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id) + let l:options = { + \ 'mode': 'raw', + \ 'out_cb': function('s:HandleCommandMessage'), + \} + let l:job_id = ale#job#Start(a:command, l:options) + else + let l:job_id = l:conn.id + endif + + if l:job_id <= 0 + return 0 + endif + + let l:conn.id = l:job_id + " Add the callback to the List if it's not there already. + call uniq(sort(add(l:conn.callback_list, a:callback))) + call ale#lsp#RegisterProject(l:conn, a:project_root) + + return l:job_id +endfunction + +" Connect to an address and set up a callback for handling responses. +function! ale#lsp#ConnectToAddress(address, project_root, callback) abort + let l:conn = s:FindConnection('id', a:address) + " Get the current connection or a new one. + let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() + + if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) isnot# 'open' + let l:conn.channnel = ch_open(a:address, { + \ 'mode': 'raw', + \ 'waittime': 0, + \ 'callback': function('s:HandleChannelMessage'), + \}) + endif + + if ch_status(l:conn.channnel) is# 'fail' + return 0 + endif + + let l:conn.id = a:address + " Add the callback to the List if it's not there already. + call uniq(sort(add(l:conn.callback_list, a:callback))) + call ale#lsp#RegisterProject(l:conn, a:project_root) + + return 1 +endfunction + +" Stop all LSP connections, closing all jobs and channels, and removing any +" queued messages. +function! ale#lsp#StopAll() abort + for l:conn in s:connections + if has_key(l:conn, 'channel') + call ch_close(l:conn.channel) + else + call ale#job#Stop(l:conn.id) + endif + endfor + + let s:connections = [] +endfunction + +function! s:SendMessageData(conn, data) abort + if has_key(a:conn, 'executable') + call ale#job#SendRaw(a:conn.id, a:data) + elseif has_key(a:conn, 'channel') && ch_status(a:conn.channnel) is# 'open' + " Send the message to the server + call ch_sendraw(a:conn.channel, a:data) + else + 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:project_root = get(a:000, 0, '') + + let l:conn = s:FindConnection('id', a:conn_id) + + if empty(l:conn) + return 0 + endif + + let l:project = ale#lsp#GetProject(l:conn, l:project_root) + + if empty(l:project) + return 0 + endif + + " If we haven't initialized the server yet, then send the message for it. + if !l:project.initialized + " Only send the init message once. + if !l:project.init_request_id + let [l:init_id, l:init_data] = ale#lsp#CreateMessageData( + \ ale#lsp#message#Initialize(l:project_root), + \) + + let l:project.init_request_id = l:init_id + + call s:SendMessageData(l:conn, l:init_data) + endif + endif + + let [l:id, l:data] = ale#lsp#CreateMessageData(a:message) + + if l:project.initialized + " Send the message now. + call s:SendMessageData(l:conn, l:data) + else + " Add the message we wanted to send to a List to send later. + call add(l:project.message_queue, l:data) + endif + + return l:id == 0 ? -1 : l:id +endfunction + +function! ale#lsp#OpenDocumentIfNeeded(conn_id, buffer, project_root, language_id) abort + let l:conn = s:FindConnection('id', a:conn_id) + let l:opened = 0 + + if !empty(l:conn) && index(l:conn.open_documents, a:buffer) < 0 + if empty(a:language_id) + let l:message = ale#lsp#tsserver_message#Open(a:buffer) + else + let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id) + endif + + call ale#lsp#Send(a:conn_id, l:message, a:project_root) + call add(l:conn.open_documents, a:buffer) + let l:opened = 1 + endif + + return l:opened +endfunction diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim new file mode 100644 index 0000000..0b73cfc --- /dev/null +++ b/autoload/ale/lsp/message.vim @@ -0,0 +1,118 @@ +" Author: w0rp +" Description: Language Server Protocol message implementations +" +" Messages in this movie will be returned in the format +" [is_notification, method_name, params?] +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) abort + " TODO: Define needed capabilities. + return [0, 'initialize', { + \ 'processId': getpid(), + \ 'rootPath': a:root_path, + \ 'capabilities': {}, + \}] +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 + let l:lines = getbufline(a:buffer, 1, '$') + + return [1, 'textDocument/didOpen', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ 'languageId': a:language_id, + \ 'version': ale#lsp#message#GetNextVersionID(), + \ 'text': join(l:lines, "\n") . "\n", + \ }, + \}] +endfunction + +function! ale#lsp#message#DidChange(buffer) abort + let l:lines = getbufline(a:buffer, 1, '$') + + " For changes, we simply send the full text of the document to the server. + return [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ 'version': ale#lsp#message#GetNextVersionID(), + \ }, + \ 'contentChanges': [{'text': join(l:lines, "\n") . "\n"}] + \}] +endfunction + +function! ale#lsp#message#DidSave(buffer) abort + return [1, 'textDocument/didSave', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \}] +endfunction + +function! ale#lsp#message#DidClose(buffer) abort + return [1, 'textDocument/didClose', { + \ 'textDocument': { + \ 'uri': ale#path#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#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column}, + \}] + + 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#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column}, + \}] +endfunction diff --git a/autoload/ale/lsp/reset.vim b/autoload/ale/lsp/reset.vim new file mode 100644 index 0000000..c206ed0 --- /dev/null +++ b/autoload/ale/lsp/reset.vim @@ -0,0 +1,25 @@ +" Stop all LSPs and remove all of the data for them. +function! ale#lsp#reset#StopAllLSPs() abort + call ale#lsp#StopAll() + + if exists('*ale#definition#ClearLSPData') + " Clear the mapping for connections, etc. + call ale#definition#ClearLSPData() + endif + + if exists('*ale#engine#ClearLSPData') + " Clear the mapping for connections, etc. + call ale#engine#ClearLSPData() + + " Remove the problems for all of the LSP linters in every buffer. + for l:buffer_string in keys(g:ale_buffer_info) + let l:buffer = str2nr(l:buffer_string) + + for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype')) + if !empty(l:linter.lsp) + call ale#engine#HandleLoclist(l:linter.name, l:buffer, []) + endif + endfor + endfor + endif +endfunction diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim new file mode 100644 index 0000000..5a43128 --- /dev/null +++ b/autoload/ale/lsp/response.vim @@ -0,0 +1,74 @@ +" Author: w0rp +" Description: Parsing and transforming of LSP server responses. + +" 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 + +" Parse the message for textDocument/publishDiagnostics +function! ale#lsp#response#ReadDiagnostics(response) abort + let l:loclist = [] + + for l:diagnostic in a:response.params.diagnostics + let l:severity = get(l:diagnostic, 'severity', 0) + let l:loclist_item = { + \ 'text': l:diagnostic.message, + \ 'type': 'E', + \ 'lnum': l:diagnostic.range.start.line + 1, + \ 'col': l:diagnostic.range.start.character + 1, + \ 'end_lnum': l:diagnostic.range.end.line + 1, + \ 'end_col': l:diagnostic.range.end.character + 1, + \} + + 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') + let l:loclist_item.nr = l:diagnostic.code + 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, + \} + + if has_key(l:diagnostic, 'code') + let l:loclist_item.nr = l:diagnostic.code + endif + + if get(l:diagnostic, 'category') is# 'warning' + let l:loclist_item.type = 'W' + endif + + if get(l:diagnostic, 'category') is# 'suggestion' + let l:loclist_item.type = 'I' + endif + + call add(l:loclist, l:loclist_item) + endfor + + return l:loclist +endfunction diff --git a/autoload/ale/lsp/tsserver_message.vim b/autoload/ale/lsp/tsserver_message.vim new file mode 100644 index 0000000..b9bd7a0 --- /dev/null +++ b/autoload/ale/lsp/tsserver_message.vim @@ -0,0 +1,63 @@ +" 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) abort + return [0, 'ts@completions', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \ 'prefix': a:prefix, + \}] +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 diff --git a/autoload/ale/node.vim b/autoload/ale/node.vim new file mode 100644 index 0000000..f75280b --- /dev/null +++ b/autoload/ale/node.vim @@ -0,0 +1,42 @@ +" Author: w0rp +" Description: Functions for working with Node executables. + +call ale#Set('windows_node_executable_path', 'node.exe') + +" 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 Node program. +" +" The use_global and executable options for the relevant program will be used. +function! ale#node#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 + + for l:path in a:path_list + let l:executable = ale#path#FindNearestFile(a:buffer, l:path) + + if !empty(l:executable) + return l:executable + endif + endfor + + return ale#Var(a:buffer, a:base_var_name . '_executable') +endfunction + +" 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 ale#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/path.vim b/autoload/ale/path.vim new file mode 100644 index 0000000..16dabf2 --- /dev/null +++ b/autoload/ale/path.vim @@ -0,0 +1,192 @@ +" 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. +" +" 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') + return substitute(simplify(a: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 + +" 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, an 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 serach fails, try the global executable instead. + if empty(l:path) + let l:path = a:global_fallback + endif + + return l:path +endfunction + +" Output 'cd && ' +" This function can be used changing the directory for a linter command. +function! ale#path#CdString(directory) abort + return 'cd ' . ale#Escape(a:directory) . ' && ' +endfunction + +" Output 'cd && ' +" This function can be used changing the directory for a linter command. +function! ale#path#BufferCdString(buffer) abort + return ale#path#CdString(fnamemodify(bufname(a:buffer), ':p:h')) +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 = fnamemodify(tempname(), ':h') + +" Given a filename, return 1 if the file represents some temporary file +" created by Vim. +function! ale#path#IsTempName(filename) abort + return a:filename[:len(s:temp_dir) - 1] is# s:temp_dir +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 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#ToURI(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#FromURI(uri) abort + let l:i = len('file://') + let l:encoded_path = a:uri[: l:i - 1] is# 'file://' ? a:uri[l:i :] : a:uri + + let l:path = ale#uri#Decode(l:encoded_path) + + " If the path is like /C:/foo/bar, it should be C:\foo\bar instead. + if l:path =~# '^/[a-zA-Z]:' + let l:path = substitute(l:path[1:], '/', '\\', 'g') + endif + + return l:path +endfunction diff --git a/autoload/ale/pattern_options.vim b/autoload/ale/pattern_options.vim new file mode 100644 index 0000000..e58b8cf --- /dev/null +++ b/autoload/ale/pattern_options.vim @@ -0,0 +1,44 @@ +" 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 + if !g:ale_pattern_options_enabled || empty(g:ale_pattern_options) + return + endif + + " The items will only be sorted whenever the patterns change. + if g:ale_pattern_options != s:last_pattern_options + let s:last_pattern_options = deepcopy(g:ale_pattern_options) + " The patterns are sorted, so they are applied consistently. + let s:sorted_items = sort( + \ items(g:ale_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/preview.vim b/autoload/ale/preview.vim new file mode 100644 index 0000000..3b1c16a --- /dev/null +++ b/autoload/ale/preview.vim @@ -0,0 +1,18 @@ +" Author: w0rp +" Description: Preview windows for showing whatever information in. + +" Open a preview window and show some lines in it. +function! ale#preview#Show(lines) abort + silent pedit ALEPreviewWindow + wincmd P + setlocal modifiable + setlocal noreadonly + setlocal nobuflisted + setlocal filetype=ale-preview + setlocal buftype=nofile + setlocal bufhidden=wipe + :%d + call setline(1, a:lines) + setlocal nomodifiable + setlocal readonly +endfunction diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim new file mode 100644 index 0000000..82dd9d7 --- /dev/null +++ b/autoload/ale/python.vim @@ -0,0 +1,104 @@ +" Author: w0rp +" Description: Functions for integrating with Python linters. + +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' +let g:ale_virtualenv_dir_names = get(g:, 'ale_virtualenv_dir_names', [ +\ '.env', +\ 'env', +\ 've-py3', +\ 've', +\ 'virtualenv', +\ 'venv', +\]) + +function! ale#python#FindProjectRootIni(buffer) abort + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + if filereadable(l:path . '/MANIFEST.in') + \|| filereadable(l:path . '/setup.cfg') + \|| filereadable(l:path . '/pytest.ini') + \|| filereadable(l:path . '/tox.ini') + \|| filereadable(l:path . '/mypy.ini') + \|| filereadable(l:path . '/pycodestyle.cfg') + \|| filereadable(l:path . '/flake8.cfg') + 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 + +" 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 diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim new file mode 100644 index 0000000..b981ded --- /dev/null +++ b/autoload/ale/ruby.vim @@ -0,0 +1,22 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Functions for integrating with Ruby tools + +" Find the nearest dir contining "app", "db", and "config", and assume it is +" 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 diff --git a/autoload/ale/semver.vim b/autoload/ale/semver.vim index b153dd1..6b0fd34 100644 --- a/autoload/ale/semver.vim +++ b/autoload/ale/semver.vim @@ -1,27 +1,55 @@ -" Given some text, parse a semantic versioning string from the text -" into a triple of integeers [major, minor, patch]. +let s:version_cache = {} + +" Reset the version cache used for parsing the version. +function! ale#semver#ResetVersionCache() abort + let s:version_cache = {} +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 no match can be performed, then an empty List will be returned instead. -function! ale#semver#Parse(text) abort - let l:match = matchlist(a:text, '^ *\(\d\+\)\.\(\d\+\)\.\(\d\+\)') +" If the version cannot be found, an empty List will be returned instead. +function! ale#semver#GetVersion(executable, version_lines) abort + let l:version = get(s:version_cache, a:executable, []) - if empty(l:match) - return [] - endif + for l:line in a:version_lines + let l:match = matchlist(l:line, '\v(\d+)\.(\d+)\.(\d+)') - return [l:match[1] + 0, l:match[2] + 0, l:match[3] + 0] + 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 + +" Return 1 if the semver version has been cached for a given executable. +function! ale#semver#HasVersion(executable) abort + return has_key(s:version_cache, a:executable) endfunction " Given two triples of integers [major, minor, patch], compare the triples -" and return 1 if the lhs is greater than or equal to the rhs. -function! ale#semver#GreaterOrEqual(lhs, rhs) abort +" 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 a:lhs[2] >= a:rhs[2] + return get(a:lhs, 2) >= get(a:rhs, 2) endif endif diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim index e2b3fa7..1c439bc 100644 --- a/autoload/ale/sign.vim +++ b/autoload/ale/sign.vim @@ -2,60 +2,158 @@ scriptencoding utf8 " Author: w0rp " Description: Draws error and warning signs into signcolumn -let b:dummy_sign_set_map = {} - 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('ALEError') - highlight link ALEError SpellBad +if !hlexists('ALEStyleWarningSign') + highlight link ALEStyleWarningSign ALEWarningSign endif -if !hlexists('ALEWarning') - highlight link ALEWarning SpellCap +if !hlexists('ALEInfoSign') + highlight link ALEInfoSign ALEWarningSign +endif + +if !hlexists('ALESignColumnWithErrors') + highlight link ALESignColumnWithErrors error +endif + +if !hlexists('ALESignColumnWithoutErrors') + function! s:SetSignColumnWithoutErrorsHighlight() abort + redir => l:output + silent highlight SignColumn + redir end + + 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 + + call s:SetSignColumnWithoutErrorsHighlight() + delfunction s:SetSignColumnWithoutErrorsHighlight endif " Signs show up on the left for error markers. execute 'sign define ALEErrorSign text=' . g:ale_sign_error -\ . ' texthl=ALEErrorSign' +\ . ' texthl=ALEErrorSign linehl=ALEErrorLine' +execute 'sign define ALEStyleErrorSign text=' . g:ale_sign_style_error +\ . ' texthl=ALEStyleErrorSign linehl=ALEErrorLine' execute 'sign define ALEWarningSign text=' . g:ale_sign_warning -\ . ' texthl=ALEWarningSign' +\ . ' texthl=ALEWarningSign linehl=ALEWarningLine' +execute 'sign define ALEStyleWarningSign text=' . g:ale_sign_style_warning +\ . ' texthl=ALEStyleWarningSign linehl=ALEWarningLine' +execute 'sign define ALEInfoSign text=' . g:ale_sign_info +\ . ' texthl=ALEInfoSign linehl=ALEInfoLine' sign define ALEDummySign +let s:error_priority = 1 +let s:warning_priority = 2 +let s:info_priority = 3 +let s:style_error_priority = 4 +let s:style_warning_priority = 5 + +function! ale#sign#GetSignName(sublist) abort + let l:priority = s:style_warning_priority + + " Determine the highest priority item for the line. + for l:item in a:sublist + if l:item.type is# 'I' + let l:item_priority = s:info_priority + elseif l:item.type is# 'W' + if get(l:item, 'sub_type', '') is# 'style' + let l:item_priority = s:style_warning_priority + else + let l:item_priority = s:warning_priority + endif + else + if get(l:item, 'sub_type', '') is# 'style' + let l:item_priority = s:style_error_priority + else + let l:item_priority = s:error_priority + endif + endif + + if l:item_priority < l:priority + let l:priority = l:item_priority + endif + endfor + + if l:priority is# s:error_priority + return 'ALEErrorSign' + endif + + if l:priority is# s:warning_priority + return 'ALEWarningSign' + endif + + if l:priority is# s:style_error_priority + return 'ALEStyleErrorSign' + endif + + if l:priority is# s:style_warning_priority + return 'ALEStyleWarningSign' + endif + + if l:priority is# s:info_priority + return 'ALEInfoSign' + endif + + " Use the error sign for invalid severities. + return 'ALEErrorSign' +endfunction + " Read sign data for a buffer to a list of lines. function! ale#sign#ReadSigns(buffer) abort redir => l:output - silent exec 'sign place buffer=' . a:buffer + silent execute 'sign place buffer=' . a:buffer redir end return split(l:output, "\n") endfunction -" Given a list of lines for sign output, return a list of sign IDs +" Given a list of lines for sign output, return a List of [line, id, group] function! ale#sign#ParseSigns(line_list) abort " Matches output like : " line=4 id=1 name=ALEErrorSign " строка=1 id=1000001 имя=ALEErrorSign " 行=1 識別子=1000001 名前=ALEWarningSign " línea=12 id=1000001 nombre=ALEWarningSign - let l:pattern = '^.*=\d*\s\+.*=\(\d\+\)\s\+.*=ALE\(Warning\|Error\|Dummy\)Sign' - - let l:id_list = [] + " riga=1 id=1000001, nome=ALEWarningSign + let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)' + let l:result = [] + let l:is_dummy_sign_set = 0 for l:line in a:line_list let l:match = matchlist(l:line, l:pattern) if len(l:match) > 0 - call add(l:id_list, l:match[1] + 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:id_list + return [l:is_dummy_sign_set, l:result] endfunction function! ale#sign#FindCurrentSigns(buffer) abort @@ -64,90 +162,225 @@ function! ale#sign#FindCurrentSigns(buffer) abort return ale#sign#ParseSigns(l:line_list) endfunction -" Given a loclist, combine the loclist into a list of signs such that only -" one sign appears per line. Error lines will take precedence. -" The loclist will have been previously sorted. -function! ale#sign#CombineSigns(loclist) abort - let l:signlist = [] +" 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 - let l:should_append = 1 - - if l:obj.lnum < 1 - " Skip warnings and errors at line 0, etc. + if l:obj.bufnr != a:buffer continue endif - if len(l:signlist) > 0 && l:signlist[-1].lnum == l:obj.lnum - " We can't add the same line twice, because signs must be - " unique per line. - let l:should_append = 0 - - if l:signlist[-1].type ==# 'W' && l:obj.type ==# 'E' - " If we had a warning previously, but now have an error, - " we replace the object to set an error instead. - let l:signlist[-1] = l:obj - 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 - if l:should_append - call add(l:signlist, l:obj) + 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 - return l:signlist + " 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 type(0) && 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 + \ . ' 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) + \ . ' 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 + \ . ' 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 + \ . ' 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 - let l:signlist = ale#sign#CombineSigns(a:loclist) + 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:current_id_list = ale#sign#FindCurrentSigns(a:buffer) - let l:dummy_sign_set = 0 + let [l:is_dummy_sign_set, l:current_sign_list] = + \ ale#sign#FindCurrentSigns(a:buffer) - " Check if we set the dummy sign already. - for l:current_id in l:current_id_list - if l:current_id == g:ale_sign_offset - let l:dummy_sign_set = 1 - endif - endfor + " Update the line numbers for items from before which may have moved. + call s:UpdateLineNumbers(a:buffer, l:current_sign_list, a:loclist) - " If we haven't already set a dummy sign, and we have some previous signs - " or always want a dummy sign, then set one, to keep the sign column open. - if !l:dummy_sign_set && (len(l:signlist) > 0 || g:ale_sign_column_always) - execute 'sign place ' . g:ale_sign_offset - \ . ' line=1 name=ALEDummySign buffer=' - \ . a:buffer + " Group items after updating the line numbers. + let l:grouped_items = s:GroupLoclistItems(a:buffer, a:loclist) - let l:dummy_sign_set = 1 + " 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 - " Now remove the previous signs. The dummy will hold the column open - " while we add the new signs, if we had signs before. - for l:current_id in l:current_id_list - if l:current_id != g:ale_sign_offset - exec 'sign unplace ' . l:current_id . ' buffer=' . a:buffer - endif + for l:command in l:command_list + silent! execute l:command endfor - " Add the new signs, - for l:index in range(0, len(l:signlist) - 1) - let l:sign = l:signlist[l:index] - let l:type = l:sign['type'] ==# 'W' ? 'ALEWarningSign' : 'ALEErrorSign' - - let l:sign_line = 'sign place ' . (l:index + g:ale_sign_offset + 1) - \. ' line=' . l:sign['lnum'] - \. ' name=' . l:type - \. ' buffer=' . a:buffer - - exec l:sign_line - endfor - - " Remove the dummy sign now we've updated the signs, unless we want - " to keep it, which will keep the sign column open even when there are - " no warnings or errors. - if l:dummy_sign_set && !g:ale_sign_column_always - execute 'sign unplace ' . g:ale_sign_offset . ' buffer=' . a:buffer + " 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 diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim index c01dd34..3f53368 100644 --- a/autoload/ale/statusline.vim +++ b/autoload/ale/statusline.vim @@ -1,28 +1,58 @@ " Author: KabbAmine " 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 - let l:errors = 0 - let l:warnings = 0 + if !exists('g:ale_buffer_info') || !has_key(g:ale_buffer_info, a:buffer) + return + endif - for l:entry in a:loclist - if l:entry.type ==# 'E' - let l:errors += 1 + let l:loclist = filter(copy(a:loclist), 'v:val.bufnr == a:buffer') + let l:count = s:CreateCountDict() + let l:count.total = len(l:loclist) + + for l:entry in l:loclist + if l:entry.type is# 'W' + if get(l:entry, 'sub_type', '') is# 'style' + let l:count.style_warning += 1 + else + let l:count.warning += 1 + endif + elseif l:entry.type is# 'I' + let l:count.info += 1 + elseif get(l:entry, 'sub_type', '') is# 'style' + let l:count.style_error += 1 else - let l:warnings += 1 + let l:count.error += 1 endif endfor - let g:ale_buffer_info[a:buffer].count = [l:errors, l:warnings] + " 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 endfunction -" Set the error and warning counts, calling for an update only if needed. -" If counts cannot be set, return 0. -function! s:SetupCount(buffer) abort - if !has_key(g:ale_buffer_info, a:buffer) - " Linters have not been run for the buffer yet, so stop here. - return 0 +" Get the counts for the buffer, and update the counts if needed. +function! s:GetCounts(buffer) abort + if !exists('g:ale_buffer_info') || !has_key(g:ale_buffer_info, a:buffer) + return s:CreateCountDict() endif " Cache is cold, so manually ask for an update. @@ -30,32 +60,23 @@ function! s:SetupCount(buffer) abort call ale#statusline#Update(a:buffer, g:ale_buffer_info[a:buffer].loclist) endif - return 1 -endfunction - -" Returns a tuple of errors and warnings for use in third-party integrations. -function! ale#statusline#Count(buffer) abort - if !s:SetupCount(a:buffer) - return [0, 0] - endif - return g:ale_buffer_info[a:buffer].count endfunction -" Returns a formatted string that can be integrated in the statusline. -function! ale#statusline#Status() abort +" 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 + +" This is the historical format setting which could be configured before. +function! s:StatusForListFormat() abort let [l:error_format, l:warning_format, l:no_errors] = g:ale_statusline_format - let l:buffer = bufnr('%') - - if !s:SetupCount(l:buffer) - return l:no_errors - endif - - let [l:error_count, l:warning_count] = g:ale_buffer_info[l:buffer].count + let l:counts = s:GetCounts(bufnr('')) " Build strings based on user formatting preferences. - let l:errors = l:error_count ? printf(l:error_format, l:error_count) : '' - let l:warnings = l:warning_count ? printf(l:warning_format, l:warning_count) : '' + let l:errors = l:counts[0] ? printf(l:error_format, l:counts[0]) : '' + let l:warnings = l:counts[1] ? printf(l:warning_format, l:counts[1]) : '' " Different formats based on the combination of errors and warnings. if empty(l:errors) && empty(l:warnings) @@ -68,3 +89,24 @@ function! ale#statusline#Status() abort return l:res endfunction + +" Returns a formatted string that can be integrated in the statusline. +" +" This function is deprecated, and should not be used. Use the airline plugin +" instead, or write your own status function with ale#statusline#Count() +function! ale#statusline#Status() abort + if !get(g:, 'ale_deprecation_ale_statusline_status', 0) + execute 'echom ''ale#statusline#Status() is deprecated, use ale#statusline#Count() to write your own function.''' + let g:ale_deprecation_ale_statusline_status = 1 + endif + + if !exists('g:ale_statusline_format') + return 'OK' + endif + + if type(g:ale_statusline_format) == type([]) + return s:StatusForListFormat() + endif + + return '' +endfunction diff --git a/autoload/ale/test.vim b/autoload/ale/test.vim new file mode 100644 index 0000000..bea10c5 --- /dev/null +++ b/autoload/ale/test.vim @@ -0,0 +1,54 @@ +" 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 + +" 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: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 + + silent! noautocmd execute 'file ' . fnameescape(ale#path#Simplify(l:full_path)) +endfunction diff --git a/autoload/ale/toggle.vim b/autoload/ale/toggle.vim new file mode 100644 index 0000000..e9cc29b --- /dev/null +++ b/autoload/ale/toggle.vim @@ -0,0 +1,193 @@ +function! ale#toggle#InitAuGroups() abort + " This value used to be a Boolean as a Number, and is now a String. + let l:text_changed = '' . g:ale_lint_on_text_changed + + augroup ALEPatternOptionsGroup + autocmd! + autocmd BufEnter,BufRead * call ale#pattern_options#SetOptions(str2nr(expand(''))) + augroup END + + augroup ALERunOnTextChangedGroup + autocmd! + if g:ale_enabled + if l:text_changed is? 'always' || l:text_changed is# '1' + autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay) + elseif l:text_changed is? 'normal' + autocmd TextChanged * call ale#Queue(g:ale_lint_delay) + elseif l:text_changed is? 'insert' + autocmd TextChangedI * call ale#Queue(g:ale_lint_delay) + endif + endif + augroup END + + augroup ALERunOnEnterGroup + autocmd! + if g:ale_enabled + " Handle everything that needs to happen when buffers are entered. + autocmd BufEnter * call ale#events#EnterEvent(str2nr(expand(''))) + endif + if g:ale_enabled && g:ale_lint_on_enter + autocmd BufWinEnter,BufRead * call ale#Queue(0, 'lint_file', str2nr(expand(''))) + " Track when the file is changed outside of Vim. + autocmd FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand(''))) + endif + augroup END + + augroup ALERunOnFiletypeChangeGroup + autocmd! + if g:ale_enabled && g:ale_lint_on_filetype_changed + " Only start linting if the FileType actually changes after + " opening a buffer. The FileType will fire when buffers are opened. + autocmd FileType * call ale#events#FileTypeEvent( + \ str2nr(expand('')), + \ expand('') + \) + endif + augroup END + + augroup ALERunOnSaveGroup + autocmd! + autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand(''))) + augroup END + + augroup ALERunOnInsertLeave + autocmd! + if g:ale_enabled && g:ale_lint_on_insert_leave + autocmd InsertLeave * call ale#Queue(0) + endif + augroup END + + augroup ALECursorGroup + autocmd! + if g:ale_enabled && g:ale_echo_cursor + autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() + " Look for a warning to echo as soon as we leave Insert mode. + " The script's position variable used when moving the cursor will + " not be changed here. + autocmd InsertLeave * call ale#cursor#EchoCursorWarning() + endif + augroup END + + if !g:ale_enabled + augroup! ALERunOnTextChangedGroup + augroup! ALERunOnEnterGroup + augroup! ALERunOnInsertLeave + augroup! ALECursorGroup + endif +endfunction + +function! s:EnablePreamble() abort + " Set pattern options again, if enabled. + if g:ale_pattern_options_enabled + call ale#pattern_options#SetOptions(bufnr('')) + endif + + " Lint immediately, including running linters against the file. + call ale#Queue(0, 'lint_file') + + if g:ale_set_balloons + call ale#balloon#Enable() + endif +endfunction + +function! s:DisablePostamble() abort + " Remove highlights for the current buffer now. + if g:ale_set_highlights + call ale#highlight#UpdateHighlights() + endif + + if g:ale_set_balloons + call ale#balloon#Disable() + endif +endfunction + +function! s:CleanupEveryBuffer() abort + 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#toggle#Toggle() abort + let g:ale_enabled = !get(g:, 'ale_enabled') + + if g:ale_enabled + call s:EnablePreamble() + else + call s:CleanupEveryBuffer() + call s:DisablePostamble() + endif + + call ale#toggle#InitAuGroups() +endfunction + +function! ale#toggle#Enable() abort + if !g:ale_enabled + " Set pattern options again, if enabled. + if g:ale_pattern_options_enabled + call ale#pattern_options#SetOptions(bufnr('')) + endif + + call ale#toggle#Toggle() + endif +endfunction + +function! ale#toggle#Disable() abort + if g:ale_enabled + call ale#toggle#Toggle() + endif +endfunction + +function! ale#toggle#Reset() abort + call s: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 + execute '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 0000000..934637d --- /dev/null +++ b/autoload/ale/uri.vim @@ -0,0 +1,18 @@ +" This probably doesn't handle Unicode characters well. +function! ale#uri#Encode(value) abort + return substitute( + \ a:value, + \ '\([^a-zA-Z0-9\\/$\-_.!*''(),]\)', + \ '\=printf(''%%%02x'', char2nr(submatch(1)))', + \ 'g' + \) +endfunction + +function! ale#uri#Decode(value) abort + return substitute( + \ a:value, + \ '%\(\x\x\)', + \ '\=nr2char(''0x'' . submatch(1))', + \ 'g' + \) +endfunction diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index 6c6f20b..b94a11b 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -1,28 +1,23 @@ " Author: w0rp " Description: Contains miscellaneous functions -function! s:FindWrapperScript() abort - for l:parent in split(&runtimepath, ',') - " Expand the path to deal with ~ issues. - let l:path = expand(l:parent . '/' . 'stdin-wrapper') - - if filereadable(l:path) - if has('win32') - return l:path . '.exe' - endif - - return l:path - endif - endfor +" A wrapper function for mode() so we can test calls for it. +function! ale#util#Mode(...) abort + return call('mode', a:000) endfunction -let g:ale#util#stdin_wrapper = s:FindWrapperScript() +" A wrapper function for feedkeys so we can test calls for it. +function! ale#util#FeedKeys(...) abort + return call('feedkeys', a:000) +endfunction -" A null file for sending output to nothing. -let g:ale#util#nul_file = '/dev/null' +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' + if has('win32') + let g:ale#util#nul_file = 'nul' + endif endif " Return the number of lines for a given buffer. @@ -30,49 +25,6 @@ function! ale#util#GetLineCount(buffer) abort return len(getbufline(a:buffer, 1, '$')) endfunction -" Given a buffer and a filename, find the nearest file by searching upwards -" through the paths relative to the given buffer. -function! ale#util#FindNearestFile(buffer, filename) abort - let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p') - - 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#util#FindNearestDirectory(buffer, directory_name) abort - let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p') - - 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, an 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#util#ResolveLocalPath(buffer, search_string, global_fallback) abort - " Search for a locally installed file first. - let l:path = ale#util#FindNearestFile(a:buffer, a:search_string) - - " If the serach fails, try the global executable instead. - if empty(l:path) - let l:path = a:global_fallback - endif - - return l:path -endfunction - function! ale#util#GetFunction(string_or_ref) abort if type(a:string_or_ref) == type('') return function(a:string_or_ref) @@ -81,57 +33,281 @@ function! ale#util#GetFunction(string_or_ref) abort return a:string_or_ref endfunction +" Compare two loclist items for ALE, sorted by their buffers, filenames, and +" line numbers and column numbers. function! ale#util#LocItemCompare(left, right) abort - if a:left['lnum'] < a:right['lnum'] + if a:left.bufnr < a:right.bufnr return -1 endif - if a:left['lnum'] > a:right['lnum'] + if a:left.bufnr > a:right.bufnr return 1 endif - if a:left['col'] < a:right['col'] + 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['col'] > a:right['col'] + 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 return 0 endfunction -" This function will perform a binary search to find a message from the -" loclist to echo when the cursor moves. -function! ale#util#BinarySearch(loclist, line, column) abort +" Compare two loclist items, including the text for the items. +" +" This function can be used for de-duplicating lists. +function! ale#util#LocItemCompareWithText(left, right) abort + let l:cmp_value = ale#util#LocItemCompare(a:left, a:right) + + if l:cmp_value + return l:cmp_value + endif + + if a:left.text < a:right.text + return -1 + endif + + if a:left.text > a:right.text + return 1 + endif + + return 0 +endfunction + +" This function will perform a binary search and a small sequential search +" on the list to find the last problem in the buffer and line which is +" on or before the column. The index of the problem will be returned. +" +" -1 will be returned if nothing can be found. +function! ale#util#BinarySearch(loclist, buffer, line, column) abort let l:min = 0 let l:max = len(a:loclist) - 1 - let l:last_column_match = -1 while 1 if l:max < l:min - return l:last_column_match + return -1 endif let l:mid = (l:min + l:max) / 2 - let l:obj = a:loclist[l:mid] + let l:item = a:loclist[l:mid] - " Binary search to get on the same line - if a:loclist[l:mid]['lnum'] < a:line + " Binary search for equal buffers, equal lines, then near columns. + if l:item.bufnr < a:buffer let l:min = l:mid + 1 - elseif a:loclist[l:mid]['lnum'] > a:line + elseif l:item.bufnr > a:buffer + let l:max = l:mid - 1 + elseif l:item.lnum < a:line + let l:min = l:mid + 1 + elseif l:item.lnum > a:line let l:max = l:mid - 1 else - let l:last_column_match = l:mid + " This part is a small sequential search. + let l:index = l:mid - " Binary search to get the same column, or near it - if a:loclist[l:mid]['col'] < a:column - let l:min = l:mid + 1 - elseif a:loclist[l:mid]['col'] > a:column - let l:max = l:mid - 1 - else - return l:mid - endif + " Search backwards to find the first problem on the line. + while l:index > 0 + \&& a:loclist[l:index - 1].bufnr == a:buffer + \&& a:loclist[l:index - 1].lnum == a:line + let l:index -= 1 + endwhile + + " Find the last problem on or before this column. + while l:index < l:max + \&& a:loclist[l:index + 1].bufnr == a:buffer + \&& a:loclist[l:index + 1].lnum == a:line + \&& a:loclist[l:index + 1].col <= a:column + let l:index += 1 + endwhile + + return l:index endif endwhile endfunction + +" A function for testing if a function is running inside a sandbox. +" See :help sandbox +function! ale#util#InSandbox() abort + try + function! s:SandboxCheck() abort + endfunction + catch /^Vim\%((\a\+)\)\=:E48/ + " E48 is the sandbox error. + return 1 + endtry + + return 0 +endfunction + +" Get the number of milliseconds since some vague, but consistent, point in +" the past. +" +" This function can be used for timing execution, etc. +" +" The time will be returned as a Number. +function! ale#util#ClockMilliseconds() abort + return float2nr(reltimefloat(reltime()) * 1000) +endfunction + +" Given a single line, or a List of lines, and a single pattern, or a List +" of patterns, return all of the matches for the lines(s) from the given +" patterns, using matchlist(). +" +" 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) == type([]) ? a:lines : [a:lines] + let l:patterns = type(a:patterns) == type([]) ? 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 + +function! s:LoadArgCount(function) abort + let l:Function = a:function + + redir => l:output + silent! function Function + redir END + + if !exists('l:output') + return 0 + endif + + 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) == type('') ? 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/ + return a:default + endtry +endfunction + +" Write a file, including carriage return characters for DOS files. +" +" The buffer number is required for determining the fileformat setting for +" the buffer. +function! ale#util#Writefile(buffer, lines, filename) abort + let l:corrected_lines = getbufvar(a:buffer, '&fileformat') is# 'dos' + \ ? map(copy(a:lines), 'v:val . "\r"') + \ : a:lines + + call writefile(l:corrected_lines, a:filename) " no-custom-checks +endfunction + +if !exists('s:patial_timers') + let s:partial_timers = {} +endif + +function! s:ApplyPartialTimer(timer_id) abort + let [l:Callback, l:args] = remove(s:partial_timers, a:timer_id) + call call(l:Callback, [a:timer_id] + l:args) +endfunction + +" Given a delay, a callback, a List of arguments, start a timer with +" timer_start() and call the callback provided with [timer_id] + args. +" +" The timer must not be stopped with timer_stop(). +" Use ale#util#StopPartialTimer() instead, which can stop any timer, and will +" clear any arguments saved for executing callbacks later. +function! ale#util#StartPartialTimer(delay, callback, args) abort + let l:timer_id = timer_start(a:delay, function('s:ApplyPartialTimer')) + let s:partial_timers[l:timer_id] = [a:callback, a:args] + + return l:timer_id +endfunction + +function! ale#util#StopPartialTimer(timer_id) abort + call timer_stop(a:timer_id) + + if has_key(s:partial_timers, a:timer_id) + call remove(s:partial_timers, a:timer_id) + endif +endfunction diff --git a/custom-checks b/custom-checks deleted file mode 100755 index 798049e..0000000 --- a/custom-checks +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash -eu - -# 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: ./custom-checks [--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 - -# Called to output an error. -# If called at least one, the return code for this script will be 1. -output_error() { - echo "$FILENAME:$LINE_NUMBER $1" - RETURN_CODE=1 -} - -# This function is called for each line in each file to check syntax. -check_line() { - line="$1" - - if [[ "$line" =~ ^function ]]; then - if ! [[ "$line" =~ abort$ ]]; then - if ((FIX_ERRORS)); then - # Use sed to add the 'abort' flag - sed -i "${LINE_NUMBER}s/$/ abort/" "$FILENAME" - else - output_error 'Function without abort keyword (See :help except-compat)' - fi - fi - fi - - if [[ "$line" =~ ' '+$ ]]; then - output_error 'Trailing whitespace' - fi -} - -# Loop through all of the vim files and keep track of the file line numbers. -for FILENAME in $(find "$1" -name '*.vim'); do - LINE_NUMBER=0 - - while read; do - LINE_NUMBER=$(expr $LINE_NUMBER + 1) - - check_line "$REPLY" - done < "$FILENAME" -done - -exit $RETURN_CODE diff --git a/doc/ale-asciidoc.txt b/doc/ale-asciidoc.txt new file mode 100644 index 0000000..b6b64fd --- /dev/null +++ b/doc/ale-asciidoc.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE AsciiDoc Integration *ale-asciidoc-options* + + +=============================================================================== +write-good *ale-asciidoc-write-good* + +See |ale-write-good-options| + + +=============================================================================== +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 0000000..a97c6d0 --- /dev/null +++ b/doc/ale-asm.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE ASM Integration *ale-asm-options* + + +=============================================================================== +gcc *ale-asm-gcc* + +g:ale_asm_gcc_executable *g:ale_asm_gcc_executable* + *b:ale_asm_gcc_executable* + Type: |String| + Default: `'gcc'` + +This variable can be changed to use a different executable for gcc. + + +g:ale_asm_gcc_options *g:ale_asm_gcc_options* + *b:ale_asm_gcc_options* + Type: |String| + Default: `'-Wall'` + + This variable can be set to pass additional options to gcc. + + +=============================================================================== + 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 0000000..b9c5c34 --- /dev/null +++ b/doc/ale-awk.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Awk Integration *ale-awk-options* + + +=============================================================================== +gawk *ale-awk-gawk* + +g:ale_awk_gawk_executable *g:ale_awk_gawk_executable* + *b:ale_awk_gawk_executable* + Type: |String| + Default: `'gawk'` + + This variable sets executable used for gawk. + + +g:ale_awk_gawk_options *g:ale_awk_gawk_options* + *b:ale_awk_gawk_options* + 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-c.txt b/doc/ale-c.txt new file mode 100644 index 0000000..cf483fb --- /dev/null +++ b/doc/ale-c.txt @@ -0,0 +1,193 @@ +=============================================================================== +ALE C Integration *ale-c-options* + + +=============================================================================== +Global Options + +g:ale_c_build_dir_names *g:ale_c_build_dir_names* + *b:ale_c_build_dir_names* + + Type: |List| + Default: `['build', 'bin']` + + A list of directory names to be used when searching upwards from cpp + files to discover compilation databases with. For directory named `'foo'`, + ALE will search for `'foo/compile_commands.json'` in all directories on and above + the directory containing the cpp file to find path to compilation database. + This feature is useful for the clang tools wrapped around LibTooling (namely + here, clang-tidy) + + +g:ale_c_build_dir *g:ale_c_build_dir* + *b:ale_c_build_dir* + + Type: |String| + Default: `''` + + A path to the directory containing the `compile_commands.json` file to use + with c-family linters. Usually setting this option to a non-empty string + will override the |g:ale_c_build_dir_names| option to impose a compilation + database (it can be useful if multiple builds are in multiple build + subdirectories in the project tree). + This feature is also most useful for the clang tools linters, wrapped + aroung LibTooling (namely clang-tidy here) + +=============================================================================== +clang *ale-c-clang* + +g:ale_c_clang_executable *g:ale_c_clang_executable* + *b:ale_c_clang_executable* + Type: |String| + Default: `'clang'` + + This variable can be changed to use a different executable for clang. + + +g:ale_c_clang_options *g:ale_c_clang_options* + *b:ale_c_clang_options* + Type: |String| + Default: `'-std=c11 -Wall'` + + This variable can be changed to modify flags given to clang. + + +=============================================================================== +clang-format *ale-c-clangformat* + +g:ale_c_clangformat_executable *g:ale_c_clangformat_executable* + *b:ale_c_clangformat_executable* + Type: |String| + Default: `'clang-format'` + + This variable can be changed to use a different executable for clang-format. + + +g:ale_c_clangformat_options *g:ale_c_clangformat_options* + *b:ale_c_clangformat_options* + Type: |String| + Default: `''` + + This variable can be change to modify flags given to clang-format. + + +=============================================================================== +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|. + + +g:ale_c_clangtidy_checks *g:ale_c_clangtidy_checks* + *b: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 + + +g:ale_c_clangtidy_executable *g:ale_c_clangtidy_executable* + *b:ale_c_clangtidy_executable* + Type: |String| + Default: `'clang-tidy'` + + This variable can be changed to use a different executable for clangtidy. + + +g:ale_c_clangtidy_options *g:ale_c_clangtidy_options* + *b:ale_c_clangtidy_options* + Type: |String| + Default: `''` + + This variable can be changed to modify 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. + + +=============================================================================== +cppcheck *ale-c-cppcheck* + +g:ale_c_cppcheck_executable *g:ale_c_cppcheck_executable* + *b:ale_c_cppcheck_executable* + Type: |String| + Default: `'cppcheck'` + + This variable can be changed to use a different executable for cppcheck. + + +g:ale_c_cppcheck_options *g:ale_c_cppcheck_options* + *b:ale_c_cppcheck_options* + Type: |String| + Default: `'--enable=style'` + + This variable can be changed to modify flags given to cppcheck. + + +=============================================================================== +flawfinder *ale-c-flawfinder* + +g:ale_c_flawfinder_executable *g:ale_c_flawfinder_executable* + *b:ale_c_flawfinder_executable* + Type: |String| + Default: `'flawfinder'` + + This variable can be changed to use a different executable for flawfinder. + + +g:ale_c_flawfinder_minlevel *g:ale_c_flawfinder_minlevel* + *b:ale_c_flawfinder_minlevel* + Type: |Number| + Default: `1` + + This variable can be changed to ignore risks under the given risk threshold. + + +g:ale_c_flawfinder_options *g:ale-c-flawfinder* + *b:ale-c-flawfinder* + Type: |String| + Default: `''` + + This variable can be used to pass extra options into the flawfinder command. + + +=============================================================================== +gcc *ale-c-gcc* + +g:ale_c_gcc_executable *g:ale_c_gcc_executable* + *b:ale_c_gcc_executable* + Type: |String| + Default: `'gcc'` + + This variable can be changed to use a different executable for gcc. + + +g:ale_c_gcc_options *g:ale_c_gcc_options* + *b:ale_c_gcc_options* + Type: |String| + Default: `'-std=c11 -Wall'` + + This variable can be change to modify flags given to gcc. + + +=============================================================================== + 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 0000000..5024e27 --- /dev/null +++ b/doc/ale-chef.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE Chef Integration *ale-chef-options* + + +=============================================================================== +foodcritic *ale-chef-foodcritic* + +g:ale_chef_foodcritic_options *g:ale_chef_foodcritic_options* + *b:ale_chef_foodcritic_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to foodcritic. + + +g:ale_chef_foodcritic_executable *g:ale_chef_foodcritic_executable* + *b: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 0000000..a83e336 --- /dev/null +++ b/doc/ale-clojure.txt @@ -0,0 +1,21 @@ +=============================================================================== +ALE Clojure Integration *ale-clojure-options* + + +=============================================================================== +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-cmake.txt b/doc/ale-cmake.txt new file mode 100644 index 0000000..fb46336 --- /dev/null +++ b/doc/ale-cmake.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE CMake Integration *ale-cmake-options* + + +=============================================================================== +cmakelint *ale-cmake-cmakelint* + +g:ale_cmake_cmakelint_executable *g:ale_cmake_cmakelint_executable* + *b:ale_cmake_cmakelint_executable* + Type: |String| + Default: `'cmakelint'` + + This variable can be set to change the path the cmakelint. + + +g:ale_cmake_cmakelint_options *g:ale_cmake_cmakelint_options* + *b:ale_cmake_cmakelint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to cmakelint. + + +=============================================================================== + 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 0000000..315f293 --- /dev/null +++ b/doc/ale-cpp.txt @@ -0,0 +1,203 @@ +=============================================================================== +ALE C++ Integration *ale-cpp-options* + + +=============================================================================== +Global Options + +The |g:ale_c_build_dir_names| and |g:ale_c_build_dir| also apply to some C++ +linters too. + + +=============================================================================== +clang *ale-cpp-clang* + +g:ale_cpp_clang_executable *g:ale_cpp_clang_executable* + *b:ale_cpp_clang_executable* + Type: |String| + Default: `'clang++'` + + This variable can be changed to use a different executable for clang. + + +g:ale_cpp_clang_options *g:ale_cpp_clang_options* + *b:ale_cpp_clang_options* + Type: |String| + Default: `'-std=c++14 -Wall'` + + This variable can be changed to modify flags given to clang. + + +=============================================================================== +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|. + + +g:ale_cpp_clangcheck_executable *g:ale_cpp_clangcheck_executable* + *b:ale_cpp_clangcheck_executable* + Type: |String| + Default: `'clang-check'` + + This variable can be changed to use a different executable for clangcheck. + + +g:ale_cpp_clangcheck_options *g:ale_cpp_clangcheck_options* + *b: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. + + +=============================================================================== +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|. + + +g:ale_cpp_clangtidy_checks *g:ale_cpp_clangtidy_checks* + *b: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. + + +g:ale_cpp_clangtidy_executable *g:ale_cpp_clangtidy_executable* + *b:ale_cpp_clangtidy_executable* + Type: |String| + Default: `'clang-tidy'` + + This variable can be changed to use a different executable for clangtidy. + + +g:ale_cpp_clangtidy_options *g:ale_cpp_clangtidy_options* + *b:ale_cpp_clangtidy_options* + Type: |String| + Default: `''` + + This variable can be changed to modify 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. + + +=============================================================================== +cppcheck *ale-cpp-cppcheck* + +g:ale_cpp_cppcheck_executable *g:ale_cpp_cppcheck_executable* + *b:ale_cpp_cppcheck_executable* + Type: |String| + Default: `'cppcheck'` + + This variable can be changed to use a different executable for cppcheck. + + +g:ale_cpp_cppcheck_options *g:ale_cpp_cppcheck_options* + *b:ale_cpp_cppcheck_options* + Type: |String| + Default: `'--enable=style'` + + This variable can be changed to modify flags given to cppcheck. + + +=============================================================================== +cpplint *ale-cpp-cpplint* + +g:ale_cpp_cpplint_executable *g:ale_cpp_cpplint_executable* + *b:ale_cpp_cpplint_executable* + Type: |String| + Default: `'cpplint'` + + This variable can be changed to use a different executable for cpplint. + + +g:ale_cpp_cpplint_options *g:ale_cpp_cpplint_options* + *b:ale_cpp_cpplint_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to cpplint. + + +=============================================================================== +flawfinder *ale-cpp-flawfinder* + +g:ale_cpp_flawfinder_executable *g:ale_cpp_flawfinder_executable* + *b:ale_cpp_flawfinder_executable* + Type: |String| + Default: `'flawfinder'` + + This variable can be changed to use a different executable for flawfinder. + + +g:ale_cpp_flawfinder_minlevel *g:ale_cpp_flawfinder_minlevel* + *b:ale_cpp_flawfinder_minlevel* + Type: |Number| + Default: `1` + + This variable can be changed to ignore risks under the given risk threshold. + + +g:ale_cpp_flawfinder_options *g:ale-cpp-flawfinder* + *b:ale-cpp-flawfinder* + Type: |String| + Default: `''` + + This variable can be used to pass extra options into the flawfinder command. + + +=============================================================================== +gcc *ale-cpp-gcc* + +g:ale_cpp_gcc_executable *g:ale_cpp_gcc_executable* + *b:ale_cpp_gcc_executable* + Type: |String| + Default: `'gcc'` + + This variable can be changed to use a different executable for gcc. + + +g:ale_cpp_gcc_options *g:ale_cpp_gcc_options* + *b:ale_cpp_gcc_options* + Type: |String| + Default: `'-std=c++14 -Wall'` + + This variable can be changed to modify flags given to gcc. + + +=============================================================================== + 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 0000000..3a02df6 --- /dev/null +++ b/doc/ale-cs.txt @@ -0,0 +1,102 @@ +=============================================================================== +ALE C# Integration *ale-cs-options* + + +=============================================================================== +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. + + +g:ale_cs_mcs_options *g:ale_cs_mcs_options* + *b: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 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. + + +g:ale_cs_mcsc_options *g:ale_cs_mcsc_options* + *b: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`. + + +g:ale_cs_mcsc_source *g:ale_cs_mcsc_source* + *b: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. + + +g:ale_cs_mcsc_assembly_path *g:ale_cs_mcsc_assembly_path* + *b: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. + + +g:ale_cs_mcsc_assemblies *g:ale_cs_mcsc_assemblies* + *b: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', + \] +< + +=============================================================================== + 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 0000000..474445b --- /dev/null +++ b/doc/ale-css.txt @@ -0,0 +1,39 @@ +=============================================================================== +ALE CSS Integration *ale-css-options* + + +=============================================================================== +prettier *ale-css-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +stylelint *ale-css-stylelint* + +g:ale_css_stylelint_executable *g:ale_css_stylelint_executable* + *b:ale_css_stylelint_executable* + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + +g:ale_css_stylelint_options *g:ale_css_stylelint_options* + *b:ale_css_stylelint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + +g:ale_css_stylelint_use_global *g:ale_css_stylelint_use_global* + *b:ale_css_stylelint_use_global* + Type: |String| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== + 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 0000000..052b336 --- /dev/null +++ b/doc/ale-cuda.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE CUDA Integration *ale-cuda-options* + + +=============================================================================== +nvcc *ale-cuda-nvcc* + +g:ale_cuda_nvcc_executable *g:ale_cuda_nvcc_executable* + *b: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. + + +g:ale_cuda_nvcc_options *g:ale_cuda_nvcc_options* + *b: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-dart.txt b/doc/ale-dart.txt new file mode 100644 index 0000000..c6faa5c --- /dev/null +++ b/doc/ale-dart.txt @@ -0,0 +1,38 @@ +=============================================================================== +ALE Dart Integration *ale-dart-options* + + +=============================================================================== +dartanalyzer *ale-dart-dartanalyzer* + +Installation +------------------------------------------------------------------------------- + +Install Dart via whatever means. `dartanalyzer` will be included in the SDK. + +You can add the SDK to `$PATH`, as described here: +https://www.dartlang.org/tools/sdk + +If you have installed Dart on Linux, you can also try the following: > + " Set the executable path for dartanalyzer to the absolute path to it. + let g:ale_dart_dartanalyzer_executable = '/usr/lib/dart/bin/dartanalyzer' +< +... or similarly for wherever your Dart SDK lives. This should work without +having to modify `$PATH`. + +ALE can only check for problems with `dartanalyzer` with the file on disk. +See |ale-lint-file-linters| + +Options +------------------------------------------------------------------------------- + +g:ale_dart_dartanalyzer_executable *g:ale_dart_dartanalyzer_executable* + *b:ale_dart_dartanalyzer_executable* + Type: |String| + Default: `'dartanalyzer'` + + This variable can be set to change the path to dartanalyzer. + + +=============================================================================== + 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 0000000..805cc47 --- /dev/null +++ b/doc/ale-dockerfile.txt @@ -0,0 +1,37 @@ +=============================================================================== +ALE Dockerfile Integration *ale-dockerfile-options* + + +=============================================================================== +hadolint *ale-dockerfile-hadolint* + + hadolint can be found at: https://github.com/hadolint/hadolint + + +g:ale_dockerfile_hadolint_use_docker *g:ale_dockerfile_hadolint_use_docker* + *b: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. + + +g:ale_dockerfile_hadolint_image *g:ale_dockerfile_hadolint_image* + *b: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 0000000..b7d4922 --- /dev/null +++ b/doc/ale-elixir.txt @@ -0,0 +1,32 @@ +=============================================================================== +ALE Elixir Integration *ale-elixir-options* + + +=============================================================================== +mix *ale-elixir-mix* + +g:ale_elixir_mix_options *g:ale_elixir_mix_options* + *b:ale_elixir_mix_options* + Type: |String| + Default: `'mix'` + + + This variable can be changed to specify the mix executable. + +=============================================================================== +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. + +=============================================================================== + 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 0000000..e96205d --- /dev/null +++ b/doc/ale-elm.txt @@ -0,0 +1,50 @@ +=============================================================================== +ALE Elm Integration *ale-elm-options* + + +=============================================================================== +elm-format *ale-elm-elm-format* + +g:ale_elm_format_executable *g:ale_elm_format_executable* + *b:ale_elm_format_executable* + Type: |String| + Default: `'elm-format'` + + See |ale-integrations-local-executables| + + +g:ale_elm_format_use_global *g:ale_elm_format_use_global* + *b:ale_elm_format_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +g:ale_elm_format_options *g:ale_elm_format_options* + *b:ale_elm_format_options* + Type: |String| + Default: `'--yes'` + + This variable can be set to pass additional options to elm-format. + +=============================================================================== +elm-make *ale-elm-elm-make* + +g:ale_elm_make_executable *g:ale_elm_make_executable* + *b:ale_elm_make_executable* + Type: |String| + Default: `'elm-make'` + + See |ale-integrations-local-executables| + + +g:ale_elm_make_use_global *g:ale_elm_make_use_global* + *b:ale_elm_make_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-erlang.txt b/doc/ale-erlang.txt new file mode 100644 index 0000000..ad3c1e5 --- /dev/null +++ b/doc/ale-erlang.txt @@ -0,0 +1,29 @@ +=============================================================================== +ALE Erlang Integration *ale-erlang-options* + + +=============================================================================== +erlc *ale-erlang-erlc* + +g:ale_erlang_erlc_options *g:ale_erlang_erlc_options* + *b:ale_erlang_erlc_options* + Type: |String| + Default: `''` + + This variable controls additional parameters passed to `erlc`, such as `-I` + or `-pa`. + + +------------------------------------------------------------------------------- +syntaxerl *ale-erlang-syntaxerl* + +g:ale_erlang_syntaxerl_executable *g:ale_erlang_syntaxerl_executable* + *b: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 0000000..a0f6f4f --- /dev/null +++ b/doc/ale-eruby.txt @@ -0,0 +1,15 @@ +=============================================================================== +ALE Eruby Integration *ale-eruby-options* + +There are three linters for `eruby` files: + +- `erb` +- `erubis` +- `erubi` + +`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. To selectively enable a subset, see |g:ale_linters|. + +=============================================================================== + 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 0000000..8450b38 --- /dev/null +++ b/doc/ale-fish.txt @@ -0,0 +1,14 @@ +=============================================================================== +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. + +=============================================================================== + 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 0000000..ed6bc72 --- /dev/null +++ b/doc/ale-fortran.txt @@ -0,0 +1,36 @@ +=============================================================================== +ALE Fortran Integration *ale-fortran-options* + + +=============================================================================== +gcc *ale-fortran-gcc* + +g:ale_fortran_gcc_executable *g:ale_fortran_gcc_executable* + *b:ale_fortran_gcc_executable* + Type: |String| + Default: `'gcc'` + + This variable can be changed to modify the executable used for checking + Fortran code with GCC. + + +g:ale_fortran_gcc_options *g:ale_fortran_gcc_options* + *b:ale_fortran_gcc_options* + Type: |String| + Default: `'-Wall'` + + This variable can be changed to modify flags given to gcc. + + +g:ale_fortran_gcc_use_free_form *g:ale_fortran_gcc_use_free_form* + *b: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. + + +=============================================================================== + 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 0000000..ac0870c --- /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 0000000..0849c37 --- /dev/null +++ b/doc/ale-fuse.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE FusionScript Integration *ale-fuse-options* + + +=============================================================================== +fusion-lint *ale-fuse-fusionlint* + +g:ale_fusion_fusionlint_executable *g:ale_fuse_fusionlint_executable* + *b:ale_fuse_fusionlint_executable* + Type: |String| + Default: `'fusion-lint'` + + This variable can be changed to change the path to fusion-lint. + + +g:ale_fuse_fusionlint_options *g:ale_fuse_fusionlint_options* + *b: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 0000000..71813dd --- /dev/null +++ b/doc/ale-gitcommit.txt @@ -0,0 +1,42 @@ +=============================================================================== +ALE Git Commit Integration *ale-gitcommit-options* + + +=============================================================================== +gitlint *ale-gitcommit-gitlint* + +g:ale_gitcommit_gitlint_executable *g:ale_gitcommit_gitlint_executable* + *b:ale_gitcommit_gitlint_executable* + Type: |String| + Default: `'gitlint'` + + This variable can be changed to modify the executable used for gitlint. + + +g:ale_gitcommit_gitlint_options *g:ale_gitcommit_gitlint_options* + *b:ale_gitcommit_gitlint_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the gitlint + invocation. + + For example, to dinamically set the gitlint configuration file path, you + may want to set > + + let g:ale_gitcommit_gitlint_options = '-C /home/user/.config/gitlint.ini' +< + +g:ale_gitcommit_gitlint_use_global *g:ale_gitcommit_gitlint_use_global* + *b:ale_gitcommit_gitlint_use_global* + Type: |Number| + Default: `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-glsl.txt b/doc/ale-glsl.txt new file mode 100644 index 0000000..257de75 --- /dev/null +++ b/doc/ale-glsl.txt @@ -0,0 +1,56 @@ +=============================================================================== +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* + +g:ale_glsl_glslang_executable *g:ale_glsl_glslang_executable* + *b:ale_glsl_glslang_executable* + Type: |String| + Default: `'glslangValidator'` + + This variable can be changed to change the path to glslangValidator. + + +g:ale_glsl_glslang_options *g:ale_glsl_glslang_options* + *b:ale_glsl_glslang_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to glslangValidator. + + +=============================================================================== +glslls *ale-glsl-glslls* + +g:ale_glsl_glslls_executable *g:ale_glsl_glslls_executable* + *b:ale_glsl_glslls_executable* + Type: |String| + Default: `'glslls'` + + This variable can be changed to change the path to glslls. + See |ale-integrations-local-executables| + +g:ale_glsl_glslls_logfile *g:ale_glsl_glslls_logfile* + *b: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 0000000..b80bd45 --- /dev/null +++ b/doc/ale-go.txt @@ -0,0 +1,107 @@ +=============================================================================== +ALE Go Integration *ale-go-options* + + +=============================================================================== +Integration Information + +The `gometalinter` linter is disabled by default. ALE enables `gofmt`, +`golint` and `go vet` by default. It also supports `staticcheck`, `go +build` and `gosimple`. + +To enable `gometalinter`, update |g:ale_linters| as appropriate: +> + " Enable all of the linters you want for Go. + let g:ale_linters = {'go': ['gometalinter', 'gofmt']} +< +A possible configuration is to enable `gometalinter` and `gofmt` but paired +with the `--fast` option, set by |g:ale_go_gometalinter_options|. This gets you +the benefit of running a number of linters, more than ALE would by default, +while ensuring it doesn't run any linters known to be slow or resource +intensive. + + +=============================================================================== +gobuild *ale-go-gobuild* + +g:ale_go_gobuild_options *g:ale_go_gobuild_options* + *b: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* + +g:ale_go_gofmt_options *g:ale_go_gofmt_options* + *b:ale_go_gofmt_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the gofmt fixer. + + +=============================================================================== +gometalinter *ale-go-gometalinter* + +`gometalinter` is a `lint_file` linter, which only lints files that are +written to disk. This differs from the default behavior of linting the buffer. +See: |ale-lint-file| + +g:ale_go_gometalinter_executable *g:ale_go_gometalinter_executable* + *b:ale_go_gometalinter_executable* + Type: |String| + Default: `'gometalinter'` + + The executable that will be run for gometalinter. + + +g:ale_go_gometalinter_options *g:ale_go_gometalinter_options* + *b:ale_go_gometalinter_options* + Type: |String| + Default: `''` + + This variable can be changed to alter the command-line arguments to the + gometalinter invocation. + + Since `gometalinter` runs a number of linters that can consume a lot of + resources it's recommended to set this option to a value of `--fast` if you + use `gometalinter` as one of the linters in |g:ale_linters|. This disables a + number of linters known to be slow or consume a lot of resources. + + +g:ale_go_gometalinter_package *g:ale_go_gometalinter_package* + *b:ale_go_gometalinter_package* + Type: |Number| + Default: `0` + + When set to `1`, the whole Go package will be checked instead of only the + current file. + + +=============================================================================== +staticcheck *ale-go-staticcheck* + +g:ale_go_staticcheck_options *g:ale_go_staticcheck_options* + *b:ale_go_staticcheck_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the staticcheck + linter. + + +g:ale_go_staticcheck_package *g:ale_go_staticcheck_package* + *b:ale_go_staticcheck_package* + Type: |Number| + Default: `0` + + When set to `1`, the whole Go package will be checked instead of only the + current file. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-graphql.txt b/doc/ale-graphql.txt new file mode 100644 index 0000000..603694b --- /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-handlebars.txt b/doc/ale-handlebars.txt new file mode 100644 index 0000000..6908aac --- /dev/null +++ b/doc/ale-handlebars.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Handlebars Integration *ale-handlebars-options* + + +=============================================================================== +ember-template-lint *ale-handlebars-embertemplatelint* + +g:ale_handlebars_embertemplatelint_executable + *g:ale_handlebars_embertemplatelint_executable* + Type: |String| *b:ale_handlebars_embertemplatelint_executable* + Default: `'ember-template-lint'` + + See |ale-integrations-local-executables| + + +g:ale_handlebars_embertemplatelint_use_global + *g:ale_handlebars_embertemplatelint_use_global* + Type: |Number| *b:ale_handlebars_embertemplatelint_use_global* + Default: `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 0000000..9fab39b --- /dev/null +++ b/doc/ale-haskell.txt @@ -0,0 +1,66 @@ +=============================================================================== +ALE Haskell Integration *ale-haskell-options* + + +=============================================================================== +brittany *ale-haskell-brittany* + +g:ale_haskell_brittany_executable *g:ale_haskell_brittany_executable* + *b:ale_haskell_brittany_executable* + Type: |String| + Default: `'brittany'` + + This variable can be changed to use a different executable for brittany. + +=============================================================================== +ghc *ale-haskell-ghc* + +g:ale_haskell_ghc_options *g:ale_haskell_ghc_options* + *b:ale_haskell_ghc_options* + Type: |String| + Default: `'-fno-code -v0'` + + This variable can be changed to modify flags given to ghc. + +=============================================================================== +hdevtools *ale-haskell-hdevtools* + +g:ale_haskell_hdevtools_executable *g:ale_haskell_hdevtools_executable* + *b:ale_haskell_hdevtools_executable* + Type: |String| + Default: `'hdevtools'` + + This variable can be changed to use a different executable for hdevtools. + + +g:ale_haskell_hdevtools_options *g:ale_haskell_hdevtools_options* + *b:ale_haskell_hdevtools_options* + Type: |String| + Default: `'-g -Wall'` + + This variable can be changed to modify flags given to hdevtools. + +=============================================================================== +hfmt *ale-haskell-hfmt* + +g:ale_haskell_hfmt_executable *g:ale_haskell_hfmt_executable* + *b:ale_haskell_hfmt_executable* + Type: |String| + Default: `'hfmt'` + + This variable can be changed to use a different executable for hfmt. + +=============================================================================== +stack-build *ale-haskell-stack-build* + +g:ale_haskell_stack_build_options *g:ale_haskell_stack_build_options* + *b: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. + + +=============================================================================== + 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 0000000..416e932 --- /dev/null +++ b/doc/ale-html.txt @@ -0,0 +1,81 @@ +=============================================================================== +ALE HTML Integration *ale-html-options* + + +=============================================================================== +htmlhint *ale-html-htmlhint* + +g:ale_html_htmlhint_executable *g:ale_html_htmlhint_executable* + *b:ale_html_htmlhint_executable* + Type: |String| + Default: `'htmlhint'` + + See |ale-integrations-local-executables| + + +g:ale_html_htmlhint_options *g:ale_html_htmlhint_options* + *b:ale_html_htmlhint_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to HTMLHint. + + +g:ale_html_htmlhint_use_global *g:ale_html_htmlhint_use_global* + *b:ale_html_htmlhint_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. + +g:ale_html_tidy_executable *g:ale_html_tidy_executable* + *b:ale_html_tidy_executable* + Type: |String| + Default: `'tidy'` + + This variable can be changed to change the path to tidy. + + +g:ale_html_tidy_options *g:ale_html_tidy_options* + *b: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 + + +=============================================================================== +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-idris.txt b/doc/ale-idris.txt new file mode 100644 index 0000000..c7500b0 --- /dev/null +++ b/doc/ale-idris.txt @@ -0,0 +1,23 @@ +=============================================================================== +ALE Idris Integration *ale-idris-options* + +=============================================================================== +idris *ale-idris-idris* + +g:ale_idris_idris_executable *g:ale_idris_idris_executable* + *b:ale_idris_idris_executable* + Type: |String| + Default: `'idris'` + + This variable can be changed to change the path to idris. + + +g:ale_idris_idris_options *g:ale_idris_idris_options* + *b:ale_idris_idris_options* + Type: |String| + Default: `'--total --warnpartial --warnreach --warnipkg'` + + This variable can be changed to modify flags given to idris. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-java.txt b/doc/ale-java.txt new file mode 100644 index 0000000..0d2011f --- /dev/null +++ b/doc/ale-java.txt @@ -0,0 +1,57 @@ +=============================================================================== +ALE Java Integration *ale-java-options* + + +=============================================================================== +checkstyle *ale-java-checkstyle* + +g:ale_java_checkstyle_options *g:ale_java_checkstyle_options* + *b:ale_java_checkstyle_options* + + Type: String + Default: '-c /google_checks.xml' + + This variable can be changed to modify flags given to checkstyle. + + +=============================================================================== +javac *ale-java-javac* + +g:ale_java_javac_classpath *g:ale_java_javac_classpath* + *b:ale_java_javac_classpath* + Type: |String| + Default: `''` + + This variable can be set to change the global classpath for Java. + + +g:ale_java_javac_options *g:ale_java_javac_options* + *b:ale_java_javac_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to javac. + + +=============================================================================== +google-java-format *ale-java-google-java-format* + + +g:ale_java_google_java_format_executable + *g:ale_java_google_java_format_executable* + *b:ale_java_google_java_format_executable* + Type: |String| + Default: `'google-java-format'` + + See |ale-integrations-local-executables| + + +g:ale_java_google_java_format_options *g:ale_java_google_java_format_options* + *b:ale_java_google_java_format_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional 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 0000000..f625fd7 --- /dev/null +++ b/doc/ale-javascript.txt @@ -0,0 +1,298 @@ +=============================================================================== +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"] +< + +=============================================================================== +eslint *ale-javascript-eslint* + +g:ale_javascript_eslint_executable *g:ale_javascript_eslint_executable* + *b:ale_javascript_eslint_executable* + Type: |String| + Default: `'eslint'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_eslint_options *g:ale_javascript_eslint_options* + *b:ale_javascript_eslint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to eslint. + + +g:ale_javascript_eslint_use_global *g:ale_javascript_eslint_use_global* + *b:ale_javascript_eslint_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +g:ale_javascript_eslint_suppress_eslintignore + *g:ale_javascript_eslint_suppress_eslintignore* + *b:ale_javascript_eslint_suppress_eslintignore* + Type: |Number| + Default: `0` + + This variable can be set to `1` to disable warnings for files being ignored + by eslint. + + +g:ale_javascript_eslint_suppress_missing_config + *g:ale_javascript_eslint_suppress_missing_config* + *b: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. + + +=============================================================================== +flow *ale-javascript-flow* + +g:ale_javascript_flow_executable *g:ale_javascript_flow_executable* + *b:ale_javascript_flow_executable* + Type: |String| + Default: `'flow'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_flow_use_home_config *g:ale_javascript_flow_use_home_config* + *b: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. + + +g:ale_javascript_flow_use_global *g:ale_javascript_flow_use_global* + *b:ale_javascript_flow_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +importjs *ale-javascript-importjs* + +g:ale_javascript_importjs_executable *g:ale_javascript_importjs_executable* + *b:ale_javascript_importjs_executable* + Type: |String| + Default: `'importjs'` + + +=============================================================================== +jscs *ale-javascript-jscs* + +g:ale_javascript_jscs_executable *g:ale_javascript_jscs_executable* + *b:ale_javascript_jscs_executable* + Type: |String| + Default: `'jscs'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_jscs_use_global *g:ale_javascript_jscs_use_global* + *b:ale_javascript_jscs_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +jshint *ale-javascript-jshint* + +g:ale_javascript_jshint_executable *g:ale_javascript_jshint_executable* + *b:ale_javascript_jshint_executable* + Type: |String| + Default: `'jshint'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_jshint_use_global *g:ale_javascript_jshint_use_global* + *b:ale_javascript_jshint_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier *ale-javascript-prettier* + +g:ale_javascript_prettier_executable *g:ale_javascript_prettier_executable* + *b:ale_javascript_prettier_executable* + Type: |String| + Default: `'prettier'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_prettier_options *g:ale_javascript_prettier_options* + *b:ale_javascript_prettier_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to prettier. + + +g:ale_javascript_prettier_use_global *g:ale_javascript_prettier_use_global* + *b:ale_javascript_prettier_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier-eslint *ale-javascript-prettier-eslint* + +g:ale_javascript_prettier_eslint_executable + *g:ale_javascript_prettier_eslint_executable* + *b:ale_javascript_prettier_eslint_executable* + Type: |String| + Default: `'prettier-eslint'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_prettier_eslint_options + *g:ale_javascript_prettier_eslint_options* + *b:ale_javascript_prettier_eslint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to prettier-eslint. + + +g:ale_javascript_prettier_eslint_use_global + *g:ale_javascript_prettier_eslint_use_global* + *b:ale_javascript_prettier_eslint_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier-standard *ale-javascript-prettier-standard* + + +g:ale_javascript_prettier_standard_executable + *g:ale_javascript_prettier_standard_executable* + *b:ale_javascript_prettier_standard_executable* + Type: |String| + Default: `'prettier-standard'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_prettier_standard_options + *g:ale_javascript_prettier_standard_options* + *b:ale_javascript_prettier_standard_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to prettier-standard. + + +g:ale_javascript_prettier_standard_use_global + *g:ale_javascript_prettier_standard_use_global* + *b:ale_javascript_prettier_standard_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + + + +=============================================================================== +standard *ale-javascript-standard* + +g:ale_javascript_standard_executable *g:ale_javascript_standard_executable* + *b:ale_javascript_standard_executable* + Type: |String| + Default: `'standard'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_standard_options *g:ale_javascript_standard_options* + *b:ale_javascript_standard_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to standard. + + +g:ale_javascript_standard_use_global *g:ale_javascript_standard_use_global* + *b:ale_javascript_standard_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +xo *ale-javascript-xo* + +g:ale_javascript_xo_executable *g:ale_javascript_xo_executable* + *b:ale_javascript_xo_executable* + Type: |String| + Default: `'xo'` + + See |ale-integrations-local-executables| + + +g:ale_javascript_xo_options *g:ale_javascript_xo_options* + *b:ale_javascript_xo_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to xo. + + +g:ale_javascript_xo_use_global *g:ale_javascript_xo_use_global* + *b:ale_javascript_xo_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-json.txt b/doc/ale-json.txt new file mode 100644 index 0000000..1e97abc --- /dev/null +++ b/doc/ale-json.txt @@ -0,0 +1,84 @@ +=============================================================================== +ALE JSON Integration *ale-json-options* + + +=============================================================================== +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|. + +g:ale_json_fixjson_executable *g:ale_json_fixjson_executable* + *b:ale_json_fixjson_executable* + + Type: |String| + Default: `'fixjson'` + + The executable that will be run for fixjson. + +g:ale_json_fixjson_options *g:ale_json_fixjson_options* + *b:ale_json_fixjson_options* + + Type: |String| + Default: `''` + + This variable can add extra options to the command executed for running + fixjson. + +g:ale_json_fixjson_use_global *g:ale_json_fixjson_use_global* + *b:ale_json_fixjson_use_global* + + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +jsonlint *ale-json-jsonlint* + +There are no options available. + + +=============================================================================== +jq *ale-json-jq* + +g:ale_json_jq_executable *g:ale_json_jq_executable* + *b:ale_json_jq_executable* + Type: |String| + Default: `'jq'` + + This option can be changed to change the path for `jq`. + + +g:ale_json_jq_options *g:ale_json_jq_options* + *b:ale_json_jq_options* + Type: |String| + Default: `''` + + This option can be changed to pass extra options to `jq`. + + +=============================================================================== +prettier *ale-json-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-kotlin.txt b/doc/ale-kotlin.txt new file mode 100644 index 0000000..571d2ba --- /dev/null +++ b/doc/ale-kotlin.txt @@ -0,0 +1,90 @@ +=============================================================================== +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* + +g:ale_kotlin_kotlinc_options *g:ale_kotlin_kotlinc_options* + Type: |String| + Default: `''` + + Additional options to pass to the kotlin compiler + +g:ale_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 + +g:ale_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 + +g:ale_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. + +g:ale_kotlin_kotlinc_sourcepath *g:ale_kotlin_kotlinc_sourcepath* + Type: |String| + Default: `''` + + A string containing the paths (separated by space) of the source + directories. + +g:ale_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. + +g:ale_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* + +g:ale_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'` + +g:ale_kotlin_ktlint_rulesets *g:ale_kotlin_ktlint_rulesets* + Type: |List| of |String|s + 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-rulset.jar', + 'com.ktlint.rulesets:mycustomrule:1.0.0'] + + 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 0000000..87fbd4e --- /dev/null +++ b/doc/ale-latex.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE LaTeX Integration *ale-latex-options* + + +=============================================================================== +write-good *ale-latex-write-good* + +See |ale-write-good-options| + + +=============================================================================== +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 0000000..05f56e2 --- /dev/null +++ b/doc/ale-less.txt @@ -0,0 +1,66 @@ +=============================================================================== +ALE Less Integration *ale-less-options* + + +=============================================================================== +lessc *ale-less-lessc* + +g:ale_less_lessc_executable *g:ale_less_lessc_executable* + *b:ale_less_lessc_executable* + Type: |String| + Default: `'lessc'` + + See |ale-integrations-local-executables| + + +g:ale_less_lessc_options *g:ale_less_lessc_options* + *b:ale_less_lessc_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to lessc. + + +g:ale_less_lessc_use_global *g:ale_less_lessc_use_global* + *b:ale_less_lessc_use_global* + Type: |String| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +prettier *ale-less-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +stylelint *ale-less-stylelint* + +g:ale_less_stylelint_executable *g:ale_less_stylelint_executable* + *b:ale_less_stylelint_executable* + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + +g:ale_less_stylelint_options *g:ale_less_stylelint_options* + *b:ale_less_stylelint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + +g:ale_less_stylelint_use_global *g:ale_less_stylelint_use_global* + *b:ale_less_stylelint_use_global* + Type: |String| + Default: `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 0000000..2f4a46b --- /dev/null +++ b/doc/ale-llvm.txt @@ -0,0 +1,19 @@ +=============================================================================== +ALE LLVM Integration *ale-llvm-options* + + +=============================================================================== +llc *ale-llvm-llc* + +g:ale_llvm_llc_executable *g:ale_llvm_llc_executable* + *b: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 0000000..f1286f8 --- /dev/null +++ b/doc/ale-lua.txt @@ -0,0 +1,34 @@ +=============================================================================== +ALE Lua Integration *ale-lua-options* + +=============================================================================== +luac *ale-lua-luac* + +g:ale_lua_luac_executable *g:ale_lua_luac_executable* + *b:ale_lua_luac_executable* + Type: |String| + Default: `'luac'` + + This variable can be changed to change the path to luac. + +=============================================================================== +luacheck *ale-lua-luacheck* + +g:ale_lua_luacheck_executable *g:ale_lua_luacheck_executable* + *b:ale_lua_luacheck_executable* + Type: |String| + Default: `'luacheck'` + + This variable can be changed to change the path to luacheck. + + +g:ale_lua_luacheck_options *g:ale_lua_luacheck_options* + *b:ale_lua_luacheck_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to luacheck. + + +=============================================================================== + 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 0000000..9a5290b --- /dev/null +++ b/doc/ale-markdown.txt @@ -0,0 +1,37 @@ +=============================================================================== +ALE Markdown Integration *ale-markdown-options* + + +=============================================================================== +mdl *ale-markdown-mdl* + +g:ale_markdown_mdl_executable *g:ale_markdown_mdl_executable* + *b:ale_markdown_mdl_executable* + Type: |String| + Default: `'mdl'` + + See |ale-integrations-local-executables| + + +g:ale_markdown_mdl_options *g:ale_markdown_mdl_options* + *b:ale_markdown_mdl_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to mdl. + + +=============================================================================== +prettier *ale-markdown-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +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-nroff.txt b/doc/ale-nroff.txt new file mode 100644 index 0000000..62ec789 --- /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-objc.txt b/doc/ale-objc.txt new file mode 100644 index 0000000..35b9a79 --- /dev/null +++ b/doc/ale-objc.txt @@ -0,0 +1,17 @@ +=============================================================================== +ALE Objective-C Integration *ale-objc-options* + + +=============================================================================== +clang *ale-objc-clang* + +g:ale_objc_clang_options *g:ale_objc_clang_options* + *b:ale_objc_clang_options* + Type: |String| + Default: `'-std=c11 -Wall'` + + This variable can be changed to modify flags given to clang. + + +=============================================================================== + 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 0000000..73d68a2 --- /dev/null +++ b/doc/ale-objcpp.txt @@ -0,0 +1,17 @@ +=============================================================================== +ALE Objective-C++ Integration *ale-objcpp-options* + + +=============================================================================== +clang *ale-objcpp-clang* + +g:ale_objcpp_clang_options *g:ale_objcpp_clang_options* + *b:ale_objcpp_clang_options* + Type: |String| + Default: `'-std=c++14 -Wall'` + + This variable can be changed to modify flags given to clang. + + +=============================================================================== + 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 0000000..cfa365a --- /dev/null +++ b/doc/ale-ocaml.txt @@ -0,0 +1,37 @@ +=============================================================================== +ALE OCaml Integration *ale-ocaml-options* + + +=============================================================================== +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). + +=============================================================================== +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 + +g:ale_ocaml_ols_executable *g:ale_ocaml_ols_executable* + *b:ale_ocaml_ols_executable* + Type: |String| + Default: `'ocaml-language-server'` + + This variable can be set to change the executable path for `ols`. + +g:ale_ocaml_ols_use_global *g:ale_ocaml_ols_use_global* + *b:ale_ocaml_ols_use_global* + Type: |String| + Default: `0` + + This variable can be set to `1` to always use the globally installed + executable. See also |ale-integrations-local-executables|. + +=============================================================================== + 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 0000000..0a4adff --- /dev/null +++ b/doc/ale-perl.txt @@ -0,0 +1,82 @@ +=============================================================================== +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* + +g:ale_perl_perl_executable *g:ale_perl_perl_executable* + *b:ale_perl_perl_executable* + Type: |String| + Default: `'perl'` + + This variable can be changed to modify the executable used for linting perl. + + +g:ale_perl_perl_options *g:ale_perl_perl_options* + *b: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* + +g:ale_perl_perlcritic_executable *g:ale_perl_perlcritic_executable* + *b:ale_perl_perlcritic_executable* + Type: |String| + Default: `'perlcritic'` + + This variable can be changed to modify the perlcritic executable used for + linting perl. + + +g:ale_perl_perlcritic_profile *g:ale_perl_perlcritic_profile* + *b: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. + + +g:ale_perl_perlcritic_options *g:ale_perl_perlcritic_options* + *b:ale_perl_perlcritic_options* + Type: |String| + Default: `''` + + This variable can be changed to supply additional command-line arguments to + the perlcritic invocation. + + +g:ale_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. + + +=============================================================================== + 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 0000000..7edfe23 --- /dev/null +++ b/doc/ale-php.txt @@ -0,0 +1,187 @@ +=============================================================================== +ALE PHP Integration *ale-php-options* + + +=============================================================================== +hack *ale-php-hack* + +There are no options for this linter. + + +=============================================================================== +hackfmt *ale-php-hackfmt* + +g:ale_php_hackfmt_options *g:ale_php_hackfmt_options* + *b:ale_php_hackfmt_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the hackfmt fixer. + + +=============================================================================== +langserver *ale-php-langserver* + +g:ale_php_langserver_executable *g:ale_php_langserver_executable* + *b:ale_php_langserver_executable* + Type: |String| + Default: `'php-language-server.php'` + + The variable can be set to configure the executable that will be used for + running the PHP language server. `vendor` directory executables will be + preferred instead of this setting if |g:ale_php_langserver_use_global| is `0`. + + See: |ale-integrations-local-executables| + + +g:ale_php_langserver_use_global *g:ale_php_langserver_use_global* + *b:ale_php_langserver_use_global* + Type: |Number| + Default: `0` + + This variable can be set to `1` to force the language server to be run with + the executable set for |g:ale_php_langserver_executable|. + + See: |ale-integrations-local-executables| + + +=============================================================================== +phan *ale-php-phan* + +WARNING: please do not use this 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. + +g:ale_php_phan_minimum_severity *g:ale_php_phan_minimum_severity* + *b:ale_php_phan_minimum_severity* + Type: |Number| + Default: `0` + + This variable defines the minimum severity level + + +=============================================================================== +phpcbf *ale-php-phpcbf* + +g:ale_php_phpcbf_executable *g:ale_php_phpcbf_executable* + *b:ale_php_phpcbf_executable* + Type: |String| + Default: `'phpcbf'` + + See |ale-integrations-local-executables| + + +g:ale_php_phpcbf_standard *g:ale_php_phpcbf_standard* + *b:ale_php_phpcbf_standard* + Type: |String| + Default: `''` + + This variable can be set to specify the coding standard used by phpcbf. If no + coding standard is specified, phpcbf will default to fixing against the + PEAR coding standard, or the standard you have set as the default. + + +g:ale_php_phpcbf_use_global *g:ale_php_phpcbf_use_global* + *b:ale_php_phpcbf_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +phpcs *ale-php-phpcs* + +g:ale_php_phpcs_executable *g:ale_php_phpcs_executable* + *b:ale_php_phpcs_executable* + Type: |String| + Default: `'phpcs'` + + See |ale-integrations-local-executables| + + +g:ale_php_phpcs_standard *g:ale_php_phpcs_standard* + *b: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. + + +g:ale_php_phpcs_use_global *g:ale_php_phpcs_use_global* + *b:ale_php_phpcs_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +phpmd *ale-php-phpmd* + +g:ale_php_phpmd_executable *g:ale_php_phpmd_executable* + *b:ale_php_phpmd_executable* + Type: |String| + Default: `'phpmd'` + + This variable sets executable used for phpmd. + + +g:ale_php_phpmd_ruleset *g:ale_php_phpmd_ruleset* + *b: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* + +g:ale_php_phpstan_executable *g:ale_php_phpstan_executable* + *b:ale_php_phpstan_executable* + Type: |String| + Default: `'phpstan'` + + This variable sets executable used for phpstan. + + +g:ale_php_phpstan_level *g:ale_php_phpstan_level* + *b:ale_php_phpstan_level* + Type: |Number| + Default: `4` + + This variable controls the rule levels. 0 is the loosest and 4 is the + strictest. + + +g:ale_php_phpstan_configuration *g:ale_php_phpstan_configuration* + *b:ale_php_phpstan_configuration* + Type: |String| + Default: `''` + + This variable sets path to phpstan configuration file. + + +=============================================================================== +php-cs-fixer *ale-php-php-cs-fixer* + +g:ale_php_cs_fixer_executable *g:ale_php_cs_fixer_executable* + *b:ale_php_cs_fixer_executable* + Type: |String| + Default: `'php-cs-fixer'` + + This variable sets executable used for php-cs-fixer. + +g:ale_php_cs_fixer_use_global *g:ale_php_cs_fixer_use_global* + *b:ale_php_cs_fixer_use_global* + Type: |Boolean| + Default: `0` + + This variable force globally installed fixer. + +=============================================================================== + 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 0000000..1e03b7b --- /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 0000000..c7cc0bb --- /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 0000000..3b32168 --- /dev/null +++ b/doc/ale-pony.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Pony Integration *ale-pony-options* + + +=============================================================================== +ponyc *ale-pony-ponyc* + +g:ale_pony_ponyc_executable *g:ale_pony_ponyc_executable* + *b:ale_pony_ponyc_executable* + Type: |String| + Default: `'ponyc'` + + See |ale-integrations-local-executables| + + +g:ale_pony_ponyc_options *g:ale_pony_ponyc_options* + *b:ale_pony_ponyc_options* + Type: |String| + Default: `'--pass paint'` + + This variable can be set to pass options to ponyc. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-proto.txt b/doc/ale-proto.txt new file mode 100644 index 0000000..734e23d --- /dev/null +++ b/doc/ale-proto.txt @@ -0,0 +1,33 @@ +=============================================================================== +ALE Proto Integration *ale-proto-options* + + +=============================================================================== +Integration Information + +Linting of `.proto` files requires that the `protoc` binary is installed in the +system path and that the `protoc-gen-lint` plugin for the `protoc` binary is also +installed. + +To enable `.proto` file linting, update |g:ale_linters| as appropriate: +> + " Enable linter for .proto files + let g:ale_linters = {'proto': ['protoc-gen-lint']} +< +=============================================================================== +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. + +g:ale_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. + +=============================================================================== + 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 0000000..0107148 --- /dev/null +++ b/doc/ale-pug.txt @@ -0,0 +1,44 @@ +=============================================================================== +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. + +g:ale_pug_puglint_executable *g:ale_pug_puglint_executable* + *b:ale_pug_puglint_executable* + Type: |String| + Default: `'pug-lint'` + + See |ale-integrations-local-executables| + + +g:ale_pug_puglint_options *g:ale_pug_puglint_options* + *b:ale_pug_puglint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to pug-lint. + + +g:ale_pug_puglint_use_global *g:ale_pug_puglint_use_global* + *b:ale_pug_puglint_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-puppet.txt b/doc/ale-puppet.txt new file mode 100644 index 0000000..604565e --- /dev/null +++ b/doc/ale-puppet.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE Puppet Integration *ale-puppet-options* + + +=============================================================================== +puppetlint *ale-puppet-puppetlint* + +g:ale_puppet_puppetlint_executable *g:ale_puppet_puppetlint_executable* + *b:ale_puppet_puppetlint_executable* + Type: |String| + Default: `'puppet-lint'` + + This variable can be changed to specify the executable used for puppet-lint. + + +g:ale_puppet_puppetlint_options *g:ale_puppet_puppetlint_options* + *b: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. + + +=============================================================================== + 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 0000000..4d55e75 --- /dev/null +++ b/doc/ale-python.txt @@ -0,0 +1,282 @@ +=============================================================================== +ALE Python Integration *ale-python-options* + + +=============================================================================== +autopep8 *ale-python-autopep8* + +g:ale_python_autopep8_executable *g:ale_python_autopep8_executable* + *b:ale_python_autopep8_executable* + Type: |String| + Default: `'autopep8'` + + See |ale-integrations-local-executables| + + +g:ale_python_autopep8_options *g:ale_python_autopep8_options* + *b:ale_python_autopep8_options* + Type: |String| + Default: `''` + + This variable can be set to pass extra options to autopep8. + + +g:ale_python_autopep8_use_global *g:ale_python_autopep8_use_global* + *b:ale_python_autopep8_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +flake8 *ale-python-flake8* + +g:ale_python_flake8_executable *g:ale_python_flake8_executable* + *b:ale_python_flake8_executable* + Type: |String| + Default: `'flake8'` + + This variable can be changed to modify the executable used for flake8. + + +g:ale_python_flake8_options *g:ale_python_flake8_options* + *b: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`). + + +g:ale_python_flake8_use_global *g:ale_python_flake8_use_global* + *b:ale_python_flake8_use_global* + Type: |Number| + Default: `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. + + +=============================================================================== +isort *ale-python-isort* + +g:ale_python_isort_executable *g:ale_python_isort_executable* + *b:ale_python_isort_executable* + Type: |String| + Default: `'isort'` + + See |ale-integrations-local-executables| + + +g:ale_python_isort_use_global *g:ale_python_isort_use_global* + *b:ale_python_isort_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +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. + + +g:ale_python_mypy_executable *g:ale_python_mypy_executable* + *b:ale_python_mypy_executable* + Type: |String| + Default: `'mypy'` + + See |ale-integrations-local-executables| + +g:ale_python_mypy_ignore_invalid_syntax + *g:ale_python_mypy_ignore_invalid_syntax* + *b: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. + + +g:ale_python_mypy_options *g:ale_python_mypy_options* + *b:ale_python_mypy_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the mypy + invocation. + + +g:ale_python_mypy_use_global *g:ale_python_mypy_use_global* + *b:ale_python_mypy_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +prospector *ale-python-prospector* + +g:ale_python_prospector_executable *g:ale_python_prospector_executable* + *b:ale_python_prospector_executable* + Type: |String| + Default: `'prospector'` + + See |ale-integrations-local-executables| + + +g:ale_python_prospector_options *g:ale_python_prospector_options* + *b: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`). + + +g:ale_python_prospector_use_global *g:ale_python_prospector_use_global* + *b:ale_python_prospector_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +pycodestyle *ale-python-pycodestyle* + + +g:ale_python_pycodestyle_executable *g:ale_python_pycodestyle_executable* + *b:ale_python_pycodestyle_executable* + Type: |String| + Default: `'pycodestyle'` + + See |ale-integrations-local-executables| + + +g:ale_python_pycodestyle_options *g:ale_python_pycodestyle_options* + *b:ale_python_pycodestyle_options* + Type: |String| + Default: `''` + + This variable can be changed to add command-line arguments to the + pycodestyle invocation. + + +g:ale_python_pycodestyle_use_global *g:ale_python_pycodestyle_use_global* + *b:ale_python_pycodestyle_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +pylint *ale-python-pylint* + +g:ale_python_pylint_executable *g:ale_python_pylint_executable* + *b:ale_python_pylint_executable* + Type: |String| + Default: `'pylint'` + + See |ale-integrations-local-executables| + + +g:ale_python_pylint_options *g:ale_python_pylint_options* + *b: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`). + + +g:ale_python_pylint_use_global *g:ale_python_pylint_use_global* + *b:ale_python_pylint_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +pyls *ale-python-pyls* + +g:ale_python_pyls_executable *g:ale_python_pyls_executable* + *b:ale_python_pyls_executable* + Type: |String| + Default: `'pyls'` + + See |ale-integrations-local-executables| + + +g:ale_python_pyls_use_global *g:ale_python_pyls_use_global* + *b:ale_python_pyls_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +yapf *ale-python-yapf* + +g:ale_python_yapf_executable *g:ale_python_yapf_executable* + *b:ale_python_yapf_executable* + Type: |String| + Default: `'yapf'` + + See |ale-integrations-local-executables| + + +g:ale_python_yapf_use_global *g:ale_python_yapf_use_global* + *b:ale_python_yapf_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-r.txt b/doc/ale-r.txt new file mode 100644 index 0000000..f85f48f --- /dev/null +++ b/doc/ale-r.txt @@ -0,0 +1,29 @@ +=============================================================================== +ALE R Integration *ale-r-options* + + +=============================================================================== +lintr *ale-r-lintr* + +g:ale_r_lintr_options *g:ale_r_lintr_options* + *b: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. + + +g:ale_r_lintr_lint_package *g:ale_r_lintr_lint_package* + *b: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. + +=============================================================================== + 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 0000000..36ddd75 --- /dev/null +++ b/doc/ale-reasonml.txt @@ -0,0 +1,54 @@ +=============================================================================== +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 + +g:ale_reason_ols_executable *g:ale_reason_ols_executable* + *b:ale_reason_ols_executable* + Type: |String| + Default: `'ocaml-language-server'` + + This variable can be set to change the executable path for `ols`. + +g:ale_reason_ols_use_global *g:ale_reason_ols_use_global* + *b:ale_reason_ols_use_global* + Type: |String| + Default: `0` + + This variable can be set to `1` to always use the globally installed + executable. See also |ale-integrations-local-executables|. + +=============================================================================== +refmt *ale-reasonml-refmt* + +g:ale_reasonml_refmt_executable *g:ale_reasonml_refmt_executable* + *b:ale_reasonml_refmt_executable* + Type: |String| + Default: `'refmt'` + + This variable can be set to pass the path of the refmt fixer. + +g:ale_reasonml_refmt_options *g:ale_reasonml_refmt_options* + *b: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-restructuredtext.txt b/doc/ale-restructuredtext.txt new file mode 100644 index 0000000..02fbc4a --- /dev/null +++ b/doc/ale-restructuredtext.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE reStructuredText Integration *ale-restructuredtext-options* + + +=============================================================================== +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-ruby.txt b/doc/ale-ruby.txt new file mode 100644 index 0000000..94181ed --- /dev/null +++ b/doc/ale-ruby.txt @@ -0,0 +1,90 @@ +=============================================================================== +ALE Ruby Integration *ale-ruby-options* + + +=============================================================================== +brakeman *ale-ruby-brakeman* + +g:ale_ruby_brakeman_options *g:ale_ruby_brakeman_options* + *b:ale_ruby_brakeman_options* + Type: |String| + Default: `''` + + The contents of this variable will be passed through to brakeman. + + +=============================================================================== +rails_best_practices *ale-ruby-rails_best_practices* + +g:ale_ruby_rails_best_practices_executable + *g:ale_ruby_rails_best_practices_executable* + *b: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'`. + +g:ale_ruby_rails_best_practices_options + *g:ale_ruby_rails_best_practices_options* + *b: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* + +g:ale_ruby_reek_show_context *g:ale_ruby_reek_show_context* + *b: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. + + +g:ale_ruby_reek_show_wiki_link *g:ale_ruby_reek_show_wiki_link* + *b: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* + +g:ale_ruby_rubocop_executable *g:ale_ruby_rubocop_executable* + *b:ale_ruby_rubocop_executable* + Type: String + Default: `'rubocop'` + + Override the invoked rubocop binary. This is useful for running rubocop + from binstubs or a bundle. + + +g:ale_ruby_rubocop_options *g:ale_ruby_rubocop_options* + *b:ale_ruby_rubocop_options* + Type: |String| + Default: `''` + + This variable can be change to modify flags given to rubocop. + + +=============================================================================== +ruby *ale-ruby-ruby* + +g:ale_ruby_ruby_executable *g:ale_ruby_ruby_executable* + *b:ale_ruby_ruby_executable* + Type: String + Default: `'ruby'` + + This variable can be changed to use a different executable for ruby. + + +=============================================================================== + 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 0000000..dad9ae6 --- /dev/null +++ b/doc/ale-rust.txt @@ -0,0 +1,153 @@ +=============================================================================== +ALE Rust Integration *ale-rust-options* + *ale-integration-rust* + +=============================================================================== +Integration Information + + Since Vim does not detect the rust file type out-of-the-box, you need the + runtime files for rust from here: https://github.com/rust-lang/rust.vim + + Note that there are three possible linters for Rust files: + + 1. rustc -- The Rust compiler is used to check the currently edited file. + So, if your project consists of multiple files, you will get some errors + 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 contained in Cargo projects. + 4. rustfmt -- If you have `rustfmt` installed, you can use it as a fixer to + consistently reformat your Rust code. + + Only cargo is enabled by default. To switch to using rustc instead of cargo, + configure |g:ale_linters| appropriately: > + + " See the help text for the option for more information. + let g:ale_linters = {'rust': ['rustc']} +< + + Also note that rustc 1.12. or later is needed. + + +=============================================================================== +cargo *ale-rust-cargo* + +g:ale_rust_cargo_use_check *g:ale_rust_cargo_use_check* + *b:ale_rust_cargo_use_check* + Type: |Number| + Default: `1` + + 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. + + +g:ale_rust_cargo_check_all_targets *g:ale_rust_cargo_check_all_targets* + *b: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|, + + +g:ale_rust_cargo_default_feature_behavior + *g:ale_rust_cargo_default_feature_behavior* + *b: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. + + +g:ale_rust_cargo_include_features *g:ale_rust_cargo_include_features* + *b: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|. + + +=============================================================================== +rls *ale-rust-rls* + +g:ale_rust_rls_executable *g:ale_rust_rls_executable* + *b:ale_rust_rls_executable* + Type: |String| + Default: `'rls'` + + This variable can be modified to change the executable path for `rls`. + + +g:ale_rust_rls_toolchain *g:ale_rust_rls_toolchain* + *b:ale_rust_rls_toolchain* + Type: |String| + Default: `'nightly'` + + This option can be set to change the toolchain used for `rls`. Possible + values include `'nightly'`, `'beta'`, and `'stable'`. + + The `rls` server will only be started once per executable. + + +=============================================================================== +rustc *ale-rust-rustc* + + +g:ale_rust_rustc_options *g:ale_rust_rustc_options* + *b:ale_rust_rustc_options* + Type: |String| + Default: `'-Z no-trans'` + + The variable can be used to change the options passed to `rustc`. + + `-Z no-trans` should only work with nightly builds of Rust. Be careful when + setting the options, as running `rustc` could execute code or generate + binary files. + + +g:ale_rust_ignore_error_codes *g:ale_rust_ignore_error_codes* + *b: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'] + + +=============================================================================== +rustfmt *ale-rust-rustfmt* + +g:ale_rust_rustfmt_options *g:ale_rust_rustfmt_options* + *b:ale_rust_rustfmt_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the rustfmt fixer. + + +=============================================================================== + 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 0000000..5465957 --- /dev/null +++ b/doc/ale-sass.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE SASS Integration *ale-sass-options* + + +=============================================================================== +stylelint *ale-sass-stylelint* + +g:ale_sass_stylelint_executable *g:ale_sass_stylelint_executable* + *b:ale_sass_stylelint_executable* + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + +g:ale_sass_stylelint_use_global *g:ale_sass_stylelint_use_global* + *b:ale_sass_stylelint_use_global* + Type: |String| + Default: `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 0000000..9c9472f --- /dev/null +++ b/doc/ale-scala.txt @@ -0,0 +1,40 @@ +=============================================================================== +ALE Scala Integration *ale-scala-options* + + +=============================================================================== +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. + + +g:ale_scalastyle_config_loc *g:ale_scalastyle_config_loc* + + 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. + + +g:ale_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 0000000..19695a7 --- /dev/null +++ b/doc/ale-scss.txt @@ -0,0 +1,31 @@ +=============================================================================== +ALE SCSS Integration *ale-scss-options* + + +=============================================================================== +prettier *ale-scss-prettier* + +See |ale-javascript-prettier| for information about the available options. + + +=============================================================================== +stylelint *ale-scss-stylelint* + +g:ale_scss_stylelint_executable *g:ale_scss_stylelint_executable* + *b:ale_scss_stylelint_executable* + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + +g:ale_scss_stylelint_use_global *g:ale_scss_stylelint_use_global* + *b:ale_scss_stylelint_use_global* + Type: |String| + Default: `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 0000000..941dc59 --- /dev/null +++ b/doc/ale-sh.txt @@ -0,0 +1,72 @@ +=============================================================================== +ALE Shell Integration *ale-sh-options* + + +=============================================================================== +shell *ale-sh-shell* + +g:ale_sh_shell_default_shell *g:ale_sh_shell_default_shell* + *b: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* + +g:ale_sh_shellcheck_executable *g:ale_sh_shellcheck_executable* + *b:ale_sh_shellcheck_executable* + Type: |String| + Default: `'shellcheck'` + + This variable sets executable used for shellcheck. + + +g:ale_sh_shellcheck_options *g:ale_sh_shellcheck_options* + *b: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' +< + +g:ale_sh_shellcheck_exclusions *g:ale_sh_shellcheck_exclusions* + *b: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* + +g:ale_sh_shfmt_options *g:ale_sh_shfmt_options* + *b: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 0000000..cc8d679 --- /dev/null +++ b/doc/ale-sml.txt @@ -0,0 +1,36 @@ +=============================================================================== +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. + +------------------------------------------------------------------------------- + +g:ale_sml_smlnj_cm_file *g:ale_sml_smlnj_cm_file* + *b: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 0000000..4b74a27 --- /dev/null +++ b/doc/ale-solidity.txt @@ -0,0 +1,24 @@ +=============================================================================== +ALE Solidity Integration *ale-solidity-options* + + +=============================================================================== +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). + + +=============================================================================== + 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 0000000..3da950c --- /dev/null +++ b/doc/ale-spec.txt @@ -0,0 +1,43 @@ +=============================================================================== +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* + +g:ale_spec_rpmlint_executable *g:ale_spec_rpmlint_executable* + *b:ale_spec_rpmlint_executable* + Type: |String| + Default: `'rpmlint'` + + This variable sets executable used for rpmlint. + + +g:ale_spec_rpmlint_options *g:ale_spec_rpmlint_options* + *b: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-stylus.txt b/doc/ale-stylus.txt new file mode 100644 index 0000000..59d9055 --- /dev/null +++ b/doc/ale-stylus.txt @@ -0,0 +1,33 @@ +=============================================================================== +ALE Stylus Integration *ale-stylus-options* + + +=============================================================================== +stylelint *ale-stylus-stylelint* + +g:ale_stylus_stylelint_executable *g:ale_stylus_stylelint_executable* + *b:ale_stylus_stylelint_executable* + Type: |String| + Default: `'stylelint'` + + See |ale-integrations-local-executables| + + +g:ale_stylus_stylelint_options *g:ale_stylus_stylelint_options* + *b:ale_stylus_stylelint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to stylelint. + + +g:ale_stylus_stylelint_use_global *g:ale_stylus_stylelint_use_global* + *b:ale_stylus_stylelint_use_global* + Type: |String| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== + 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 0000000..497c9fd --- /dev/null +++ b/doc/ale-tcl.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Tcl Integration *ale-tcl-options* + + +=============================================================================== +nagelfar *ale-tcl-nagelfar* + +g:ale_tcl_nagelfar_executable *g:ale_tcl_nagelfar_executable* + *b:ale_tcl_nagelfar_executable* + Type: |String| + Default: `'nagelfar.tcl'` + + This variable can be changed to change the path to nagelfar. + + +g:ale_tcl_nagelfar_options *g:ale_tcl_nagelfar_options* + *b: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 0000000..ec86e9a --- /dev/null +++ b/doc/ale-terraform.txt @@ -0,0 +1,29 @@ +=============================================================================== +ALE Terraform Integration *ale-terraform-options* + + +=============================================================================== +tflint *ale-terraform-tflint* + +g:ale_terraform_tflint_executable *g:ale_terraform_tflint_executable* + *b:ale_terraform_tflint_executable* + + Type: |String| + Default: `'tflint'` + + This variable can be changed to use a different executable for tflint. + + +g:ale_terraform_tflint_options *g:ale_terraform_tflint_options* + *b: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. + + +=============================================================================== + 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 0000000..24aa311 --- /dev/null +++ b/doc/ale-tex.txt @@ -0,0 +1,36 @@ +=============================================================================== +ALE TeX Integration *ale-tex-options* + + +=============================================================================== +chktex *ale-tex-chktex* + +g:ale_tex_chktex_executable *g:ale_tex_chktex_executable* + *b:ale_tex_chktex_executable* + Type: |String| + Default: `'chktex'` + + This variable can be changed to change the path to chktex. + + +g:ale_tex_chktex_options *g:ale_tex_chktex_options* + *b:ale_tex_chktex_options* + Type: |String| + Default: `'-I'` + + This variable can be changed to modify flags given to chktex. + + +------------------------------------------------------------------------------ +lacheck *ale-tex-lacheck* + +g:ale_lacheck_executable *g:ale_lacheck_executable* + *b:ale_lacheck_executable* + Type: |String| + Default: '`lacheck`' + + This variable can be changed to change the path to lacheck. + + +=============================================================================== + 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 0000000..f8ed342 --- /dev/null +++ b/doc/ale-texinfo.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Texinfo Integration *ale-texinfo-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 0000000..a4dfa5e --- /dev/null +++ b/doc/ale-text.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE Text Integration *ale-text-options* + + +=============================================================================== +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 0000000..ed858db --- /dev/null +++ b/doc/ale-thrift.txt @@ -0,0 +1,46 @@ +=============================================================================== +ALE Thrift Integration *ale-thrift-options* + + +=============================================================================== +thrift *ale-thrift-thrift* + +The `thrift` linter works by compiling the buffer's contents and reporting any +errors reported by the parser and the configured code generator(s). + +g:ale_thrift_thrift_executable *g:ale_thrift_thrift_executable* + *b:ale_thrift_thrift_executable* + Type: |String| + Default: `'thrift'` + + See |ale-integrations-local-executables| + + +g:ale_thrift_thrift_generators *g:ale_thrift_thrift_generators* + *b:ale_thrift_thrift_generators* + Type: |List| of |String|s + Default: `['cpp']` + + This list must contain one or more named code generators. Generator options + can be included as part of each string, e.g. `['py:dynamic']`. + + +g:ale_thrift_thrift_includes *g:ale_thrift_thrift_includes* + *b:ale_thrift_thrift_includes* + Type: |List| of |String|s + Default: `[]` + + This list contains paths that will be searched for thrift `include` + directives. + + +g:ale_thrift_thrift_options *g:ale_thrift_thrift_options* + *b:ale_thrift_thrift_options* + Type: |String| + Default: `'-strict'` + + This variable can be changed to customize the additional command-line + arguments that are passed to the thrift compiler. + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-typescript.txt b/doc/ale-typescript.txt new file mode 100644 index 0000000..d83a2df --- /dev/null +++ b/doc/ale-typescript.txt @@ -0,0 +1,103 @@ +=============================================================================== +ALE TypeScript Integration *ale-typescript-options* + + +=============================================================================== +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. + + +=============================================================================== +tslint *ale-typescript-tslint* + +g:ale_typescript_tslint_executable *g:ale_typescript_tslint_executable* + *b:ale_typescript_tslint_executable* + Type: |String| + Default: `'tslint'` + + See |ale-integrations-local-executables| + + +g:ale_typescript_tslint_config_path *g:ale_typescript_tslint_config_path* + *b: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. + + +g:ale_typescript_tslint_ignore_empty_files + *g:ale_typescript_tslint_ignore_empty_files* + *b: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. + + +g:ale_typescript_tslint_rules_dir *g:ale_typescript_tslint_rules_dir* + *b:ale_typescript_tslint_rules_dir* + Type: |String| + Default: `''` + + If this variable is set, ALE will use it as the rules directory for tslint. + + +g:ale_typescript_tslint_use_global *g:ale_typescript_tslint_use_global* + *b:ale_typescript_tslint_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +tsserver *ale-typescript-tsserver* + +g:ale_typescript_tsserver_executable *g:ale_typescript_tsserver_executable* + *b: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`. + + +g:ale_typescript_tsserver_config_path *g:ale_typescript_tsserver_config_path* + *b: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. + + +g:ale_typescript_tsserver_use_global *g:ale_typescript_tsserver_use_global* + *b:ale_typescript_tsserver_use_global* + Type: |Number| + Default: `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. + + +=============================================================================== + 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 0000000..2b8ce5e --- /dev/null +++ b/doc/ale-verilog.txt @@ -0,0 +1,43 @@ +=============================================================================== +ALE Verilog/SystemVerilog Integration *ale-verilog-options* + + +=============================================================================== +ALE can use two different linters for Verilog HDL: + + iverilog: + Using `iverilog -t null -Wall` + + verilator + Using `verilator --lint-only -Wall` + +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'],} +< + +=============================================================================== +iverilog *ale-verilog-iverilog* + + No additional options + + +=============================================================================== +verilator *ale-verilog-verilator* + +g:ale_verilog_verilator_options *g:ale_verilog_verilator_options* + *b: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. + +=============================================================================== + 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 0000000..3cbe20d --- /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 0000000..30ac3a1 --- /dev/null +++ b/doc/ale-vim.txt @@ -0,0 +1,19 @@ +=============================================================================== +ALE Vim Integration *ale-vim-options* + + +=============================================================================== +vint *ale-vim-vint* + +g:ale_vim_vint_show_style_issues *g:ale_vim_vint_show_style_issues* + *b: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 0000000..937b603 --- /dev/null +++ b/doc/ale-vue.txt @@ -0,0 +1,11 @@ +=============================================================================== +ALE Vue Integration *ale-vue-options* + + +=============================================================================== +prettier *ale-vue-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-xhtml.txt b/doc/ale-xhtml.txt new file mode 100644 index 0000000..3cc639e --- /dev/null +++ b/doc/ale-xhtml.txt @@ -0,0 +1,12 @@ +=============================================================================== +ALE XHTML Integration *ale-xhtml-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 0000000..6c8af6c --- /dev/null +++ b/doc/ale-xml.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE XML Integration *ale-xml-options* + + +=============================================================================== +xmllint *ale-xml-xmllint* + +g:ale_xml_xmllint_executable *g:ale_xml_xmllint_executable* + *b:ale_xml_xmllint_executable* + Type: |String| + Default: `'xmllint'` + + This variable can be set to change the path to xmllint. + + +g:ale_xml_xmllint_options *g:ale_xml_xmllint_options* + *b:ale_xml_xmllint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to xmllint. + + +=============================================================================== + 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 0000000..a902f25 --- /dev/null +++ b/doc/ale-yaml.txt @@ -0,0 +1,78 @@ +=============================================================================== +ALE YAML Integration *ale-yaml-options* + + +=============================================================================== +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 +------------------------------------------------------------------------------- + +g:ale_yaml_swaglint_executable *g:ale_yaml_swaglint_executable* + *b:ale_yaml_swaglint_executable* + Type: |String| + Default: `'swaglint'` + + This variable can be set to change the path to swaglint. + + +g:ale_yaml_swaglint_use_global *g:ale_yaml_swaglint_use_global* + *b:ale_yaml_swaglint_use_global* + Type: |String| + Default: `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 +------------------------------------------------------------------------------- + +g:ale_yaml_yamllint_executable *g:ale_yaml_yamllint_executable* + *b:ale_yaml_yamllint_executable* + Type: |String| + Default: `'yamllint'` + + This variable can be set to change the path to yamllint. + + +g:ale_yaml_yamllint_options *g:ale_yaml_yamllint_options* + *b:ale_yaml_yamllint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to yamllint. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale.txt b/doc/ale.txt index 28b5eee..2e98cd7 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -1,4 +1,4 @@ -*ale.txt* For Vim version 8.0. Last change: 2016 October 10 +*ale.txt* For Vim version 8.0. *ale* ALE - Asynchronous Lint Engine @@ -8,40 +8,258 @@ CONTENTS *ale-contents* 1. Introduction.........................|ale-introduction| 2. Supported Languages & Tools..........|ale-support| - 3. Global Options.......................|ale-options| - 4. Linter Specific Options..............|ale-linter-options| - 4.1. eslint..........................|ale-linter-options-eslint| - 4.2. flow............................|ale-linter-options-flow| - 4.3. jshint..........................|ale-linter-options-jshint| - 4.4. phpcs...........................|ale-linter-options-phpcs| - 4.5. html-tidy.......................|ale-linter-options-html-tidy| - 4.6. c-gcc...........................|ale-linter-options-c-gcc| - 4.7. cpp-gcc.........................|ale-linter-options-cpp-gcc| - 4.8. fortran-gcc.....................|ale-linter-options-fortran-gcc| - 4.9. shell...........................|ale-linter-options-shell| - 4.10. shellcheck......................|ale-linter-options-shellcheck| - 4.11. vint............................|ale-linter-options-vint| - 4.12. luacheck........................|ale-linter-options-luacheck| - 4.13. c-cppcheck......................|ale-linter-options-c-cppcheck| - 4.14. cpp-cppcheck....................|ale-linter-options-cpp-cppcheck| - 4.15. htmlhint........................|ale-linter-options-htmlhint| - 4.16. c-clang.........................|ale-linter-options-c-clang| - 4.17. python-flake8...................|ale-linter-options-python-flake8| - 4.18. ruby-rubocop....................|ale-linter-options-ruby-rubocop| - 4.19. chktex..........................|ale-linter-options-chktex| - 4.20. lacheck.........................|ale-linter-options-lacheck| - 4.21. stylelint.......................|ale-linter-options-stylelint| - 4.22. rustc...........................|ale-linter-options-rustc| - 4.23. python-mypy.....................|ale-linter-options-python-mypy| - 4.24. python-pylint...................|ale-linter-options-python-pylint| - 4.25. erlang..........................|ale-linter-options-erlang| - 5. Linter Integration Notes.............|ale-linter-integration| - 5.1. merlin..........................|ale-linter-integration-ocaml-merlin| - 5.2. rust.............................|ale-integration-rust| - 6. Commands/Keybinds....................|ale-commands| - 7. API..................................|ale-api| - 8. Special Thanks.......................|ale-special-thanks| - 9. Contact..............................|ale-contact| + 3. Linting..............................|ale-lint| + 4. Fixing Problems......................|ale-fix| + 5. Language Server Protocol Support.....|ale-lsp| + 5.1 Completion........................|ale-completion| + 5.2 Go To Definition..................|ale-go-to-definition| + 6. Global Options.......................|ale-options| + 6.1 Highlights........................|ale-highlights| + 6.2 Options for write-good Linter.....|ale-write-good-options| + 7. Integration Documentation............|ale-integrations| + asciidoc..............................|ale-asciidoc-options| + write-good..........................|ale-asciidoc-write-good| + asm...................................|ale-asm-options| + gcc.................................|ale-asm-gcc| + awk...................................|ale-awk-options| + gawk................................|ale-awk-gawk| + c.....................................|ale-c-options| + clang...............................|ale-c-clang| + clang-format........................|ale-c-clangformat| + clangtidy...........................|ale-c-clangtidy| + cppcheck............................|ale-c-cppcheck| + flawfinder..........................|ale-c-flawfinder| + gcc.................................|ale-c-gcc| + chef..................................|ale-chef-options| + foodcritic..........................|ale-chef-foodcritic| + clojure...............................|ale-clojure-options| + joker...............................|ale-clojure-joker| + cmake.................................|ale-cmake-options| + cmakelint...........................|ale-cmake-cmakelint| + cpp...................................|ale-cpp-options| + clang...............................|ale-cpp-clang| + clangcheck..........................|ale-cpp-clangcheck| + clang-format........................|ale-cpp-clangformat| + clangtidy...........................|ale-cpp-clangtidy| + cppcheck............................|ale-cpp-cppcheck| + cpplint.............................|ale-cpp-cpplint| + flawfinder..........................|ale-cpp-flawfinder| + gcc.................................|ale-cpp-gcc| + c#....................................|ale-cs-options| + mcs.................................|ale-cs-mcs| + mcsc................................|ale-cs-mcsc| + css...................................|ale-css-options| + prettier............................|ale-css-prettier| + stylelint...........................|ale-css-stylelint| + cuda..................................|ale-cuda-options| + nvcc................................|ale-cuda-nvcc| + dart..................................|ale-dart-options| + dartanalyzer........................|ale-dart-dartanalyzer| + dockerfile............................|ale-dockerfile-options| + hadolint............................|ale-dockerfile-hadolint| + elixir................................|ale-elixir-options| + mix.................................|ale-elixir-mix| + dialyxir............................|ale-elixir-dialyxir| + elm...................................|ale-elm-options| + elm-format..........................|ale-elm-elm-format| + elm-make............................|ale-elm-elm-make| + erlang................................|ale-erlang-options| + erlc................................|ale-erlang-erlc| + syntaxerl...........................|ale-erlang-syntaxerl| + eruby.................................|ale-eruby-options| + fish..................................|ale-fish-options| + fortran...............................|ale-fortran-options| + gcc.................................|ale-fortran-gcc| + fountain..............................|ale-fountain-options| + fusionscript..........................|ale-fuse-options| + fusion-lint.........................|ale-fuse-fusionlint| + git commit............................|ale-gitcommit-options| + gitlint.............................|ale-gitcommit-gitlint| + glsl..................................|ale-glsl-options| + glslang.............................|ale-glsl-glslang| + glslls..............................|ale-glsl-glslls| + go....................................|ale-go-options| + gobuild.............................|ale-go-gobuild| + gofmt...............................|ale-go-gofmt| + gometalinter........................|ale-go-gometalinter| + staticcheck.........................|ale-go-staticcheck| + graphql...............................|ale-graphql-options| + eslint..............................|ale-graphql-eslint| + gqlint..............................|ale-graphql-gqlint| + prettier............................|ale-graphql-prettier| + handlebars............................|ale-handlebars-options| + ember-template-lint.................|ale-handlebars-embertemplatelint| + haskell...............................|ale-haskell-options| + brittany............................|ale-haskell-brittany| + ghc.................................|ale-haskell-ghc| + hdevtools...........................|ale-haskell-hdevtools| + hfmt................................|ale-haskell-hfmt| + stack-build.........................|ale-haskell-stack-build| + html..................................|ale-html-options| + htmlhint............................|ale-html-htmlhint| + tidy................................|ale-html-tidy| + write-good..........................|ale-html-write-good| + idris.................................|ale-idris-options| + idris...............................|ale-idris-idris| + java..................................|ale-java-options| + checkstyle..........................|ale-java-checkstyle| + javac...............................|ale-java-javac| + google-java-format..................|ale-java-google-java-format| + javascript............................|ale-javascript-options| + eslint..............................|ale-javascript-eslint| + flow................................|ale-javascript-flow| + importjs............................|ale-javascript-importjs| + jscs................................|ale-javascript-jscs| + jshint..............................|ale-javascript-jshint| + prettier............................|ale-javascript-prettier| + prettier-eslint.....................|ale-javascript-prettier-eslint| + prettier-standard...................|ale-javascript-prettier-standard| + standard............................|ale-javascript-standard| + xo..................................|ale-javascript-xo| + json..................................|ale-json-options| + fixjson.............................|ale-json-fixjson| + jsonlint............................|ale-json-jsonlint| + jq..................................|ale-json-jq| + prettier............................|ale-json-prettier| + kotlin................................|ale-kotlin-options| + kotlinc.............................|ale-kotlin-kotlinc| + ktlint..............................|ale-kotlin-ktlint| + latex.................................|ale-latex-options| + write-good..........................|ale-latex-write-good| + less..................................|ale-less-options| + lessc...............................|ale-less-lessc| + prettier............................|ale-less-prettier| + stylelint...........................|ale-less-stylelint| + llvm..................................|ale-llvm-options| + llc.................................|ale-llvm-llc| + lua...................................|ale-lua-options| + luac................................|ale-lua-luac| + luacheck............................|ale-lua-luacheck| + markdown..............................|ale-markdown-options| + mdl.................................|ale-markdown-mdl| + prettier............................|ale-markdown-prettier| + write-good..........................|ale-markdown-write-good| + nroff.................................|ale-nroff-options| + write-good..........................|ale-nroff-write-good| + objc..................................|ale-objc-options| + clang...............................|ale-objc-clang| + objcpp................................|ale-objcpp-options| + clang...............................|ale-objcpp-clang| + ocaml.................................|ale-ocaml-options| + merlin..............................|ale-ocaml-merlin| + ols.................................|ale-ocaml-ols| + perl..................................|ale-perl-options| + perl................................|ale-perl-perl| + perlcritic..........................|ale-perl-perlcritic| + php...................................|ale-php-options| + hack................................|ale-php-hack| + hackfmt.............................|ale-php-hackfmt| + langserver..........................|ale-php-langserver| + phan................................|ale-php-phan| + phpcbf..............................|ale-php-phpcbf| + phpcs...............................|ale-php-phpcs| + phpmd...............................|ale-php-phpmd| + phpstan.............................|ale-php-phpstan| + php-cs-fixer........................|ale-php-php-cs-fixer| + po....................................|ale-po-options| + write-good..........................|ale-po-write-good| + pod...................................|ale-pod-options| + write-good..........................|ale-pod-write-good| + pony..................................|ale-pony-options| + ponyc...............................|ale-pony-ponyc| + proto.................................|ale-proto-options| + protoc-gen-lint.....................|ale-proto-protoc-gen-lint| + pug...................................|ale-pug-options| + puglint.............................|ale-pug-puglint| + puppet................................|ale-puppet-options| + puppetlint..........................|ale-puppet-puppetlint| + python................................|ale-python-options| + autopep8............................|ale-python-autopep8| + flake8..............................|ale-python-flake8| + isort...............................|ale-python-isort| + mypy................................|ale-python-mypy| + prospector..........................|ale-python-prospector| + pycodestyle.........................|ale-python-pycodestyle| + pylint..............................|ale-python-pylint| + pyls................................|ale-python-pyls| + yapf................................|ale-python-yapf| + r.....................................|ale-r-options| + lintr...............................|ale-r-lintr| + reasonml..............................|ale-reasonml-options| + merlin..............................|ale-reasonml-merlin| + ols.................................|ale-reasonml-ols| + refmt...............................|ale-reasonml-refmt| + restructuredtext......................|ale-restructuredtext-options| + write-good..........................|ale-restructuredtext-write-good| + ruby..................................|ale-ruby-options| + brakeman............................|ale-ruby-brakeman| + rails_best_practices................|ale-ruby-rails_best_practices| + reek................................|ale-ruby-reek| + rubocop.............................|ale-ruby-rubocop| + ruby................................|ale-ruby-ruby| + rust..................................|ale-rust-options| + cargo...............................|ale-rust-cargo| + rls.................................|ale-rust-rls| + rustc...............................|ale-rust-rustc| + rustfmt.............................|ale-rust-rustfmt| + sass..................................|ale-sass-options| + stylelint...........................|ale-sass-stylelint| + scala.................................|ale-scala-options| + scalastyle..........................|ale-scala-scalastyle| + scss..................................|ale-scss-options| + prettier............................|ale-scss-prettier| + stylelint...........................|ale-scss-stylelint| + sh....................................|ale-sh-options| + shell...............................|ale-sh-shell| + shellcheck..........................|ale-sh-shellcheck| + shfmt...............................|ale-sh-shfmt| + sml...................................|ale-sml-options| + smlnj...............................|ale-sml-smlnj| + solidity..............................|ale-solidity-options| + solhint.............................|ale-solidity-solhint| + solium..............................|ale-solidity-solium| + spec..................................|ale-spec-options| + rpmlint.............................|ale-spec-rpmlint| + stylus................................|ale-stylus-options| + stylelint...........................|ale-stylus-stylelint| + tcl...................................|ale-tcl-options| + nagelfar............................|ale-tcl-nagelfar| + terraform.............................|ale-terraform-options| + tflint..............................|ale-terraform-tflint| + tex...................................|ale-tex-options| + chktex..............................|ale-tex-chktex| + lacheck.............................|ale-tex-lacheck| + texinfo...............................|ale-texinfo-options| + write-good..........................|ale-texinfo-write-good| + text..................................|ale-text-options| + write-good..........................|ale-text-write-good| + thrift................................|ale-thrift-options| + thrift..............................|ale-thrift-thrift| + typescript............................|ale-typescript-options| + eslint..............................|ale-typescript-eslint| + prettier............................|ale-typescript-prettier| + tslint..............................|ale-typescript-tslint| + tsserver............................|ale-typescript-tsserver| + verilog/systemverilog.................|ale-verilog-options| + iverilog............................|ale-verilog-iverilog| + verilator...........................|ale-verilog-verilator| + vim...................................|ale-vim-options| + vint................................|ale-vim-vint| + vim help..............................|ale-vim-help-options| + write-good..........................|ale-vim-help-write-good| + vue...................................|ale-vue-options| + prettier............................|ale-vue-prettier| + xhtml.................................|ale-xhtml-options| + write-good..........................|ale-xhtml-write-good| + xml...................................|ale-xml-options| + xmllint.............................|ale-xml-xmllint| + yaml..................................|ale-yaml-options| + swaglint............................|ale-yaml-swaglint| + yamllint............................|ale-yaml-yamllint| + 8. Commands/Keybinds....................|ale-commands| + 9. API..................................|ale-api| + 10. Special Thanks......................|ale-special-thanks| + 11. Contact.............................|ale-contact| =============================================================================== 1. Introduction *ale-introduction* @@ -52,7 +270,7 @@ using the |job-control| features available in Vim 8 and NeoVim. For Vim 8, Vim must be compiled with the |job| and |channel| and |timer| features as a minimum. -ALE supports the following key features: +ALE supports the following key features for linting: 1. Running linters when text is changed. 2. Running linters when files are opened. @@ -60,58 +278,356 @@ ALE supports the following key features: 4. Populating the |loclist| 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. =============================================================================== 2. Supported Languages & Tools *ale-support* The following languages and tools are supported. -* Ansible: 'ansible-lint' -* Bash: 'shell' (-n flag), 'shellcheck' -* Bourne Shell: 'shell' (-n flag), 'shellcheck' -* C: 'cppcheck', 'gcc', 'clang' -* C++ (filetype cpp): 'cppcheck', 'gcc' -* C#: 'mcs' -* Chef: 'foodcritic' -* CoffeeScript: 'coffee', 'coffelint' -* CSS: 'csslint', 'stylelint' -* Cython (pyrex filetype): 'cython' -* D: 'dmd' -* Dockerfile: 'hadolint' -* Elixir: 'credo' -* Elm: 'elm-make' -* Erlang: 'erlc' -* Fortran: 'gcc' -* Go: 'gofmt -e', 'go vet', 'golint', 'go build' -* Haskell: 'ghc', 'hlint' -* HTML: 'HTMLHint', 'tidy' -* JavaScript: 'eslint', 'jscs', 'jshint', 'flow' -* JSON: 'jsonlint' -* LaTeX: 'chktex', 'lacheck' -* Lua: 'luacheck' -* Markdown: 'mdl' -* MATLAB: 'mlint' -* OCaml: 'merlin' (see |ale-linter-integration-ocaml-merlin|) -* Perl: 'perl' (-c flag), 'perlcritic' -* PHP: 'hack', 'php' (-l flag), 'phpcs' -* Pug: 'pug-lint' -* Puppet: 'puppet', 'puppet-lint' -* Python: 'flake8', 'mypy', 'pylint' -* Rust: 'rustc' (see |ale-integration-rust|) -* Ruby: 'rubocop' -* SASS: 'sasslint', 'stylelint' -* SCSS: 'sasslint', 'scsslint', 'stylelint' -* Scala: 'scalac' -* Swift: 'swiftlint' -* Tex: 'proselint' -* Text: 'proselint' -* TypeScript: 'tslint', 'typecheck' -* Verilog: 'iverilog', 'verilator' -* Vim: 'vint' -* YAML: 'yamllint' +Notes: + +`^` No linters for text or Vim help filetypes are enabled by default. +`!!` These linters check only files on disk. See |ale-lint-file-linters| + +* ASM: `gcc` +* Ansible: `ansible-lint` +* API Blueprint: `drafter` +* AsciiDoc: `alex`!!, `proselint`, `redpen`, `write-good` +* Awk: `gawk` +* Bash: `shell` (-n flag), `shellcheck`, `shfmt` +* Bourne Shell: `shell` (-n flag), `shellcheck`, `shfmt` +* C: `cppcheck`, `cpplint`!!, `clang`, `clangtidy`!!, `clang-format`, `flawfinder`, `gcc` +* C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `flawfinder`, `gcc` +* CUDA: `nvcc`!! +* C#: `mcs`, `mcsc`!! +* Chef: `foodcritic` +* Clojure: `joker` +* CMake: `cmakelint` +* CoffeeScript: `coffee`, `coffeelint` +* Crystal: `crystal`!! +* CSS: `csslint`, `prettier`, `stylelint` +* Cython (pyrex filetype): `cython` +* D: `dmd` +* Dafny: `dafny`!! +* Dart: `dartanalyzer`!!, `language_server` +* Dockerfile: `hadolint` +* Elixir: `credo`, `dialyxir`, `dogma`!! +* Elm: `elm-format, elm-make` +* Erb: `erb`, `erubi`, `erubis` +* Erlang: `erlc`, `SyntaxErl` +* Fish: `fish` (-n flag) +* Fortran: `gcc` +* Fountain: `proselint` +* FusionScript: `fusion-lint` +* Git Commit Messages: `gitlint` +* GLSL: glslang, `glslls` +* Go: `gofmt`, `goimports`, `go vet`!!, `golint`, `gotype`, `gometalinter`!!, `go build`!!, `gosimple`!!, `staticcheck`!! +* GraphQL: `eslint`, `gqlint`, `prettier` +* Haml: `haml-lint` +* Handlebars: `ember-template-lint` +* Haskell: `brittany`, `ghc`, `stack-ghc`, `stack-build`!!, `ghc-mod`, `stack-ghc-mod`, `hlint`, `hdevtools`, `hfmt` +* HTML: `alex`!!, `HTMLHint`, `proselint`, `tidy`, `write-good` +* Idris: `idris` +* Java: `checkstyle`, `javac`, `google-java-format` +* JavaScript: `eslint`, `flow`, `jscs`, `jshint`, `prettier`, `prettier-eslint`, `prettier-standard`, `standard`, `xo` +* JSON: `fixjson`, `jsonlint`, `jq`, `prettier` +* Kotlin: `kotlinc`, `ktlint` +* LaTeX (tex): `alex`!!, `chktex`, `lacheck`, `proselint`, `redpen`, `vale`, `write-good` +* Less: `lessc`, `prettier`, `stylelint` +* LLVM: `llc` +* Lua: `luac`, `luacheck` +* Mail: `alex`!!, `proselint`, `vale` +* Make: `checkmake` +* Markdown: `alex`!!, `mdl`, `prettier`, `proselint`, `redpen`, `remark-lint`, `vale`, `write-good` +* MATLAB: `mlint` +* Nim: `nim check`!! +* nix: `nix-instantiate` +* nroff: `alex`!!, `proselint`, `write-good` +* Objective-C: `clang` +* Objective-C++: `clang` +* OCaml: `merlin` (see |ale-ocaml-merlin|), `ols` +* Perl: `perl -c`, `perl-critic` +* PHP: `hack`, `hackfmt`, `langserver`, `phan`, `php -l`, `phpcs`, `phpmd`, `phpstan`, `phpcbf`, `php-cs-fixer` +* PO: `alex`!!, `msgfmt`, `proselint`, `write-good` +* Pod: `alex`!!, `proselint`, `write-good` +* Pony: `ponyc` +* proto: `protoc-gen-lint` +* Pug: `pug-lint` +* Puppet: `puppet`, `puppet-lint` +* Python: `autopep8`, `flake8`, `isort`, `mypy`, `prospector`, `pycodestyle`, `pyls`, `pylint`!!, `yapf` +* R: `lintr` +* ReasonML: `merlin`, `ols`, `refmt` +* reStructuredText: `alex`!!, `proselint`, `redpen`, `rstcheck`, `vale`, `write-good` +* Re:VIEW: `redpen` +* RPM spec: `rpmlint` +* Ruby: `brakeman`, `rails_best_practices`!!, `reek`, `rubocop`, `ruby` +* Rust: `cargo`!!, `rls`, `rustc` (see |ale-integration-rust|), `rustfmt` +* SASS: `sass-lint`, `stylelint` +* SCSS: `prettier`, `sass-lint`, `scss-lint`, `stylelint` +* Scala: `scalac`, `scalastyle` +* Slim: `slim-lint` +* SML: `smlnj` +* Solidity: `solhint, solium` +* Stylus: `stylelint` +* SQL: `sqlint` +* Swift: `swiftlint`, `swiftformat` +* Tcl: `nagelfar`!! +* Terraform: `tflint` +* Texinfo: `alex`!!, `proselint`, `write-good` +* Text^: `alex`!!, `proselint`, `vale`, `write-good`, `redpen` +* Thrift: `thrift` +* TypeScript: `eslint`, `prettier`, `tslint`, `tsserver`, `typecheck` +* Verilog: `iverilog`, `verilator` +* Vim: `vint` +* Vim help^: `alex`!!, `proselint`, `write-good` +* Vue: `prettier` +* XHTML: `alex`!!, `proselint`, `write-good` +* XML: `xmllint` +* YAML: `swaglint`, `yamllint` =============================================================================== -3. Global Options *ale-options* +3. Linting *ale-lint* + +ALE's primary focus is on checking for problems with your code with various +programs via some Vim code for integrating with those programs, referred to +as 'linters.' ALE supports a wide array of programs for linting by default, +but additional programs can be added easily by defining files in |runtimepath| +with the filename pattern `ale_linters//.vim`. For more +information on defining new linters, see the extensive documentation +for |ale#linter#Define()|. + +Without any configuration, ALE will attempt to check all of the code for every +file you open in Vim with all available tools by default. To see what ALE +is doing, and what options have been set, try using the |:ALEInfo| command. + +Most of the linters ALE runs will check the Vim buffer you are editing instead +of the file on disk. This allows you to check your code for errors before you +have even saved your changes. ALE will check your code in the following +circumstances, which can be configured with the associated options. + +* When you modify a buffer. - |g:ale_lint_on_text_changed| +* When you open a new or modified buffer. - |g:ale_lint_on_enter| +* When you save a buffer. - |g:ale_lint_on_save| +* When the filetype changes for a buffer. - |g:ale_lint_on_filetype_changed| +* If ALE is used to check code manually. - |:ALELint| + +In addition to the above options, ALE can also check buffers for errors when +you leave insert mode with |g:ale_lint_on_insert_leave|, which is off by +default. It is worth reading the documentation for every option. + + *ale-lint-file-linters* + +Some programs must be run against files which have been saved to disk, and +simply do not support reading temporary files or stdin, either of which are +required for ALE to be able to check for errors as you type. The programs +which behave this way are documented in the lists and tables of supported +programs. ALE will only lint files with these programs in the following +circumstances. + +* When you open a new or modified buffer. - |g:ale_lint_on_enter| +* When you save a buffer. - |g:ale_lint_on_save| +* When the filetype changes for a buffer. - |g:ale_lint_on_filetype_changed| +* If ALE is used to check code manually. - |:ALELint| + +ALE will report problems with your code in the following ways, listed with +their relevant options. + +* By updating loclist. (On by default) - |g:ale_set_loclist| +* By updating quickfix. (Off by default) - |g:ale_set_quickfix| +* By setting error highlights. - |g:ale_set_highlights| +* By creating signs in the sign column. - |g:ale_set_signs| +* By echoing messages based on your cursor. - |g:ale_echo_cursor| +* By showing balloons for your mouse cursor - |g:ale_set_balloons| + +Please consult the documentation for each option, which can reveal some other +ways of tweaking the behaviour of each way of displaying problems. You can +disable or enable whichever options you prefer. + +Most settings can be configured for each buffer. (|b:| instead of |g:|), +including disabling ALE for certain buffers with |b:ale_enabled|. The +|g:ale_pattern_options| setting can be used to configure files differently +based on regular expressions for filenames. For configuring entire projects, +the buffer-local options can be used with external plugins for reading Vim +project configuration files. Buffer-local settings can also be used in +ftplugin files for different filetypes. + + +=============================================================================== +4. Fixing Problems *ale-fix* + +ALE can fix problems with files with the |ALEFix| command. When |ALEFix| is +run, the variable |g:ale_fixers| will be read for getting a |List| of commands +for filetypes, split on `.`, and the functions named in |g:ale_fixers| will be +executed for fixing the errors. + +The |ALEFixSuggest| command can be used to suggest tools that be used to +fix problems for the current buffer. + +The values for `g:ale_fixers` can be a list of |String|, |Funcref|, or +|lambda| values. String values must either name a function, or a short name +for a function set in the ALE fixer registry. + +Each function for fixing errors must accept either one argument `(buffer)` or +two arguments `(buffer, lines)`, representing the buffer being fixed and the +lines to fix. The functions must return either `0`, for changing nothing, a +|List| for new lines to set, or a |Dictionary| for describing a command to be +run in the background. + +Functions receiving a variable number of arguments will not receive the second +argument `lines`. Functions should name two arguments if the `lines` argument +is desired. This is required to avoid unnecessary copying of the lines of +the buffers being checked. + +When a |Dictionary| is returned for an |ALEFix| callback, the following keys +are supported for running the commands. + + `command` A |String| for the command to run. This key is required. + + When `%t` is included in a command string, a temporary + file will be created, containing the lines from the file + after previous adjustment have been done. + + `read_temporary_file` When set to `1`, ALE will read the contents of the + temporary file created for `%t`. This option can be used + for commands which need to modify some file on disk in + order to fix files. + + `process_with` An optional callback for post-processing. + + The callback must accept two arguments, + `(buffer, output)`, which can be used for converting + the output from a command into lines to replace the + buffer's contents with. + + A |List| of |String|s must be returned. + + `chain_with` An optional key for defining a callback to call next. + + The callback must accept two or three arguments, + `(buffer, output)` or `(buffer, output, input)` . + Functions receiving a variable number of arguments will + only receive the first two values. The `output` argument + will contain the lines of output from the command run. + The `input` argument is the List of lines for the + buffer, after applying any previous fixers. + + The callback must return the same values returned for + any fixer function. This allows fixer functions to be + chained recursively. + + When the command string returned for a fixer is an empty + string, the next command in the chain will still be run. + This allows commands to be skipped, like version checks + that are cached. An empty List will be passed to the + next callback in the chain for the `output`. + + `read_buffer` An optional key for disabling reading the buffer. + + When set to `0`, ALE will not pipe the buffer's data + 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 `0` when `chain_with` is defined + as anything other than `v:null`, and defaults to `1` + otherwise. This is so earlier commands in a chain + do not receive the buffer's data by default. + + *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. + + +=============================================================================== +5. Language Server Protocol Support *ale-lsp* + +ALE offers some support for integrating 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. + +ALE supports the following LSP/tsserver features. + +1. Diagnostics/linting - Enabled via selecting linters as usual. +2. Completion (Only for tsserver) +3. Go to definition + + +------------------------------------------------------------------------------- +5.1 Completion *ale-completion* + +NOTE: At the moment, only `tsserver` for TypeScript code is supported for +completion. + +ALE offers limited support for automatic completion of code while you type. +Completion is only supported while a least one LSP linter is enabled. ALE +will only suggest symbols provided by the LSP servers. + +Suggestions will be made while you type after completion is enabled. +Completion can be enabled by setting |g:ale_completion_enabled| to `1`. The +delay for completion can be configured with |g:ale_completion_delay|. ALE will +only suggest so many possible matches for completion. The maximum number of +items can be controlled with |g:ale_completion_max_suggestions|. + + +------------------------------------------------------------------------------- +5.2 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 following commands are supported: + +|ALEGoToDefinition| - Open the definition of the symbol under the cursor. +|ALEGoToDefinitionInTab| - The same, but for opening the file in a new tab. + + +=============================================================================== +6. Global Options *ale-options* g:airline#extensions#ale#enabled *g:airline#extensions#ale#enabled* @@ -124,6 +640,109 @@ g:airline#extensions#ale#enabled *g:airline#extensions#ale#enabled* |airline#extensions#ale#warning_symbol|. +g:ale_cache_executable_check_failures *g:ale_cache_executable_check_failures* + + Type: |Number| + Default: `0` + + When set to `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 `1`, Vim will have to be restarted after new + executables are installed for ALE to be able to run linters for those + executables. + + +g:ale_change_sign_column_color *g:ale_change_sign_column_color* + + Type: |Number| + Default: `0` + + When set to `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. + + +g:ale_command_wrapper *g:ale_command_wrapper* + *b: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. + + +g:ale_completion_delay *g:ale_completion_delay* + + Type: |Number| + Default: `100` + + The number of milliseconds before ALE will send a request to a language + server for completions after you have finished typing. + + See |ale-completion| + + +g:ale_completion_enabled *g:ale_completion_enabled* + + Type: |Number| + Default: `0` + + When this option is set to `1`, completion support will be enabled. + + See |ale-completion| + + +g:ale_completion_max_suggestions *g:ale_completion_max_suggestions* + + Type: |Number| + Default: `50` + + The maximum number of items ALE will suggest in completion menus for + automatic completion. + + Setting this number higher will require more processing time, and may + suggest too much noise. Setting this number lower will require less + processing time, but some suggestions will not be included, so you might not + be able to see the suggestions you want. + + Adjust this option as needed, depending on the complexity of your codebase + and your available processing power. + + g:ale_echo_cursor *g:ale_echo_cursor* Type: |Number| @@ -137,49 +756,194 @@ g:ale_echo_cursor *g:ale_echo_cursor* The format of the message can be customizable in |g:ale_echo_msg_format|. +g:ale_echo_delay *g:ale_echo_delay* + *b:ale_echo_delay* + Type: |Number| + Default: `10` + + Given any integer, this option controls the number of milliseconds before + ALE will echo 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. + + g:ale_echo_msg_error_str *g:ale_echo_msg_error_str* Type: |String| - Default: `Error` + Default: `'Error'` - The string used for error severity in the echoed message. - Note |`g:ale_echo_cursor`| should be set to 1 - Note |`g:ale_echo_msg_format`| should contain the `%severity%` handler + The string used for `%severity%` for errors. See |g:ale_echo_msg_format| g:ale_echo_msg_format *g:ale_echo_msg_format* +b:ale_echo_msg_format *b:ale_echo_msg_format* Type: |String| - Default: `%s` + Default: `'%code: %%s'` - This variable defines the format of the echoed message. The `%s` is the - error message itself, and it can contain the following handlers: - - `%linter%` for linter's name - - `%severity%` for the type of severity - Note |`g:ale_echo_cursor`| should be setted to 1 + 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 withe severity of the problem + + 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 differnt languages. (Say in ftplugin + files.) + + +g:ale_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| g:ale_echo_msg_warning_str *g:ale_echo_msg_warning_str* Type: |String| - Default: `Warning` + Default: `'Warning'` - The string used for warning severity in the echoed message. - Note |`g:ale_echo_cursor`| should be set to 1 - Note |`g:ale_echo_msg_format`| should contain the `%severity%` handler + The string used for `%severity%` for warnings. See |g:ale_echo_msg_format| -g:ale_keep_list_window_open *g:ale_keep_list_window_open* +g:ale_emit_conflict_warnings *g:ale_emit_conflict_warnings* + + Type: |Number| + Default: `1` + + When set to `0`, ALE will not emit any warnings on startup about conflicting + plugins. ALE will probably not work if other linting plugins are installed. + + When this option is set to `1`, ALE will add its `after` directory to + |runtimepath| automatically, so the checks can be applied. Setting this + option to `0` before ALE is loaded will prevent ALE from modifying + |runtimepath|. + + +g:ale_enabled *g:ale_enabled* + *b:ale_enabled* + + 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. + + +g:ale_fixers *g:ale_fixers* + *b: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. + + +g:ale_fix_on_save *g:ale_fix_on_save* +b:ale_fix_on_save *b:ale_fix_on_save* Type: |Number| Default: `0` - When set to `1`, this option will keep the loclist or quickfix windows - event after all warnings/errors have been removed for files. By default - the loclist or quicfix windows will be closed automatically when there - are no warnings or errors. + When set to 1, ALE will fix files when they are saved. - See: |g:ale_open_list| + 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. + + Fixing files can be disabled or enabled for individual buffers by setting + `b:ale_fix_on_save` to `0` or `1`. + + +g:ale_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. + + +g:ale_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. + + +g:ale_keep_list_window_open *g:ale_keep_list_window_open* + *b: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| + + +g:ale_list_window_size *g:ale_list_window_size* + *b: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. g:ale_lint_delay *g:ale_lint_delay* @@ -189,7 +953,7 @@ g:ale_lint_delay *g:ale_lint_delay* 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 `1`. + |g:ale_lint_on_text_changed| variable set to `always`, `insert`, or `normal`. g:ale_lint_on_enter *g:ale_lint_on_enter* @@ -197,16 +961,40 @@ g:ale_lint_on_enter *g:ale_lint_on_enter* Type: |Number| Default: `1` - When this option is set to `1`, the |BufEnter| and |BufRead| events 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 + When this option is set to `1`, the |BufWinEnter| and |BufRead| events 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 behaviour. + 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. + + A |BufWinLeave| event will be used to look for the |E924|, |E925|, or |E926| + errors after moving from a loclist or quickfix window to a new buffer. If + prompts for these errors are opened after moving to new buffers, then ALE + will automatically send the `` key needed to close the prompt. + + +g:ale_lint_on_filetype_changed *g:ale_lint_on_filetype_changed* + + Type: |Number| + Default: `1` + + This option will cause ALE to run whenever the filetype is changed. 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. + + If |g:ale_lint_on_enter| is set to `0`, then ALE will not lint a file when + the filetype is initially set. Otherwise ALE would still lint files when + buffers are opened, and the option for doing so is turned off. + g:ale_lint_on_save *g:ale_lint_on_save* Type: |Number| - Default: `0` + 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 @@ -217,21 +1005,38 @@ g:ale_lint_on_save *g:ale_lint_on_save* g:ale_lint_on_text_changed *g:ale_lint_on_text_changed* - Type: |Number| - Default: `1` + Type: |String| + Default: `always` By default, ALE will check files with the various supported programs when text is changed by using the |TextChanged| event. If this behaviour is not - desired, then this option can be disabled by setting it to 0. The + desired, then this option can be disabled by setting it to `never`. The |g:ale_lint_delay| variable will be used to set a |timer_start()| on a delay, and each change to a file will continue to call |timer_stop()| and |timer_start()| repeatedly until the timer ticks by, and the linters will be run. The checking of files will run in the background, so it should not - inhibit editing files. + inhibit editing files. This option can also be set to `insert` or `normal` + to lint when text is changed only in insert or normal mode respectively. +g:ale_lint_on_insert_leave *g:ale_lint_on_insert_leave* + + Type: |Number| + Default: `0` + + 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 keybind. > + + " Make using Ctrl+C do the same as Escape, to trigger autocmd commands + inoremap +< + g:ale_linter_aliases *g:ale_linter_aliases* - + *b:ale_linter_aliases* Type: |Dictionary| Default: `{}` @@ -257,27 +1062,51 @@ g:ale_linter_aliases *g:ale_linter_aliases* 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']}` + + 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|, to tell ALE to load the + linters for specific filetypes for a given buffer. > + + let b:ale_linter_aliases = ['html', 'javascript', 'css'] +< + No linters will be loaded when the buffer's filetype is empty. g:ale_linters *g:ale_linters* - + *b:ale_linters* Type: |Dictionary| 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. - Only the filetypes specified in the dictionary will be limited in terms - of which linters will be run. + 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: > { - \ 'zsh': ['shell'], \ 'csh': ['shell'], + \ 'go': ['gofmt', 'golint', 'go vet'], + \ 'help': [], + \ 'perl': ['perlcritic'], + \ 'python': ['flake8', 'mypy', 'pylint'], + \ 'rust': ['cargo'], + \ 'spec': [], + \ 'text': [], + \ 'zsh': ['shell'], \} < 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: > + file. For example, you can enable only `eslint` for JavaScript files: > let g:ale_linters = {'javascript': ['eslint']} < @@ -286,24 +1115,211 @@ g:ale_linters *g:ale_linters* let g:ale_linters = {'javascript': []} < - All linters available for a given filetype can be enabled by using the - string `'all'`: > + 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'} < + 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. -g:ale_open_list *g:ale_open_list* + `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' +< + 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|. + + +g:ale_linters_explicit *g:ale_linters_explicit* Type: |Number| Default: `0` - When set to `1`, this will cause ALE to automatically open a window for - the loclist (|lopen|) or for the quickfix list instead if - |g:ale_set_quickfix| is `1`. (|copen|) + When set to `1`, 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. + + +g:ale_list_vertical *g:ale_list_vertical* + *b:ale_list_vertical* + Type: |Number| + Default: `0` + + When set to `1`, this will cause ALE to open any windows (loclist or + quickfix) vertically instead of horizontally (|vert| |lopen|) or (|vert| + |copen|) + + +g:ale_loclist_msg_format *g:ale_loclist_msg_format* +b:ale_loclist_msg_format *b:ale_loclist_msg_format* + + Type: |String| + Default: `g:ale_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. + + +g:ale_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|. + + +g:ale_max_signs *g:ale_max_signs* + *b:ale_max_signs* + Type: |Number| + Default: `-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|. + + +g:ale_maximum_file_size *g:ale_maximum_file_size* + *b:ale_maximum_file_size* + Type: |Number| + Default: `0` + + 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. + + +g:ale_open_list *g:ale_open_list* + *b:ale_open_list* + Type: |Number| or |String| + Default: `0` + + When set to `1`, this will cause ALE to automatically open a window for the + loclist (|lopen|) or for the quickfix list instead if |g:ale_set_quickfix| + is `1`. (|copen|) + + When set to `'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 `1`, in which case the window will be kept open until closed manually. + to `1`, in which case the window will be kept open when no problems are + found. + + The window size can be configured with |g:ale_list_window_size|. + + Windows can be opened vertically with |g:ale_list_vertical|. + + If you want to close the loclist window automatically when the buffer is + closed, you can set up the following |autocmd| command: > + + augroup CloseLoclistWindowGroup + autocmd! + autocmd QuitPre * if empty(&buftype) | lclose | endif + augroup END +< + +g:ale_pattern_options *g:ale_pattern_options* + + Type: |Dictionary| + Default: `{}` + + 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`. + + +g:ale_pattern_options_enabled *g:ale_pattern_options_enabled* + + Type: |Number| + Default: `!empty(g:ale_pattern_options)` + + This option can be used for turning the behaviour of setting + |g:ale_pattern_options| on or off. By default, setting a single key for + |g:ale_pattern_options| will turn this option on, as long as the setting is + configured before ALE is loaded. + + +g:ale_set_balloons *g:ale_set_balloons* + + Type: |Number| + Default: `has('balloon_eval')` + + When this option is set to `1`, balloon messages will be displayed for + problems. Problems nearest to the cursor on the line the cursor is over will + be displayed. + + +g:ale_set_highlights *g:ale_set_highlights* + + Type: |Number| + Default: `has('syntax')` + + When this option is set to `1`, highlights will be set for problems. + + ALE will use the following highlight groups for problems: + + |ALEError| - Items with `'type': 'E'` + |ALEWarning| - Items with `'type': 'W'` + |ALEInfo.| - Items with `'type': 'I'` + |ALEStyleError| - Items with `'type': 'E'` and `'sub_type': 'style'` + |ALEStyleWarning| - Items with `'type': 'W'` and `'sub_type': 'style'` + + When |g:ale_set_signs| is set to `0`, the following highlights for entire + lines will be set. + + |ALEErrorLine| - All items with `'type': 'E'` + |ALEWarningLine| - All items with `'type': 'W'` + |ALEInfoLine| - All items with `'type': 'I'` + + 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. g:ale_set_loclist *g:ale_set_loclist* @@ -321,9 +1337,17 @@ g:ale_set_quickfix *g:ale_set_quickfix* Type: |Number| Default: `0` - When this option is set to `1`, the |quickfix| list will be populated with any - warnings and errors which are found by ALE, instead of the |loclist|. The - loclist will never be populated when this option is on. + When this option is set to `1`, the |quickfix| list will be populated with + any problems which are found by ALE, instead of the |loclist|. The loclist + will never be populated when this option is on. + + Problems from every buffer ALE has checked will be included in the quickfix + list, which can be checked with |:copen|. Problems will be de-duplicated. + + 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 instead. g:ale_set_signs *g:ale_set_signs* @@ -332,10 +1356,35 @@ g:ale_set_signs *g:ale_set_signs* Default: `has('signs')` When this option is set to `1`, the |sign| column will be populated with - signs marking where errors and warnings appear in the file. The - `ALEErrorSign` and `ALEWarningSign` highlight groups will be used to provide - highlighting for the signs. The text used for signs can be customised with - the |g:ale_sign_error| and |g:ale_sign_warning| options. + signs marking where problems appear in the file. + + ALE will use the following highlight groups for problems: + + |ALEErrorSign| - Items with `'type': 'E'` + |ALEWarningSign| - Items with `'type': 'W'` + |ALEInfoSign| - Items with `'type': 'I'` + |ALEStyleErrorSign| - Items with `'type': 'E'` and `'sub_type': 'style'` + |ALEStyleWarningSign| - Items with `'type': 'W'` and `'sub_type': 'style'` + + 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'` + |ALEWarningLine| - All items with `'type': 'W'` + |ALEInfoLine| - All items with `'type': 'I'` + + 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|. g:ale_sign_column_always *g:ale_sign_column_always* @@ -354,9 +1403,31 @@ g:ale_sign_error *g:ale_sign_error* Type: |String| Default: `'>>'` - This string can be changed to change the characters used for the sign gutter - for lines which at least one error on them. Lines with both errors and - warnings on them will show the error marker, as errors take precedence. + The sign for errors in the sign gutter. + + +g:ale_sign_info *g:ale_sign_info* + + Type: |String| + Default: `g:ale_sign_warning` + + The sign for "info" markers in the sign gutter. + + +g:ale_sign_style_error *g:ale_sign_style_error* + + Type: |String| + Default: `g:ale_sign_error` + + The sign for style errors in the sign gutter. + + +g:ale_sign_style_warning *g:ale_sign_style_warning* + + Type: |String| + Default: `g:ale_sign_warning` + + The sign for style warnings in the sign gutter. g:ale_sign_offset *g:ale_sign_offset* @@ -378,561 +1449,334 @@ g:ale_sign_warning *g:ale_sign_warning* Type: |String| Default: `'--'` - This string can be changed to change the characters used for the sign gutter - for lines which at least one warning on them. + The sign for warnings in the sign gutter. -g:ale_statusline_format *g:ale_statusline_format* +g:ale_type_map *g:ale_type_map* + *b: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'}} +< + 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'}} +< + Type maps can be set per-buffer with `b:ale_type_map`. + + +g:ale_virtualenv_dir_names *g:ale_virtualenv_dir_names* +b:ale_virtualenv_dir_names *b:ale_virtualenv_dir_names* Type: |List| - Default: `['%d error(s)', '%d warning(s)', 'OK']` + Default: `['.env', 'env', 've-py3', 've', 'virtualenv', 'venv']` - This variable defines the format of |`ale#statusline#status()`| output. - - The 1st element is for errors - - The 2nd element is for warnings - - The 3rd element is for when no errors are detected + A list of directory names to be used when searching upwards from Python + files to discover virtulenv 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. + + +g:ale_warn_about_trailing_blank_lines *g:ale_warn_about_trailing_blank_lines* +b:ale_warn_about_trailing_blank_lines *b: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|. g:ale_warn_about_trailing_whitespace *g:ale_warn_about_trailing_whitespace* +b:ale_warn_about_trailing_whitespace *b: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 in signs, the loclist, and echo messages, etc. If these - errors are found to be too irritating while edits are being made, and you - have configured Vim to automatically remove trailing whitespace, then you - can disable these warnings for some linters by setting this option to `0`. + 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. -=============================================================================== -4. Linter Specific Options *ale-linter-options* -Some linters have specific options which can be configured for each of them, -for customising their behaviour. - -------------------------------------------------------------------------------- -4.1. eslint *ale-linter-options-eslint* - -g:ale_javascript_eslint_executable *g:ale_javascript_eslint_executable* +g:ale_windows_node_executable_path *g:ale_windows_node_executable_path* + *b:ale_windows_node_executable_path* Type: |String| - Default: `'eslint'` + Default: `'node.exe'` - ALE will first discover the eslint path in an ancestor node_modules - directory. If no such path exists, this variable will be used instead. + This variable is used as the path to the executable to use for executing + scripts with Node.js on Windows. - This variable can be set to change the path to eslint. If you have eslint_d - installed, you can set this option to use eslint_d instead. - - If you wish to use only a globally installed version of eslint, set - |g:ale_javascript_eslint_use_global| to `1`. + 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. -g:ale_javascript_eslint_options *g:ale_javascript_eslint_options* +------------------------------------------------------------------------------- +6.1. Highlights *ale-highlights* +ALEError *ALEError* + + Default: `highlight link ALEError SpellBad` + + The highlight used for highlighted errors. See |g:ale_set_highlights|. + + +ALEErrorLine *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 *ALEErrorSign* + + Default: `highlight link ALEErrorSign error` + + The highlight used for error signs. See |g:ale_set_signs|. + + +ALEInfo *ALEInfo.* + *ALEInfo-highlight* + Default: `highlight link ALEInfo ALEWarning` + + The highlight used for highlighted info messages. See |g:ale_set_highlights|. + + +ALEInfoSign *ALEInfoSign* + + Default: `highlight link ALEInfoSign ALEWarningSign` + + The highlight used for info message signs. See |g:ale_set_signs|. + + +ALEInfoLine *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|. + + +ALEStyleError *ALEStyleError* + + Default: `highlight link ALEStyleError ALEError` + + The highlight used for highlighted style errors. See |g:ale_set_highlights|. + + +ALEStyleErrorSign *ALEStyleErrorSign* + + Default: `highlight link ALEStyleErrorSign ALEErrorSign` + + The highlight used for style error signs. See |g:ale_set_signs|. + + +ALEStyleWarning *ALEStyleWarning* + + Default: `highlight link ALEStyleWarning ALEError` + + The highlight used for highlighted style warnings. See |g:ale_set_highlights|. + + +ALEStyleWarningSign *ALEStyleWarningSign* + + Default: `highlight link ALEStyleWarningSign ALEWarningSign` + + The highlight used for style warning signs. See |g:ale_set_signs|. + + +ALEWarning *ALEWarning* + + Default: `highlight link ALEWarning SpellCap` + + The highlight used for highlighted warnings. See |g:ale_set_highlights|. + + +ALEWarningLine *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 *ALEWarningSign* + + Default: `highlight link ALEWarningSign todo` + + The highlight used for warning signs. See |g:ale_set_signs|. + + +------------------------------------------------------------------------------- +6.2. Options for write-good *ale-write-good-options* + +The options for the write-good linter are global because it does not make +sense to have them specified on a per-language basis. + +g:ale_writegood_executable *g:ale_writegood_executable* + *b:ale_writegood_executable* + Type: |String| + Default: `'writegood'` + + See |ale-integrations-local-executables| + + +g:ale_writegood_options *g:ale_writegood_options* + *b:ale_writegood_options* Type: |String| Default: `''` - This variable can be set to pass additional options to eslint. + This variable can be set to pass additional options to writegood. -g:ale_javascript_eslint_use_global *g:ale_javascript_eslint_use_global* - - Type: |String| - Default: `0` - - This variable controls whether or not ALE will search for a local path for - eslint first. If this variable is set to `1`, then ALE will always use the - global version of eslint, in preference to locally installed versions of - eslint in node_modules. - -------------------------------------------------------------------------------- -4.2. flow *ale-linter-options-flow* - -g:ale_javascript_flow_executable *g:ale_javascript_flow_executable* - - Type: |String| - Default: `'flow'` - - ALE will first discover the flow 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 flow, set - |g:ale_javascript_flow_use_global| to `1`. - - -g:ale_javascript_flow_use_global *g:ale_javascript_flow_use_global* - - Type: |String| - Default: `0` - - This variable controls whether or not ALE will search for a local path for - flow first. If this variable is set to `1`, then ALE will always use the - global version of flow, in preference to locally installed versions of - flow in node_modules. - -------------------------------------------------------------------------------- -4.3. jshint *ale-linter-options-jshint* - -g:ale_javascript_jshint_executable *g:ale_javascript_jshint_executable* - - Type: |String| - Default: `'jshint'` - - ALE will first discover the jshint path in an ancestor node_modules - directory. If no such path exists, this variable will be used instead. - - This variable can be changed to change the path to jshint. - - If you wish to use only a globally installed version of jshint, set - |g:ale_javascript_jshint_use_global| to `1`. - - -g:ale_javascript_jshint_use_global *g:ale_javascript_jshint_use_global* - - Type: |String| - Default: `0` - - This variable controls whether or not ALE will search for a local path for - jshint first. If this variable is set to `1`, then ALE will always use the - global version of jshint, in preference to locally installed versions of - jshint in node_modules. - -------------------------------------------------------------------------------- -4.4. phpcs *ale-linter-options-phpcs* - -g:ale_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. - -------------------------------------------------------------------------------- -4.5. html-tidy *ale-linter-options-html-tidy* - -g:ale_html_tidy_executable *g:ale_html_tidy_executable* - - Type: |String| - Default: `'tidy'` - - This variable can be changed to change the path to tidy. - - -g:ale_html_tidy_args *g:ale_html_tidy_args* - - 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 - -------------------------------------------------------------------------------- -4.6. c-gcc *ale-linter-options-c-gcc* - -g:ale_c_gcc_options *g:ale_c_gcc_options* - - Type: |String| - Default: `'-std=c11 -Wall'` - - This variable can be change to modify flags given to gcc. - -------------------------------------------------------------------------------- -4.7. cpp-gcc *ale-linter-options-cpp-gcc* - -g:ale_cpp_gcc_options *g:ale_cpp_gcc_options* - - Type: |String| - Default: `'-std=c++14 -Wall'` - - This variable can be changed to modify flags given to gcc. - -------------------------------------------------------------------------------- -4.8. fortran-gcc *ale-linter-options-fortran-gcc* - -g:ale_fortran_gcc_options *g:ale_fortran_gcc_options* - - Type: |String| - Default: `'-Wall'` - - This variable can be changed to modify flags given to gcc. - -------------------------------------------------------------------------------- -4.9. shell *ale-linter-options-shell* - -g:ale_linters_sh_shell_default_shell *g:ale_linters_sh_shell_default_shell* - - Type: |String| - Default: The current shell (`$SHELL`) or `'bash'` if that cannot be read. - - 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. - -------------------------------------------------------------------------------- -4.10. shellcheck *ale-linter-options-shellcheck* - -g:ale_linters_sh_shellckeck_exclusions *g:ale_linters_sh_shellckeck_exclusions* - - Type: |String| - Default: `''` - - Set this variable to exclude test(s) for shellcheck (-e/--exclude option). - -------------------------------------------------------------------------------- -4.11. vint *ale-linter-options-vint* - -g:ale_vim_vint_show_style_issues *g:ale_vim_vint_show_style_issues* - +g:ale_writegood_use_global *g:ale_writegood_use_global* + *b:ale_writegood_use_global* 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. - -------------------------------------------------------------------------------- -4.12. luacheck *ale-linter-options-luacheck* - -g:ale_lua_luacheck_executable *g:ale_lua_luacheck_executable* - - Type: |String| - Default: `'luacheck'` - - This variable can be changed to change the path to luacheck. - -------------------------------------------------------------------------------- -4.13. c-cppcheck *ale-linter-options-c-cppcheck* - -g:ale_c_cppcheck_options *g:ale_c_cppcheck_options* - - Type: |String| - Default: `'--enable=style'` - - This variable can be changed to modify flags given to cppcheck. - -------------------------------------------------------------------------------- -4.14. cpp-cppcheck *ale-linter-options-cpp-cppcheck* - -g:ale_cpp_cppcheck_options *g:ale_cpp_cppcheck_options* - - Type: |String| - Default: `'--enable=style'` - - This variable can be changed to modify flags given to cppcheck. - -------------------------------------------------------------------------------- -4.15. htmlhint *ale-linter-options-htmlhint* - -g:ale_html_htmlhint_options *g:ale_html_htmlhint_options* - - Type: |String| - Default: `'--format=unix'` - - This variable can be changed to modify flags given to HTMLHint. - - -g:ale_html_htmlhint_executable *g:ale_html_htmlhint_executable* - - Type: |String| - Default: `'htmlhint'` - - ALE will first discover the htmlhint 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 htmlhint, set - |g:ale_html_htmlhint_use_global| to `1`. - - -g:ale_html_htmlhint_use_global *g:ale_html_htmlhint_use_global* - - Type: |String| Default: `0` - This variable controls whether or not ALE will search for a local path for - htmlhint first. If this variable is set to `1`, then ALE will always use the - global version of htmlhint, in preference to locally installed versions of - htmlhint in node_modules. - -------------------------------------------------------------------------------- -4.16. c-clang *ale-linter-options-c-clang* - -g:ale_c_clang_options *g:ale_c_clang_options* - - Type: |String| - Default: `'-std=c11 -Wall'` - - This variable can be change to modify flags given to clang. - -------------------------------------------------------------------------------- -4.17. python-flake8 *ale-linter-options-python-flake8* - -g:ale_python_flake8_executable *g:ale_python_flake8_executable* - - Type: |String| - Default: `'flake8'` - - This variable can be changed to modify the executable used for flake8. + See |ale-integrations-local-executables| -g:ale_python_flake8_args *g:ale_python_flake8_args* +=============================================================================== +7. Integration Documentation *ale-integrations* - Type: |String| - Default: `''` +Linter and fixer options are documented in individual help files. See the +table of contents at |ale-contents|. - This variable can be changed to add command-line arguments to the flake8 - invocation. +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`. - For example, to dynamically switch between programs targeting Python 2 and - Python 3, you may want to set > + *ale-integrations-local-executables* - let g:ale_python_flake8_executable = 'python3' " or 'python' for Python 2 - let g:ale_python_flake8_args = '-m flake8' +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 < - after making sure it's installed for the appropriate Python versions (e.g. - `python3 -m pip install --user flake8`). -------------------------------------------------------------------------------- -4.18. ruby-rubocop *ale-linter-options-ruby-rubocop* +The option |g:ale_virtualenv_dir_names| controls the local virtualenv paths +ALE will use to search for Python executables. -g:ale_ruby_rubocop_options *g:ale_ruby_rubocop_options* - - Type: |String| - Default: `''` - - This variable can be change to modify flags given to rubocop. - -------------------------------------------------------------------------------- -4.19. chktex *ale-linter-options-chktex* - -g:ale_tex_chktex_executable *g:ale_tex_chktex_executable* - - Type: |String| - Default: `'chktex'` - - This variable can be changed to change the path to chktex. - - -g:ale_tex_chktex_options *g:ale_tex_chktex_options* - - Type: |String| - Default: `'-I'` - - This variable can be changed to modify flags given to chktex. - ------------------------------------------------------------------------------- -4.20. lacheck *ale-linter-options-lacheck* - -g:ale_lacheck_executable *g:ale_lacheck_executable* - - Type: |String| - Default: '`lacheck`' - - This variable can be changed to change the path to lacheck. - -------------------------------------------------------------------------------- -4.21. stylelint *ale-linter-options-stylelint* - -g:ale_css_stylelint_executable *g:ale_css_stylelint_executable* - - Type: |String| - Default: `'stylelint'` - - ALE will first discover the stylelint 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 stylelint, set - |g:ale_css_stylelint_use_global| to `1`. - - -g:ale_css_stylelint_use_global *g:ale_css_stylelint_use_global* - - Type: |String| - Default: `0` - - This variable controls whether or not ALE will search for a local path for - stylelint first. If this variable is set to `1`, then ALE will always use the - global version of stylelint, in preference to locally installed versions of - stylelint in node_modules. - - -g:ale_sass_stylelint_executable *g:ale_sass_stylelint_executable* - - Type: |String| - Default: `'stylelint'` - - ALE will first discover the stylelint 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 stylelint, set - |g:ale_sass_stylelint_use_global| to `1`. - - -g:ale_sass_stylelint_use_global *g:ale_sass_stylelint_use_global* - - Type: |String| - Default: `0` - - This variable controls whether or not ALE will search for a local path for - stylelint first. If this variable is set to `1`, then ALE will always use the - global version of stylelint, in preference to locally installed versions of - stylelint in node_modules. - - -g:ale_scss_stylelint_executable *g:ale_scss_stylelint_executable* - - Type: |String| - Default: `'stylelint'` - - ALE will first discover the stylelint 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 stylelint, set - |g:ale_scss_stylelint_use_global| to `1`. - - -g:ale_scss_stylelint_use_global *g:ale_scss_stylelint_use_global* - - Type: |String| - Default: `0` - - This variable controls whether or not ALE will search for a local path for - stylelint first. If this variable is set to `1`, then ALE will always use the - global version of stylelint, in preference to locally installed versions of - stylelint in node_modules. - ------------------------------------------------------------------------------- -4.22. rustc *ale-linter-options-rustc* - -g:ale_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'] - - -------------------------------------------------------------------------------- -4.24. python-mypy *ale-linter-options-python-mypy* - -g:ale_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. - - -------------------------------------------------------------------------------- -4.24. python-pylint *ale-linter-options-python-pylint* - -g:ale_python_pylint_executable *g:ale_python_pylint_executable* - - Type: |String| - Default: `'pylint'` - - This variable can be changed to modify the executable used for pylint. - - -g:ale_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' - - after making sure it's installed for the appropriate Python versions (e.g. - `python3 -m pip install --user pylint`). - ------------------------------------------------------------------------------- -4.25. erlang *ale-linter-options-erlang* - -g:ale_erlang_erlc_options *g:ale_erlang_erlc_options* - - Type: |String| - Default: '`''`' - - This variable controls additional parameters passed to `erlc`, such as `-I` - or `-pa`. =============================================================================== -5. Linter Integration Notes *ale-linter-integration* +8. Commands/Keybinds *ale-commands* -Some linters may have requirements for some other plugins being installed. +ALEFix *ALEFix* -------------------------------------------------------------------------------- -5.1. ocaml-merlin *ale-integration-ocaml-merlin* + Fix problems with the current buffer. See |ale-fix| for more information. - 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). + A plug mapping `(ale_fix)` is defined for this command. -------------------------------------------------------------------------------- -5.2. rust *ale-integration-rust* - Since Vim does not detect the rust file type out-of-the-box, you need the - runtime files for rust from here: https://github.com/rust-lang/rust.vim +ALEFixSuggest *ALEFixSuggest* - Note that there are two possible linters for rust files: + Suggest tools that can be used to fix problems in the current buffer. - 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. That means it is highly - recommended to turn off |g:ale_lint_on_text_changed| and to turn on - |g:ale_lint_on_save|. + See |ale-fix| for more information. - Also note that rustc 1.12. or later is needed. -=============================================================================== -6. Commands/Keybinds *ale-commands* +ALEGoToDefinition *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. + + A plug mapping `(ale_go_to_definition)` is defined for this command. + + +ALEGoToDefinitionInTab *ALEGoToDefinitionInTab* + + The same as |ALEGoToDefinition|, but opens results in a new tab. + + A plug mapping `(ale_go_to_definition_in_tab)` is defined for this + command. + + *:ALELint* +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 set to `1`, or in + other words linters which check the file instead of the Vim buffer. + + A plug mapping `(ale_lint)` is defined for this command. + ALEPrevious *ALEPrevious* ALEPreviousWrap *ALEPreviousWrap* ALENext *ALENext* ALENextWrap *ALENextWrap* +ALEFirst *ALEFirst* +ALELast *ALELast* *ale-navigation-commands* - Move between warnings or errors in a buffer. + 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. + `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_next) - ALENext (ale_next_wrap) - ALENextWrap + (ale_first) - ALEFirst + (ale_last) - ALELast < For example, these commands could be bound to the keys Ctrl + j and Ctrl + k: > @@ -945,24 +1789,204 @@ ALENextWrap *ALENextWrap* nmap (ale_next_wrap) < -=============================================================================== -7. API *ale-api* +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 *ALEDetail* + + Show the full linter message for the current line in the preview window. + This will only have an effect on lines that contain a linter message. The + preview window can be easily closed with the `q` key. + + A plug mapping `(ale_detail)` is defined for this command. + + + *:ALEInfo* +ALEInfo *ALEInfo* +ALEInfoToClipboard *ALEInfoToClipboard* + + 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. + + The command `:ALEInfoToClipboard` can be used to output ALEInfo directly to + your clipboard. This might not work on every machine. + + +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. + + +=============================================================================== +9. API *ale-api* + +ale#Queue(delay, [linting_flag, buffer_number]) *ale#Queue()* -ale#Queue(delay) *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 again from the same buffer + An optional `linting_flag` argument can be given. If `linting_flag` + is `'lint_file'`, then linters where the `lint_file` option is set to `1` will be + run. Linters with `lint_file` set to `1` are not run by default. + + An optional `buffer_number` argument can be given for specifying the buffer + to check. The active buffer (`bufnr('')`) will be checked by default. + + *ale-cool-down* + If an exception is thrown when queuing/running ALE linters, ALE will enter + a cool down period where it will stop checking anything for a short period + of time. This is to prevent ALE from seriously annoying users if a linter + is broken, or when developing ALE itself. + + +ale#engine#CreateDirectory(buffer) *ale#engine#CreateDirectory()* + + Create a new temporary directory with a unique name, and manage that + directory with |ale#engine#ManageDirectory()|, so it will be removed as + soon as possible. + + It is advised to only call this function from a callback function for + returning a linter command to run. + + +ale#engine#EscapeCommandPart(command_part) *ale#engine#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#engine#GetLoclist(buffer) *ale#engine#GetLoclist()* - Given a buffer number, this function will rerurn the list of warnings and - errors reported by ALE for a given buffer in the format accepted by - |setqflist()|. + 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#engine#ManageFile(buffer, filename) *ale#engine#ManageFile()* + + Given a buffer number for a buffer currently running some linting tasks + and a filename, register a filename with ALE for automatic deletion after + linting is complete, or when Vim exits. + + 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 separetly with the |ale#engine#ManageDirectory| function. + + +ale#engine#ManageDirectory(buffer, directory) *ale#engine#ManageDirectory()* + + Like |ale#engine#ManageFile()|, but directories and all of their contents + will be deleted, akin to `rm -rf directory`, which could lead to loss of + data if mistakes are made. This command will also delete any temporary + filenames given to it. + + It is advised to use |ale#engine#ManageFile()| instead for deleting single + files. + + +ale#fix#registry#Add(name, func, filetypes, desc, [aliases]) + *ale#fix#registry#Add()* + + 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`. 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: @@ -985,10 +2009,57 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* search by in future before being passed on to the |loclist|, etc. - This argument is required. + This argument is required, unless the linter is an + LSP linter. In which case, this argument must not be + defined, as LSP linters handle diangostics + automatically. See |ale-lsp-linters|. + + The keys for each item in the List will be handled in + the following manner: + *ale-loclist-format* + `text` - This error message is required. + `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 asssumed + to be the buffer being checked, unless the `bufnr` + key is also set with a valid number for some other + buffer. + `vcol` - Defaults to `0`. + `type` - Defaults to `'E'`. + `nr` - Defaults to `-1`. `executable` A |String| naming the executable itself which - will be run. This value will be used to check if the + will be run. This value will be used to check if the program requested is installed or not. Either this or the `executable_callback` argument @@ -1011,6 +2082,12 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* place of `command` when more complicated processing is needed. + If an empty string is returned from the callback, + no jobs for linting will be run for that linter. + This can be used for skipping a linter call, + say if no configuration file was found. + + *ale-command-chain* `command_chain` A |List| of |Dictionary| items defining a series of commands to be run. At least one |Dictionary| should be provided. Each Dictionary must contain the @@ -1027,6 +2104,13 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* a `(buffer)` argument, as there are no previous commands to run which return `output`. + If an empty string is returned for a command in a + chain, that command in the chain will be skipped, + and the next function in the chain will be called + immediately instead. If the last command in a chain + returns an empty string, then no linting will be + performed. + Commands in the chain will all use the `output_stream` value provided in the root |Dictionary|. Each command in the chain can also @@ -1034,10 +2118,14 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* See the `output_stream` description for more information. - The Vim buffer being checked for linter will only - be sent to the final command in the chain. Previous - commands in the chain will receive no input from - stdin. + Commands in the chain all behave as if `read_buffer` + is set to `0` by default, except for the last command + in the chain, which uses the value set for + `read_buffer` in the root |Dictionary|. Each command + in the chain can also provide a `read_buffer` key + to override these values. + See the `read_buffer` description for more + information. `output_stream` A |String| for the output stream the lines of output should be read from for the command which is run. The @@ -1048,64 +2136,208 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* instead of stdout. The option `'both'` will read from both stder and stdout at the same time. + `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. + + *ale-lint-file* + `lint_file` A |Number| (`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. + + Linters set with this option 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 with this option + set to `1` will also be run when linters are run + manually, per |ALELintPost-autocmd|. + + When this option is set to `1`, `read_buffer` will + be set automatically to `0`. The two options cannot + be used together. + + *ale-lsp-linters* + `lsp` A |String| for defining LSP (Language Server Protocol) + linters. + + This argument may be omitted or `''` when a linter + does not represent an LSP linter. + + When this argument is set to `'stdio'`, then the + linter will be defined as an LSP linter which keeps a + process for a language server runnning, and + communicates with it directly via a |channel|. + + When this argument is not empty, then the + `project_callback` and `language_callback` arguments + must also be defined. + + LSP linters handle diagnostics automatically, so + the `callback` argument must not be defined. + + `project_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned representing the path to the project for the + file being checked with the language server. If an + empty string is returned, the file will not be + checked at all. + + This argument must only be set if the `lsp` argument + is also set to a non-empty string. + + `language_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned representing the name of the language being + checked. + + This argument must only be set if the `lsp` argument + is also set to a non-empty string. + + `aliases` A |List| of aliases for the linter name. + + This 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. + Only one of `command`, `command_callback`, or `command_chain` should be specified. `command_callback` is generally recommended when a command string needs to be generated dynamically, or any global options are used. `command_chain` is recommended where any system calls need to be made to retrieve some kind of information before running the final command. - Some programs for checking for errors are not capable of receiving input - from stdin, as is required by ALE. To remedy this, a wrapper script is - provided named in the variable |g:ale#util#stdin_wrapper|. This variable - can be called with the regular arguments for any command to forward data - from stdin to the program, by way of creating a temporary file. The first - argument to the stdin wrapper must be a file extension to save the temporary - file with, and the following arguments are the command as normal. + If temporary files or directories are created for commands run with + `command_callback` or `command_chain`, then these tempoary files or + directories can be managed by ALE, for automatic deletion. + See |ale#engine#ManageFile()| and |ale#engine#ManageDirectory| for more + information. + + *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': g:ale#util#stdin_wrapper . ' .hs ghc -fno-code -v0', + '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 behaviour of + |ale#engine#ManageDirectory|. This option can be used for some linters which + do not support reading from stdin. + + For example: > + 'command': 'ghc -fno-code -v0 %t', +< + 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. + + If a callback for a command generates part of a command string which might + possibly contain `%%`, `%s`, or `%t` where the special formatting behaviour + is not desired, the |ale#engine#EscapeCommandPart()| function can be used to + replace those characters to avoid formatting issues. + + *ale-linter-loading-behaviour* + + 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. + 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-seperated to invoke linters for multiple filetypes: + 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-seperated filetypes are broken up into their + are applied after dot-separated filetypes are broken up into their components. -ale#statusline#Status() *ale#statusline#Status()* - Return a formatted string that can be added to the statusline. - The output's format is defined in |`g:ale_statusline_format`|. - To enable it, the following should be present in your |statusline| settings: > - %{ale#statusline#Status()} +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. -g:ale#util#stdin_wrapper *g:ale#util#stdin_wrapper* - This variable names a wrapper script for sending stdin input to programs - which cannot accept input via stdin. See |ale#linter#Define()| for more. +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: > -ALELint *ALELint* - This |User| autocommand is triggered by ALE every time it completes a lint - operation. It can be used to update statuslines, send notifications, or - complete any other operation that needs to be done after a lint run. - It can be used simply: - autocmd User ALELint echom "ALE run!" + " Print a message indicating how many times ALE has checked this buffer. + echo 'ALE has checked this buffer ' . get(b:, 'ale_linted') . ' time(s).' + " Print 'checked' using getbufvar() if a buffer has been checked. + echo getbufvar(bufnr(''), 'ale_linted', 0) > 0 ? 'checked' : 'not checked' +< +ALELintPre *ALELintPre-autocmd* +ALELintPost *ALELintPost-autocmd* + + These |User| autocommands are triggered before and after every lint cycle. + They can be used to update statuslines, send notifications, etc. + The autocmd commands are run with |:silent|, so |:unsilent| is required for + echoing messges. + + 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 +< =============================================================================== -8. Special Thanks *ale-special-thanks* +10. Special Thanks *ale-special-thanks* Special thanks to Mark Grealish (https://www.bhalash.com/) for providing ALE's snazzy looking ale glass logo. Cheers, Mark! =============================================================================== -9. Contact *ale-contact* +11. Contact *ale-contact* If you like this plugin, and wish to get in touch, check out the GitHub page for issues and more at https://github.com/w0rp/ale @@ -1113,10 +2345,8 @@ page for issues and more at https://github.com/w0rp/ale If you wish to contact the author of this plugin directly, please feel free to send an email to devw0rp@gmail.com. - Please drink responsibly, or not at all, which is ironically the preference of w0rp, who is teetotal. - vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/ftplugin/ale-fix-suggest.vim b/ftplugin/ale-fix-suggest.vim new file mode 100644 index 0000000..189a4dc --- /dev/null +++ b/ftplugin/ale-fix-suggest.vim @@ -0,0 +1,2 @@ +" Close the ALEFixSuggest window with the q key. +noremap q :q! diff --git a/ftplugin/ale-preview.vim b/ftplugin/ale-preview.vim new file mode 100644 index 0000000..ffbffbd --- /dev/null +++ b/ftplugin/ale-preview.vim @@ -0,0 +1,2 @@ +" Close the ALEPreviewWindow window with the q key. +noremap q :q! diff --git a/img/example.gif b/img/example.gif index 1ad8f75..1e443bf 100644 Binary files a/img/example.gif and b/img/example.gif differ diff --git a/plugin/ale.vim b/plugin/ale.vim index 5c31987..1aa3582 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -4,11 +4,13 @@ " Sanity Checks -if exists('g:loaded_ale') +if exists('g:loaded_ale_dont_use_this_in_other_plugins_please') finish endif -let g:loaded_ale = 1 +" 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') @@ -20,11 +22,40 @@ else endif if !s:has_features - echoerr 'ALE requires NeoVim >= 0.1.5 or Vim 8 with +timers +job +channel' - echoerr 'Please update your editor appropriately.' + " Only output a warning if editing some special files. + if index(['', 'gitcommit'], &filetype) == -1 + execute 'echoerr ''ALE requires NeoVim >= 0.1.5 or Vim 8 with +timers +job +channel''' + execute 'echoerr ''Please update your editor appropriately.''' + endif + + " Stop here, as it won't work. finish endif +if has('nvim') && !has('nvim-0.2.0') && !get(g:, 'ale_use_deprecated_neovim') + execute 'echom ''ALE support for NeoVim versions below 0.2.0 is deprecated.''' + execute 'echom ''Use `let g:ale_use_deprecated_neovim = 1` to silence this warning for now.''' +endif + +" This flag can be set to 0 to disable emitting conflict warnings. +let g:ale_emit_conflict_warnings = get(g:, 'ale_emit_conflict_warnings', 1) + +if g:ale_emit_conflict_warnings +\&& match(&runtimepath, '[/\\]ale[/\\]after') < 0 + " Add the after directory to the runtimepath + " This is only done if the after directory isn't already in runtimepath + let &runtimepath .= ',' . expand(':p:h:h') . '/after' +endif + +" Set this flag so that other plugins can use it, like airline. +let g:loaded_ale = 1 + +" Set the TMPDIR environment variable if it is not set automatically. +" This can automatically fix some environments. +if has('unix') && empty($TMPDIR) + let $TMPDIR = '/tmp' +endif + " 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 = {} @@ -33,10 +64,21 @@ let g:ale_buffer_info = {} " This option prevents ALE autocmd commands from being run for particular " filetypes which can cause issues. -let g:ale_filetype_blacklist = ['nerdtree', 'unite'] +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', {}) +call ale#Set('linters', {}) +" This option can be changed to only enable explicitly selected linters. +call ale#Set('linters_explicit', 0) + +" 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', {}) @@ -47,32 +89,28 @@ let g:ale_linter_aliases = get(g:, 'ale_linter_aliases', {}) " 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 0 to disable linting when text is changed. -let g:ale_lint_on_text_changed = get(g:, 'ale_lint_on_text_changed', 1) -if g:ale_lint_on_text_changed - augroup ALERunOnTextChangedGroup - autocmd! - autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay) - augroup END -endif +" This flag can be set to 'never' to disable linting when text is changed. +" This flag can also be set to 'insert' or 'normal' to lint when text is +" changed only in insert or normal mode respectively. +let g:ale_lint_on_text_changed = get(g:, 'ale_lint_on_text_changed', 'always') + +" This flag can be set to 1 to enable linting when leaving insert mode. +let g:ale_lint_on_insert_leave = get(g:, 'ale_lint_on_insert_leave', 0) " This flag can be set to 0 to disable linting when the buffer is entered. let g:ale_lint_on_enter = get(g:, 'ale_lint_on_enter', 1) -if g:ale_lint_on_enter - augroup ALERunOnEnterGroup - autocmd! - autocmd BufEnter,BufRead * call ale#Queue(300) - augroup END -endif " This flag can be set to 1 to enable linting when a buffer is written. -let g:ale_lint_on_save = get(g:, 'ale_lint_on_save', 0) -if g:ale_lint_on_save - augroup ALERunOnSaveGroup - autocmd! - autocmd BufWrite * call ale#Queue(0) - augroup END -endif +let g:ale_lint_on_save = get(g:, 'ale_lint_on_save', 1) + +" This flag can be set to 1 to enable linting when the filetype is changed. +let g:ale_lint_on_filetype_changed = get(g:, 'ale_lint_on_filetype_changed', 1) + +call ale#Set('fix_on_save', 0) + +" This flag may be set to 0 to disable ale. After ale is loaded, :ALEToggle +" should be used instead. +let g:ale_enabled = get(g:, 'ale_enabled', 1) " These flags dictates if ale uses the quickfix or the loclist (loclist is the " default, quickfix overrides loclist). @@ -85,13 +123,32 @@ let g:ale_open_list = get(g:, 'ale_open_list', 0) " This flag dictates if ale keeps open loclist even if there is no error in loclist let g:ale_keep_list_window_open = get(g:, 'ale_keep_list_window_open', 0) +" This flag dictates that quickfix windows should be opened vertically +let g:ale_list_vertical = get(g:, 'ale_list_vertical', 0) + +" The window size to set for the quickfix and loclist windows +call ale#Set('list_window_size', 10) + " This flag can be set to 0 to disable setting signs. " This is enabled by default only if the 'signs' feature exists. let g:ale_set_signs = get(g:, 'ale_set_signs', has('signs')) +" This flag can be set to some integer to control the maximum number of signs +" that ALE will set. +let g:ale_max_signs = get(g:, 'ale_max_signs', -1) -" These variables dicatate what sign is used to indicate errors and warnings. -let g:ale_sign_error = get(g:, 'ale_sign_error', '>>') -let g:ale_sign_warning = get(g:, 'ale_sign_warning', '--') +" This flag can be set to 1 to enable changing the sign column colors when +" there are errors. +call ale#Set('change_sign_column_color', 0) + +" This flag can be set to 0 to disable setting error highlights. +let g:ale_set_highlights = get(g:, 'ale_set_highlights', has('syntax')) + +" These variables dictate what sign is used to indicate errors and warnings. +call ale#Set('sign_error', '>>') +call ale#Set('sign_style_error', g:ale_sign_error) +call ale#Set('sign_warning', '--') +call ale#Set('sign_style_warning', g:ale_sign_warning) +call ale#Set('sign_info', g:ale_sign_warning) " This variable sets an offset which can be set for sign IDs. " This ID can be changed depending on what IDs are set for other plugins. @@ -101,66 +158,166 @@ let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) " This flag can be set to 1 to keep sign gutter always open let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0) -" String format for the echoed message -" A %s is mandatory -" It can contain 2 handlers: %linter%, %severity% -let g:ale_echo_msg_format = get(g:, 'ale_echo_msg_format', '%s') +" A string format for the echoed message +call ale#Set('echo_msg_format', '%code: %%s') +" The same for the loclist. +call ale#Set('loclist_msg_format', g:ale_echo_msg_format) " Strings used for severity in the echoed message let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error') +let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info') let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') " This flag can be set to 0 to disable echoing when the cursor moves. let g:ale_echo_cursor = get(g:, 'ale_echo_cursor', 1) -if g:ale_echo_cursor - augroup ALECursorGroup - autocmd! - autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() - augroup END -endif +" Controls the milliseconds delay before echoing a message. +let g:ale_echo_delay = get(g:, 'ale_echo_delay', 10) -" String format for statusline -" Its a list where: -" * The 1st element is for errors -" * The 2nd element is for warnings -" * The 3rd element is when there are no errors +" This flag can be set to 0 to disable balloon support. +call ale#Set('set_balloons', has('balloon_eval')) + +" A deprecated setting for ale#statusline#Status() +" See :help ale#statusline#Count() for getting status reports. let g:ale_statusline_format = get(g:, 'ale_statusline_format', \ ['%d error(s)', '%d warning(s)', 'OK'] \) " This flag can be set to 0 to disable warnings for trailing whitespace -let g:ale_warn_about_trailing_whitespace = -\ get(g:, 'ale_warn_about_trailing_whitespace', 1) +call ale#Set('warn_about_trailing_whitespace', 1) +" This flag can be set to 0 to disable warnings for trailing blank lines +call ale#Set('warn_about_trailing_blank_lines', 1) + +" A flag for controlling the maximum size of the command history to store. +let g:ale_max_buffer_history_size = get(g:, 'ale_max_buffer_history_size', 20) + +" A flag for enabling or disabling the command history. +let g:ale_history_enabled = get(g:, 'ale_history_enabled', 1) + +" A flag for storing the full output of commands in the history. +let g:ale_history_log_output = get(g:, 'ale_history_log_output', 1) + +" A flag for caching failed executable checks. +" This is off by default, because it will cause problems. +call ale#Set('cache_executable_check_failures', 0) + +" A dictionary mapping regular expression patterns to arbitrary buffer +" variables to be set. Useful for configuration ALE based on filename +" patterns. +call ale#Set('pattern_options', {}) +call ale#Set('pattern_options_enabled', !empty(g:ale_pattern_options)) + +" A maximum file size for checking for errors. +call ale#Set('maximum_file_size', 0) + +" Remapping of linter problems. +call ale#Set('type_map', {}) + +" Enable automatic completion with LSP servers and tsserver +call ale#Set('completion_enabled', 0) +call ale#Set('completion_delay', 100) +call ale#Set('completion_max_suggestions', 50) + +" A setting for wrapping commands. +call ale#Set('command_wrapper', '') + +if g:ale_set_balloons + call ale#balloon#Enable() +endif + +if g:ale_completion_enabled + call ale#completion#Enable() +endif " Define commands for moving through warnings and errors. -command! ALEPrevious :call ale#loclist_jumping#Jump('before', 0) -command! ALEPreviousWrap :call ale#loclist_jumping#Jump('before', 1) -command! ALENext :call ale#loclist_jumping#Jump('after', 0) -command! ALENextWrap :call ale#loclist_jumping#Jump('after', 1) +command! -bar ALEPrevious :call ale#loclist_jumping#Jump('before', 0) +command! -bar ALEPreviousWrap :call ale#loclist_jumping#Jump('before', 1) +command! -bar ALENext :call ale#loclist_jumping#Jump('after', 0) +command! -bar ALENextWrap :call ale#loclist_jumping#Jump('after', 1) +command! -bar ALEFirst :call ale#loclist_jumping#JumpToIndex(0) +command! -bar ALELast :call ale#loclist_jumping#JumpToIndex(-1) -" Define command to get information about current filetype. -command! ALEInfo :call ale#linter#Info() +" 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 for linting manually. +command! -bar ALELint :call ale#Queue(0, 'lint_file') + +" Define a command to get information about current filetype. +command! -bar ALEInfo :call ale#debugging#Info() +" The same, but copy output to your clipboard. +command! -bar ALEInfoToClipboard :call ale#debugging#InfoToClipboard() + +" Fix problems in files. +command! -bar ALEFix :call ale#fix#Fix() +" 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 ALEGoToDefinition :call ale#definition#GoTo({}) +command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in_tab': 1}) " mappings for commands nnoremap (ale_previous) :ALEPrevious nnoremap (ale_previous_wrap) :ALEPreviousWrap nnoremap (ale_next) :ALENext nnoremap (ale_next_wrap) :ALENextWrap +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) :ALEGoToDefinitionInTab + +" Set up autocmd groups now. +call ale#toggle#InitAuGroups() " Housekeeping augroup ALECleanupGroup autocmd! " Clean up buffers automatically when they are unloaded. - autocmd BufUnload * call ale#cleanup#Buffer(expand('')) + autocmd BufDelete * call ale#engine#Cleanup(str2nr(expand(''))) + autocmd QuitPre * call ale#events#QuitEvent(str2nr(expand(''))) augroup END " Backwards Compatibility function! ALELint(delay) abort + if !get(g:, 'ale_deprecation_ale_lint', 0) + execute 'echom ''ALELint() is deprecated, use ale#Queue() instead.''' + let g:ale_deprecation_ale_lint = 1 + endif + call ale#Queue(a:delay) endfunction function! ALEGetStatusLine() abort + if !get(g:, 'ale_deprecation_ale_get_status_line', 0) + execute 'echom ''ALEGetStatusLine() is deprecated.''' + let g:ale_deprecation_ale_get_status_line = 1 + endif + return ale#statusline#Status() endfunction diff --git a/run-tests b/run-tests new file mode 100755 index 0000000..6004911 --- /dev/null +++ b/run-tests @@ -0,0 +1,139 @@ +#!/bin/bash -eu + +# Author: w0rp +# +# This script runs tests for the ALE project. The following options are +# accepted: +# +# -v Enable verbose output +# --neovim-only Run tests only for NeoVim +# --vim-only Run tests only for Vim + +current_image_id=d5a1b5915b09 +image=w0rp/ale + +tests='test/*.vader test/*/*.vader test/*/*/*.vader test/*/*/*.vader' +# These flags are forwarded to the script for running Vader tests. +verbose_flag='' +quiet_flag='' +run_neovim_tests=1 +run_vim_tests=1 +run_vint=1 +run_custom_checks=1 + +while [ $# -ne 0 ]; do + case $1 in + -v) + verbose_flag='-v' + shift + ;; + -q) + quiet_flag='-q' + shift + ;; + --neovim-only) + run_vim_tests=0 + run_vint=0 + run_custom_checks=0 + shift + ;; + --vim-only) + run_neovim_tests=0 + run_vint=0 + run_custom_checks=0 + shift + ;; + --no-vint) + run_vint=0 + shift + ;; + --vint-only) + run_vim_tests=0 + run_neovim_tests=0 + run_custom_checks=0 + shift + ;; + --no-custom-checks) + run_custom_checks=0 + shift + ;; + --custom-checks-only) + run_vim_tests=0 + run_neovim_tests=0 + run_vint=0 + shift + ;; + --) + 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="$*" +fi + +# Delete .swp files in the test directory, which cause Vim 8 to hang. +find test -name '*.swp' -delete + +docker images -q w0rp/ale | grep "^$current_image_id" > /dev/null \ + || docker pull "$image" + +output_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir') + +trap '{ rm -rf "$output_dir"; }' EXIT + +file_number=0 +pid_list='' + +for vim in $(docker run --rm "$image" ls /vim-build/bin | grep '^neovim\|^vim' ); do + if ((run_vim_tests)) || [[ $vim =~ ^neovim ]] && ((run_neovim_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_vint)); then + echo "Starting Vint..." + file_number=$((file_number+1)) + test/script/run-vint > "$output_dir/$file_number" 2>&1 & + pid_list="$pid_list $!" +fi + +if ((run_custom_checks)); then + 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 + index=$((index+1)) + + if ! wait "$pid"; then + failed=1 + fi + + cat "$output_dir/$index" +done + +exit $failed diff --git a/run-tests.bat b/run-tests.bat new file mode 100644 index 0000000..4650516 --- /dev/null +++ b/run-tests.bat @@ -0,0 +1,16 @@ +@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 +type nul > "%VADER_OUTPUT_FILE%" +C:\vim\vim\vim80\vim.exe -u test/vimrc "+Vader! %tests%" +type "%VADER_OUTPUT_FILE%" +del "%VADER_OUTPUT_FILE%" diff --git a/stdin-wrapper b/stdin-wrapper deleted file mode 100755 index de4af0e..0000000 --- a/stdin-wrapper +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -# Authors: w0rp , hauleth -# Description: This script implements a wrapper for any program which does not accept -# stdin input on most Unix machines. The input to the script is read to a -# temporary file, and the first argument sets a particular file extension -# for the temporary file. - -set -eu - -# All of the following arguments are read as command to run. -file_extension="$1" -shift - -temp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'ale_linter') -temp_file="$temp_dir/file$file_extension" -trap 'rm -r "$temp_dir"' EXIT - -cat > "$temp_file" - -"$@" "$temp_file" diff --git a/stdin-wrapper.exe b/stdin-wrapper.exe deleted file mode 100644 index d79f678..0000000 Binary files a/stdin-wrapper.exe and /dev/null differ diff --git a/stdin_wrapper.d b/stdin_wrapper.d deleted file mode 100644 index 8714bc2..0000000 --- a/stdin_wrapper.d +++ /dev/null @@ -1,84 +0,0 @@ -// Author: w0rp -// Description: This file provides a D program for implementing -// the stdin-wrapper on Windows. - -import std.algorithm; -import std.array; -import std.file; -import std.process; -import std.stdio; -import std.path; - -@safe -auto createTemporaryFilename(string fileExtension) { - import std.uuid; - - string filename; - - do { - const randomPart = randomUUID().toString.replace("-", "_"); - - filename = buildPath(tempDir(), "ale_" ~ randomPart ~ fileExtension); - } while (exists(filename)); - - return filename; -} - -@trusted -void readStdinToFile(ref File tempFile) { - stdin.byChunk(4096).copy(tempFile.lockingTextWriter()); -} - -// Expand program names like "csslint" to "csslint.cmd" -// D is not able to perform this kind of expanstion in spawnProcess -@safe -string expandedProgramName(string name) { - auto extArray = environment["PATHEXT"].split(";"); - - foreach(pathDir; environment["PATH"].split(";")) { - foreach(extension; extArray) { - const candidate = buildPath(pathDir, name ~ extension); - - if (exists(candidate)) { - return candidate; - } - } - } - - // We were given a full path for a program name, so use that. - if (exists(name)) { - return name; - } - - return ""; -} - -@trusted -int runLinterProgram(string[] args) { - const expandedName = expandedProgramName(args[0]); - - writeln(expandedName); - - if (expandedName) { - return wait(spawnProcess([expandedName] ~ args[1..$])); - } - - return 1; -} - -@safe -int main(string[] args) { - const filename = createTemporaryFilename(args[1]); - - auto tempFile = File(filename, "w"); - - scope(exit) { - tempFile.close(); - remove(filename); - } - - readStdinToFile(tempFile); - tempFile.close(); - - return runLinterProgram(args[2..$] ~ [filename]); -} diff --git a/syntax/ale-fix-suggest.vim b/syntax/ale-fix-suggest.vim new file mode 100644 index 0000000..b112f5b --- /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/test/.config/nvim/init.vim b/test/.config/nvim/init.vim new file mode 120000 index 0000000..90f52f0 --- /dev/null +++ b/test/.config/nvim/init.vim @@ -0,0 +1 @@ +../../vimrc \ No newline at end of file diff --git a/test/command_callback/c_paths/dummy.c b/test/command_callback/c_paths/dummy.c new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/cargo_paths/Cargo.toml b/test/command_callback/cargo_paths/Cargo.toml new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/cppcheck_paths/one/compile_commands.json b/test/command_callback/cppcheck_paths/one/compile_commands.json new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/cppcheck_paths/one/two/three/file.c b/test/command_callback/cppcheck_paths/one/two/three/file.c new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/cppcheck_paths/one/two/three/file.cpp b/test/command_callback/cppcheck_paths/one/two/three/file.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/dart_paths/.packages b/test/command_callback/dart_paths/.packages new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/htmlhint_paths/node_modules/.bin/htmlhint b/test/command_callback/htmlhint_paths/node_modules/.bin/htmlhint new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/htmlhint_paths/with_config/.htmlhintrc b/test/command_callback/htmlhint_paths/with_config/.htmlhintrc new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/java_paths/src/main/java/com/something/dummy b/test/command_callback/java_paths/src/main/java/com/something/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/java_paths/src/test/java/com/something/dummy b/test/command_callback/java_paths/src/test/java/com/something/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/java_paths_with_jaxb/src/main/java/com/something/dummy b/test/command_callback/java_paths_with_jaxb/src/main/java/com/something/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/java_paths_with_jaxb/src/main/jaxb/com/something/dummy b/test/command_callback/java_paths_with_jaxb/src/main/jaxb/com/something/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/lessc_paths/node_modules/.bin/lessc b/test/command_callback/lessc_paths/node_modules/.bin/lessc new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/ols_paths/.merlin b/test/command_callback/ols_paths/.merlin new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/ols_paths/node_modules/.bin/ocaml-language-server b/test/command_callback/ols_paths/node_modules/.bin/ocaml-language-server new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/php-langserver-project/vendor/bin/php-language-server.php b/test/command_callback/php-langserver-project/vendor/bin/php-language-server.php new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/php_paths/project-with-php-cs-fixer/test.php b/test/command_callback/php_paths/project-with-php-cs-fixer/test.php new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/php_paths/project-with-php-cs-fixer/vendor/bin/php-cs-fixer b/test/command_callback/php_paths/project-with-php-cs-fixer/vendor/bin/php-cs-fixer new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/php_paths/project-with-phpcbf/foo/test.php b/test/command_callback/php_paths/project-with-phpcbf/foo/test.php new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/php_paths/project-with-phpcbf/vendor/bin/phpcbf b/test/command_callback/php_paths/project-with-phpcbf/vendor/bin/phpcbf new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/php_paths/project-without-php-cs-fixer/test.php b/test/command_callback/php_paths/project-without-php-cs-fixer/test.php new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/php_paths/project-without-phpcbf/foo/test.php b/test/command_callback/php_paths/project-without-phpcbf/foo/test.php new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/puglint_project/node_modules/.bin/pug-lint b/test/command_callback/puglint_project/node_modules/.bin/pug-lint new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/puglint_project/package.json b/test/command_callback/puglint_project/package.json new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/puglint_project/puglint_rc_dir/.pug-lintrc b/test/command_callback/puglint_project/puglint_rc_dir/.pug-lintrc new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/puglint_project/puglint_rc_js_dir/.pug-lintrc.js b/test/command_callback/puglint_project/puglint_rc_js_dir/.pug-lintrc.js new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/puglint_project/puglint_rc_json_dir/.pug-lintrc.json b/test/command_callback/puglint_project/puglint_rc_json_dir/.pug-lintrc.json new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/puppet_paths/dummy.pp b/test/command_callback/puppet_paths/dummy.pp new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_manifest/MANIFEST.in b/test/command_callback/python_paths/namespace_package_manifest/MANIFEST.in new file mode 100644 index 0000000..4617b0e --- /dev/null +++ b/test/command_callback/python_paths/namespace_package_manifest/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include *.ini *.cfg *.txt +include requirements/*.txt diff --git a/test/command_callback/python_paths/namespace_package_manifest/namespace/foo/__init__.py b/test/command_callback/python_paths/namespace_package_manifest/namespace/foo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_manifest/namespace/foo/bar.py b/test/command_callback/python_paths/namespace_package_manifest/namespace/foo/bar.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_pytest/namespace/foo/__init__.py b/test/command_callback/python_paths/namespace_package_pytest/namespace/foo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_pytest/namespace/foo/bar.py b/test/command_callback/python_paths/namespace_package_pytest/namespace/foo/bar.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_pytest/pytest.ini b/test/command_callback/python_paths/namespace_package_pytest/pytest.ini new file mode 100644 index 0000000..1433c6c --- /dev/null +++ b/test/command_callback/python_paths/namespace_package_pytest/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +DJANGO_SETTINGS_MODULE=foo.settings diff --git a/test/command_callback/python_paths/namespace_package_setup/namespace/foo/__init__.py b/test/command_callback/python_paths/namespace_package_setup/namespace/foo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_setup/namespace/foo/bar.py b/test/command_callback/python_paths/namespace_package_setup/namespace/foo/bar.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_setup/setup.cfg b/test/command_callback/python_paths/namespace_package_setup/setup.cfg new file mode 100644 index 0000000..791f075 --- /dev/null +++ b/test/command_callback/python_paths/namespace_package_setup/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 119 diff --git a/test/command_callback/python_paths/namespace_package_tox/namespace/foo/__init__.py b/test/command_callback/python_paths/namespace_package_tox/namespace/foo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_tox/namespace/foo/bar.py b/test/command_callback/python_paths/namespace_package_tox/namespace/foo/bar.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/namespace_package_tox/tox.ini b/test/command_callback/python_paths/namespace_package_tox/tox.ini new file mode 100644 index 0000000..edd8788 --- /dev/null +++ b/test/command_callback/python_paths/namespace_package_tox/tox.ini @@ -0,0 +1,3 @@ +[tox] +envlist = + py352 diff --git a/test/command_callback/python_paths/no_virtualenv/subdir/foo/COMMIT_EDITMSG b/test/command_callback/python_paths/no_virtualenv/subdir/foo/COMMIT_EDITMSG new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/no_virtualenv/subdir/foo/__init__.py b/test/command_callback/python_paths/no_virtualenv/subdir/foo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/no_virtualenv/subdir/foo/bar.py b/test/command_callback/python_paths/no_virtualenv/subdir/foo/bar.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/dir_with_yapf_config/.style.yapf b/test/command_callback/python_paths/with_virtualenv/dir_with_yapf_config/.style.yapf new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/activate b/test/command_callback/python_paths/with_virtualenv/env/Scripts/activate new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/autopep8.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/autopep8.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/flake8.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/flake8.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/gitlint.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/gitlint.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/isort.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/isort.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/mypy.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/mypy.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/pyflakes.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/pyflakes.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/pylint.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/pylint.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/pyls.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/pyls.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/Scripts/yapf.exe b/test/command_callback/python_paths/with_virtualenv/env/Scripts/yapf.exe new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/activate b/test/command_callback/python_paths/with_virtualenv/env/bin/activate new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/autopep8 b/test/command_callback/python_paths/with_virtualenv/env/bin/autopep8 new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/flake8 b/test/command_callback/python_paths/with_virtualenv/env/bin/flake8 new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/gitlint b/test/command_callback/python_paths/with_virtualenv/env/bin/gitlint new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/isort b/test/command_callback/python_paths/with_virtualenv/env/bin/isort new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/mypy b/test/command_callback/python_paths/with_virtualenv/env/bin/mypy new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/pyflakes b/test/command_callback/python_paths/with_virtualenv/env/bin/pyflakes new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/pylint b/test/command_callback/python_paths/with_virtualenv/env/bin/pylint new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/pyls b/test/command_callback/python_paths/with_virtualenv/env/bin/pyls new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/env/bin/yapf b/test/command_callback/python_paths/with_virtualenv/env/bin/yapf new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/subdir/foo/COMMIT_EDITMSG b/test/command_callback/python_paths/with_virtualenv/subdir/foo/COMMIT_EDITMSG new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/subdir/foo/__init__.py b/test/command_callback/python_paths/with_virtualenv/subdir/foo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/python_paths/with_virtualenv/subdir/foo/bar.py b/test/command_callback/python_paths/with_virtualenv/subdir/foo/bar.py new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/ruby_paths/dummy.rb b/test/command_callback/ruby_paths/dummy.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/ruby_paths/with_config/.rubocop.yml b/test/command_callback/ruby_paths/with_config/.rubocop.yml new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/rust-rls-project/Cargo.toml b/test/command_callback/rust-rls-project/Cargo.toml new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/standard-test-files/with-bin/node_modules/.bin/standard b/test/command_callback/standard-test-files/with-bin/node_modules/.bin/standard new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js b/test/command_callback/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/stylelint_paths/node_modules/.bin/stylelint b/test/command_callback/stylelint_paths/node_modules/.bin/stylelint new file mode 100755 index 0000000..e69de29 diff --git a/test/command_callback/swaglint_paths/docs/swagger.yaml b/test/command_callback/swaglint_paths/docs/swagger.yaml new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/swaglint_paths/node_modules/.bin/swaglint b/test/command_callback/swaglint_paths/node_modules/.bin/swaglint new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/swift_paths/dummy.swift b/test/command_callback/swift_paths/dummy.swift new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/test_asm_gcc_command_callbacks.vader b/test/command_callback/test_asm_gcc_command_callbacks.vader new file mode 100644 index 0000000..ce8b906 --- /dev/null +++ b/test/command_callback/test_asm_gcc_command_callbacks.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_asm_gcc_executable + Save g:ale_asm_gcc_options + + unlet! g:ale_asm_gcc_executable + unlet! b:ale_asm_gcc_executable + unlet! g:ale_asm_gcc_options + unlet! b:ale_asm_gcc_options + + runtime ale_linters/asm/gcc.vim + + let b:command_tail = ' -x assembler -fsyntax-only -iquote' + \ . ' ' . ale#Escape(getcwd()) + \ . ' -Wall -' + +After: + Restore + unlet! b:command_tail + unlet! b:ale_asm_gcc_executable + unlet! b:ale_asm_gcc_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'gcc', ale_linters#asm#gcc#GetExecutable(bufnr('')) + + let b:ale_asm_gcc_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#asm#gcc#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('gcc') . b:command_tail, + \ ale_linters#asm#gcc#GetCommand(bufnr('')) + + let b:ale_asm_gcc_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#asm#gcc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_brakeman_command_callback.vader b/test/command_callback/test_brakeman_command_callback.vader new file mode 100644 index 0000000..1772c9d --- /dev/null +++ b/test/command_callback/test_brakeman_command_callback.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_ruby_brakeman_options + + runtime ale_linters/ruby/brakeman.vim + + let g:ale_ruby_brakeman_options = '' + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The brakeman command callback should detect absence of a valid Rails app): + call ale#test#SetFilename('../ruby_fixtures/not_a_rails_app/test.rb') + + AssertEqual + \ '', + \ ale_linters#ruby#brakeman#GetCommand(bufnr('')) + +Execute(The brakeman command callback should find a valid Rails app root): + call ale#test#SetFilename('../ruby_fixtures/valid_rails_app/db/test.rb') + + AssertEqual + \ 'brakeman -f json -q -p ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../ruby_fixtures/valid_rails_app')), + \ ale_linters#ruby#brakeman#GetCommand(bufnr('')) + +Execute(The brakeman command callback should include configured options): + call ale#test#SetFilename('../ruby_fixtures/valid_rails_app/db/test.rb') + + let g:ale_ruby_brakeman_options = '--combobulate' + + AssertEqual + \ 'brakeman -f json -q --combobulate -p ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/../ruby_fixtures/valid_rails_app')), + \ ale_linters#ruby#brakeman#GetCommand(bufnr('')) diff --git a/test/command_callback/test_c_clang_command_callbacks.vader b/test/command_callback/test_c_clang_command_callbacks.vader new file mode 100644 index 0000000..d6fc8ca --- /dev/null +++ b/test/command_callback/test_c_clang_command_callbacks.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_c_clang_executable + Save g:ale_c_clang_options + + unlet! g:ale_c_clang_executable + unlet! b:ale_c_clang_executable + unlet! g:ale_c_clang_options + unlet! b:ale_c_clang_options + + runtime ale_linters/c/clang.vim + + let b:command_tail = ' -S -x c -fsyntax-only -iquote' + \ . ' ' . ale#Escape(getcwd()) + \ . ' -std=c11 -Wall -' + +After: + Restore + unlet! b:command_tail + unlet! b:ale_c_clang_executable + unlet! b:ale_c_clang_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'clang', ale_linters#c#clang#GetExecutable(bufnr('')) + + let b:ale_c_clang_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#c#clang#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('clang') . b:command_tail, + \ ale_linters#c#clang#GetCommand(bufnr('')) + + let b:ale_c_clang_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#c#clang#GetCommand(bufnr('')) diff --git a/test/command_callback/test_c_clang_tidy_command_callback.vader b/test/command_callback/test_c_clang_tidy_command_callback.vader new file mode 100644 index 0000000..6f75d77 --- /dev/null +++ b/test/command_callback/test_c_clang_tidy_command_callback.vader @@ -0,0 +1,102 @@ +Before: + Save g:ale_c_clangtidy_checks + Save g:ale_c_clangtidy_options + Save g:ale_c_build_dir + + unlet! g:ale_c_build_dir + unlet! b:ale_c_build_dir + unlet! g:ale_c_clangtidy_checks + unlet! b:ale_c_clangtidy_checks + unlet! g:ale_c_clangtidy_options + unlet! b:ale_c_clangtidy_options + + runtime ale_linters/c/clangtidy.vim + + call ale#test#SetFilename('test.c') + +After: + unlet! b:ale_c_build_dir + unlet! b:ale_c_clangtidy_checks + unlet! b:ale_c_clangtidy_options + unlet! b:ale_c_clangtidy_executable + + Restore + call ale#linter#Reset() + +Execute(The clangtidy command default should be correct): + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s', + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + +Execute(You should be able to remove the -checks option for clang-tidy): + let b:ale_c_clangtidy_checks = [] + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' %s', + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + +Execute(You should be able to set other checks for clang-tidy): + let b:ale_c_clangtidy_checks = ['-*', 'clang-analyzer-*'] + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('-*,clang-analyzer-*') . ' %s', + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + +Execute(You should be able to manually set compiler flags for clang-tidy): + let b:ale_c_clangtidy_options = '-Wall' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -- -Wall', + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + \ +Execute(The build directory should be configurable): + let b:ale_c_build_dir = '/foo/bar' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar'), + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + +Execute(The build directory setting should override the options): + let b:ale_c_build_dir = '/foo/bar' + let b:ale_c_clangtidy_options = '-Wall' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar'), + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + +Execute(The build directory should be ignored for header files): + call ale#test#SetFilename('test.h') + + let b:ale_c_build_dir = '/foo/bar' + let b:ale_c_clangtidy_options = '-Wall' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -- -Wall', + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + \ + call ale#test#SetFilename('test.h') + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -- -Wall', + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) + +Execute(The executable should be configurable): + let b:ale_c_clangtidy_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') + \ . ' -checks=' . ale#Escape('*') . ' %s', + \ ale_linters#c#clangtidy#GetCommand(bufnr('')) diff --git a/test/command_callback/test_c_cppcheck_command_callbacks.vader b/test/command_callback/test_c_cppcheck_command_callbacks.vader new file mode 100644 index 0000000..1643e3e --- /dev/null +++ b/test/command_callback/test_c_cppcheck_command_callbacks.vader @@ -0,0 +1,49 @@ +Before: + Save g:ale_c_cppcheck_executable + Save g:ale_c_cppcheck_options + + unlet! g:ale_c_cppcheck_executable + unlet! b:ale_c_cppcheck_executable + unlet! g:ale_c_cppcheck_options + unlet! b:ale_c_cppcheck_options + + runtime ale_linters/c/cppcheck.vim + + let b:command_tail = ' -q --language=c --enable=style %t' + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + unlet! b:command_tail + unlet! b:ale_c_cppcheck_executable + unlet! b:ale_c_cppcheck_options + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The executable should be configurable): + AssertEqual 'cppcheck', ale_linters#c#cppcheck#GetExecutable(bufnr('')) + + let b:ale_c_cppcheck_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#c#cppcheck#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('cppcheck') . b:command_tail, + \ ale_linters#c#cppcheck#GetCommand(bufnr('')) + + let b:ale_c_cppcheck_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#c#cppcheck#GetCommand(bufnr('')) + +Execute(cppcheck for C++ should detect compile_commands.json files): + call ale#test#SetFilename('cppcheck_paths/one/foo.cpp') + + AssertEqual + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) . ' && ' + \ . ale#Escape('cppcheck') + \ . ' -q --language=c --project=compile_commands.json --enable=style %t', + \ ale_linters#c#cppcheck#GetCommand(bufnr('')) diff --git a/test/command_callback/test_c_flawfinder_command_callbacks.vader b/test/command_callback/test_c_flawfinder_command_callbacks.vader new file mode 100644 index 0000000..38a602d --- /dev/null +++ b/test/command_callback/test_c_flawfinder_command_callbacks.vader @@ -0,0 +1,51 @@ +Before: + Save g:ale_c_flawfinder_executable + Save g:ale_c_flawfinder_options + Save g:ale_c_flawfinder_minlevel + + unlet! g:ale_c_flawfinder_executable + unlet! b:ale_c_flawfinder_executable + unlet! g:ale_c_flawfinder_options + unlet! b:ale_c_flawfinder_options + unlet! g:ale_c_flawfinder_minlevel + unlet! b:ale_c_flawfinder_minlevel + + runtime ale_linters/c/flawfinder.vim + +After: + unlet! b:ale_c_flawfinder_executable + unlet! b:ale_c_flawfinder_options + unlet! b:ale_c_flawfinder_minlevel + + Restore + call ale#linter#Reset() + +Execute(The flawfinder command should be correct): + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) + +Execute(The minlevel of flawfinder should be configurable): + let b:ale_c_flawfinder_minlevel = 8 + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=8 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) + +Execute(Additional flawfinder options should be configurable): + let b:ale_c_flawfinder_options = ' --foobar' + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --foobar --minlevel=1 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) + +Execute(The flawfinder exectable should be configurable): + let b:ale_c_flawfinder_executable = 'foo/bar' + + AssertEqual + \ ale#Escape('foo/bar') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#c#flawfinder#GetCommand(bufnr('')) diff --git a/test/command_callback/test_c_gcc_command_callbacks.vader b/test/command_callback/test_c_gcc_command_callbacks.vader new file mode 100644 index 0000000..8038f41 --- /dev/null +++ b/test/command_callback/test_c_gcc_command_callbacks.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_c_gcc_executable + Save g:ale_c_gcc_options + + unlet! g:ale_c_gcc_executable + unlet! b:ale_c_gcc_executable + unlet! g:ale_c_gcc_options + unlet! b:ale_c_gcc_options + + runtime ale_linters/c/gcc.vim + + let b:command_tail = ' -S -x c -fsyntax-only -iquote' + \ . ' ' . ale#Escape(getcwd()) + \ . ' -std=c11 -Wall -' + +After: + Restore + unlet! b:command_tail + unlet! b:ale_c_gcc_executable + unlet! b:ale_c_gcc_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'gcc', ale_linters#c#gcc#GetExecutable(bufnr('')) + + let b:ale_c_gcc_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#c#gcc#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('gcc') . b:command_tail, + \ ale_linters#c#gcc#GetCommand(bufnr('')) + + let b:ale_c_gcc_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#c#gcc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cargo_command_callbacks.vader b/test/command_callback/test_cargo_command_callbacks.vader new file mode 100644 index 0000000..9c06f27 --- /dev/null +++ b/test/command_callback/test_cargo_command_callbacks.vader @@ -0,0 +1,164 @@ +Before: + Save g:ale_rust_cargo_use_check + Save g:ale_rust_cargo_check_all_targets + Save g:ale_rust_cargo_default_feature_behavior + Save g:ale_rust_cargo_include_features + + unlet! g:ale_rust_cargo_use_check + unlet! g:ale_cargo_check_all_targets + unlet! g:ale_rust_cargo_default_feature_behavior + unlet! g:ale_rust_cargo_include_features + + runtime ale_linters/rust/cargo.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + + let g:suffix = ' --frozen --message-format=json -q' + +After: + Restore + + unlet! g:suffix + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + call ale#semver#ResetVersionCache() + +Execute(An empty string should be returned for the cargo executable when there's no Cargo.toml file): + AssertEqual + \ '', + \ ale_linters#rust#cargo#GetCargoExecutable(bufnr('')) + +Execute(The executable should be returned when there is a Cargo.toml file): + call ale#test#SetFilename('cargo_paths/test.rs') + + AssertEqual + \ 'cargo', + \ ale_linters#rust#cargo#GetCargoExecutable(bufnr('')) + +Execute(The VersionCheck function should return the --version command): + AssertEqual + \ 'cargo --version', + \ ale_linters#rust#cargo#VersionCheck(bufnr('')) + +Execute(The default command should be correct): + AssertEqual + \ 'cargo build' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + +Execute(`cargo check` should be used when the version is new enough): + AssertEqual + \ 'cargo check' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.17.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo check' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + +Execute(`cargo build` should be used when cargo is too old): + AssertEqual + \ 'cargo build' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.16.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo build' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + +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 + + AssertEqual + \ 'cargo build' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.24.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo build' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + +Execute(`cargo check` should be used when the version is new enough): + AssertEqual + \ 'cargo check' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo check' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + +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 + + AssertEqual + \ 'cargo check --all-targets' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo check --all-targets' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + +Execute(--no-default-features should be used when g:ale_rust_cargo_default_feature_behavior is none): + let g:ale_rust_cargo_default_feature_behavior = 'none' + + AssertEqual + \ 'cargo check' . g:suffix . ' --no-default-features', + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + +Execute(g:ale_rust_cargo_include_features added when g:ale_rust_cargo_default_feature_behavior is none): + let g:ale_rust_cargo_default_feature_behavior = 'none' + let g:ale_rust_cargo_include_features = 'foo bar' + + AssertEqual + \ 'cargo check' . g:suffix . ' --no-default-features --features ' . + \ (fnamemodify(&shell, ':t') is? 'cmd.exe' ? '"foo bar"' : "'foo bar'"), + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + +Execute(g:ale_rust_cargo_include_features added and escaped): + let g:ale_rust_cargo_default_feature_behavior = 'default' + let g:ale_rust_cargo_include_features = "foo bar baz" + + AssertEqual + \ 'cargo check' . g:suffix . ' --features ' . + \ (fnamemodify(&shell, ':t') is? 'cmd.exe' ? '"foo bar baz"' : "'foo bar baz'"), + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + +Execute(--all-features should be used when g:ale_rust_cargo_default_feature_behavior is all): + let g: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 g:ale_rust_cargo_include_features = 'foo bar' + + AssertEqual + \ 'cargo check' . g:suffix . ' --all-features', + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) diff --git a/test/command_callback/test_clang_tidy_command_callback.vader b/test/command_callback/test_clang_tidy_command_callback.vader new file mode 100644 index 0000000..f82efff --- /dev/null +++ b/test/command_callback/test_clang_tidy_command_callback.vader @@ -0,0 +1,97 @@ +Before: + Save g:ale_cpp_clangtidy_checks + Save g:ale_cpp_clangtidy_options + Save g:ale_c_build_dir + + unlet! g:ale_c_build_dir + unlet! b:ale_c_build_dir + unlet! g:ale_cpp_clangtidy_checks + unlet! b:ale_cpp_clangtidy_checks + unlet! g:ale_cpp_clangtidy_options + unlet! b:ale_cpp_clangtidy_options + + runtime ale_linters/cpp/clangtidy.vim + + call ale#test#SetFilename('test.cpp') + +After: + unlet! b:ale_c_build_dir + unlet! b:ale_cpp_clangtidy_checks + unlet! b:ale_cpp_clangtidy_options + unlet! b:ale_cpp_clangtidy_executable + + Restore + call ale#linter#Reset() + +Execute(The clangtidy command default should be correct): + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + +Execute(You should be able to remove the -checks option for clang-tidy): + let b:ale_cpp_clangtidy_checks = [] + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' %s', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + +Execute(You should be able to set other checks for clang-tidy): + let b:ale_cpp_clangtidy_checks = ['-*', 'clang-analyzer-*'] + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('-*,clang-analyzer-*') . ' %s', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + +Execute(You should be able to manually set compiler flags for clang-tidy): + let b:ale_cpp_clangtidy_options = '-Wall' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s -- -Wall', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + \ +Execute(The build directory should be configurable): + let b:ale_c_build_dir = '/foo/bar' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s -p ' . ale#Escape('/foo/bar'), + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + +Execute(The build directory setting should override the options): + let b:ale_c_build_dir = '/foo/bar' + let b:ale_cpp_clangtidy_options = '-Wall' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s -p ' . ale#Escape('/foo/bar'), + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + +Execute(The build directory should be ignored for header files): + call ale#test#SetFilename('test.h') + + let b:ale_c_build_dir = '/foo/bar' + let b:ale_cpp_clangtidy_options = '-Wall' + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s -- -Wall', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + \ + call ale#test#SetFilename('test.hpp') + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s -- -Wall', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + +Execute(The executable should be configurable): + let b:ale_cpp_clangtidy_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') + \ . ' -checks=' . ale#Escape('*') . ' %s', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cpp_clang_command_callbacks.vader b/test/command_callback/test_cpp_clang_command_callbacks.vader new file mode 100644 index 0000000..67d6898 --- /dev/null +++ b/test/command_callback/test_cpp_clang_command_callbacks.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_cpp_clang_executable + Save g:ale_cpp_clang_options + + unlet! g:ale_cpp_clang_executable + unlet! b:ale_cpp_clang_executable + unlet! g:ale_cpp_clang_options + unlet! b:ale_cpp_clang_options + + runtime ale_linters/cpp/clang.vim + + let b:command_tail = ' -S -x c++ -fsyntax-only -iquote' + \ . ' ' . ale#Escape(getcwd()) + \ . ' -std=c++14 -Wall -' + +After: + Restore + unlet! b:command_tail + unlet! b:ale_cpp_clang_executable + unlet! b:ale_cpp_clang_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'clang++', ale_linters#cpp#clang#GetExecutable(bufnr('')) + + let b:ale_cpp_clang_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cpp#clang#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('clang++') . b:command_tail, + \ ale_linters#cpp#clang#GetCommand(bufnr('')) + + let b:ale_cpp_clang_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#cpp#clang#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cpp_clangcheck_command_callbacks.vader b/test/command_callback/test_cpp_clangcheck_command_callbacks.vader new file mode 100644 index 0000000..f708c52 --- /dev/null +++ b/test/command_callback/test_cpp_clangcheck_command_callbacks.vader @@ -0,0 +1,63 @@ +Before: + Save g:ale_cpp_clangcheck_executable + Save g:ale_cpp_clangcheck_options + + unlet! g:ale_cpp_clangcheck_executable + unlet! b:ale_cpp_clangcheck_executable + unlet! g:ale_cpp_clangcheck_options + unlet! b:ale_cpp_clangcheck_options + + runtime ale_linters/cpp/clangcheck.vim + +After: + Restore + unlet! b:command_tail + unlet! b:ale_cpp_clangcheck_executable + unlet! b:ale_cpp_clangcheck_options + unlet! b:ale_c_build_dir + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'clang-check', ale_linters#cpp#clangcheck#GetExecutable(bufnr('')) + + let b:ale_cpp_clangcheck_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cpp#clangcheck#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('clang-check') + \ . ' -analyze %s' + \ . ' -extra-arg -Xclang -extra-arg -analyzer-output=text', + \ ale_linters#cpp#clangcheck#GetCommand(bufnr('')) + + let b:ale_cpp_clangcheck_executable = 'foobar' + + " The extra arguments in the command are used to prevent .plist files from + " being generated. + AssertEqual + \ ale#Escape('foobar') + \ . ' -analyze %s' + \ . ' -extra-arg -Xclang -extra-arg -analyzer-output=text', + \ ale_linters#cpp#clangcheck#GetCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_cpp_clangcheck_options = '--something' + + AssertEqual + \ ale#Escape('clang-check') + \ . ' -analyze %s' + \ . ' -extra-arg -Xclang -extra-arg -analyzer-output=text' + \ . ' --something', + \ ale_linters#cpp#clangcheck#GetCommand(bufnr('')) + +Execute(The build directory should be used when set): + let b:ale_cpp_clangcheck_options = '--something' + let b:ale_c_build_dir = '/foo/bar' + + AssertEqual + \ ale#Escape('clang-check') + \ . ' -analyze %s ' + \ . '--something -p ' + \ . ale#Escape('/foo/bar'), + \ ale_linters#cpp#clangcheck#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cpp_cppcheck_command_callbacks.vader b/test/command_callback/test_cpp_cppcheck_command_callbacks.vader new file mode 100644 index 0000000..2c9d729 --- /dev/null +++ b/test/command_callback/test_cpp_cppcheck_command_callbacks.vader @@ -0,0 +1,49 @@ +Before: + Save g:ale_cpp_cppcheck_executable + Save g:ale_cpp_cppcheck_options + + unlet! g:ale_cpp_cppcheck_executable + unlet! b:ale_cpp_cppcheck_executable + unlet! g:ale_cpp_cppcheck_options + unlet! b:ale_cpp_cppcheck_options + + runtime ale_linters/cpp/cppcheck.vim + + let b:command_tail = ' -q --language=c++ --enable=style %t' + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + unlet! b:command_tail + unlet! b:ale_cpp_cppcheck_executable + unlet! b:ale_cpp_cppcheck_options + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The executable should be configurable): + AssertEqual 'cppcheck', ale_linters#cpp#cppcheck#GetExecutable(bufnr('')) + + let b:ale_cpp_cppcheck_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cpp#cppcheck#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('cppcheck') . b:command_tail, + \ ale_linters#cpp#cppcheck#GetCommand(bufnr('')) + + let b:ale_cpp_cppcheck_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#cpp#cppcheck#GetCommand(bufnr('')) + +Execute(cppcheck for C++ should detect compile_commands.json files): + call ale#test#SetFilename('cppcheck_paths/one/foo.cpp') + + AssertEqual + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) . ' && ' + \ . ale#Escape('cppcheck') + \ . ' -q --language=c++ --project=compile_commands.json --enable=style %t', + \ ale_linters#cpp#cppcheck#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cpp_flawfinder_command_callbacks.vader b/test/command_callback/test_cpp_flawfinder_command_callbacks.vader new file mode 100644 index 0000000..8769ec9 --- /dev/null +++ b/test/command_callback/test_cpp_flawfinder_command_callbacks.vader @@ -0,0 +1,51 @@ +Before: + Save g:ale_cpp_flawfinder_executable + Save g:ale_cpp_flawfinder_options + Save g:ale_cpp_flawfinder_minlevel + + unlet! g:ale_cpp_flawfinder_executable + unlet! b:ale_cpp_flawfinder_executable + unlet! g:ale_cpp_flawfinder_options + unlet! b:ale_cpp_flawfinder_options + unlet! g:ale_cpp_flawfinder_minlevel + unlet! b:ale_cpp_flawfinder_minlevel + + runtime ale_linters/cpp/flawfinder.vim + +After: + unlet! b:ale_cpp_flawfinder_executable + unlet! b:ale_cpp_flawfinder_options + unlet! b:ale_cpp_flawfinder_minlevel + + Restore + call ale#linter#Reset() + +Execute(The flawfinder command should be correct): + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) + +Execute(The minlevel of flawfinder should be configurable): + let b:ale_cpp_flawfinder_minlevel = 8 + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --minlevel=8 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) + +Execute(Additional flawfinder options should be configurable): + let b:ale_cpp_flawfinder_options = ' --foobar' + + AssertEqual + \ ale#Escape('flawfinder') + \ . ' -CDQS --foobar --minlevel=1 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) + +Execute(The flawfinder exectable should be configurable): + let b:ale_cpp_flawfinder_executable = 'foo/bar' + + AssertEqual + \ ale#Escape('foo/bar') + \ . ' -CDQS --minlevel=1 %t', + \ ale_linters#cpp#flawfinder#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cpp_gcc_command_callbacks.vader b/test/command_callback/test_cpp_gcc_command_callbacks.vader new file mode 100644 index 0000000..9ab4d5c --- /dev/null +++ b/test/command_callback/test_cpp_gcc_command_callbacks.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_cpp_gcc_executable + Save g:ale_cpp_gcc_options + + unlet! g:ale_cpp_gcc_executable + unlet! b:ale_cpp_gcc_executable + unlet! g:ale_cpp_gcc_options + unlet! b:ale_cpp_gcc_options + + runtime ale_linters/cpp/gcc.vim + + let b:command_tail = ' -S -x c++ -fsyntax-only -iquote' + \ . ' ' . ale#Escape(getcwd()) + \ . ' -std=c++14 -Wall -' + +After: + Restore + unlet! b:command_tail + unlet! b:ale_cpp_gcc_executable + unlet! b:ale_cpp_gcc_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'gcc', ale_linters#cpp#gcc#GetExecutable(bufnr('')) + + let b:ale_cpp_gcc_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cpp#gcc#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('gcc') . b:command_tail, + \ ale_linters#cpp#gcc#GetCommand(bufnr('')) + + let b:ale_cpp_gcc_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#cpp#gcc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cpplint_command_callbacks.vader b/test/command_callback/test_cpplint_command_callbacks.vader new file mode 100644 index 0000000..34746a1 --- /dev/null +++ b/test/command_callback/test_cpplint_command_callbacks.vader @@ -0,0 +1,42 @@ +Before: + Save g:ale_cpp_cpplint_executable + Save g:ale_cpp_cpplint_options + + unlet! g:ale_cpp_cpplint_executable + unlet! b:ale_cpp_cpplint_executable + unlet! g:ale_cpp_cpplint_options + unlet! b:ale_cpp_cpplint_options + + runtime ale_linters/cpp/cpplint.vim + +After: + Restore + unlet! b:command_tail + unlet! b:ale_cpp_cpplint_executable + unlet! b:ale_cpp_cpplint_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'cpplint', ale_linters#cpp#cpplint#GetExecutable(bufnr('')) + + let b:ale_cpp_cpplint_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cpp#cpplint#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('cpplint') . ' %s', + \ ale_linters#cpp#cpplint#GetCommand(bufnr('')) + + let b:ale_cpp_cpplint_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . ' %s', + \ ale_linters#cpp#cpplint#GetCommand(bufnr('')) + \ +Execute(The options should be configurable): + let b:ale_cpp_cpplint_options = '--something' + + AssertEqual + \ ale#Escape('cpplint') . ' --something %s', + \ ale_linters#cpp#cpplint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_cs_mcs_command_callbacks.vader b/test/command_callback/test_cs_mcs_command_callbacks.vader new file mode 100644 index 0000000..30f067e --- /dev/null +++ b/test/command_callback/test_cs_mcs_command_callbacks.vader @@ -0,0 +1,34 @@ +Before: + Save g:ale_cs_mcs_options + + unlet! g:ale_cs_mcs_options + + runtime ale_linters/cs/mcs.vim + + let b:command_tail = ' -unsafe --parse' + +After: + Restore + unlet! b:command_tail + unlet! b:ale_cs_mcs_options + call ale#linter#Reset() + +Execute(Check for proper default command): + + let b:command = ale_linters#cs#mcs#GetCommand(bufnr('')) + let b:command = substitute(b:command,'\s\+',' ','g') + + AssertEqual + \ b:command, + \ 'mcs -unsafe --parse %t' + +Execute(The options should be be used in the command): + + let b:ale_cs_mcs_options = '-pkg:dotnet' + let b:command = ale_linters#cs#mcs#GetCommand(bufnr('')) + let b:command = substitute(b:command,'\s\+',' ','g') + + AssertEqual + \ b:command, + \ 'mcs' . b:command_tail . ' ' . b:ale_cs_mcs_options . ' %t', + diff --git a/test/command_callback/test_cs_mcsc_command_callbacks.vader b/test/command_callback/test_cs_mcsc_command_callbacks.vader new file mode 100644 index 0000000..cb52c96 --- /dev/null +++ b/test/command_callback/test_cs_mcsc_command_callbacks.vader @@ -0,0 +1,93 @@ +Before: + Save g:ale_cs_mcsc_options + Save g:ale_cs_mcsc_source + Save g:ale_cs_mcsc_assembly_path + Save g:ale_cs_mcsc_assemblies + Save g:ale_buffer_info + + let g:ale_buffer_info = {bufnr(''): {'temporary_file_list': []}} + + unlet! g:ale_cs_mcsc_options + unlet! g:ale_cs_mcsc_source + unlet! g:ale_cs_mcsc_assembly_path + unlet! g:ale_cs_mcsc_assemblies + + let g:prefix = ' -out:TEMP -t:module -recurse:' . ale#Escape('*.cs') + + function! GetCommand() + let l:command = ale_linters#cs#mcsc#GetCommand(bufnr('')) + let l:command = join(split(l:command)) + + return substitute(l:command, ':[^ ]\+ -t:module', ':TEMP -t:module', '') + endfunction + + runtime ale_linters/cs/mcsc.vim + +After: + Restore + + unlet! b:ale_cs_mcsc_options + unlet! g:ale_cs_mcsc_source + unlet! g:ale_cs_mcsc_assembly_path + unlet! g:ale_cs_mcsc_assemblies + unlet! g:ale_prefix + + delfunction GetCommand + + call ale#linter#Reset() + +Execute(The mcsc linter should return the correct default command): + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mcs -unsafe' . g:prefix, + \ GetCommand() + +Execute(The options should be be used in the command): + let g:ale_cs_mcsc_options = '-pkg:dotnet' + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mcs -unsafe -pkg:dotnet' . g:prefix, + \ GetCommand() + +Execute(The souce path should be be used in the command): + let g:ale_cs_mcsc_source = '../foo/bar' + + AssertEqual + \ 'cd ' . ale#Escape('../foo/bar') . ' && ' + \ . 'mcs -unsafe' . g:prefix, + \ GetCommand() + +Execute(The list of search pathes for assemblies should be be used in the command if not empty): + let g:ale_cs_mcsc_assembly_path = ['/usr/lib/mono', '../foo/bar'] + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mcs -unsafe' + \ . ' -lib:' . ale#Escape('/usr/lib/mono') . ',' . ale#Escape('../foo/bar') + \ . g:prefix, + \ GetCommand() + + let g:ale_cs_mcsc_assembly_path = [] + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mcs -unsafe' . g:prefix, + \ GetCommand() + +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'] + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mcs -unsafe' + \ . ' -r:' . ale#Escape('foo.dll') . ',' . ale#Escape('bar.dll') + \ . g:prefix, + \ GetCommand() + + let g:ale_cs_mcsc_assemblies = [] + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . 'mcs -unsafe' . g:prefix, + \ GetCommand() diff --git a/test/command_callback/test_cuda_nvcc_command_callbacks.vader b/test/command_callback/test_cuda_nvcc_command_callbacks.vader new file mode 100644 index 0000000..af199d3 --- /dev/null +++ b/test/command_callback/test_cuda_nvcc_command_callbacks.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_cuda_nvcc_executable + Save g:ale_cuda_nvcc_options + + unlet! g:ale_cuda_nvcc_executable + unlet! b:ale_cuda_nvcc_executable + unlet! g:ale_cuda_nvcc_options + unlet! b:ale_cuda_nvcc_options + + runtime ale_linters/cuda/nvcc.vim + +After: + Restore + unlet! b:ale_cuda_nvcc_executable + unlet! b:ale_cuda_nvcc_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'nvcc', ale_linters#cuda#nvcc#GetExecutable(bufnr('')) + + let b:ale_cuda_nvcc_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cuda#nvcc#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('nvcc') . ' -cuda -std=c++11 %s' + \ . ' -o ' . g:ale#util#nul_file, + \ ale_linters#cuda#nvcc#GetCommand(bufnr('')) + + let b:ale_cuda_nvcc_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . ' -cuda -std=c++11 %s' + \ . ' -o ' . g:ale#util#nul_file, + \ ale_linters#cuda#nvcc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_dartanalyzer_command_callback.vader b/test/command_callback/test_dartanalyzer_command_callback.vader new file mode 100644 index 0000000..c7b5139 --- /dev/null +++ b/test/command_callback/test_dartanalyzer_command_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_dart_dartanalyzer_executable + unlet! g:ale_dart_dartanalyzer_executable + + runtime ale_linters/dart/dartanalyzer.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default command and executable should be correct): + AssertEqual + \ 'dartanalyzer', + \ ale_linters#dart#dartanalyzer#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('dartanalyzer') . ' %s', + \ ale_linters#dart#dartanalyzer#GetCommand(bufnr('')) + +Execute(The executable should be configurable): + let g:ale_dart_dartanalyzer_executable = '/usr/lib/dart/bin/dartanalyzer' + + AssertEqual + \ '/usr/lib/dart/bin/dartanalyzer', + \ ale_linters#dart#dartanalyzer#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('/usr/lib/dart/bin/dartanalyzer') . ' %s', + \ ale_linters#dart#dartanalyzer#GetCommand(bufnr('')) + +Execute(The .packages file should be set if detected): + call ale#test#SetFilename('dart_paths/foo') + + AssertEqual + \ ale#Escape('dartanalyzer') + \ . ' --packages ' . ale#Escape(ale#path#Simplify(g:dir . '/dart_paths/.packages')) + \ . ' %s', + \ ale_linters#dart#dartanalyzer#GetCommand(bufnr('')) diff --git a/test/command_callback/test_erb_command_callback.vader b/test/command_callback/test_erb_command_callback.vader new file mode 100644 index 0000000..481f64f --- /dev/null +++ b/test/command_callback/test_erb_command_callback.vader @@ -0,0 +1,21 @@ +Before: + runtime ale_linters/eruby/erb.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(Executable should not contain any filter code by default): + call ale#test#SetFilename('../ruby_fixtures/not_a_rails_app/file.rb') + + AssertEqual + \ 'erb -P -T - -x %t | ruby -c', + \ ale_linters#eruby#erb#GetCommand(bufnr('')) + +Execute(Executable should filter invalid eRuby when inside a Rails project): + call ale#test#SetFilename('../ruby_fixtures/valid_rails_app/app/views/my_great_view.html.erb') + + AssertEqual + \ 'ruby -r erb -e ' . ale#Escape('puts ERB.new($stdin.read.gsub(%{<%=},%{<%}), nil, %{-}).src') . '< %t | ruby -c', + \ ale_linters#eruby#erb#GetCommand(bufnr('')) diff --git a/test/command_callback/test_erlang_syntaxerl_command_callback.vader b/test/command_callback/test_erlang_syntaxerl_command_callback.vader new file mode 100644 index 0000000..a9a1a50 --- /dev/null +++ b/test/command_callback/test_erlang_syntaxerl_command_callback.vader @@ -0,0 +1,66 @@ +Before: + Save g:ale_erlang_syntaxerl_executable + + unlet! g:ale_erlang_syntaxerl_executable + unlet! b:ale_erlang_syntaxerl_executable + + runtime ale_linters/erlang/syntaxerl.vim + +After: + Restore + + call ale#linter#Reset() + +Execute (The executable should be correct): + AssertEqual 'syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + AssertEqual '/some/other/syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + AssertEqual '/yet/another/syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + +Execute (The executable should be presented in the feature check command): + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + + AssertEqual + \ ale#Escape('/some/other/syntaxerl') . ' -h', + \ ale_linters#erlang#syntaxerl#FeatureCheck(bufnr('')) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + + AssertEqual + \ ale#Escape('/yet/another/syntaxerl') . ' -h', + \ ale_linters#erlang#syntaxerl#FeatureCheck(bufnr('')) + +Execute (The executable should be presented in the command): + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + + AssertEqual + \ ale#Escape('/some/other/syntaxerl') . ' %t', + \ ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), []) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + + AssertEqual + \ ale#Escape('/yet/another/syntaxerl') . ' %t', + \ ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), []) + +Execute (The -b option should be used when available): + AssertEqual ale#Escape('syntaxerl') . ' %t', ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), [ + \ 'Syntax checker for Erlang (0.14.0)', + \ 'Usage: syntaxerl [-d | --debug] ', + \ ' syntaxerl <-h | --help>', + \ ' -d, --debug Enable debug output', + \ ' -h, --help Show this message', + \ ]) + + AssertEqual ale#Escape('syntaxerl') . ' -b %s %t', ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), [ + \ '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', + \ ]) diff --git a/test/command_callback/test_erubi_command_callback.vader b/test/command_callback/test_erubi_command_callback.vader new file mode 100644 index 0000000..1953d76 --- /dev/null +++ b/test/command_callback/test_erubi_command_callback.vader @@ -0,0 +1,31 @@ +Before: + runtime ale_linters/eruby/erubi.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(Executable should not contain any filter code by default): + call ale#test#SetFilename('../ruby_fixtures/not_a_rails_app/file.rb') + + AssertEqual + \ 'ruby -r erubi/capture_end -e ' . ale#Escape('puts Erubi::CaptureEndEngine.new($stdin.read).src') . '< %t | ruby -c', + \ ale_linters#eruby#erubi#GetCommand(bufnr(''), []) + +Execute(Executable should filter invalid eRuby when inside a Rails project): + call ale#test#SetFilename('../ruby_fixtures/valid_rails_app/app/views/my_great_view.html.erb') + + AssertEqual + \ 'ruby -r erubi/capture_end -e ' . ale#Escape('puts Erubi::CaptureEndEngine.new($stdin.read.gsub(%{<%=},%{<%}), nil, %{-}).src') . '< %t | ruby -c', + \ ale_linters#eruby#erubi#GetCommand(bufnr(''), []) + +Execute(Command should be blank if the first command in the chain return output): + let output_lines = [ + \ "/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'", + \] + + AssertEqual + \ '', + \ ale_linters#eruby#erubi#GetCommand(bufnr(''), output_lines) diff --git a/test/command_callback/test_erubis_command_callback.vader b/test/command_callback/test_erubis_command_callback.vader new file mode 100644 index 0000000..574dd68 --- /dev/null +++ b/test/command_callback/test_erubis_command_callback.vader @@ -0,0 +1,21 @@ +Before: + runtime ale_linters/eruby/erubis.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(Executable should not contain any filter code by default): + call ale#test#SetFilename('../ruby_fixtures/not_a_rails_app/file.rb') + + AssertEqual + \ 'erubis -x %t | ruby -c', + \ ale_linters#eruby#erubis#GetCommand(bufnr('')) + +Execute(Executable should filter invalid eRuby when inside a Rails project): + call ale#test#SetFilename('../ruby_fixtures/valid_rails_app/app/views/my_great_view.html.erb') + + AssertEqual + \ 'ruby -r erubis -e ' . ale#Escape('puts Erubis::Eruby.new($stdin.read.gsub(%{<%=},%{<%})).src') . '< %t | ruby -c', + \ ale_linters#eruby#erubis#GetCommand(bufnr('')) diff --git a/test/command_callback/test_flake8_command_callback.vader b/test/command_callback/test_flake8_command_callback.vader new file mode 100644 index 0000000..1784b81 --- /dev/null +++ b/test/command_callback/test_flake8_command_callback.vader @@ -0,0 +1,181 @@ +Before: + Save g:ale_python_flake8_executable + Save g:ale_python_flake8_options + Save g:ale_python_flake8_use_global + + unlet! g:ale_python_flake8_executable + unlet! g:ale_python_flake8_args + unlet! g:ale_python_flake8_options + unlet! g:ale_python_flake8_use_global + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + + runtime ale_linters/python/flake8.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + unlet! g:ale_python_flake8_args + + unlet! b:bin_dir + unlet! b:executable + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + call ale#semver#ResetVersionCache() + +Execute(The flake8 callbacks should return the correct default values): + AssertEqual + \ 'flake8', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('flake8') . ' --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) + + " Try with older versions. + call ale#semver#ResetVersionCache() + AssertEqual + \ ale#Escape('flake8') . ' --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + +Execute(The flake8 command callback should let you set options): + let g:ale_python_flake8_options = '--some-option' + + AssertEqual + \ ale#Escape('flake8') + \ . ' --some-option --format=default' + \ . ' --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.4']) + + call ale#semver#ResetVersionCache() + + AssertEqual + \ ale#Escape('flake8') + \ . ' --some-option --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + +Execute(You should be able to set a custom executable and it should be escaped): + let g:ale_python_flake8_executable = 'executable with spaces' + + AssertEqual + \ 'executable with spaces', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('executable with spaces') . ' --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ ale#Escape('executable with spaces') + \ . ' --format=default' + \ . ' --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) + +Execute(The flake8 callbacks should detect virtualenv directories): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/flake8' + \) + + AssertEqual + \ b:executable, + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape(b:executable) . ' --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ ale#Escape(b:executable) + \ . ' --format=default --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via Manifest.in): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/namespace_package_manifest/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/python_paths/namespace_package_manifest'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via setup.cf): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/namespace_package_setup/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/python_paths/namespace_package_setup'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via pytest.ini): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/namespace_package_pytest/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/python_paths/namespace_package_pytest'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for namespace package via tox.ini): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/namespace_package_tox/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/python_paths/namespace_package_tox'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The FindProjectRoot should detect the project root directory for non-namespace package): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/python_paths/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): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_flake8_executable = 'python' + let g:ale_python_flake8_options = '-m flake8 --some-option' + + AssertEqual + \ 'python', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('python') . ' -m flake8 --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ ale#Escape('python') . ' -m flake8 --some-option --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + + call ale#semver#ResetVersionCache() + + " Leading spaces shouldn't matter + let g:ale_python_flake8_options = ' -m flake8 --some-option' + + AssertEqual + \ 'python', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('python') . ' -m flake8 --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ ale#Escape('python') . ' -m flake8 --some-option --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + +Execute(Using `python2 -m flake8` should be supported with the old args option): + let g:ale_python_flake8_executable = 'python2' + let g:ale_python_flake8_args = '-m flake8' + let g:ale_python_flake8_use_global = 0 + + unlet! g:ale_python_flake8_options + + call ale#linter#Reset() + runtime ale_linters/python/flake8.vim + + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ 'python2', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('python2') . ' -m flake8 --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ ale#Escape('python2') . ' -m flake8 --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) diff --git a/test/command_callback/test_foodcritic_command_callback.vader b/test/command_callback/test_foodcritic_command_callback.vader new file mode 100644 index 0000000..e3ad8a7 --- /dev/null +++ b/test/command_callback/test_foodcritic_command_callback.vader @@ -0,0 +1,44 @@ +Before: + Save g:ale_chef_foodcritic_executable + Save g:ale_chef_foodcritic_options + + unlet! g:ale_chef_foodcritic_executable + unlet! g:ale_chef_foodcritic_options + + call ale#test#SetDirectory('/testplugin/test') + + runtime ale_linters/chef/foodcritic.vim + +After: + Restore + + unlet! b:ale_chef_foodcritic_executable + unlet! b:ale_chef_foodcritic_options + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual + \ 'foodcritic', + \ ale_linters#chef#foodcritic#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foodcritic') . ' %s', + \ ale_linters#chef#foodcritic#GetCommand(bufnr('')) + +Execute(Extra options should be included with escapeed tildes (~)): + let b:ale_chef_foodcritic_options = '-t ~F011' + + AssertEqual + \ ale#Escape('foodcritic') . ' -t \~F011 %s', + \ ale_linters#chef#foodcritic#GetCommand(bufnr('')) + +Execute(The executable should be configurable): + let b:ale_chef_foodcritic_executable = 'foobar' + + AssertEqual + \ 'foobar', + \ ale_linters#chef#foodcritic#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foobar') . ' %s', + \ ale_linters#chef#foodcritic#GetCommand(bufnr('')) diff --git a/test/command_callback/test_fusionlint_command_callback.vader b/test/command_callback/test_fusionlint_command_callback.vader new file mode 100644 index 0000000..34a4413 --- /dev/null +++ b/test/command_callback/test_fusionlint_command_callback.vader @@ -0,0 +1,34 @@ +Before: + Save g:ale_fuse_fusionlint_options + Save g:ale_fuse_fusionlint_executable + + unlet! g:ale_fuse_fusionlint_options + unlet! g:ale_fuse_fusionlint_executable + + runtime ale_linters/fuse/fusionlint.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The fuse fusionlint command callback should return the correct default string): + AssertEqual ale#Escape('fusion-lint') . ' --filename %s -i', + \ join(split(ale_linters#fuse#fusionlint#GetCommand(1))) + +Execute(The fuse fusionlint command callback should let you set options): + let g:ale_fuse_fusionlint_options = '--example-option argument' + + AssertEqual + \ ale#Escape('fusion-lint') + \ . ' --example-option argument --filename %s -i', + \ join(split(ale_linters#fuse#fusionlint#GetCommand(1))) + +Execute(The fusionlint executable should be configurable): + let g:ale_fuse_fusionlint_executable = 'util/linter.fuse' + + AssertEqual 'util/linter.fuse', ale_linters#fuse#fusionlint#GetExecutable(1) + AssertEqual + \ ale#Escape('util/linter.fuse') + \ . ' --filename %s -i', + \ join(split(ale_linters#fuse#fusionlint#GetCommand(1))) diff --git a/test/command_callback/test_gfortran_command_callback.vader b/test/command_callback/test_gfortran_command_callback.vader new file mode 100644 index 0000000..501e92f --- /dev/null +++ b/test/command_callback/test_gfortran_command_callback.vader @@ -0,0 +1,31 @@ +Before: + runtime ale_linters/fortran/gcc.vim + +After: + call ale#linter#Reset() + let g:ale_fortran_gcc_options = '-Wall' + let g:ale_fortran_gcc_use_free_form = 1 + let g:ale_fortran_gcc_executable = 'gcc' + +Execute(The fortran gcc command callback should return the correct default string): + AssertEqual 'gcc -S -x f95 -fsyntax-only -ffree-form -Wall -', + \ join(split(ale_linters#fortran#gcc#GetCommand(1))) + +Execute(The fortran gcc command callback should let you set options): + let g:ale_fortran_gcc_options = '-Wotherthings' + + AssertEqual 'gcc -S -x f95 -fsyntax-only -ffree-form -Wotherthings -', + \ join(split(ale_linters#fortran#gcc#GetCommand(1))) + +Execute(The fortran gcc command callback should let you use -ffixed-form): + let g:ale_fortran_gcc_use_free_form = 0 + + AssertEqual 'gcc -S -x f95 -fsyntax-only -ffixed-form -Wall -', + \ join(split(ale_linters#fortran#gcc#GetCommand(1))) + +Execute(The fortran executable should be configurable): + let g:ale_fortran_gcc_executable = 'gfortran' + + AssertEqual 'gfortran', ale_linters#fortran#gcc#GetExecutable(1) + AssertEqual 'gfortran -S -x f95 -fsyntax-only -ffree-form -Wall -', + \ join(split(ale_linters#fortran#gcc#GetCommand(1))) diff --git a/test/command_callback/test_gitlint_command_callback.vader b/test/command_callback/test_gitlint_command_callback.vader new file mode 100644 index 0000000..6ff95ea --- /dev/null +++ b/test/command_callback/test_gitlint_command_callback.vader @@ -0,0 +1,84 @@ +Before: + Save g:ale_gitcommit_gitlint_executable + Save g:ale_gitcommit_gitlint_options + Save g:ale_gitcommit_gitlint_use_global + + unlet! g:ale_gitcommit_gitlint_executable + unlet! g:ale_gitcommit_gitlint_options + unlet! g:ale_gitcommit_gitlint_use_global + + runtime ale_linters/gitcommit/gitlint.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + let b:command_tail = ' lint' + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! b:bin_dir + unlet! b:executable + +Execute(The gitlint callbacks should return the correct default values): + AssertEqual + \ 'gitlint', + \ ale_linters#gitcommit#gitlint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('gitlint') . b:command_tail, + \ ale_linters#gitcommit#gitlint#GetCommand(bufnr('')) + +Execute(The gitlint executable should be configurable, and escaped properly): + let g:ale_gitcommit_gitlint_executable = 'executable with spaces' + + AssertEqual + \ 'executable with spaces', + \ ale_linters#gitcommit#gitlint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('executable with spaces') . b:command_tail, + \ ale_linters#gitcommit#gitlint#GetCommand(bufnr('')) + +Execute(The gitlint command callback should let you set options): + let g:ale_gitcommit_gitlint_options = '--some-option' + + AssertEqual + \ ale#Escape('gitlint') . ' --some-option' . b:command_tail, + \ ale_linters#gitcommit#gitlint#GetCommand(bufnr('')) + +Execute(The gitlint callbacks shouldn't detect virtualenv directories where they don't exist): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/COMMIT_EDITMSG') + + AssertEqual + \ 'gitlint', + \ ale_linters#gitcommit#gitlint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('gitlint') . b:command_tail, + \ ale_linters#gitcommit#gitlint#GetCommand(bufnr('')) + +Execute(The gitlint callbacks should detect virtualenv directories): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/COMMIT_EDITMSG') + + let b:executable = ale#path#Simplify( + \ g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/gitlint' + \) + + AssertEqual + \ b:executable, + \ ale_linters#gitcommit#gitlint#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) . b:command_tail, + \ ale_linters#gitcommit#gitlint#GetCommand(bufnr('')) + +Execute(You should able able to use the global gitlint instead): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/COMMIT_EDITMSG') + let g:ale_gitcommit_gitlint_use_global = 1 + + AssertEqual + \ 'gitlint', + \ ale_linters#gitcommit#gitlint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('gitlint') . b:command_tail, + \ ale_linters#gitcommit#gitlint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_glslang_command_callback.vader b/test/command_callback/test_glslang_command_callback.vader new file mode 100644 index 0000000..1b1722a --- /dev/null +++ b/test/command_callback/test_glslang_command_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_glsl_glslang_executable + Save g:ale_glsl_glslang_options + + unlet! g:ale_glsl_glslang_executable + unlet! g:ale_glsl_glslang_options + + runtime ale_linters/glsl/glslang.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + call ale#linter#Reset() + +Execute(Executable should default to glslangValidator): + AssertEqual + \ 'glslangValidator', + \ ale_linters#glsl#glslang#GetExecutable(bufnr('')) + +Execute(Executable should be configurable): + let g:ale_glsl_glslang_executable = 'foobar' + AssertEqual + \ 'foobar', + \ ale_linters#glsl#glslang#GetExecutable(bufnr('')) + +Execute(Command should use executable): + AssertEqual + \ 'glslangValidator -C %t', + \ ale_linters#glsl#glslang#GetCommand(bufnr('')) + + let g:ale_glsl_glslang_executable = 'foobar' + AssertEqual + \ 'foobar -C %t', + \ ale_linters#glsl#glslang#GetCommand(bufnr('')) + +Execute(Options should work): + let g:ale_glsl_glslang_options = '--test' + AssertEqual + \ 'glslangValidator --test -C %t', + \ ale_linters#glsl#glslang#GetCommand(bufnr('')) diff --git a/test/command_callback/test_glslls_command_callback.vader b/test/command_callback/test_glslls_command_callback.vader new file mode 100644 index 0000000..e64c235 --- /dev/null +++ b/test/command_callback/test_glslls_command_callback.vader @@ -0,0 +1,37 @@ +Before: + Save g:ale_glsl_glslls_executable + Save g:ale_glsl_glslls_logfile + + unlet! g:ale_glsl_glslls_executable + unlet! g:ale_glsl_glslls_logfile + + runtime ale_linters/glsl/glslls.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + call ale#linter#Reset() + +Execute(Executable should default to 'glslls'): + AssertEqual + \ 'glslls', + \ ale_linters#glsl#glslls#GetExecutable(bufnr('')) + +Execute(Executable should be configurable): + let g:ale_glsl_glslls_executable = 'foobar' + AssertEqual + \ 'foobar', + \ ale_linters#glsl#glslls#GetExecutable(bufnr('')) + +Execute(Command should use executable): + let command1 = ale_linters#glsl#glslls#GetCommand(bufnr('')) + AssertEqual command1, ale#Escape('glslls') . ' --stdin' + + let g:ale_glsl_glslls_executable = 'foobar' + let command2 = ale_linters#glsl#glslls#GetCommand(bufnr('')) + AssertEqual command2, ale#Escape('foobar') . ' --stdin' + +Execute(Setting logfile should work): + let g:ale_glsl_glslls_logfile = '/tmp/test.log' + let command = ale_linters#glsl#glslls#GetCommand(bufnr('')) + AssertEqual command, ale#Escape('glslls') . ' --verbose -l /tmp/test.log --stdin' diff --git a/test/command_callback/test_gobuild_command_callback.vader b/test/command_callback/test_gobuild_command_callback.vader new file mode 100644 index 0000000..240f29c --- /dev/null +++ b/test/command_callback/test_gobuild_command_callback.vader @@ -0,0 +1,52 @@ +Before: + Save g:ale_go_gobuild_options + + unlet! g:ale_go_gobuild_options + + let g:env_prefix = has('win32') + \ ? 'set GOPATH=' . ale#Escape('/foo/bar') . ' && ' + \ : 'GOPATH=' . ale#Escape('/foo/bar') . ' ' + + runtime ale_linters/go/gobuild.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + + call ale_linters#go#gobuild#ResetEnv() + +After: + Restore + + unlet! g:env_prefix + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The default gobuild command should be correct): + AssertEqual + \ ale_linters#go#gobuild#GetCommand(bufnr(''), ['/foo/bar', '/foo/baz']), + \ g:env_prefix . 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'go test -c -o /dev/null ./' + +Execute(The command for getting GOPATH should be correct): + AssertEqual ale_linters#go#gobuild#GoEnv(bufnr('')), 'go env GOPATH GOROOT' + + call ale_linters#go#gobuild#GetCommand(bufnr(''), ['/foo/bar', '/foo/baz']) + + " We shouldn't run `go env` many times after we've got it. + AssertEqual ale_linters#go#gobuild#GoEnv(bufnr('')), '' + +Execute(The GOPATH output should be used after it has been read once): + call ale_linters#go#gobuild#GetCommand(bufnr(''), ['/foo/bar', '/foo/baz']) + + AssertEqual + \ ale_linters#go#gobuild#GetCommand(bufnr(''), []), + \ g:env_prefix . 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'go test -c -o /dev/null ./' + +Execute(Extra options should be supported): + let g:ale_go_gobuild_options = '--foo-bar' + + AssertEqual + \ ale_linters#go#gobuild#GetCommand(bufnr(''), ['/foo/bar', '/foo/baz']), + \ g:env_prefix . 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'go test --foo-bar -c -o /dev/null ./' diff --git a/test/command_callback/test_gometalinter_command_callback.vader b/test/command_callback/test_gometalinter_command_callback.vader new file mode 100644 index 0000000..93a541c --- /dev/null +++ b/test/command_callback/test_gometalinter_command_callback.vader @@ -0,0 +1,61 @@ +Before: + Save b:ale_go_gometalinter_executable + Save b:ale_go_gometalinter_options + Save b:ale_go_gometalinter_lint_package + + let b:ale_go_gometalinter_executable = 'gometalinter' + let b:ale_go_gometalinter_options = '' + let b:ale_go_gometalinter_lint_package = 0 + + runtime ale_linters/go/gometalinter.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.go') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The gometalinter callback should return the right defaults): + AssertEqual + \ 'gometalinter', + \ ale_linters#go#gometalinter#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('gometalinter') + \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(expand('%' . ':t'))) + \ . ' .', + \ ale_linters#go#gometalinter#GetCommand(bufnr('')) + +Execute(The gometalinter callback should use a configured executable): + let b:ale_go_gometalinter_executable = 'something else' + + AssertEqual + \ 'something else', + \ ale_linters#go#gometalinter#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('something else') + \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(expand('%' . ':t'))) + \ . ' .', + \ ale_linters#go#gometalinter#GetCommand(bufnr('')) + +Execute(The gometalinter callback should use configured options): + let b:ale_go_gometalinter_options = '--foobar' + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('gometalinter') + \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(expand('%' . ':t'))) + \ . ' --foobar' . ' .', + \ ale_linters#go#gometalinter#GetCommand(bufnr('')) + +Execute(The gometalinter `lint_package` option should use the correct command): + let b:ale_go_gometalinter_lint_package = 1 + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('gometalinter') . ' .', + \ ale_linters#go#gometalinter#GetCommand(bufnr('')) diff --git a/test/command_callback/test_gotype_command_callback.vader b/test/command_callback/test_gotype_command_callback.vader new file mode 100644 index 0000000..f95e842 --- /dev/null +++ b/test/command_callback/test_gotype_command_callback.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/go/gotype.vim + call ale#test#SetFilename('../go_files/testfile2.go') + +After: + call ale#linter#Reset() + + +Execute(The gotype callback should include other files from the directory but exclude the file itself): + let dir = expand('#' . bufnr('') . ':p:h') + AssertEqual + \ "gotype %t ". ale#Escape(ale#path#Simplify(dir . "/testfile.go")), + \ ale_linters#go#gotype#GetCommand(bufnr('')) + +Execute(The gotype callback should ignore test files): + call ale#test#SetFilename('bla_test.go') + AssertEqual + \ 0, + \ ale_linters#go#gotype#GetCommand(bufnr('')) diff --git a/test/command_callback/test_govet_command_callback.vader b/test/command_callback/test_govet_command_callback.vader new file mode 100644 index 0000000..a9b2960 --- /dev/null +++ b/test/command_callback/test_govet_command_callback.vader @@ -0,0 +1,16 @@ +Before: + runtime ale_linters/go/govet.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The default command should be correct): + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ' go vet .', + \ ale_linters#go#govet#GetCommand(bufnr('')) diff --git a/test/command_callback/test_haml_hamllint_command_callback.vader b/test/command_callback/test_haml_hamllint_command_callback.vader new file mode 100644 index 0000000..0d9b1e0 --- /dev/null +++ b/test/command_callback/test_haml_hamllint_command_callback.vader @@ -0,0 +1,72 @@ +Before: + runtime ale_linters/haml/hamllint.vim + + let g:default_command = 'haml-lint %t' + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + unlet! g:default_command + unlet! b:conf + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The default command should be correct): + AssertEqual g:default_command, ale_linters#haml#hamllint#GetCommand(bufnr('')) + +Execute(The command should have the .rubocop.yml prepended as an env var if one exists): + call ale#test#SetFilename('../hamllint-test-files/rubocop-yml/subdir/file.haml') + let b:conf = ale#path#Simplify(g:dir . '/../hamllint-test-files/rubocop-yml/.rubocop.yml') + + if has('win32') + " Windows uses 'set var=... && command' + AssertEqual + \ 'set HAML_LINT_RUBOCOP_CONF=' + \ . ale#Escape(b:conf) + \ . ' && ' . g:default_command, + \ ale_linters#haml#hamllint#GetCommand(bufnr('')) + else + " Unix uses 'var=... command' + AssertEqual + \ 'HAML_LINT_RUBOCOP_CONF=' + \ . ale#Escape(b:conf) + \ . ' ' . g:default_command, + \ ale_linters#haml#hamllint#GetCommand(bufnr('')) + endif + +Execute(The command should have the nearest .haml-lint.yml set as --config if it exists): + call ale#test#SetFilename('../hamllint-test-files/haml-lint-yml/subdir/file.haml') + let b:conf = ale#path#Simplify(g:dir . '/../hamllint-test-files/haml-lint-yml/.haml-lint.yml') + + AssertEqual + \ 'haml-lint --config ' + \ . ale#Escape(b:conf) + \ . ' %t', + \ ale_linters#haml#hamllint#GetCommand(bufnr('')) + +Execute(The command should include a .rubocop.yml and a .haml-lint if both are found): + call ale#test#SetFilename('../hamllint-test-files/haml-lint-and-rubocop/subdir/file.haml') + let b:conf_hamllint = ale#path#Simplify(g:dir . '/../hamllint-test-files/haml-lint-and-rubocop/.haml-lint.yml') + let b:conf_rubocop = ale#path#Simplify(g:dir . '/../hamllint-test-files/haml-lint-and-rubocop/.rubocop.yml') + + if has('win32') + " Windows uses 'set var=... && command' + AssertEqual + \ 'set HAML_LINT_RUBOCOP_CONF=' + \ . ale#Escape(b:conf_rubocop) + \ . ' && haml-lint --config ' + \ . ale#Escape(b:conf_hamllint) + \ . ' %t', + \ ale_linters#haml#hamllint#GetCommand(bufnr('')) + else + " Unix uses 'var=... command' + AssertEqual + \ 'HAML_LINT_RUBOCOP_CONF=' + \ . ale#Escape(b:conf_rubocop) + \ . ' haml-lint --config ' + \ . ale#Escape(b:conf_hamllint) + \ . ' %t', + \ ale_linters#haml#hamllint#GetCommand(bufnr('')) + endif diff --git a/test/command_callback/test_haskell_ghc_command_callbacks.vader b/test/command_callback/test_haskell_ghc_command_callbacks.vader new file mode 100644 index 0000000..edaf2b9 --- /dev/null +++ b/test/command_callback/test_haskell_ghc_command_callbacks.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_haskell_ghc_options + + unlet! g:ale_haskell_ghc_options + unlet! b:ale_haskell_ghc_options + + runtime ale_linters/haskell/ghc.vim + +After: + Restore + unlet! b:ale_haskell_ghc_options + call ale#linter#Reset() + +Execute(The options should be used in the command): + AssertEqual + \ 'ghc -fno-code -v0 %t', + \ ale_linters#haskell#ghc#GetCommand(bufnr('')) + + let b:ale_haskell_ghc_options = 'foobar' + + AssertEqual + \ 'ghc foobar %t', + \ ale_linters#haskell#ghc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_haskell_hdevtools_command_callbacks.vader b/test/command_callback/test_haskell_hdevtools_command_callbacks.vader new file mode 100644 index 0000000..c5320c5 --- /dev/null +++ b/test/command_callback/test_haskell_hdevtools_command_callbacks.vader @@ -0,0 +1,37 @@ +Before: + Save g:ale_haskell_hdevtools_executable + Save g:ale_haskell_hdevtools_options + + unlet! g:ale_haskell_hdevtools_executable + unlet! b:ale_haskell_hdevtools_executable + unlet! g:ale_haskell_hdevtools_options + unlet! b:ale_haskell_hdevtools_options + + runtime ale_linters/haskell/hdevtools.vim + + let b:command_tail = ' check -g -Wall -p %s %t' + +After: + Restore + unlet! b:command_tail + unlet! b:ale_haskell_hdevtools_executable + unlet! b:ale_haskell_hdevtools_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'hdevtools', ale_linters#haskell#hdevtools#GetExecutable(bufnr('')) + + let b:ale_haskell_hdevtools_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#haskell#hdevtools#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('hdevtools') . b:command_tail, + \ ale_linters#haskell#hdevtools#GetCommand(bufnr('')) + + let b:ale_haskell_hdevtools_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . b:command_tail, + \ ale_linters#haskell#hdevtools#GetCommand(bufnr('')) diff --git a/test/command_callback/test_htmlhint_command_callback.vader b/test/command_callback/test_htmlhint_command_callback.vader new file mode 100644 index 0000000..5bb21a6 --- /dev/null +++ b/test/command_callback/test_htmlhint_command_callback.vader @@ -0,0 +1,71 @@ +Before: + Save g:ale_html_htmlhint_options + Save g:ale_html_htmlhint_executable + Save g:ale_html_htmlhint_use_global + + unlet! g:ale_html_htmlhint_options + unlet! g:ale_html_htmlhint_executable + unlet! g:ale_html_htmlhint_use_global + + runtime ale_linters/html/htmlhint.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('htmlhint_paths/test.html') + + let g:node_executable = ale#path#Simplify( + \ g:dir + \ . '/htmlhint_paths/node_modules/.bin/htmlhint' + \) + let g:config_path = ale#path#Simplify( + \ g:dir + \ . '/htmlhint_paths/with_config/.htmlhintrc' + \) + +After: + Restore + + unlet! g:node_executable + unlet! g:config_path + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual + \ ale#Escape(g:node_executable) . ' --format=unix %t', + \ ale_linters#html#htmlhint#GetCommand(bufnr('')) + +Execute(The global executable should be uesd if the option is set): + let g:ale_html_htmlhint_executable = 'foo' + let g:ale_html_htmlhint_use_global = 1 + + AssertEqual + \ ale#Escape('foo') . ' --format=unix %t', + \ ale_linters#html#htmlhint#GetCommand(bufnr('')) + +" 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' + + AssertEqual + \ ale#Escape(g:node_executable) . ' --format=unix %t', + \ ale_linters#html#htmlhint#GetCommand(bufnr('')) + +Execute(The configuration file should be automatically detected): + call ale#test#SetFilename('htmlhint_paths/with_config/test.html') + + AssertEqual + \ ale#Escape(g:node_executable) + \ . ' --config ' . ale#Escape(g:config_path) + \ . ' --format=unix %t', + \ ale_linters#html#htmlhint#GetCommand(bufnr('')) + +" 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('htmlhint_paths/with_config/test.html') + let g:ale_html_htmlhint_options = '--config=/foo/bar/.htmlhintrc' + + AssertEqual + \ ale#Escape(g:node_executable) + \ . ' --config=/foo/bar/.htmlhintrc' + \ . ' --format=unix %t', + \ ale_linters#html#htmlhint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_idris_command_callbacks.vader b/test/command_callback/test_idris_command_callbacks.vader new file mode 100644 index 0000000..03a69f3 --- /dev/null +++ b/test/command_callback/test_idris_command_callbacks.vader @@ -0,0 +1,42 @@ +Before: + Save g:ale_idris_idris_executable + Save g:ale_idris_idris_options + + unlet! g:ale_idris_idris_executable + unlet! b:ale_idris_idris_executable + unlet! g:ale_idris_idris_options + unlet! b:ale_idris_idris_options + + runtime ale_linters/idris/idris.vim + +After: + Restore + unlet! b:command_tail + unlet! b:ale_idris_idris_executable + unlet! b:ale_idris_idris_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'idris', ale_linters#idris#idris#GetExecutable(bufnr('')) + + let b:ale_idris_idris_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#idris#idris#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('idris') . ' --total --warnpartial --warnreach --warnipkg --check %s', + \ ale_linters#idris#idris#GetCommand(bufnr('')) + + let b:ale_idris_idris_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . ' --total --warnpartial --warnreach --warnipkg --check %s', + \ ale_linters#idris#idris#GetCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_idris_idris_options = '--something' + + AssertEqual + \ ale#Escape('idris') . ' --something --check %s', + \ ale_linters#idris#idris#GetCommand(bufnr('')) diff --git a/test/command_callback/test_iverilog_command_callback.vader b/test/command_callback/test_iverilog_command_callback.vader new file mode 100644 index 0000000..2c63317 --- /dev/null +++ b/test/command_callback/test_iverilog_command_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_verilog_iverilog_options + + unlet! g:ale_verilog_iverilog_options + + runtime ale_linters/verilog/iverilog.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The default iverilog command should be correct): + AssertEqual + \ 'iverilog -t null -Wall %t', + \ ale_linters#verilog#iverilog#GetCommand(bufnr('')) + +Execute(iverilog options should be configurable): + " Additional args for the linter + let g:ale_verilog_iverilog_options = '-y.' + + AssertEqual + \ 'iverilog -t null -Wall -y. %t', + \ ale_linters#verilog#iverilog#GetCommand(bufnr('')) diff --git a/test/command_callback/test_javac_command_callback.vader b/test/command_callback/test_javac_command_callback.vader new file mode 100644 index 0000000..7823d03 --- /dev/null +++ b/test/command_callback/test_javac_command_callback.vader @@ -0,0 +1,187 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/command_callback') + + Save g:ale_java_javac_options + Save g:ale_java_javac_classpath + + unlet! g:ale_java_javac_options + unlet! g:ale_java_javac_classpath + + let g:cp_sep = has('unix') ? ':' : ';' + + 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 + + runtime ale_linters/java/javac.vim + call ale#engine#InitBufferInfo(bufnr('')) + + call ale#test#SetFilename('dummy.java') + + let g:prefix = 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + +After: + call ale#test#RestoreDirectory() + + Restore + + unlet! g:cp_sep + unlet! g:prefix + + delfunction GetCommand + + call ale#linter#Reset() + " We need to clean up the buffer to remove the temporary directories created + " for the command. + call ale#engine#Cleanup(bufnr('')) + +Execute(The javac callback should return the correct default value): + AssertEqual g:prefix . ' -d TEMP %t', GetCommand([]) + +Execute(The javac callback should use g:ale_java_javac_classpath correctly): + let g:ale_java_javac_classpath = 'foo.jar' + + AssertEqual + \ g:prefix + \ . ' -cp ' . ale#Escape('foo.jar') + \ . ' -d TEMP %t', + \ GetCommand([]) + +Execute(The javac callback should include discovered classpaths): + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join(['/foo/bar.jar', '/xyz/abc.jar'], g:cp_sep)) + \ . ' -d TEMP %t', + \ GetCommand([ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ ]) + +Execute(The javac callback should combine discovered classpaths and manual ones): + let g:ale_java_javac_classpath = 'configured.jar' + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -d TEMP %t', + \ GetCommand([ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ ]) + + let g:ale_java_javac_classpath = 'configured.jar' . g:cp_sep . 'configured2.jar' + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ 'configured2.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -d TEMP %t', + \ GetCommand([ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ ]) + +Execute(The javac callback should detect source directories): + call ale#engine#Cleanup(bufnr('')) + noautocmd e! java_paths/src/main/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ . ' -sourcepath ' . ale#Escape( + \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/') + \ ) + \ . ' -d TEMP %t', + \ GetCommand([]) + +Execute(The javac callback should combine detected source directories and classpaths): + call ale#engine#Cleanup(bufnr('')) + call ale#test#SetFilename('java_paths/src/main/java/com/something/dummy.java') + call ale#engine#InitBufferInfo(bufnr('')) + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ . ' -cp ' . ale#Escape(join(['/foo/bar.jar', '/xyz/abc.jar'], g:cp_sep)) + \ . ' -sourcepath ' . ale#Escape( + \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/') + \ ) + \ . ' -d TEMP %t', + \ GetCommand([ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ ]) + +Execute(The javac callback should use g:ale_java_javac_options correctly): + let g:ale_java_javac_options = '--anything --else' + + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), []) + + AssertEqual + \ g:prefix + \ . ' -d TEMP --anything --else %t', + \ GetCommand([]) + +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! java_paths/src/test/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/'), + \ ale#path#Simplify(g:dir . '/java_paths/src/test/java/'), + \ ], g:cp_sep)) + \ . ' -d TEMP %t', + \ GetCommand([]) + +Execute(The javac callback should include src/main/jaxb when available): + call ale#engine#Cleanup(bufnr('')) + noautocmd e! java_paths_with_jaxb/src/main/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && javac -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/java_paths_with_jaxb/src/main/java/'), + \ ale#path#Simplify(g:dir . '/java_paths_with_jaxb/src/main/jaxb/'), + \ ], g:cp_sep)) + \ . ' -d TEMP %t', + \ GetCommand([]) diff --git a/test/command_callback/test_jscs_command_callback.vader b/test/command_callback/test_jscs_command_callback.vader new file mode 100644 index 0000000..f118c03 --- /dev/null +++ b/test/command_callback/test_jscs_command_callback.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/javascript/jscs.vim + +After: + call ale#linter#Reset() + let g:ale_javascript_jscs_executable = 'jscs' + +Execute(Should return the correct default values): + AssertEqual + \ 'jscs', + \ ale_linters#javascript#jscs#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('jscs') . ' --reporter inline --no-colors -', + \ ale_linters#javascript#jscs#GetCommand(bufnr('')) + + +Execute(Should allow using a custom executable): + let g:ale_javascript_jscs_executable = 'foobar' + + AssertEqual + \ 'foobar', + \ ale_linters#javascript#jscs#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foobar') . ' --reporter inline --no-colors -', + \ ale_linters#javascript#jscs#GetCommand(bufnr('')) diff --git a/test/command_callback/test_less_stylelint_command_callback.vader b/test/command_callback/test_less_stylelint_command_callback.vader new file mode 100644 index 0000000..a5912ec --- /dev/null +++ b/test/command_callback/test_less_stylelint_command_callback.vader @@ -0,0 +1,60 @@ +Before: + Save g:ale_less_stylelint_executable + Save g:ale_less_stylelint_use_global + Save g:ale_less_stylelint_options + + unlet! b:executable + + unlet! g:ale_less_stylelint_executable + unlet! g:ale_less_stylelint_use_global + unlet! g:ale_less_stylelint_options + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('testfile.less') + + runtime ale_linters/less/stylelint.vim + +After: + Restore + + unlet! b:executable + unlet! b:ale_less_stylelint_executable + unlet! b:ale_less_stylelint_use_global + unlet! b:ale_less_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('stylelint_paths/nested/testfile.less') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/stylelint_paths/node_modules/.bin/stylelint' + \) + + AssertEqual b:executable, ale_linters#less#stylelint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape(b:executable) . ' --stdin-filename %s', + \ ale_linters#less#stylelint#GetCommand(bufnr('')) + +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('stylelint_paths/nested/testfile.less') + + AssertEqual 'foobar', ale_linters#less#stylelint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foobar') . ' --stdin-filename %s', + \ ale_linters#less#stylelint#GetCommand(bufnr('')) + +Execute(Extra options should be configurable): + let b:ale_less_stylelint_options = '--whatever' + + AssertEqual 'stylelint', ale_linters#less#stylelint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('stylelint') . ' --whatever --stdin-filename %s', + \ ale_linters#less#stylelint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_lessc_command_callback.vader b/test/command_callback/test_lessc_command_callback.vader new file mode 100644 index 0000000..ec2899d --- /dev/null +++ b/test/command_callback/test_lessc_command_callback.vader @@ -0,0 +1,82 @@ +Before: + Save g:ale_less_lessc_executable + Save g:ale_less_lessc_use_global + Save g:ale_less_lessc_options + + unlet! b:executable + + unlet! g:ale_less_lessc_executable + unlet! g:ale_less_lessc_use_global + unlet! g:ale_less_lessc_options + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('testfile.less') + + runtime ale_linters/less/lessc.vim + +After: + Restore + + unlet! b:executable + unlet! b:ale_less_lessc_executable + unlet! b:ale_less_lessc_use_global + unlet! b:ale_less_lessc_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('lessc_paths/nested/testfile.less') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/lessc_paths/node_modules/.bin/lessc' + \) + + AssertEqual + \ b:executable, + \ ale_linters#less#lessc#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) + \ . ' --no-color --lint' + \ . ' --include-path=' + \ . ale#Escape(ale#path#Simplify(g:dir . '/lessc_paths/nested')) + \ . ' -', + \ ale_linters#less#lessc#GetCommand(bufnr('')) + +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('lessc_paths/nested/testfile.less') + + AssertEqual + \ 'foobar', + \ ale_linters#less#lessc#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('foobar') + \ . ' --no-color --lint' + \ . ' --include-path=' + \ . ale#Escape(ale#path#Simplify(g:dir . '/lessc_paths/nested')) + \ . ' -', + \ ale_linters#less#lessc#GetCommand(bufnr('')) + +Execute(Extra options should be configurable): + let b:ale_less_lessc_options = '--whatever' + + AssertEqual + \ 'lessc', + \ ale_linters#less#lessc#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('lessc') + \ . ' --no-color --lint' + \ . ' --include-path=' + \ . ale#Escape(ale#path#Simplify(g:dir)) + \ . ' --whatever' + \ . ' -', + \ ale_linters#less#lessc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_lintr_command_callback.vader b/test/command_callback/test_lintr_command_callback.vader new file mode 100644 index 0000000..e655328 --- /dev/null +++ b/test/command_callback/test_lintr_command_callback.vader @@ -0,0 +1,48 @@ +Before: + Save g:ale_r_lintr_options + + unlet! g:ale_r_lintr_options + unlet! b:ale_r_lintr_options + + runtime ale_linters/r/lintr.vim + +After: + Restore + + unlet! b:ale_r_lintr_options + + call ale#linter#Reset() + +Execute(The default lintr command should be correct): + AssertEqual + \ 'cd ' . ale#Escape(getcwd()) . ' && ' + \ . 'Rscript -e ' + \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' + \ . 'lint(cache = FALSE, commandArgs(TRUE), ' + \ . 'with_defaults())') + \ . ' %t', + \ ale_linters#r#lintr#GetCommand(bufnr('')) + +Execute(The lintr options should be configurable): + let b:ale_r_lintr_options = 'with_defaults(object_usage_linter = NULL)' + + AssertEqual + \ 'cd ' . ale#Escape(getcwd()) . ' && ' + \ . 'Rscript -e ' + \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' + \ . 'lint(cache = FALSE, commandArgs(TRUE), ' + \ . 'with_defaults(object_usage_linter = NULL))') + \ . ' %t', + \ ale_linters#r#lintr#GetCommand(bufnr('')) + +Execute(If the lint_package flag is set, lintr::lint_package should be called): + let b:ale_r_lintr_lint_package = 1 + + AssertEqual + \ 'cd ' . ale#Escape(getcwd()) . ' && ' + \ . 'Rscript -e ' + \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' + \ . 'lint_package(cache = FALSE, ' + \ . 'linters = with_defaults())') + \ . ' %t', + \ ale_linters#r#lintr#GetCommand(bufnr('')) diff --git a/test/command_callback/test_llc_command_callback.vader b/test/command_callback/test_llc_command_callback.vader new file mode 100644 index 0000000..296b277 --- /dev/null +++ b/test/command_callback/test_llc_command_callback.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_llvm_llc_executable + + unlet! g:ale_llvm_llc_executable + unlet! b:ale_llvm_llc_executable + + runtime ale_linters/llvm/llc.vim + + 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: + unlet! g:ale_llvm_llc_executable + unlet! b:ale_llvm_llc_executable + delfunction AssertHasPrefix + Restore + +Execute(llc command is customizable): + let cmd = ale_linters#llvm#llc#GetCommand(bufnr('')) + call AssertHasPrefix(cmd, ale#Escape('llc')) + + let g:ale_llvm_llc_executable = 'llc-5.0' + let cmd = ale_linters#llvm#llc#GetCommand(bufnr('')) + call AssertHasPrefix(cmd, ale#Escape('llc-5.0')) + + let b:ale_llvm_llc_executable = 'llc-4.0' + let cmd = ale_linters#llvm#llc#GetCommand(bufnr('')) + call AssertHasPrefix(cmd, ale#Escape('llc-4.0')) + +Execute(GetCommand() escapes the returned path): + let b:ale_llvm_llc_executable = '/path/space contained/llc' + let cmd = ale_linters#llvm#llc#GetCommand(bufnr('')) + call AssertHasPrefix(cmd, ale#Escape('/path/space contained/llc')) + +Execute(GetExecutable() does not escape the returned path): + let b:ale_llvm_llc_executable = '/path/space contained/llc' + AssertEqual ale_linters#llvm#llc#GetExecutable(bufnr('')), '/path/space contained/llc' diff --git a/test/command_callback/test_luac_command_callback.vader b/test/command_callback/test_luac_command_callback.vader new file mode 100644 index 0000000..f9eb4d3 --- /dev/null +++ b/test/command_callback/test_luac_command_callback.vader @@ -0,0 +1,16 @@ +Before: + runtime ale_linters/lua/luac.vim + +After: + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual ale#Escape('luac') . ' -p -', + \ join(split(ale_linters#lua#luac#GetCommand(1))) + +Execute(The luac executable should be configurable): + let g:ale_lua_luac_executable = 'luac.sh' + + AssertEqual 'luac.sh', ale_linters#lua#luac#GetExecutable(1) + AssertEqual ale#Escape('luac.sh') . ' -p -', + \ join(split(ale_linters#lua#luac#GetCommand(1))) diff --git a/test/command_callback/test_luacheck_command_callback.vader b/test/command_callback/test_luacheck_command_callback.vader new file mode 100644 index 0000000..6f7f3a0 --- /dev/null +++ b/test/command_callback/test_luacheck_command_callback.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/lua/luacheck.vim + +After: + call ale#linter#Reset() + let g:ale_lua_luacheck_options = '' + let g:ale_lua_luacheck_executable = 'luacheck' + +Execute(The lua luacheck command callback should return the correct default string): + AssertEqual ale#Escape('luacheck') . ' --formatter plain --codes --filename %s -', + \ join(split(ale_linters#lua#luacheck#GetCommand(1))) + +Execute(The lua luacheck command callback should let you set options): + let g:ale_lua_luacheck_options = '--config filename' + + AssertEqual ale#Escape('luacheck') . ' --config filename --formatter plain --codes --filename %s -', + \ join(split(ale_linters#lua#luacheck#GetCommand(1))) + +Execute(The luacheck executable should be configurable): + let g:ale_lua_luacheck_executable = 'luacheck.sh' + + AssertEqual 'luacheck.sh', ale_linters#lua#luacheck#GetExecutable(1) + AssertEqual ale#Escape('luacheck.sh') . ' --formatter plain --codes --filename %s -', + \ join(split(ale_linters#lua#luacheck#GetCommand(1))) diff --git a/test/command_callback/test_markdown_mdl_command_callback.vader b/test/command_callback/test_markdown_mdl_command_callback.vader new file mode 100644 index 0000000..3a68a4b --- /dev/null +++ b/test/command_callback/test_markdown_mdl_command_callback.vader @@ -0,0 +1,28 @@ +Before: + Save g:ale_markdown_mdl_executable + Save g:ale_markdown_mdl_options + + unlet! g:ale_markdown_mdl_executable + unlet! g:ale_markdown_mdl_options + + runtime ale_linters/markdown/mdl.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual ale_linters#markdown#mdl#GetExecutable(bufnr('')), 'mdl' + AssertEqual + \ ale_linters#markdown#mdl#GetCommand(bufnr('')), + \ ale#Escape('mdl') + +Execute(The executable and options should be configurable): + let g:ale_markdown_mdl_executable = 'foo bar' + let g:ale_markdown_mdl_options = '--wat' + + AssertEqual ale_linters#markdown#mdl#GetExecutable(bufnr('')), 'foo bar' + AssertEqual + \ ale_linters#markdown#mdl#GetCommand(bufnr('')), + \ ale#Escape('foo bar') . ' --wat' diff --git a/test/command_callback/test_mypy_command_callback.vader b/test/command_callback/test_mypy_command_callback.vader new file mode 100644 index 0000000..6a0add5 --- /dev/null +++ b/test/command_callback/test_mypy_command_callback.vader @@ -0,0 +1,97 @@ +Before: + Save g:ale_python_mypy_executable + Save g:ale_python_mypy_options + Save g:ale_python_mypy_use_global + + unlet! g:ale_python_mypy_executable + unlet! g:ale_python_mypy_options + unlet! g:ale_python_mypy_use_global + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + + runtime ale_linters/python/mypy.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.py') + +After: + Restore + + unlet! b:bin_dir + unlet! b:executable + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The mypy callbacks should return the correct default values): + AssertEqual + \ 'mypy', + \ ale_linters#python#mypy#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(g:dir) . ' && ' . ale#Escape('mypy') + \ . ' --show-column-numbers ' + \ . '--shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) + +Execute(The mypy executable should be configurable, and escaped properly): + let g:ale_python_mypy_executable = 'executable with spaces' + + AssertEqual + \ 'executable with spaces', + \ ale_linters#python#mypy#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(g:dir) . ' && ' . ale#Escape('executable with spaces') + \ . ' --show-column-numbers ' + \ . '--shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) + +Execute(The mypy command callback should let you set options): + let g:ale_python_mypy_options = '--some-option' + + AssertEqual + \ 'cd ' . ale#Escape(g:dir) . ' && ' . ale#Escape('mypy') + \ . ' --show-column-numbers --some-option ' + \ . '--shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) + +Execute(The mypy command should switch directories to the detected project root): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ 'mypy', + \ ale_linters#python#mypy#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/no_virtualenv/subdir')) + \ . ' && ' . ale#Escape('mypy') + \ . ' --show-column-numbers ' + \ . '--shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) + +Execute(The mypy callbacks should detect virtualenv directories and switch to the project root): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/mypy') + + AssertEqual + \ b:executable, + \ ale_linters#python#mypy#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) + \ . ' && ' . ale#Escape(b:executable) + \ . ' --show-column-numbers ' + \ . '--shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) + +Execute(You should able able to use the global mypy instead): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_mypy_use_global = 1 + + AssertEqual + \ 'mypy', + \ ale_linters#python#mypy#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) + \ . ' && ' . ale#Escape('mypy') + \ . ' --show-column-numbers ' + \ . '--shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) diff --git a/test/command_callback/test_nagelfar_command_callbacks.vader b/test/command_callback/test_nagelfar_command_callbacks.vader new file mode 100644 index 0000000..5c6be7f --- /dev/null +++ b/test/command_callback/test_nagelfar_command_callbacks.vader @@ -0,0 +1,42 @@ +Before: + Save g:ale_tcl_nagelfar_executable + Save g:ale_tcl_nagelfar_options + + unlet! g:ale_tcl_nagelfar_executable + unlet! b:ale_tcl_nagelfar_executable + unlet! g:ale_tcl_nagelfar_options + unlet! b:ale_tcl_nagelfar_options + + runtime ale_linters/tcl/nagelfar.vim + +After: + Restore + unlet! b:command_tail + unlet! b:ale_tcl_nagelfar_executable + unlet! b:ale_tcl_nagelfar_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'nagelfar.tcl', ale_linters#tcl#nagelfar#GetExecutable(bufnr('')) + + let b:ale_tcl_nagelfar_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#tcl#nagelfar#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('nagelfar.tcl') . ' %s', + \ ale_linters#tcl#nagelfar#GetCommand(bufnr('')) + + let b:ale_tcl_nagelfar_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . ' %s', + \ ale_linters#tcl#nagelfar#GetCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_tcl_nagelfar_options = '--something' + + AssertEqual + \ ale#Escape('nagelfar.tcl') . ' --something %s', + \ ale_linters#tcl#nagelfar#GetCommand(bufnr('')) diff --git a/test/command_callback/test_ocaml_ols_callbacks.vader b/test/command_callback/test_ocaml_ols_callbacks.vader new file mode 100644 index 0000000..d10898f --- /dev/null +++ b/test/command_callback/test_ocaml_ols_callbacks.vader @@ -0,0 +1,54 @@ +Before: + Save &filetype + Save g:ale_ocaml_ols_executable + Save g:ale_ocaml_ols_use_global + + let &filetype = 'ocaml' + unlet! g:ale_ocaml_ols_executable + unlet! g:ale_ocaml_ols_use_global + + runtime ale_linters/ocaml/ols.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The language string should be correct): + AssertEqual 'ocaml', ale#handlers#ols#GetLanguage(bufnr('')) + +Execute(The default executable should be correct): + AssertEqual 'ocaml-language-server', ale#handlers#ols#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('ocaml-language-server') . ' --stdio', + \ ale#handlers#ols#GetCommand(bufnr('')) + +Execute(The project root should be detected correctly): + AssertEqual '', ale#handlers#ols#GetProjectRoot(bufnr('')) + + call ale#test#SetFilename('ols_paths/file.ml') + + AssertEqual + \ ale#path#Simplify(g:dir . '/ols_paths'), + \ ale#handlers#ols#GetProjectRoot(bufnr('')) + +Execute(The local executable should be used when available): + call ale#test#SetFilename('ols_paths/file.ml') + + AssertEqual + \ ale#path#Simplify(g:dir . '/ols_paths/node_modules/.bin/ocaml-language-server'), + \ ale#handlers#ols#GetExecutable(bufnr('')) + +Execute(The gloabl executable should always be used when use_global is set): + let g:ale_ocaml_ols_use_global = 1 + call ale#test#SetFilename('ols_paths/file.ml') + + AssertEqual 'ocaml-language-server', ale#handlers#ols#GetExecutable(bufnr('')) + +Execute(The executable should be configurable): + let g:ale_ocaml_ols_executable = 'foobar' + + AssertEqual 'foobar', ale#handlers#ols#GetExecutable(bufnr('')) diff --git a/test/command_callback/test_perl_command_callback.vader b/test/command_callback/test_perl_command_callback.vader new file mode 100644 index 0000000..ba85e53 --- /dev/null +++ b/test/command_callback/test_perl_command_callback.vader @@ -0,0 +1,37 @@ +Before: + Save g:ale_perl_perl_executable + Save g:ale_perl_perl_options + + unlet! g:ale_perl_perl_executable + unlet! g:ale_perl_perl_options + + runtime ale_linters/perl/perl.vim + +After: + Restore + + unlet! b:ale_perl_perl_executable + unlet! b:ale_perl_perl_options + + call ale#linter#Reset() + +Execute(The default Perl command callback should be correct): + AssertEqual + \ 'perl', + \ ale_linters#perl#perl#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('perl') . ' -c -Mwarnings -Ilib %t', + \ ale_linters#perl#perl#GetCommand(bufnr('')) + +Execute(Overriding the executable and command should work): + let b:ale_perl_perl_executable = 'foobar' + let b:ale_perl_perl_options = '-w' + + AssertEqual + \ 'foobar', + \ ale_linters#perl#perl#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('foobar') . ' -w %t', + \ ale_linters#perl#perl#GetCommand(bufnr('')) diff --git a/test/command_callback/test_perlcritic_command_callback.vader b/test/command_callback/test_perlcritic_command_callback.vader new file mode 100644 index 0000000..6507868 --- /dev/null +++ b/test/command_callback/test_perlcritic_command_callback.vader @@ -0,0 +1,59 @@ +Before: + Save g:ale_perl_perlcritic_profile + Save g:ale_perl_perlcritic_options + Save g:ale_perl_perlcritic_executable + Save g:ale_perl_perlcritic_showrules + + unlet! g:ale_perl_perlcritic_options + unlet! g:ale_perl_perlcritic_executable + unlet! g:ale_perl_perlcritic_showrules + let g:ale_perl_perlcritic_profile = '' + + runtime ale_linters/perl/perlcritic.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.pl') + +After: + Restore + + unlet! b:ale_perl_perlcritic_profile + unlet! b:ale_perl_perlcritic_options + unlet! b:ale_perl_perlcritic_executable + unlet! b:ale_perl_perlcritic_showrules + unlet! b:readme_path + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The command should be correct with g:ale_perl_perlcritic_showrules off): + let b:ale_perl_perlcritic_showrules = 0 + + AssertEqual + \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m\n'' --nocolor', + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) + +Execute(The command should be correct with g:ale_perl_perlcritic_showrules on): + let b:ale_perl_perlcritic_showrules = 1 + + AssertEqual + \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m [%p]\n'' --nocolor', + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) + +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') + + AssertEqual + \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m\n'' --nocolor' + \ . ' --profile ' . ale#Escape(b:readme_path), + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) + +Execute(Extra options should be set appropriately): + let b:ale_perl_perlcritic_options = 'beep boop' + + AssertEqual + \ ale#Escape('perlcritic') . ' --verbose ''%l:%c %m\n'' --nocolor' + \ . ' beep boop', + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) diff --git a/test/command_callback/test_php_langserver_callbacks.vader b/test/command_callback/test_php_langserver_callbacks.vader new file mode 100644 index 0000000..0dc3063 --- /dev/null +++ b/test/command_callback/test_php_langserver_callbacks.vader @@ -0,0 +1,52 @@ +Before: + Save g:ale_php_langserver_executable + Save g:ale_php_langserver_config_path + Save g:ale_php_langserver_use_global + + unlet! g:ale_php_langserver_executable + unlet! g:ale_php_langserver_config_path + unlet! g:ale_php_langserver_use_global + + runtime ale_linters/php/langserver.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default executable path should be correct): + AssertEqual + \ 'php-language-server.php', + \ ale_linters#php#langserver#GetExecutable(bufnr('')) + AssertEqual + \ 'php ' . ale#Escape('php-language-server.php'), + \ ale_linters#php#langserver#GetCommand(bufnr('')) + +Execute(Vendor executables should be detected): + call ale#test#SetFilename('php-langserver-project/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/php-langserver-project/vendor/bin/php-language-server.php'), + \ ale_linters#php#langserver#GetExecutable(bufnr('')) + AssertEqual + \ 'php ' . ale#Escape(ale#path#Simplify( + \ g:dir + \ . '/php-langserver-project/vendor/bin/php-language-server.php' + \ )), + \ ale_linters#php#langserver#GetCommand(bufnr('')) + +Execute(The language string should be correct): + AssertEqual 'php', ale_linters#php#langserver#GetLanguage(bufnr('')) + +Execute(The project path should be correct for .git directories): + call ale#test#SetFilename('php-langserver-project/test.php') + call mkdir(g:dir . '/.git') + + AssertEqual g:dir, ale_linters#php#langserver#GetProjectRoot(bufnr('')) diff --git a/test/command_callback/test_phpmd_command_callbacks.vader b/test/command_callback/test_phpmd_command_callbacks.vader new file mode 100644 index 0000000..928b977 --- /dev/null +++ b/test/command_callback/test_phpmd_command_callbacks.vader @@ -0,0 +1,20 @@ +Before: + Save g:ale_php_phpmd_executable + + unlet! g:ale_php_phpmd_executable + + runtime ale_linters/php/phpmd.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(Custom executables should be used for the executable and command): + let g:ale_php_phpmd_executable = 'phpmd_test' + + AssertEqual 'phpmd_test', ale_linters#php#phpmd#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('phpmd_test') + \ . ' %s text cleancode,codesize,controversial,design,naming,unusedcode --ignore-violations-on-exit %t', + \ ale_linters#php#phpmd#GetCommand(bufnr('')) diff --git a/test/command_callback/test_phpstan_command_callbacks.vader b/test/command_callback/test_phpstan_command_callbacks.vader new file mode 100644 index 0000000..169c5bb --- /dev/null +++ b/test/command_callback/test_phpstan_command_callbacks.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_php_phpstan_executable + Save g:ale_php_phpstan_level + Save g:ale_php_phpstan_configuration + + unlet! g:ale_php_phpstan_executable + unlet! g:ale_php_phpstan_level + unlet! g:ale_php_phpstan_configuration + + runtime ale_linters/php/phpstan.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(Custom executables should be used for the executable and command): + let g:ale_php_phpstan_executable = 'phpstan_test' + + AssertEqual 'phpstan_test', ale_linters#php#phpstan#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('phpstan_test') . ' analyze -l4 --errorFormat raw %s', + \ ale_linters#php#phpstan#GetCommand(bufnr('')) + +Execute(project with level set to 3): + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + let g:ale_php_phpstan_level = 3 + + AssertEqual + \ ale#Escape('phpstan') . ' analyze -l3 --errorFormat raw %s', + \ ale_linters#php#phpstan#GetCommand(bufnr('')) + +Execute(Custom phpstan configuration file): + let g:ale_php_phpstan_configuration = 'phpstan_config' + + AssertEqual + \ ale#Escape('phpstan') . ' analyze -l4 --errorFormat raw -c phpstan_config %s', + \ ale_linters#php#phpstan#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pony_ponyc_command_callbacks.vader b/test/command_callback/test_pony_ponyc_command_callbacks.vader new file mode 100644 index 0000000..7acbfa9 --- /dev/null +++ b/test/command_callback/test_pony_ponyc_command_callbacks.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_pony_ponyc_options + + unlet! g:ale_pony_ponyc_options + unlet! b:ale_pony_ponyc_options + + runtime ale_linters/pony/ponyc.vim + +After: + Restore + unlet! b:ale_pony_ponyc_options + call ale#linter#Reset() + +Execute(The options should be used in the command): + AssertEqual + \ ale#Escape('ponyc') . ' --pass paint', + \ ale_linters#pony#ponyc#GetCommand(bufnr('')) + + let b:ale_pony_ponyc_options = 'foobar' + + AssertEqual + \ ale#Escape('ponyc') . ' foobar', + \ ale_linters#pony#ponyc#GetCommand(bufnr('')) diff --git a/test/command_callback/test_proto_command_callback.vader b/test/command_callback/test_proto_command_callback.vader new file mode 100644 index 0000000..76050c6 --- /dev/null +++ b/test/command_callback/test_proto_command_callback.vader @@ -0,0 +1,21 @@ +Before: + call ale#test#SetFilename('test.proto') + +After: + Restore + + unlet! b:ale_proto_protoc_gen_lint_options + + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual + \ 'protoc' . ' -I ' . ale#Escape(getcwd()) . ' --lint_out=. ' . '%s', + \ ale_linters#proto#protoc_gen_lint#GetCommand(bufnr('')) + +Execute(The callback should include any additional options): + let b:ale_proto_protoc_gen_lint_options = '--some-option' + + AssertEqual + \ 'protoc' . ' -I ' . ale#Escape(getcwd()) . ' --some-option --lint_out=. ' . '%s', + \ ale_linters#proto#protoc_gen_lint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_puglint_command_callback.vader b/test/command_callback/test_puglint_command_callback.vader new file mode 100644 index 0000000..f9b4a85 --- /dev/null +++ b/test/command_callback/test_puglint_command_callback.vader @@ -0,0 +1,71 @@ +Before: + Save g:ale_pug_puglint_options + Save g:ale_pug_puglint_executable + Save g:ale_pug_puglint_use_global + + let g:ale_pug_puglint_options = '' + let g:ale_pug_puglint_executable = 'pug-lint' + let g:ale_pug_puglint_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/command_callback') + + runtime ale_linters/pug/puglint.vim + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(puglint should detect local executables and package.json): + call ale#test#SetFilename('puglint_project/test.pug') + + AssertEqual + \ ale#path#Simplify(g:dir . '/puglint_project/node_modules/.bin/pug-lint'), + \ ale_linters#pug#puglint#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/package.json')) + \ . ' -r inline %t', + \ ale_linters#pug#puglint#GetCommand(bufnr('')) + +Execute(puglint should use global executables if configured): + let g:ale_pug_puglint_use_global = 1 + + call ale#test#SetFilename('puglint_project/test.pug') + + AssertEqual 'pug-lint', ale_linters#pug#puglint#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('pug-lint') + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/package.json')) + \ . ' -r inline %t', + \ ale_linters#pug#puglint#GetCommand(bufnr('')) + +Execute(puglint should detect .pug-lintrc): + call ale#test#SetFilename('puglint_project/puglint_rc_dir/subdir/test.pug') + + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/puglint_rc_dir/.pug-lintrc')) + \ . ' -r inline %t', + \ ale_linters#pug#puglint#GetCommand(bufnr('')) + +Execute(puglint should detect .pug-lintrc.js): + call ale#test#SetFilename('puglint_project/puglint_rc_js_dir/subdir/test.pug') + + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/puglint_rc_js_dir/.pug-lintrc.js')) + \ . ' -r inline %t', + \ ale_linters#pug#puglint#GetCommand(bufnr('')) + +Execute(puglint should detect .pug-lintrc.json): + call ale#test#SetFilename('puglint_project/puglint_rc_json_dir/subdir/test.pug') + + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/node_modules/.bin/pug-lint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/puglint_project/puglint_rc_json_dir/.pug-lintrc.json')) + \ . ' -r inline %t', + \ ale_linters#pug#puglint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pycodestyle_command_callback.vader b/test/command_callback/test_pycodestyle_command_callback.vader new file mode 100644 index 0000000..5b309e1 --- /dev/null +++ b/test/command_callback/test_pycodestyle_command_callback.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_python_pycodestyle_executable + Save g:ale_python_pycodestyle_options + Save g:ale_python_pycodestyle_use_global + + runtime ale_linters/python/pycodestyle.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The pycodestyle command callback should return default string): + AssertEqual ale#Escape('pycodestyle') . ' -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(The pycodestyle command callback should allow options): + let g:ale_python_pycodestyle_options = '--exclude=test*.py' + + AssertEqual ale#Escape('pycodestyle') . ' --exclude=test*.py -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(The pycodestyle executable should be configurable): + let g:ale_python_pycodestyle_executable = '~/.local/bin/pycodestyle' + + AssertEqual ale#Escape('~/.local/bin/pycodestyle') . ' -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pyflakes_command_callback.vader b/test/command_callback/test_pyflakes_command_callback.vader new file mode 100644 index 0000000..e8486ca --- /dev/null +++ b/test/command_callback/test_pyflakes_command_callback.vader @@ -0,0 +1,49 @@ +Before: + Save g:ale_python_pyflakes_executable + Save g:ale_python_pyflakes_use_global + + unlet! g:ale_python_pyflakes_executable + unlet! g:ale_python_pyflakes_use_global + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + + call ale#test#SetDirectory('/testplugin/test/command_callback') + + runtime ale_linters/python/pyflakes.vim + +After: + Restore + + unlet! b:bin_dir + unlet! b:executable + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The pyflakes command callback should return default string): + AssertEqual ale#Escape('pyflakes') . ' %t', + \ ale_linters#python#pyflakes#GetCommand(bufnr('')) + +Execute(The pyflakes executable should be configurable): + let g:ale_python_pyflakes_executable = '~/.local/bin/pyflakes' + + AssertEqual ale#Escape('~/.local/bin/pyflakes') . ' %t', + \ ale_linters#python#pyflakes#GetCommand(bufnr('')) + +Execute(The pyflakes executable should be run from the virtualenv path): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/pyflakes' + \) + + AssertEqual ale#Escape(b:executable) . ' %t', + \ ale_linters#python#pyflakes#GetCommand(bufnr('')) + +Execute(You should be able to override the pyflakes virtualenv lookup): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_pyflakes_use_global = 1 + + AssertEqual ale#Escape('pyflakes') . ' %t', + \ ale_linters#python#pyflakes#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pylint_command_callback.vader b/test/command_callback/test_pylint_command_callback.vader new file mode 100644 index 0000000..1ff8e35 --- /dev/null +++ b/test/command_callback/test_pylint_command_callback.vader @@ -0,0 +1,85 @@ +Before: + Save g:ale_python_pylint_executable + Save g:ale_python_pylint_options + Save g:ale_python_pylint_use_global + + unlet! g:ale_python_pylint_executable + unlet! g:ale_python_pylint_options + unlet! g:ale_python_pylint_use_global + + runtime ale_linters/python/pylint.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + + 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' + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! b:bin_dir + unlet! b:executable + +Execute(The pylint callbacks should return the correct default values): + AssertEqual + \ 'pylint', + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('pylint') . ' ' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(The pylint executable should be configurable, and escaped properly): + let g:ale_python_pylint_executable = 'executable with spaces' + + AssertEqual + \ 'executable with spaces', + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('executable with spaces') . ' ' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(The pylint command callback should let you set options): + let g:ale_python_pylint_options = '--some-option' + + AssertEqual + \ ale#Escape('pylint') . ' --some-option' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(The pylint callbacks shouldn't detect virtualenv directories where they don't exist): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/no_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ 'pylint', + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('pylint') . ' ' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(The pylint callbacks should detect virtualenv directories): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/pylint' + \) + + AssertEqual + \ b:executable, + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) . ' ' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(You should able able to use the global pylint instead): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + let g:ale_python_pylint_use_global = 1 + + AssertEqual + \ 'pylint', + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('pylint') . ' ' . b:command_tail, + \ ale_linters#python#pylint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pyls_command_callback.vader b/test/command_callback/test_pyls_command_callback.vader new file mode 100644 index 0000000..06ea718 --- /dev/null +++ b/test/command_callback/test_pyls_command_callback.vader @@ -0,0 +1,49 @@ +Before: + Save g:ale_python_pyls_executable + Save g:ale_python_pyls_use_global + + unlet! g:ale_python_pyls_executable + unlet! g:ale_python_pyls_use_global + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + + call ale#test#SetDirectory('/testplugin/test/command_callback') + + runtime ale_linters/python/pyls.vim + +After: + Restore + + unlet! b:bin_dir + unlet! b:executable + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The pyls command callback should return default string): + AssertEqual ale#Escape('pyls'), + \ ale_linters#python#pyls#GetCommand(bufnr('')) + +Execute(The pyls executable should be configurable): + let g:ale_python_pyls_executable = '~/.local/bin/pyls' + + AssertEqual ale#Escape('~/.local/bin/pyls'), + \ ale_linters#python#pyls#GetCommand(bufnr('')) + +Execute(The pyls executable should be run from the virtualenv path): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/pyls' + \) + + AssertEqual ale#Escape(b:executable), + \ ale_linters#python#pyls#GetCommand(bufnr('')) + +Execute(You should be able to override the pyls virtualenv lookup): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_pyls_use_global = 1 + + AssertEqual ale#Escape('pyls'), + \ ale_linters#python#pyls#GetCommand(bufnr('')) diff --git a/test/command_callback/test_rails_best_practices_command_callback.vader b/test/command_callback/test_rails_best_practices_command_callback.vader new file mode 100644 index 0000000..b4d2e82 --- /dev/null +++ b/test/command_callback/test_rails_best_practices_command_callback.vader @@ -0,0 +1,57 @@ +Before: + Save g:ale_ruby_rails_best_practices_executable + + let g:ale_ruby_rails_best_practices_executable = 'rails_best_practices' + + runtime ale_linters/ruby/rails_best_practices.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('../ruby_fixtures/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 . '/../ruby_fixtures/valid_rails_app') + let b:suffix = has('win32') ? '; type %t' : '' + +After: + Restore + + unlet! b:args + unlet! b:app_path + unlet! b:suffix + + call ale#test#RestoreDirectory() + +Execute(Executable should default to rails_best_practices): + AssertEqual + \ ale#Escape('rails_best_practices') + \ . ' ' . b:args + \ . ' ' . ale#Escape(b:app_path) + \ . b:suffix, + \ ale_linters#ruby#rails_best_practices#GetCommand(bufnr('')) + +Execute(Should be able to set a custom executable): + let g:ale_ruby_rails_best_practices_executable = 'bin/rails_best_practices' + + AssertEqual + \ ale#Escape('bin/rails_best_practices') + \ . ' ' . b:args + \ . ' ' . ale#Escape(b:app_path) + \ . b:suffix, + \ ale_linters#ruby#rails_best_practices#GetCommand(bufnr('')) + +Execute(Setting bundle appends 'exec rails_best_practices'): + let g:ale_ruby_rails_best_practices_executable = 'path to/bundle' + + AssertEqual + \ ale#Escape('path to/bundle') . ' exec rails_best_practices' + \ . ' ' . b:args + \ . ' ' . ale#Escape(b:app_path) + \ . b:suffix, + \ ale_linters#ruby#rails_best_practices#GetCommand(bufnr('')) + +Execute(Command callback should be empty when not in a valid Rails app): + call ale#test#SetFilename('../ruby_fixtures/not_a_rails_app/test.rb') + + AssertEqual + \ '', + \ ale_linters#ruby#rails_best_practices#GetCommand(bufnr('')) diff --git a/test/command_callback/test_reason_ols_callbacks.vader b/test/command_callback/test_reason_ols_callbacks.vader new file mode 100644 index 0000000..5fb39af --- /dev/null +++ b/test/command_callback/test_reason_ols_callbacks.vader @@ -0,0 +1,54 @@ +Before: + Save &filetype + Save g:ale_reason_ols_executable + Save g:ale_reason_ols_use_global + + let &filetype = 'reason' + unlet! g:ale_reason_ols_executable + unlet! g:ale_reason_ols_use_global + + runtime ale_linters/reason/ols.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The language string should be correct): + AssertEqual 'reason', ale#handlers#ols#GetLanguage(bufnr('')) + +Execute(The default executable should be correct): + AssertEqual 'ocaml-language-server', ale#handlers#ols#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('ocaml-language-server') . ' --stdio', + \ ale#handlers#ols#GetCommand(bufnr('')) + +Execute(The project root should be detected correctly): + AssertEqual '', ale#handlers#ols#GetProjectRoot(bufnr('')) + + call ale#test#SetFilename('ols_paths/file.re') + + AssertEqual + \ ale#path#Simplify(g:dir . '/ols_paths'), + \ ale#handlers#ols#GetProjectRoot(bufnr('')) + +Execute(The local executable should be used when available): + call ale#test#SetFilename('ols_paths/file.re') + + AssertEqual + \ ale#path#Simplify(g:dir . '/ols_paths/node_modules/.bin/ocaml-language-server'), + \ ale#handlers#ols#GetExecutable(bufnr('')) + +Execute(The gloabl executable should always be used when use_global is set): + let g:ale_reason_ols_use_global = 1 + call ale#test#SetFilename('ols_paths/file.re') + + AssertEqual 'ocaml-language-server', ale#handlers#ols#GetExecutable(bufnr('')) + +Execute(The executable should be configurable): + let g:ale_reason_ols_executable = 'foobar' + + AssertEqual 'foobar', ale#handlers#ols#GetExecutable(bufnr('')) diff --git a/test/command_callback/test_rubocop_command_callback.vader b/test/command_callback/test_rubocop_command_callback.vader new file mode 100644 index 0000000..f0aa194 --- /dev/null +++ b/test/command_callback/test_rubocop_command_callback.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_ruby_rubocop_executable + + let g:ale_ruby_rubocop_executable = 'rubocop' + + runtime ale_linters/ruby/rubocop.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('dummy.rb') + +After: + Restore + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(Executable should default to rubocop): + AssertEqual + \ ale#Escape('rubocop') + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')), + \ ale_linters#ruby#rubocop#GetCommand(bufnr('')) + +Execute(Should be able to set a custom executable): + let g:ale_ruby_rubocop_executable = 'bin/rubocop' + + AssertEqual + \ ale#Escape('bin/rubocop') + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')), + \ ale_linters#ruby#rubocop#GetCommand(bufnr('')) + +Execute(Setting bundle appends 'exec rubocop'): + let g:ale_ruby_rubocop_executable = 'path to/bundle' + + AssertEqual + \ ale#Escape('path to/bundle') . ' exec rubocop' + \ . ' --format json --force-exclusion --stdin ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')), + \ ale_linters#ruby#rubocop#GetCommand(bufnr('')) diff --git a/test/command_callback/test_ruby_command_callback.vader b/test/command_callback/test_ruby_command_callback.vader new file mode 100644 index 0000000..3813d56 --- /dev/null +++ b/test/command_callback/test_ruby_command_callback.vader @@ -0,0 +1,25 @@ +Before: + Save g:ale_ruby_ruby_executable + + unlet! g:ale_ruby_ruby_executable + + runtime ale_linters/ruby/ruby.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual 'ruby', ale_linters#ruby#ruby#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('ruby') . ' -w -c -T1 %t', + \ ale_linters#ruby#ruby#GetCommand(bufnr('')) + +Execute(The executable should be configurable): + let g:ale_ruby_ruby_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#ruby#ruby#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foobar') . ' -w -c -T1 %t', + \ ale_linters#ruby#ruby#GetCommand(bufnr('')) diff --git a/test/command_callback/test_rust_rls_callbacks.vader b/test/command_callback/test_rust_rls_callbacks.vader new file mode 100644 index 0000000..693d6e9 --- /dev/null +++ b/test/command_callback/test_rust_rls_callbacks.vader @@ -0,0 +1,41 @@ +Before: + Save g:ale_rust_rls_executable + Save g:ale_rust_rls_toolchain + + unlet! g:ale_rust_rls_executable + unlet! g:ale_rust_rls_toolchain + + runtime ale_linters/rust/rls.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default executable path should be correct): + AssertEqual 'rls', ale_linters#rust#rls#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('rls') . ' +' . ale#Escape('nightly'), + \ ale_linters#rust#rls#GetCommand(bufnr('')) + +Execute(The toolchain should be configurable): + let g:ale_rust_rls_toolchain = 'stable' + + AssertEqual + \ ale#Escape('rls') . ' +' . ale#Escape('stable'), + \ ale_linters#rust#rls#GetCommand(bufnr('')) + +Execute(The language string should be correct): + AssertEqual 'rust', ale_linters#rust#rls#GetLanguage(bufnr('')) + +Execute(The project root should be detected correctly): + AssertEqual '', ale_linters#rust#rls#GetProjectRoot(bufnr('')) + + call ale#test#SetFilename('rust-rls-project/test.rs') + + AssertEqual + \ ale#path#Simplify(g:dir . '/rust-rls-project'), + \ ale_linters#rust#rls#GetProjectRoot(bufnr('')) diff --git a/test/command_callback/test_rustc_command_callback.vader b/test/command_callback/test_rustc_command_callback.vader new file mode 100644 index 0000000..fe46c9a --- /dev/null +++ b/test/command_callback/test_rustc_command_callback.vader @@ -0,0 +1,37 @@ +Before: + Save g:ale_rust_rustc_options + + unlet! g:ale_rust_rustc_options + + runtime ale_linters/rust/rustc.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + unlet! b:ale_rust_rustc_options + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default command should be correct): + AssertEqual + \ 'rustc --error-format=json -Z no-trans -', + \ ale_linters#rust#rustc#RustcCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_rust_rustc_options = '--foo' + + AssertEqual + \ 'rustc --error-format=json --foo -', + \ ale_linters#rust#rustc#RustcCommand(bufnr('')) + +Execute(Some default paths should be included when the project is a Cargo project): + call ale#test#SetFilename('cargo_paths/test.rs') + + AssertEqual + \ 'rustc --error-format=json -Z no-trans' + \ . ' -L ' . ale#Escape(ale#path#GetAbsPath(g:dir, 'cargo_paths/target/debug/deps')) + \ . ' -L ' . ale#Escape(ale#path#GetAbsPath(g:dir, 'cargo_paths/target/release/deps')) + \ . ' -', + \ ale_linters#rust#rustc#RustcCommand(bufnr('')) diff --git a/test/command_callback/test_scalastyle_command_callback.vader b/test/command_callback/test_scalastyle_command_callback.vader new file mode 100644 index 0000000..953d57b --- /dev/null +++ b/test/command_callback/test_scalastyle_command_callback.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_scala_scalastyle_options + Save g:ale_scalastyle_config_loc + + unlet! g:ale_scala_scalastyle_options + unlet! g:ale_scalastyle_config_loc + + runtime ale_linters/scala/scalastyle.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(Should return the correct default command): + AssertEqual + \ 'scalastyle %t', + \ ale_linters#scala#scalastyle#GetCommand(bufnr('')) + +Execute(Should allow using a custom config file): + let g:ale_scalastyle_config_loc = '/dooper/config.xml' + + AssertEqual + \ 'scalastyle' + \ . ' --config ' . ale#Escape('/dooper/config.xml') + \ . ' %t', + \ ale_linters#scala#scalastyle#GetCommand(bufnr('')) + +Execute(Should allow using custom options): + let g:ale_scala_scalastyle_options = '--warnings false --quiet true' + + AssertEqual + \ 'scalastyle' + \ . ' --warnings false --quiet true' + \ . ' %t', + \ ale_linters#scala#scalastyle#GetCommand(bufnr('')) diff --git a/test/command_callback/test_shellcheck_command_callback.vader b/test/command_callback/test_shellcheck_command_callback.vader new file mode 100644 index 0000000..68694b6 --- /dev/null +++ b/test/command_callback/test_shellcheck_command_callback.vader @@ -0,0 +1,129 @@ +Before: + Save g:ale_sh_shellcheck_exclusions + Save g:ale_sh_shellcheck_executable + Save g:ale_sh_shellcheck_options + + unlet! g:ale_sh_shellcheck_exclusions + unlet! g:ale_sh_shellcheck_executable + unlet! g:ale_sh_shellcheck_options + + runtime ale_linters/sh/shellcheck.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.sh') + + let b:prefix = 'cd ' . ale#Escape(ale#path#Simplify(g:dir)) . ' && ' + let b:suffix = ' -f gcc -' + +After: + Restore + + unlet! b:ale_sh_shellcheck_exclusions + unlet! b:ale_sh_shellcheck_executable + unlet! b:ale_sh_shellcheck_options + unlet! b:is_bash + unlet! b:prefix + + call ale#test#RestoreDirectory() + + call ale#linter#Reset() + +Execute(The default shellcheck command should be correct): + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), []) + +Execute(The shellcheck command should accept options): + let b:ale_sh_shellcheck_options = '--foobar' + + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . ' --foobar' . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), []) + +Execute(The shellcheck command should accept options and exclusions): + let b:ale_sh_shellcheck_options = '--foobar' + let b:ale_sh_shellcheck_exclusions = 'foo,bar' + + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . ' --foobar -e foo,bar' . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), []) + +Execute(The shellcheck command should include the dialect): + let b:is_bash = 1 + + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . ' -s bash' . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), []) + +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' + + AssertEqual + \ b:prefix + \ . ale#Escape('shellcheck') + \ . ' -s bash --foobar -e foo,bar' + \ . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), []) + +Execute(The VersionCheck function should return the --version command): + AssertEqual + \ ale#Escape('shellcheck') . ' --version', + \ ale_linters#sh#shellcheck#VersionCheck(bufnr('')) + + let g:ale_sh_shellcheck_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar') . ' --version', + \ ale_linters#sh#shellcheck#VersionCheck(bufnr('')) + +Execute(The -x option should be added when the version is new enough): + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . ' -x' . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), [ + \ 'ShellCheck - shell script analysis tool', + \ 'version: 0.4.4', + \ 'license: GNU General Public License, version 3', + \ 'website: http://www.shellcheck.net', + \ ]) + + " We should cache the version check + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . ' -x' . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#sh#shellcheck#VersionCheck(bufnr('')) + +Execute(The version check shouldn't be run again for new versions): + call ale_linters#sh#shellcheck#GetCommand(bufnr(''), [ + \ 'ShellCheck - shell script analysis tool', + \ 'version: 0.4.4', + \ 'license: GNU General Public License, version 3', + \ 'website: http://www.shellcheck.net', + \]) + +Execute(The -x option should not be added when the version is too old): + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), [ + \ 'ShellCheck - shell script analysis tool', + \ 'version: 0.3.9', + \ 'license: GNU General Public License, version 3', + \ 'website: http://www.shellcheck.net', + \ ]) + + " We should cache the version check + AssertEqual + \ b:prefix . ale#Escape('shellcheck') . b:suffix, + \ ale_linters#sh#shellcheck#GetCommand(bufnr(''), []) + +Execute(The version check shouldn't be run again for old versions): + call ale_linters#sh#shellcheck#GetCommand(bufnr(''), [ + \ 'ShellCheck - shell script analysis tool', + \ 'version: 0.3.9', + \ 'license: GNU General Public License, version 3', + \ 'website: http://www.shellcheck.net', + \]) + + AssertEqual '', ale_linters#sh#shellcheck#VersionCheck(bufnr('')) diff --git a/test/command_callback/test_slimlint_command_callback.vader b/test/command_callback/test_slimlint_command_callback.vader new file mode 100644 index 0000000..38588a1 --- /dev/null +++ b/test/command_callback/test_slimlint_command_callback.vader @@ -0,0 +1,39 @@ +Before: + runtime ale_linters/slim/slimlint.vim + + let g:default_command = 'slim-lint %t' + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + unlet! g:default_command + unlet! b:conf + + call ale#linter#Reset() + call ale#test#RestoreDirectory() + +Execute(The default command should be correct): + AssertEqual g:default_command, ale_linters#slim#slimlint#GetCommand(bufnr('')) + +Execute(The command should have the .rubocop.yml prepended as an env var if one exists): + call ale#test#SetFilename('../slimlint-test-files/subdir/file.slim') + + let b:conf = ale#path#Simplify(g:dir . '/../slimlint-test-files/.rubocop.yml') + + if has('win32') + " Windows uses 'set var=... && command' + AssertEqual + \ 'set SLIM_LINT_RUBOCOP_CONF=' + \ . ale#Escape(b:conf) + \ . ' && ' . g:default_command, + \ ale_linters#slim#slimlint#GetCommand(bufnr('')) + else + " Unix uses 'var=... command' + AssertEqual + \ 'SLIM_LINT_RUBOCOP_CONF=' + \ . ale#Escape(b:conf) + \ . ' ' . g:default_command, + \ ale_linters#slim#slimlint#GetCommand(bufnr('')) + endif diff --git a/test/command_callback/test_standard_command_callback.vader b/test/command_callback/test_standard_command_callback.vader new file mode 100644 index 0000000..3dee285 --- /dev/null +++ b/test/command_callback/test_standard_command_callback.vader @@ -0,0 +1,87 @@ +Before: + Save g:ale_javascript_standard_executable + Save g:ale_javascript_standard_use_global + Save g:ale_javascript_standard_options + + unlet! b:executable + unlet! g:ale_javascript_standard_executable + unlet! b:ale_javascript_standard_executable + unlet! g:ale_javascript_standard_use_global + unlet! g:ale_javascript_standard_options + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('testfile.js') + + runtime ale_linters/javascript/standard.vim + +After: + Restore + + unlet! b:executable + + call ale#test#SetFilename('test.txt') + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(bin/cmd.js paths should be preferred): + call ale#test#SetFilename('standard-test-files/with-cmd/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js' + \) + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(b:executable) + \ . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(.bin directories should be used too): + call ale#test#SetFilename('standard-test-files/with-bin/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/standard-test-files/with-bin/node_modules/.bin/standard' + \) + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) + \ . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(The global executable should be used otherwise): + AssertEqual + \ 'standard', + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('standard') . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(The global executable should be configurable): + let b:ale_javascript_standard_executable = 'foobar' + + AssertEqual + \ 'foobar', + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('foobar') . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(The options should be configurable): + let b:ale_javascript_standard_options = '--wat' + + AssertEqual + \ ale#Escape('standard') . ' --wat --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) diff --git a/test/command_callback/test_staticcheck_command_callback.vader b/test/command_callback/test_staticcheck_command_callback.vader new file mode 100644 index 0000000..e9628eb --- /dev/null +++ b/test/command_callback/test_staticcheck_command_callback.vader @@ -0,0 +1,41 @@ +Before: + Save b:ale_go_staticcheck_options + Save b:ale_go_staticcheck_lint_package + + let b:ale_go_staticcheck_options = '' + let b:ale_go_staticcheck_lint_package = 0 + + runtime ale_linters/go/staticcheck.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.go') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The staticcheck callback should return the right defaults): + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'staticcheck ' + \ . ale#Escape(expand('%' . ':t')), + \ ale_linters#go#staticcheck#GetCommand(bufnr('')) + +Execute(The staticcheck callback should use configured options): + let b:ale_go_staticcheck_options = '-test' + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'staticcheck ' + \ . '-test ' . ale#Escape(expand('%' . ':t')), + \ ale_linters#go#staticcheck#GetCommand(bufnr('')) + +Execute(The staticcheck `lint_package` option should use the correct command): + let b:ale_go_staticcheck_lint_package = 1 + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'staticcheck .', + \ ale_linters#go#staticcheck#GetCommand(bufnr('')) diff --git a/test/command_callback/test_swaglint_command_callback.vader b/test/command_callback/test_swaglint_command_callback.vader new file mode 100644 index 0000000..51a1009 --- /dev/null +++ b/test/command_callback/test_swaglint_command_callback.vader @@ -0,0 +1,43 @@ +Before: + runtime ale_linters/yaml/swaglint.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + let g:ale_yaml_swaglint_executable = 'swaglint' + let g:ale_yaml_swaglint_use_global = 0 + + call ale#linter#Reset() + +Execute(The yaml swaglint command callback should return the correct default string): + AssertEqual 'swaglint', + \ ale_linters#yaml#swaglint#GetExecutable(bufnr('')) + AssertEqual 'swaglint -r compact --stdin', + \ ale_linters#yaml#swaglint#GetCommand(bufnr('')) + +Execute(The yaml swaglint command callback should be configurable): + let g:ale_yaml_swaglint_executable = '~/.local/bin/swaglint' + AssertEqual '~/.local/bin/swaglint', + \ ale_linters#yaml#swaglint#GetExecutable(bufnr('')) + AssertEqual '~/.local/bin/swaglint -r compact --stdin', + \ ale_linters#yaml#swaglint#GetCommand(bufnr('')) + +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 + AssertEqual '/usr/local/bin/swaglint', + \ ale_linters#yaml#swaglint#GetExecutable(bufnr('')) + AssertEqual '/usr/local/bin/swaglint -r compact --stdin', + \ ale_linters#yaml#swaglint#GetCommand(bufnr('')) + +Execute(The yaml swaglint command callback should allow a local installation to be used): + call ale#test#SetFilename('swaglint_paths/docs/swagger.yaml') + + AssertEqual + \ ale#path#Simplify(g:dir . '/swaglint_paths/node_modules/.bin/swaglint'), + \ ale_linters#yaml#swaglint#GetExecutable(bufnr('')) + + AssertEqual + \ ale#path#Simplify(g:dir . '/swaglint_paths/node_modules/.bin/swaglint') + \ . ' -r compact --stdin', + \ ale_linters#yaml#swaglint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_terraform_tflint_command_callback.vader b/test/command_callback/test_terraform_tflint_command_callback.vader new file mode 100644 index 0000000..a4ae56b --- /dev/null +++ b/test/command_callback/test_terraform_tflint_command_callback.vader @@ -0,0 +1,30 @@ +Before: + Save g:ale_terraform_tflint_executable + Save g:ale_terraform_tflint_options + + runtime ale_linters/terraform/tflint.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(The default executable should be configurable): + AssertEqual 'tflint', ale_linters#terraform#tflint#GetExecutable(bufnr('')) + + let g:ale_terraform_tflint_executable = 'asdf' + + AssertEqual 'asdf', ale_linters#terraform#tflint#GetExecutable(bufnr('')) + +Execute(The default command should be good): + let g:ale_terraform_tflint_executable = 'tflint' + AssertEqual + \ ale#Escape('tflint') . ' -f json %t', + \ ale_linters#terraform#tflint#GetCommand(bufnr('')) + +Execute(Overriding things should work): + let g:ale_terraform_tflint_executable = 'fnord' + let g:ale_terraform_tflint_options = '--whatever' + AssertEqual + \ ale#Escape('fnord') . ' --whatever -f json %t', + \ ale_linters#terraform#tflint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_thrift_command_callback.vader b/test/command_callback/test_thrift_command_callback.vader new file mode 100644 index 0000000..7d4e436 --- /dev/null +++ b/test/command_callback/test_thrift_command_callback.vader @@ -0,0 +1,85 @@ +Before: + Save g:ale_thrift_thrift_executable + Save g:ale_thrift_thrift_generators + Save g:ale_thrift_thrift_includes + Save g:ale_thrift_thrift_options + + unlet! b:ale_thrift_thrift_executable + unlet! b:ale_thrift_thrift_generators + unlet! b:ale_thrift_thrift_includes + unlet! b:ale_thrift_thrift_options + + function! GetCommand(buffer) abort + call ale#engine#InitBufferInfo(a:buffer) + let l: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 + + runtime ale_linters/thrift/thrift.vim + +After: + Restore + + delfunction GetCommand + unlet! b:ale_thrift_thrift_executable + unlet! b:ale_thrift_thrift_generators + unlet! b:ale_thrift_thrift_includes + unlet! b:ale_thrift_thrift_options + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'thrift', ale_linters#thrift#thrift#GetExecutable(bufnr('')) + + let b:ale_thrift_thrift_executable = 'foobar' + AssertEqual 'foobar', ale_linters#thrift#thrift#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('thrift') . ' --gen cpp -strict -out TEMP %t', + \ GetCommand(bufnr('%')) + + let b:ale_thrift_thrift_executable = 'foobar' + AssertEqual + \ ale#Escape('foobar') . ' --gen cpp -strict -out TEMP %t', + \ GetCommand(bufnr('%')) + +Execute(The list of generators should be configurable): + let b:ale_thrift_thrift_generators = ['java', 'py:dynamic'] + + AssertEqual + \ ale#Escape('thrift') . ' --gen java --gen py:dynamic -strict -out TEMP %t', + \ GetCommand(bufnr('%')) + + let b:ale_thrift_thrift_generators = [] + + AssertEqual + \ ale#Escape('thrift') . ' --gen cpp -strict -out TEMP %t', + \ GetCommand(bufnr('%')) + +Execute(The list of include paths should be configurable): + let b:ale_thrift_thrift_includes = ['included/path'] + + AssertEqual + \ ale#Escape('thrift') + \ . ' --gen cpp' + \ . ' -I included/path' + \ . ' -strict -out TEMP %t', + \ GetCommand(bufnr('%')) + +Execute(The string of compiler options should be configurable): + let b:ale_thrift_thrift_options = '-strict --allow-64bit-consts' + + AssertEqual + \ ale#Escape('thrift') + \ . ' --gen cpp -strict --allow-64bit-consts' + \ . ' -out TEMP %t', + \ GetCommand(bufnr('%')) diff --git a/test/command_callback/test_tslint_command_callback.vader b/test/command_callback/test_tslint_command_callback.vader new file mode 100644 index 0000000..4ad42fa --- /dev/null +++ b/test/command_callback/test_tslint_command_callback.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_typescript_tslint_executable + Save g:ale_typescript_tslint_config_path + Save g:ale_typescript_tslint_rules_dir + Save g:ale_typescript_tslint_use_global + + unlet! g:ale_typescript_tslint_executable + unlet! g:ale_typescript_tslint_config_path + unlet! g:ale_typescript_tslint_rules_dir + unlet! g:ale_typescript_tslint_use_global + + runtime ale_linters/typescript/tslint.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('test.ts') + +After: + Restore + + unlet! b:ale_typescript_tslint_rules_dir + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default tslint command should be correct): + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'tslint --format json %t', + \ ale_linters#typescript#tslint#GetCommand(bufnr('')) + +Execute(The rules directory option should be included if set): + let b:ale_typescript_tslint_rules_dir = '/foo/bar' + + AssertEqual + \ 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . 'tslint --format json' + \ . ' -r ' . ale#Escape('/foo/bar') + \ . ' %t', + \ ale_linters#typescript#tslint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_write_good_command_callback.vader b/test/command_callback/test_write_good_command_callback.vader new file mode 100644 index 0000000..8d9e9a0 --- /dev/null +++ b/test/command_callback/test_write_good_command_callback.vader @@ -0,0 +1,65 @@ +Before: + 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#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('testfile.txt') + + call ale#handlers#writegood#ResetOptions() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The global executable should be used when the local one cannot be found): + AssertEqual 'write-good', ale#handlers#writegood#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('write-good') . ' %t', + \ ale#handlers#writegood#GetCommand(bufnr('')) + +Execute(The options should be used in the command): + let g:ale_writegood_options = '--foo --bar' + + AssertEqual + \ ale#Escape('write-good') . ' --foo --bar %t', + \ ale#handlers#writegood#GetCommand(bufnr('')) + +Execute(Should use the node_modules/.bin executable, if available): + call ale#test#SetFilename('write-good-node-modules/test.txt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/write-good-node-modules/node_modules/.bin/write-good'), + \ ale#handlers#writegood#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape(ale#path#Simplify(g:dir . '/write-good-node-modules/node_modules/.bin/write-good')) + \ . ' %t', + \ ale#handlers#writegood#GetCommand(bufnr('')) + +Execute(Should use the node_modules/write-good executable, if available): + call ale#test#SetFilename('write-good-node-modules-2/test.txt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/write-good-node-modules-2/node_modules/write-good/bin/write-good.js'), + \ ale#handlers#writegood#GetExecutable(bufnr('')) + AssertEqual + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/write-good-node-modules-2/node_modules/write-good/bin/write-good.js')) + \ . ' %t', + \ ale#handlers#writegood#GetCommand(bufnr('')) + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('write-good-node-modules-2/test.txt') + + let g:ale_writegood_executable = 'foo-bar' + let g:ale_writegood_use_global = 1 + + AssertEqual 'foo-bar', ale#handlers#writegood#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('foo-bar') . ' %t', + \ ale#handlers#writegood#GetCommand(bufnr('')) diff --git a/test/command_callback/test_xmllint_command_callback.vader b/test/command_callback/test_xmllint_command_callback.vader new file mode 100644 index 0000000..12ca15d --- /dev/null +++ b/test/command_callback/test_xmllint_command_callback.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/xml/xmllint.vim + +After: + let g:ale_xml_xmllint_options = '' + let g:ale_xml_xmllint_executable = 'xmllint' + + call ale#linter#Reset() + +Execute(The xml xmllint command callback should return the correct default string): + AssertEqual ale#Escape('xmllint') . ' --noout -', + \ join(split(ale_linters#xml#xmllint#GetCommand(1))) + +Execute(The xml xmllint command callback should let you set options): + let g:ale_xml_xmllint_options = '--xinclude --postvalid' + + AssertEqual ale#Escape('xmllint') . ' --xinclude --postvalid --noout -', + \ join(split(ale_linters#xml#xmllint#GetCommand(1))) + +Execute(The xmllint executable should be configurable): + let g:ale_xml_xmllint_executable = '~/.local/bin/xmllint' + + AssertEqual '~/.local/bin/xmllint', ale_linters#xml#xmllint#GetExecutable(1) + AssertEqual ale#Escape('~/.local/bin/xmllint') . ' --noout -', + \ join(split(ale_linters#xml#xmllint#GetCommand(1))) diff --git a/test/command_callback/write-good-node-modules-2/node_modules/write-good/bin/write-good.js b/test/command_callback/write-good-node-modules-2/node_modules/write-good/bin/write-good.js new file mode 100644 index 0000000..e69de29 diff --git a/test/command_callback/write-good-node-modules/node_modules/.bin/write-good b/test/command_callback/write-good-node-modules/node_modules/.bin/write-good new file mode 100644 index 0000000..e69de29 diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader new file mode 100644 index 0000000..49d485f --- /dev/null +++ b/test/completion/test_completion_events.vader @@ -0,0 +1,172 @@ +Before: + Save g:ale_completion_enabled + Save g:ale_completion_delay + Save g:ale_completion_max_suggestions + Save g:ale_completion_experimental_lsp_support + Save &l:omnifunc + Save &l:completeopt + + unlet! g:ale_completion_experimental_lsp_support + + let g:ale_completion_enabled = 1 + let g:get_completions_called = 0 + let g:feedkeys_calls = [] + + runtime autoload/ale/util.vim + + function! ale#util#FeedKeys(string, mode) abort + call add(g:feedkeys_calls, [a:string, a: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() + let g:get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + sleep 1m + + AssertEqual a:expect_success, g:get_completions_called + endfunction + +After: + Restore + + unlet! g:get_completions_called + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completopt + unlet! b:ale_completion_info + unlet! b:ale_completion_response + unlet! b:ale_completion_parser + unlet! b:ale_complete_done_time + unlet! g:ale_completion_experimental_lsp_support + + delfunction CheckCompletionCalled + + " 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/util.vim + +Execute(ale#completion#GetCompletions should be called when the cursor position stays the same): + call CheckCompletionCalled(1) + +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() + 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(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' + + call ale#completion#Show('Response', 'Parser') + + AssertEqual 'FooBar', b:ale_old_omnifunc + AssertEqual 'ale#completion#OmniFunc', &l:omnifunc + +Execute(ale#completion#Show() should remember the completeopt setting and replace it): + let &l:completeopt = 'menu' + + call ale#completion#Show('Response', 'Parser') + + AssertEqual 'menu', b:ale_old_completopt + AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt + +Execute(ale#completion#OmniFunc() should also remember the completeopt setting and replace it): + let &l:completeopt = 'menu' + + call ale#completion#OmniFunc(0, '') + + AssertEqual 'menu', b:ale_old_completopt + AssertEqual 'menu,menuone,preview,noselect,noinsert', &l:completeopt + +Execute(ale#completion#Show() should make the correct feedkeys() call): + call ale#completion#Show('Response', 'Parser') + + AssertEqual [["\\", 'n']], g:feedkeys_calls + +Execute(ale#completion#Show() should set up the response and parser): + call ale#completion#Show('Response', 'Parser') + + AssertEqual 'Response', b:ale_completion_response + AssertEqual 'Parser', b:ale_completion_parser + +Execute(ale#completion#Done() should restore old omnifunc values): + let b:ale_old_omnifunc = 'FooBar' + + call ale#completion#Done() + + " We reset the old omnifunc setting and remove the buffer variable. + AssertEqual 'FooBar', &l:omnifunc + Assert !has_key(b:, 'ale_old_omnifunc') + +Execute(ale#completion#Done() should restore the old completeopt setting): + let b:ale_old_completopt = 'menu' + let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' + + call ale#completion#Done() + + AssertEqual 'menu', &l:completeopt + Assert !has_key(b:, 'ale_old_completopt') + +Execute(ale#completion#Done() should leave settings alone when none were remembered): + let &l:omnifunc = 'BazBoz' + let &l:completeopt = 'menu' + + call ale#completion#Done() + + AssertEqual 'BazBoz', &l:omnifunc + AssertEqual 'menu', &l:completeopt + +Execute(The completion request_id should be reset when queuing again): + let b:ale_completion_info = {'request_id': 123} + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + sleep 1m + + AssertEqual 0, b:ale_completion_info.request_id + +Execute(b:ale_completion_info should be set up correctly when requesting completions): + call setpos('.', [bufnr(''), 3, 14, 0]) + call ale#completion#GetCompletions() + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line_length': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ }, + \ b:ale_completion_info diff --git a/test/completion/test_completion_filtering.vader b/test/completion/test_completion_filtering.vader new file mode 100644 index 0000000..3e461ae --- /dev/null +++ b/test/completion/test_completion_filtering.vader @@ -0,0 +1,36 @@ +Execute(Prefix filtering should work for Lists of strings): + AssertEqual + \ ['FooBar', 'foo'], + \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], 'foo') + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], '.') + +Execute(Prefix filtering should work for completion items): + AssertEqual + \ [{'word': 'FooBar'}, {'word': 'foo'}], + \ ale#completion#Filter( + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ 'foo' + \ ) + AssertEqual + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ ale#completion#Filter( + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ '.' + \ ) diff --git a/test/completion/test_completion_prefixes.vader b/test/completion/test_completion_prefixes.vader new file mode 100644 index 0000000..8ac2932 --- /dev/null +++ b/test/completion/test_completion_prefixes.vader @@ -0,0 +1,19 @@ +Given typescript(): + let abc = y. + let foo = ab + let foo = (ab) + +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 prefixes should work for other filetypes): + AssertEqual 'ab', ale#completion#GetPrefix('xxxyyyzzz', 3, 14) diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader new file mode 100644 index 0000000..f21acfb --- /dev/null +++ b/test/completion/test_lsp_completion_messages.vader @@ -0,0 +1,182 @@ +Before: + Save g:ale_completion_delay + Save g:ale_completion_max_suggestions + Save g:ale_completion_info + Save g:ale_completion_experimental_lsp_support + Save &l:omnifunc + Save &l:completeopt + + unlet! g:ale_completion_experimental_lsp_support + + let g:ale_completion_enabled = 1 + + call ale#test#SetDirectory('/testplugin/test/completion') + call ale#test#SetFilename('dummy.txt') + + runtime autoload/ale/lsp.vim + + let g:message_list = [] + let g:Callback = '' + + function! ale#linter#StartLSP(buffer, linter, callback) abort + let g:Callback = a:callback + + return { + \ 'connection_id': 347, + \ 'project_root': '/foo/bar', + \} + 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 + + unlet! g:message_list + unlet! g:Callback + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completopt + unlet! b:ale_completion_info + unlet! b:ale_completion_response + unlet! b:ale_completion_parser + unlet! b:ale_complete_done_time + unlet! b:ale_linters + unlet! g:ale_completion_experimental_lsp_support + + 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 + +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() + + " 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'}]], + \ g:message_list + " We should set up the completion info correctly. + AssertEqual + \ { + \ 'line_length': 3, + \ 'conn_id': 0, + \ 'column': 3, + \ 'request_id': 0, + \ 'line': 1, + \ 'prefix': 'fo', + \ }, + \ 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'}, + \ {'name': 'FooBar'}, + \ {'name': 'frazzle'}, + \ {'name': 'FFS'}, + \ ], + \}) + + " The entry details messages should have been sent. + AssertEqual + \ [[ + \ 0, + \ 'ts@completionEntryDetails', + \ { + \ 'file': expand('%:p'), + \ 'entryNames': ['Foo', 'FooBar', 'frazzle'], + \ 'offset': 1, + \ 'line': 1, + \ }, + \ ]], + \ g:message_list + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(The right message should be sent for the initial LSP request): + let g:ale_completion_experimental_lsp_support = 1 + + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 5, 0]) + + call ale#completion#GetCompletions() + + " 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 pyls will be broken. + " + " We need to send the message for changing the document first. + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 3}, + \ }], + \ ], + \ g:message_list + " We should set up the completion info correctly. + AssertEqual + \ { + \ 'line_length': 3, + \ 'conn_id': 0, + \ 'column': 3, + \ 'request_id': 0, + \ 'line': 1, + \ 'prefix': 'fo', + \ }, + \ get(b:, 'ale_completion_info', {}) diff --git a/test/completion/test_tsserver_completion_parsing.vader b/test/completion/test_tsserver_completion_parsing.vader new file mode 100644 index 0000000..b663ef4 --- /dev/null +++ b/test/completion/test_tsserver_completion_parsing.vader @@ -0,0 +1,75 @@ +Execute(TypeScript completions responses should be parsed correctly): + AssertEqual [], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [], + \}) + AssertEqual ['foo', 'bar', 'baz'], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [ + \ {'name': 'foo'}, + \ {'name': 'bar'}, + \ {'name': 'baz'}, + \ ], + \}) + +Execute(TypeScript completion details responses should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'word': 'abc', + \ 'menu': '(property) Foo.abc: number', + \ 'info': '', + \ 'kind': 'f', + \ 'icase': 1, + \ }, + \ { + \ 'word': 'def', + \ 'menu': '(property) Foo.def: number', + \ 'info': 'foo bar baz', + \ 'kind': 'f', + \ 'icase': 1, + \ }, + \ ], + \ ale#completion#ParseTSServerCompletionEntryDetails({ + \ 'body': [ + \ { + \ 'name': 'abc', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'abc'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ }, + \ { + \ 'name': 'def', + \ 'kind': 'parameterName', + \ 'displayParts': [ + \ {'text': '('}, + \ {'text': 'property'}, + \ {'text': ')'}, + \ {'text': ' '}, + \ {'text': 'Foo'}, + \ {'text': '.'}, + \ {'text': 'def'}, + \ {'text': ':'}, + \ {'text': ' '}, + \ {'text': 'number'}, + \ ], + \ 'documentation': [ + \ {'text': 'foo'}, + \ {'text': ' '}, + \ {'text': 'bar'}, + \ {'text': ' '}, + \ {'text': 'baz'}, + \ ], + \ }, + \ ], + \}) diff --git a/test/csslint-test-files/other-app/testfile.css b/test/csslint-test-files/other-app/testfile.css new file mode 100644 index 0000000..e69de29 diff --git a/test/csslint-test-files/some-app/.csslintrc b/test/csslint-test-files/some-app/.csslintrc new file mode 100644 index 0000000..e69de29 diff --git a/test/csslint-test-files/some-app/subdir/testfile.css b/test/csslint-test-files/some-app/subdir/testfile.css new file mode 100644 index 0000000..e69de29 diff --git a/test/elixir-test-files/testfile.ex b/test/elixir-test-files/testfile.ex new file mode 100644 index 0000000..e69de29 diff --git a/test/elm-test-files/app/filetest.elm b/test/elm-test-files/app/filetest.elm new file mode 100644 index 0000000..e69de29 diff --git a/test/elm-test-files/app/node_modules/.bin/elm-make b/test/elm-test-files/app/node_modules/.bin/elm-make new file mode 100644 index 0000000..e69de29 diff --git a/test/elm-test-files/node_modules/.bin/elm-format b/test/elm-test-files/node_modules/.bin/elm-format new file mode 100644 index 0000000..e69de29 diff --git a/test/elm-test-files/src/subdir/testfile.elm b/test/elm-test-files/src/subdir/testfile.elm new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d b/test/eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/node_modules/.bin/eslint b/test/eslint-test-files/node_modules/.bin/eslint new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/other-app/subdir/testfile.js b/test/eslint-test-files/other-app/subdir/testfile.js new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/package.json b/test/eslint-test-files/package.json new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/.eslintrc.js b/test/eslint-test-files/react-app/.eslintrc.js new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js b/test/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/node_modules/standard/bin/cmd.js b/test/eslint-test-files/react-app/node_modules/standard/bin/cmd.js new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js b/test/eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/subdir-with-config/.eslintrc b/test/eslint-test-files/react-app/subdir-with-config/.eslintrc new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/subdir-with-package-json/package.json b/test/eslint-test-files/react-app/subdir-with-package-json/package.json new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/subdir/testfile.css b/test/eslint-test-files/react-app/subdir/testfile.css new file mode 100644 index 0000000..e69de29 diff --git a/test/eslint-test-files/react-app/subdir/testfile.js b/test/eslint-test-files/react-app/subdir/testfile.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader new file mode 100644 index 0000000..5b66c92 --- /dev/null +++ b/test/fix/test_ale_fix.vader @@ -0,0 +1,666 @@ +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 + + silent! cd /testplugin/test/fix + + let g:ale_enabled = 0 + let g:ale_echo_cursor = 0 + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + let g:ale_fix_buffer_data = {} + let g:ale_fixers = { + \ 'testft': [], + \} + + if !has('win32') + let &shell = '/bin/bash' + endif + + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('test.txt') + + 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 AddDollars(buffer, lines) abort + return map(a:lines, '''$'' . 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 ReplaceWithTempFile(buffer, lines) abort + return {'command': 'echo x > %t', 'read_temporary_file': 1} + 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 + + function! FirstChainCallback(buffer) + return {'command': 'echo echoline', 'chain_with': 'SecondChainCallback'} + endfunction + + function! FirstChainCallbackSkipped(buffer) + let l:ChainWith = 'SecondChainCallback' + + " Test with lambdas where support is available. + if has('lambda') + let l:ChainWith = {buffer, output -> SecondChainCallback(buffer, output)} + endif + + return {'command': '', 'chain_with': l:ChainWith} + endfunction + + function! FirstChainCallbackSecondSkipped(buffer) + return {'command': 'echo skipit', 'chain_with': 'SecondChainCallback'} + endfunction + + function! SecondChainCallback(buffer, output) + let l:previous_line = empty(a:output) + \ ? 'emptydefault' + \ : join(split(a:output[0])) + + if l:previous_line is# 'skipit' + return {'command': '', 'chain_with': 'ThirdChainCallback'} + endif + + return { + \ 'command': 'echo ' . l:previous_line, + \ 'chain_with': 'ThirdChainCallback', + \} + endfunction + + function! ThirdChainCallback(buffer, output, input) + let l:previous_line = empty(a:output) + \ ? 'thirddefault' + \ : join(split(a:output[0])) + + return a:input + [l:previous_line] + endfunction + + function! ChainWhereLastIsSkipped(buffer) + return {'command': 'echo echoline', 'chain_with': 'ChainEndSkipped'} + endfunction + + function! ChainEndSkipped(buffer, output) + return {'command': ''} + endfunction + + " echo will output a single blank line, and we should ingore 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:ale_run_synchronously + unlet! g:ale_set_lists_synchronously + unlet! g:ale_emulate_job_failure + unlet! b:ale_fixers + unlet! b:ale_fix_on_save + delfunction AddCarets + delfunction AddDollars + delfunction DoNothing + delfunction CatLine + delfunction CatLineOneArg + delfunction ReplaceWithTempFile + delfunction RemoveLastLine + delfunction RemoveLastLineOneArg + delfunction TestCallback + delfunction FirstChainCallback + delfunction FirstChainCallbackSkipped + delfunction FirstChainCallbackSecondSkipped + delfunction SecondChainCallback + delfunction ThirdChainCallback + delfunction ChainWhereLastIsSkipped + delfunction ChainEndSkipped + delfunction SetUpLinters + delfunction GetLastMessage + delfunction IgnoredEmptyOutput + delfunction EchoLineNoPipe + delfunction FixWithJSONPostProcessing + delfunction JSONPostProcessor + + call ale#test#RestoreDirectory() + + call ale#fix#registry#ResetToDefaults() + call ale#linter#Reset() + + setlocal buftype=nofile + + if filereadable('fix_test_file') + call delete('fix_test_file') + endif + + call setloclist(0, []) + + let g:ale_fix_buffer_data = {} + + " Clear the messages between tests. + echomsg '' + +Given testft (A file with three lines): + a + b + c + +Execute(ALEFix should complain when there are no functions to call): + ALEFix + AssertEqual 'No fixers have been defined. Try :ALEFixSuggest', GetLastMessage() + +Execute(ALEFix should apply simple functions): + let g:ale_fixers.testft = ['AddCarets'] + ALEFix + +Expect(The first function should be used): + ^a + ^b + ^c + +Execute(ALEFix should apply simple functions in a chain): + let g:ale_fixers.testft = ['AddCarets', 'AddDollars'] + ALEFix + +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', 'AddDollars'] + ALEFix + +Expect(Only the second function should be applied): + $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 + endif + +Expect(An extra line should be added): + a + b + c + d + +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 + endif + +Expect(The line we wrote to the temporary file should be used here): + x + +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', 'AddDollars'] + ALEFix + 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 = ['AddDollars', 'CatLine'] + ALEFix + 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', 'AddDollars'] + ALEFix + +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 + +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 + +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 + +Expect(There should be only two lines): + a + b + +Execute(ALEFix should accept funcrefs): + let g:ale_fixers.testft = [function('RemoveLastLine')] + ALEFix + +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 + 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', 'AddDollars'] + let b:ale_fixers = {'testft': ['RemoveLastLine']} + ALEFix + +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', 'AddDollars'] + let b:ale_fixers = ['RemoveLastLine'] + ALEFix + +Expect(There should be only two lines): + a + b + +Given testft (A file with three lines): + a + b + c + +Execute(ALEFix should save files on the save event): + let g:ale_fix_on_save = 1 + let g:ale_lint_on_save = 1 + let g:ale_enabled = 1 + + noautocmd silent file fix_test_file + call writefile(getline(1, '$'), 'fix_test_file') + + let g:ale_fixers.testft = ['AddDollars'] + + " We have to set the buftype to empty so the file will be written. + setlocal buftype= + + call SetUpLinters() + call ale#events#SaveEvent(bufnr('')) + + " We should save the file. + AssertEqual ['$a', '$b', '$c'], readfile('fix_test_file') + Assert !&modified, 'The 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, + \}], getloclist(0) + endif + +Expect(The buffer should 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 + + noautocmd silent file fix_test_file + + let g:ale_fixers.testft = [] + + call SetUpLinters() + call ale#events#SaveEvent(bufnr('')) + + Assert !filereadable('fix_test_file'), '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, + \}], getloclist(0) + 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 + + noautocmd silent file fix_test_file + + let g:ale_fixers.testft = ['DoNothing'] + + call SetUpLinters() + call ale#events#SaveEvent(bufnr('')) + + Assert !filereadable('fix_test_file'), '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, + \}], getloclist(0) + endif + +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): + noautocmd silent file fix_test_file + + call ale#fix#InitBufferData(bufnr(''), 'save_file') + + AssertEqual { + \ bufnr(''): { + \ 'temporary_directory_list': [], + \ 'vars': b:, + \ 'filename': ale#path#Simplify(getcwd() . '/fix_test_file'), + \ 'done': 0, + \ 'lines_before': ['a', 'b', 'c'], + \ 'should_save': 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 + +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 + 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 + + AssertEqual 'There is no fixer named `invalidname`. Check :ALEFixSuggest', GetLastMessage() + +Execute(ALE should complain about invalid fixers with minuses in the name): + let g:ale_fixers.testft = ['foo-bar'] + ALEFix + + AssertEqual 'There is no fixer named `foo-bar`. Check :ALEFixSuggest', GetLastMessage() + +Execute(ALE should tolerate valid fixers with minuses in the name): + let g:ale_fixers.testft = ['prettier-standard'] + ALEFix + +Execute(Test fixing with chained callbacks): + let g:ale_fixers.testft = ['FirstChainCallback'] + ALEFix + + " The buffer shouldn't be piped in for earlier commands in the chain. + AssertEqual + \ [ + \ string(ale#job#PrepareCommand(bufnr(''), 'echo echoline')), + \ string(ale#job#PrepareCommand(bufnr(''), 'echo echoline')), + \ ], + \ map(ale#history#Get(bufnr(''))[-2:-1], 'string(v:val.command)') + +Expect(The echoed line should be added): + a + b + c + echoline + +Execute(Test fixing with chained callback where the first command is skipped): + let g:ale_fixers.testft = ['FirstChainCallbackSkipped'] + ALEFix + +Expect(The default line should be added): + a + b + c + emptydefault + +Execute(Test fixing with chained callback where the second command is skipped): + let g:ale_fixers.testft = ['FirstChainCallbackSecondSkipped'] + ALEFix + +Expect(The default line should be added): + a + b + c + thirddefault + +Execute(Test fixing with chained callback where the final callback is skipped): + let g:ale_fixers.testft = ['ChainWhereLastIsSkipped'] + ALEFix + +Expect(The lines should be the same): + a + b + c + +Execute(Empty output should be ignored): + let g:ale_fixers.testft = ['IgnoredEmptyOutput'] + ALEFix + +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 + + 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 + +Expect(The lines in the JSON should be used): + x + y + z diff --git a/test/fix/test_ale_fix_aliases.vader b/test/fix/test_ale_fix_aliases.vader new file mode 100644 index 0000000..d3c47b3 --- /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_suggest.vader b/test/fix/test_ale_fix_suggest.vader new file mode 100644 index 0000000..1100aee --- /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/eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d b/test/fixers/eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/node_modules/.bin/eslint b/test/fixers/eslint-test-files/node_modules/.bin/eslint new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/other-app/subdir/testfile.js b/test/fixers/eslint-test-files/other-app/subdir/testfile.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/react-app/.eslintrc.js b/test/fixers/eslint-test-files/react-app/.eslintrc.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js b/test/fixers/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/react-app/node_modules/standard/bin/cmd.js b/test/fixers/eslint-test-files/react-app/node_modules/standard/bin/cmd.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js b/test/fixers/eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/react-app/subdir/testfile.css b/test/fixers/eslint-test-files/react-app/subdir/testfile.css new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/eslint-test-files/react-app/subdir/testfile.js b/test/fixers/eslint-test-files/react-app/subdir/testfile.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixers/long-line-project/setup.cfg b/test/fixers/long-line-project/setup.cfg new file mode 100644 index 0000000..43d7a3a --- /dev/null +++ b/test/fixers/long-line-project/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 90 diff --git a/test/fixers/test_autopep8_fixer_callback.vader b/test/fixers/test_autopep8_fixer_callback.vader new file mode 100644 index 0000000..5678aaf --- /dev/null +++ b/test/fixers/test_autopep8_fixer_callback.vader @@ -0,0 +1,39 @@ +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') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The autopep8 callback should return the correct default values): + AssertEqual + \ 0, + \ ale#fixers#autopep8#Fix(bufnr('')) + + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/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 . '/python_paths/with_virtualenv/subdir/foo/bar.py') + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/autopep8')) . ' --some-option -' }, + \ ale#fixers#autopep8#Fix(bufnr('')) 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 0000000..5fd991f --- /dev/null +++ b/test/fixers/test_break_up_long_lines_python_fixer.vader @@ -0,0 +1,39 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + call ale#test#RestoreDirectory() + +Execute(Long lines with basic function calls should be broken up correctly): + AssertEqual + \ [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(', + \ 'first_argument,', + \ ' second_argument,', + \ ' third_with_function_call(', + \ 'foo,', + \ ' bar,', + \ '))', + \ ], + \ ale#fixers#generic_python#BreakUpLongLines(bufnr(''), [ + \ 'def foo():', + \ ' some_variable = this_is_a_longer_function(first_argument, second_argument, third_with_function_call(foo, bar))', + \ ]) + +Execute(Longer lines should be permitted if a configuration file allows it): + call ale#test#SetFilename('long-line-project/foo/bar.py') + + AssertEqual + \ [ + \ 'x = this_line_is_between_79_and_90_characters(first, second, third, fourth, fifth)', + \ 'y = this_line_is_longer_than_90_characters(', + \ 'much_longer_word,', + \ ' another_longer_word,', + \ ' a_third_long_word,', + \ ')' + \ ], + \ ale#fixers#generic_python#BreakUpLongLines(bufnr(''), [ + \ 'x = this_line_is_between_79_and_90_characters(first, second, third, fourth, fifth)', + \ 'y = this_line_is_longer_than_90_characters(much_longer_word, another_longer_word, a_third_long_word)', + \ ]) diff --git a/test/fixers/test_brittany_fixer_callback.vader b/test/fixers/test_brittany_fixer_callback.vader new file mode 100644 index 0000000..a0182b5 --- /dev/null +++ b/test/fixers/test_brittany_fixer_callback.vader @@ -0,0 +1,23 @@ +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') + \ . ' %t', + \ }, + \ ale#fixers#brittany#Fix(bufnr('')) diff --git a/test/fixers/test_clangformat_fixer_callback.vader b/test/fixers/test_clangformat_fixer_callback.vader new file mode 100644 index 0000000..a55576b --- /dev/null +++ b/test/fixers/test_clangformat_fixer_callback.vader @@ -0,0 +1,36 @@ +Before: + Save g:ale_c_clangformat_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_c_clangformat_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The clang-format callback should return the correct default values): + call ale#test#SetFilename('c_paths/dummy.c') + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' ' + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) + +Execute(The clangformat callback should include any additional options): + call ale#test#SetFilename('c_paths/dummy.c') + let g:ale_c_clangformat_options = '--some-option' + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_clangformat_executable) + \ . ' --some-option', + \ }, + \ ale#fixers#clangformat#Fix(bufnr('')) diff --git a/test/fixers/test_elm_format_fixer_callback.vader b/test/fixers/test_elm_format_fixer_callback.vader new file mode 100644 index 0000000..d613aa8 --- /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('../elm-test-files/src/subdir/testfile.elm') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/node_modules/.bin/elm-format')) + \ . ' %t --yes', + \ }, + \ ale#fixers#format#Fix(bufnr('')) + +Execute(The elm-format command should manage use_global = 1 param): + call ale#test#SetFilename('../elm-test-files/src/subdir/testfile.elm') + let b:ale_elm_format_use_global = 1 + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('elm-format') + \ . ' %t --yes', + \ }, + \ ale#fixers#format#Fix(bufnr('')) + +Execute(The elm-format command should manage executable param): + call ale#test#SetFilename('../elm-test-files/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#format#Fix(bufnr('')) + +Execute(The elm-format command should manage empty options): + call ale#test#SetFilename('../elm-test-files/src/subdir/testfile.elm') + let b:ale_elm_format_options = '' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../elm-test-files/node_modules/.bin/elm-format')) + \ . ' %t', + \ }, + \ ale#fixers#format#Fix(bufnr('')) + +Execute(The elm-format command should manage custom options): + call ale#test#SetFilename('../elm-test-files/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 . '/../elm-test-files/node_modules/.bin/elm-format')) + \ . ' %t --param1 --param2', + \ }, + \ ale#fixers#format#Fix(bufnr('')) diff --git a/test/fixers/test_eslint_fixer_callback.vader b/test/fixers/test_eslint_fixer_callback.vader new file mode 100644 index 0000000..be33825 --- /dev/null +++ b/test/fixers/test_eslint_fixer_callback.vader @@ -0,0 +1,172 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + call ale#test#RestoreDirectory() + call ale#semver#ResetVersionCache() + +Execute(The executable path should be correct): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') + + " eslint_d output with an older eslint version is used here. + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['v4.4.1 (eslint_d v5.1.0)']) + +Execute(The lower priority configuration file in a nested directory should be preferred): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-config/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/subdir-with-config/.eslintrc')) + \ . ' --fix %t', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + +Execute(package.json should be used as a last resort): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-package-json/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + + call ale#test#SetFilename('../eslint-test-files/package.json') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/node_modules/.bin/eslint')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/package.json')) + \ . ' --fix %t', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + +Execute(The version check should be correct): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') + + AssertEqual + \ { + \ 'chain_with': 'ale#fixers#eslint#ApplyFixForVersion', + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --version' + \ }, + \ ale#fixers#eslint#Fix(bufnr('')) + +Execute(--fix-dry-run should be used for 4.9.0 and up): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') + + AssertEqual + \ { + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['4.9.0']) + +Execute(--fix-to-stdout should be used for eslint_d): + call ale#test#SetFilename('../eslint-test-files/app-with-eslint-d/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) + \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/package.json')) + \ . ' --fix %t', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['']) + + " The option should be used when eslint_d is new enough. + " We look at the ESLint version instead of the eslint_d version. + AssertEqual + \ { + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) + \ . ' --stdin-filename %s --stdin --fix-to-stdout', + \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['v3.19.0 (eslint_d v4.2.0)']) + + " The option should be used for new versions too. + AssertEqual + \ { + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) + \ . ' --stdin-filename %s --stdin --fix-to-stdout', + \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['4.9.0']) + +Execute(The version number should be cached): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-config/testfile.js') + + " Call the second callback with the version output. + call ale#fixers#eslint#ApplyFixForVersion(bufnr(''), ['4.9.0']) + + " The version command should be skipped. + AssertEqual + \ { + \ 'chain_with': 'ale#fixers#eslint#ApplyFixForVersion', + \ 'command': '', + \ }, + \ ale#fixers#eslint#Fix(bufnr('')) + + " Call it again without the version output. We should use the newer command. + AssertEqual + \ { + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', + \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', + \ }, + \ ale#fixers#eslint#ApplyFixForVersion(bufnr(''), []) + +Execute(The --fix-dry-run post-processor should handle JSON output correctly): + AssertEqual + \ [], + \ 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.', + \ ]) diff --git a/test/fixers/test_fixjson_fixer_callback.vader b/test/fixers/test_fixjson_fixer_callback.vader new file mode 100644 index 0000000..1a3bdcf --- /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('../json_files/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_gofmt_fixer_callback.vader b/test/fixers/test_gofmt_fixer_callback.vader new file mode 100644 index 0000000..14e6e06 --- /dev/null +++ b/test/fixers/test_gofmt_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_go_gofmt_executable + Save g:ale_go_gofmt_options + + " 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 + + call ale#test#RestoreDirectory() + +Execute(The gofmt callback should return the correct default values): + call ale#test#SetFilename('../go_files/testfile.go') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -l -w' + \ . ' %t', + \ }, + \ 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('../go_files/testfile.go') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -l -w' + \ . ' ' . g:ale_go_gofmt_options + \ . ' %t', + \ }, + \ ale#fixers#gofmt#Fix(bufnr('')) diff --git a/test/fixers/test_goimports_fixer_callback.vader b/test/fixers/test_goimports_fixer_callback.vader new file mode 100644 index 0000000..cec0635 --- /dev/null +++ b/test/fixers/test_goimports_fixer_callback.vader @@ -0,0 +1,41 @@ +Before: + Save g:ale_go_goimports_executable + Save g:ale_go_goimports_options + + " 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('../go_files/testfile.go') + +After: + Restore + + 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('')) 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 0000000..d64e278 --- /dev/null +++ b/test/fixers/test_goofle_java_format_fixer_callback.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_google_java_format_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_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_google_java_format_executable = has('win32') ? 'cmd' : 'echo' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(ale_google_java_format_executable) . ' --replace %t' + \ }, + \ ale#fixers#google_java_format#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 0000000..ed78fc8 --- /dev/null +++ b/test/fixers/test_hackfmt_fixer_callback.vader @@ -0,0 +1,37 @@ +Before: + Save g:ale_php_hackfmt_executable + Save g:ale_php_hackfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_php_hackfmt_executable = 'xxxinvalid' + let g:ale_php_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.php') + + 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_php_hackfmt_options = "--some-option" + call ale#test#SetFilename('../hack_files/testfile.php') + + 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 0000000..69cd03f --- /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_importjs_fixer_callback.vader b/test/fixers/test_importjs_fixer_callback.vader new file mode 100644 index 0000000..c3e57f8 --- /dev/null +++ b/test/fixers/test_importjs_fixer_callback.vader @@ -0,0 +1,35 @@ +Before: + Save g:ale_js_importjs_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_js_importjs_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#test#SetFilename('../javascript_files/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_js_importjs_executable = has('win32') ? 'cmd' : 'echo' + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#importjs#ProcessOutput', + \ 'command': ale#Escape(g:ale_js_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 0000000..7c2b515 --- /dev/null +++ b/test/fixers/test_isort_fixer_callback.vader @@ -0,0 +1,32 @@ +Before: + Save g:ale_python_isort_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_python_isort_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The isort callback should return the correct default values): + AssertEqual + \ 0, + \ ale#fixers#isort#Fix(bufnr('')) + + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir/foo')) . ' && ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/isort')) . ' -', + \ }, + \ ale#fixers#isort#Fix(bufnr('')) diff --git a/test/fixers/test_jq_fixer_callback.vader b/test/fixers/test_jq_fixer_callback.vader new file mode 100644 index 0000000..2e32bf8 --- /dev/null +++ b/test/fixers/test_jq_fixer_callback.vader @@ -0,0 +1,14 @@ +Before: + Save g:ale_json_jq_executable + Save g:ale_json_jq_options + +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' + + AssertEqual + \ {'command': ale#Escape('foo') . ' . --bar'}, + \ ale#fixers#jq#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 0000000..c6c97c5 --- /dev/null +++ b/test/fixers/test_mix_format_fixer_callback.vader @@ -0,0 +1,20 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + Save g:ale_elixir_mix_executable + + let g:ale_elixir_mix_executable = 'xxxinvalid' + +After: + call ale#test#RestoreDirectory() + +Execute(The mix_format callback should return the correct default values): + call ale#test#SetFilename('../elixir-test-files/testfile.ex') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' format %t', + \ }, + \ ale#fixers#mix_format#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 0000000..b657967 --- /dev/null +++ b/test/fixers/test_php_cs_fixer.vader @@ -0,0 +1,46 @@ +Before: + Save g:ale_php_cs_fixer_executable + let g:ale_php_cs_fixer_executable = 'php-cs-fixer' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + + +Execute(project with php-cs-fixer should use local by default): + call ale#test#SetFilename('php_paths/project-with-php-cs-fixer/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/php_paths/project-with-php-cs-fixer/vendor/bin/php-cs-fixer'), + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_cs_fixer_use_global = 1 + call ale#test#SetFilename('php_paths/project-with-php-cs-fixer/test.php') + + AssertEqual + \ 'php-cs-fixer', + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + +Execute(project without php-cs-fixer should use global): + call ale#test#SetFilename('php_paths/project-without-php-cs-fixer/test.php') + + AssertEqual + \ 'php-cs-fixer', + \ ale#fixers#php_cs_fixer#GetExecutable(bufnr('')) + + + + +Execute(The php-cs-fixer callback should return the correct default values): + call ale#test#SetFilename('php_paths/project-without-php-cs-fixer/foo/test.php') + + AssertEqual + \ {'read_temporary_file': 1, 'command': ale#Escape('php-cs-fixer') . ' fix %t' }, + \ 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 0000000..1663c89 --- /dev/null +++ b/test/fixers/test_phpcbf_fixer_callback.vader @@ -0,0 +1,112 @@ +Before: + Save g:ale_php_phpcbf_executable + Save g:ale_php_phpcbf_standard + Save g:ale_php_phpcbf_use_global + + let g:ale_php_phpcbf_executable = 'phpcbf_test' + let g:ale_php_phpcbf_standard = '' + let g:ale_php_phpcbf_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(project with phpcbf should use local by default): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf'), + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_phpcbf_use_global = 1 + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(project without phpcbf should use global): + call ale#test#SetFilename('php_paths/project-without-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(The phpcbf callback should return the correct default values): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s -' }, + \ ale#fixers#phpcbf#Fix(bufnr('')) + +Execute(The phpcbf callback should include the phpcbf_standard option): + let g:ale_php_phpcbf_standard = 'phpcbf_ruleset.xml' + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s ' . '--standard=phpcbf_ruleset.xml' . ' -'}, + \ 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_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(project with phpcbf should use local by default): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf'), + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_phpcbf_use_global = 1 + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(project without phpcbf should use global): + call ale#test#SetFilename('php_paths/project-without-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(The phpcbf callback should return the correct default values): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s -' }, + \ ale#fixers#phpcbf#Fix(bufnr('')) + +Execute(The phpcbf callback should include the phpcbf_standard option): + let g:ale_php_phpcbf_standard = 'phpcbf_ruleset.xml' + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf')) . ' --stdin-path=%s ' . '--standard=phpcbf_ruleset.xml' . ' -'}, + \ ale#fixers#phpcbf#Fix(bufnr('')) + diff --git a/test/fixers/test_prettier_eslint_fixer.callback.vader b/test/fixers/test_prettier_eslint_fixer.callback.vader new file mode 100644 index 0000000..ef0b35d --- /dev/null +++ b/test/fixers/test_prettier_eslint_fixer.callback.vader @@ -0,0 +1,113 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + + Save g:ale_javascript_prettier_eslint_executable + Save g:ale_javascript_prettier_eslint_use_global + Save g:ale_javascript_prettier_eslint_options + + unlet! g:ale_javascript_prettier_eslint_executable + unlet! g:ale_javascript_prettier_eslint_use_global + unlet! g:ale_javascript_prettier_eslint_options + + call ale#fixers#prettier_eslint#SetOptionDefaults() + +After: + Restore + + unlet! b:ale_javascript_prettier_eslint_executable + unlet! b:ale_javascript_prettier_eslint_use_global + unlet! b:ale_javascript_prettier_eslint_options + + call ale#test#RestoreDirectory() + call ale#semver#ResetVersionCache() + +Execute(The default command should be correct): + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --write' + \ }, + \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) + +Execute(Additional options should be used when set): + let b:ale_javascript_prettier_eslint_options = '--foobar' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --foobar --write' + \ }, + \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) + +Execute(--eslint-config-path should be set for 4.2.0 and up): + call ale#test#SetFilename('eslint-test-files/react-app/foo/bar.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --eslint-config-path ' . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/.eslintrc.js')) + \ . ' --write' + \ }, + \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), ['4.2.0']) + +Execute(--eslint-config-path shouldn't be used for older versions): + call ale#test#SetFilename('eslint-test-files/react-app/foo/bar.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape('prettier-eslint') + \ . ' %t' + \ . ' --write' + \ }, + \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) + +Execute(The version check should be correct): + AssertEqual + \ { + \ 'chain_with': 'ale#fixers#prettier_eslint#ApplyFixForVersion', + \ 'command': ale#Escape('prettier-eslint') . ' --version', + \ }, + \ ale#fixers#prettier_eslint#Fix(bufnr('')) + +Execute(The new --stdin-filepath option should be used when the version is new enough): + call ale#test#SetFilename('eslint-test-files/react-app/foo/bar.js') + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('prettier-eslint') + \ . ' --eslint-config-path ' . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/.eslintrc.js')) + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), ['4.4.0']) + +Execute(The version number should be cached): + call ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), ['4.4.0']) + + " The version command should be skipped. + AssertEqual + \ { + \ 'chain_with': 'ale#fixers#prettier_eslint#ApplyFixForVersion', + \ 'command': '', + \ }, + \ ale#fixers#prettier_eslint#Fix(bufnr('')) + + " The newer command should be used. + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape('prettier-eslint') + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier_eslint#ApplyFixForVersion(bufnr(''), []) diff --git a/test/fixers/test_prettier_fixer_callback.vader b/test/fixers/test_prettier_fixer_callback.vader new file mode 100644 index 0000000..c4f36f5 --- /dev/null +++ b/test/fixers/test_prettier_fixer_callback.vader @@ -0,0 +1,97 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + Save g:ale_javascript_prettier_executable + Save g:ale_javascript_prettier_options + + " Use an invalid global executable, so we don't match it. + let g:ale_javascript_prettier_executable = 'xxxinvalid' + let g:ale_javascript_prettier_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + let g:ale_has_override = {} + + call ale#test#RestoreDirectory() + call ale#semver#ResetVersionCache() + +Execute(The prettier callback should return the correct default values): + call ale#test#SetFilename('../prettier-test-files/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --write', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + +Execute(The --config option should not be set automatically): + let g:ale_javascript_prettier_use_local_config = 1 + call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --write', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + +Execute(The prettier callback should include custom prettier options): + let g:ale_javascript_prettier_options = '--no-semi' + call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' %t' + \ . ' --no-semi' + \ . ' --write', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) + +Execute(The version check should be correct): + call ale#test#SetFilename('../prettier-test-files/testfile.js') + + AssertEqual + \ { + \ 'chain_with': 'ale#fixers#prettier#ApplyFixForVersion', + \ 'command': ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --version', + \ }, + \ ale#fixers#prettier#Fix(bufnr('')) + +Execute(--stdin-filepath should be used when prettier is new enough): + let g:ale_javascript_prettier_options = '--no-semi' + call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') + + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --no-semi' + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + +Execute(The version number should be cached): + call ale#test#SetFilename('../prettier-test-files/with_config/testfile.js') + + " Call the second callback with the version output. + call ale#fixers#prettier#ApplyFixForVersion(bufnr(''), ['1.6.0']) + + " Call it again without the version output. We should use the newer command. + AssertEqual + \ { + \ 'command': 'cd ' . ale#Escape(expand('%:p:h')) . ' && ' + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --stdin-filepath %s --stdin', + \ }, + \ ale#fixers#prettier#ApplyFixForVersion(bufnr(''), []) diff --git a/test/fixers/test_puppetlint_fixer_callback.vader b/test/fixers/test_puppetlint_fixer_callback.vader new file mode 100644 index 0000000..224d72a --- /dev/null +++ b/test/fixers/test_puppetlint_fixer_callback.vader @@ -0,0 +1,27 @@ +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') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The puppetlint callback should return the correct default values): + silent execute 'file ' . fnameescape(g:dir . '/puppet_paths/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_python_add_blank_lines_fixer.vader b/test/fixers/test_python_add_blank_lines_fixer.vader new file mode 100644 index 0000000..4a91aa1 --- /dev/null +++ b/test/fixers/test_python_add_blank_lines_fixer.vader @@ -0,0 +1,111 @@ +Before: + Save g:ale_fixers + +After: + Restore + +Given python(Some Python without blank lines): + def foo(): + return 1 + + + def bar(): + return 1 + return 4 + + + def bar(): + 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(): + return 1 + + + def bar(): + return 1 + + return 4 + + + def bar(): + 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() diff --git a/test/fixers/test_refmt_fixer_callback.vader b/test/fixers/test_refmt_fixer_callback.vader new file mode 100644 index 0000000..9ec331e --- /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('../reasonml_files/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('../reasonml_files/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_rubocop_fixer_callback.vader b/test/fixers/test_rubocop_fixer_callback.vader new file mode 100644 index 0000000..045256f --- /dev/null +++ b/test/fixers/test_rubocop_fixer_callback.vader @@ -0,0 +1,54 @@ +Before: + Save g:ale_ruby_rubocop_executable + Save g:ale_ruby_rubocop_options + + " Use an invalid global executable, so we don't match it. + let g:ale_ruby_rubocop_executable = 'xxxinvalid' + let g:ale_ruby_rubocop_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The rubocop callback should return the correct default values): + call ale#test#SetFilename('ruby_paths/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --auto-correct %t', + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop callback should include configuration files): + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.rubocop.yml')) + \ . ' --auto-correct %t', + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop callback should include custom rubocop options): + let g:ale_ruby_rubocop_options = '--except Lint/Debugger' + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.rubocop.yml')) + \ . ' --except Lint/Debugger' + \ . ' --auto-correct %t', + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) diff --git a/test/fixers/test_rustfmt_fixer_callback.vader b/test/fixers/test_rustfmt_fixer_callback.vader new file mode 100644 index 0000000..95c78de --- /dev/null +++ b/test/fixers/test_rustfmt_fixer_callback.vader @@ -0,0 +1,34 @@ +Before: + Save g:ale_rust_rustfmt_executable + Save g:ale_rust_rustfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_rust_rustfmt_executable = 'xxxinvalid' + let g:ale_rust_rustfmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The rustfmt callback should return the correct default values): + call ale#test#SetFilename('../rust_files/testfile.rs') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid'), + \ }, + \ ale#fixers#rustfmt#Fix(bufnr('')) + +Execute(The rustfmt callback should include custom rustfmt options): + let g:ale_rust_rustfmt_options = "--skip-children" + call ale#test#SetFilename('../rust_files/testfile.rs') + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' ' . g:ale_rust_rustfmt_options, + \ }, + \ ale#fixers#rustfmt#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 0000000..5dc6e86 --- /dev/null +++ b/test/fixers/test_shfmt_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_sh_shfmt_executable + Save g:ale_sh_shfmt_options + +After: + Restore + +Execute(The shfmt callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('shfmt'), + \ }, + \ ale#fixers#shfmt#Fix(bufnr('')) + +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') + \ . ' --some-option', + \ }, + \ ale#fixers#shfmt#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 0000000..38f2d54 --- /dev/null +++ b/test/fixers/test_standard_fixer_callback.vader @@ -0,0 +1,17 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + call ale#test#RestoreDirectory() + +Execute(The executable path should be correct): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/standard/bin/cmd.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#standard#Fix(bufnr('')) diff --git a/test/fixers/test_stylelint_fixer_callback.vader b/test/fixers/test_stylelint_fixer_callback.vader new file mode 100644 index 0000000..90a9dc1 --- /dev/null +++ b/test/fixers/test_stylelint_fixer_callback.vader @@ -0,0 +1,17 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + call ale#test#RestoreDirectory() + +Execute(The executable path should be correct): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.css') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#stylelint#Fix(bufnr('')) diff --git a/test/fixers/test_swiftformat_fixer_callback.vader b/test/fixers/test_swiftformat_fixer_callback.vader new file mode 100644 index 0000000..e3674de --- /dev/null +++ b/test/fixers/test_swiftformat_fixer_callback.vader @@ -0,0 +1,38 @@ +Before: + Save g:ale_swift_swiftformat_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_swift_swiftformat_executable = 'xxxinvalid' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The swiftformat callback should return the correct default values): + call ale#test#SetFilename('swift_paths/dummy.swift') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_swiftformat_executable) + \ . ' %t ', + \ }, + \ ale#fixers#swiftformat#Fix(bufnr('')) + +Execute(The swiftformat callback should include any additional options): + call ale#test#SetFilename('swift_paths/dummy.swift') + let g:ale_swift_swiftformat_options = '--some-option' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_swift_swiftformat_executable) + \ . ' %t --some-option', + \ }, + \ ale#fixers#swiftformat#Fix(bufnr('')) diff --git a/test/fixers/test_trim_whitespace.vader b/test/fixers/test_trim_whitespace.vader new file mode 100644 index 0000000..2ffbcb0 --- /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 0000000..5bfafe2 --- /dev/null +++ b/test/fixers/test_tslint_fixer_callback.vader @@ -0,0 +1,41 @@ +Before: + Save g:ale_typescript_tslint_executable + Save g:ale_typescript_tslint_config_path + + let g:ale_typescript_tslint_executable = 'xxxinvalid' + let g:ale_typescript_tslint_config_path = 'tslint.json' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The tslint callback should return the correct default values): + call ale#test#SetFilename('../prettier-test-files/testfile.ts') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_typescript_tslint_executable) + \ . ' -c ' . ale#Escape('tslint.json') + \ . ' --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('../prettier-test-files/testfile.ts') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_typescript_tslint_executable) + \ . ' -c ' . ale#Escape(g:ale_typescript_tslint_config_path) + \ . ' --fix %t', + \ }, + \ ale#fixers#tslint#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 0000000..7e18a77 --- /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_yapf_fixer_callback.vader b/test/fixers/test_yapf_fixer_callback.vader new file mode 100644 index 0000000..cfc508c --- /dev/null +++ b/test/fixers/test_yapf_fixer_callback.vader @@ -0,0 +1,42 @@ +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') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + Restore + + unlet! b:bin_dir + + call ale#test#RestoreDirectory() + +Execute(The yapf callback should return the correct default values): + AssertEqual + \ 0, + \ ale#fixers#yapf#Fix(bufnr('')) + + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/yapf'))}, + \ ale#fixers#yapf#Fix(bufnr('')) + \ +Execute(The yapf should include the .style.yapf file if present): + call ale#test#SetFilename('python_paths/with_virtualenv/dir_with_yapf_config/foo/bar.py') + + AssertEqual + \ { + \ 'command': + \ ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/yapf')) + \ . ' --no-local-style' + \ . ' --style ' . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/dir_with_yapf_config/.style.yapf')), + \ }, + \ ale#fixers#yapf#Fix(bufnr('')) diff --git a/test/flow/a/.flowconfig b/test/flow/a/.flowconfig new file mode 100644 index 0000000..e69de29 diff --git a/test/flow/a/sub/dummy b/test/flow/a/sub/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/flow/b/sub/dummy b/test/flow/b/sub/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/go_files/testfile.go b/test/go_files/testfile.go new file mode 100644 index 0000000..e69de29 diff --git a/test/go_files/testfile2.go b/test/go_files/testfile2.go new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/build-gradle-project/build.gradle b/test/gradle-test-files/build-gradle-project/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/build-gradle-project/src/main/kotlin/dummy.kt b/test/gradle-test-files/build-gradle-project/src/main/kotlin/dummy.kt new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/gradle b/test/gradle-test-files/gradle new file mode 100755 index 0000000..e69de29 diff --git a/test/gradle-test-files/non-gradle-project/src/main/kotlin/dummy.kt b/test/gradle-test-files/non-gradle-project/src/main/kotlin/dummy.kt new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/settings-gradle-project/settings.gradle b/test/gradle-test-files/settings-gradle-project/settings.gradle new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/settings-gradle-project/src/main/kotlin/dummy.kt b/test/gradle-test-files/settings-gradle-project/src/main/kotlin/dummy.kt new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/unwrapped-project/build.gradle b/test/gradle-test-files/unwrapped-project/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/unwrapped-project/settings.gradle b/test/gradle-test-files/unwrapped-project/settings.gradle new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/unwrapped-project/src/main/kotlin/dummy.kt b/test/gradle-test-files/unwrapped-project/src/main/kotlin/dummy.kt new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/wrapped-project/build.gradle b/test/gradle-test-files/wrapped-project/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/wrapped-project/gradlew b/test/gradle-test-files/wrapped-project/gradlew new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/wrapped-project/settings.gradle b/test/gradle-test-files/wrapped-project/settings.gradle new file mode 100644 index 0000000..e69de29 diff --git a/test/gradle-test-files/wrapped-project/src/main/kotlin/dummy.kt b/test/gradle-test-files/wrapped-project/src/main/kotlin/dummy.kt new file mode 100644 index 0000000..e69de29 diff --git a/test/hack_files/testfile.php b/test/hack_files/testfile.php new file mode 100644 index 0000000..e69de29 diff --git a/test/hamllint-test-files/haml-lint-and-rubocop/.haml-lint.yml b/test/hamllint-test-files/haml-lint-and-rubocop/.haml-lint.yml new file mode 100644 index 0000000..e69de29 diff --git a/test/hamllint-test-files/haml-lint-and-rubocop/.rubocop.yml b/test/hamllint-test-files/haml-lint-and-rubocop/.rubocop.yml new file mode 100644 index 0000000..e69de29 diff --git a/test/hamllint-test-files/haml-lint-and-rubocop/subdir/file.haml b/test/hamllint-test-files/haml-lint-and-rubocop/subdir/file.haml new file mode 100644 index 0000000..e69de29 diff --git a/test/hamllint-test-files/haml-lint-yml/.haml-lint.yml b/test/hamllint-test-files/haml-lint-yml/.haml-lint.yml new file mode 100644 index 0000000..e69de29 diff --git a/test/hamllint-test-files/haml-lint-yml/subdir/file.haml b/test/hamllint-test-files/haml-lint-yml/subdir/file.haml new file mode 100644 index 0000000..e69de29 diff --git a/test/hamllint-test-files/rubocop-yml/.rubocop.yml b/test/hamllint-test-files/rubocop-yml/.rubocop.yml new file mode 100644 index 0000000..e69de29 diff --git a/test/hamllint-test-files/rubocop-yml/subdir/file.haml b/test/hamllint-test-files/rubocop-yml/subdir/file.haml new file mode 100644 index 0000000..e69de29 diff --git a/test/handler/test_alex_handler.vader b/test/handler/test_alex_handler.vader new file mode 100644 index 0000000..eb241f8 --- /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_ansible_lint_handler.vader b/test/handler/test_ansible_lint_handler.vader new file mode 100644 index 0000000..796277e --- /dev/null +++ b/test/handler/test_ansible_lint_handler.vader @@ -0,0 +1,58 @@ +Before: + runtime ale_linters/ansible/ansible_lint.vim + call ale#test#SetFilename('main.yml') + + let b:ale_warn_about_trailing_whitespace = 1 + +After: + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() + +Execute(The ansible-lint handler should handle basic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 35, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Trailing whitespace', + \ 'code': 'EANSIBLE0002', + \ }, + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [ + \ fnamemodify(tempname(), ':h') . '/main.yml:35: [EANSIBLE0002] Trailing whitespace', + \ ]) + +Execute(The ansible-lint handler should supress trailing whitespace output when the option is used): + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [ + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [ + \ fnamemodify(tempname(), ':h') . '/main.yml:35: [EANSIBLE0002] Trailing whitespace', + \ ]) + +Execute (The ansible-lint handler 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(''), [ + \ fnamemodify(tempname(), ':h') . '/main.yml:6:6: E111 indentation is not a multiple of four', + \ ]) + +Execute (The ansible-lint handler should ignore errors from other files): + AssertEqual + \ [ + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [ + \ '/foo/bar/roles/main.yml:6:6: E111 indentation is not a multiple of four', + \ ]) diff --git a/test/handler/test_asm_handler.vader b/test/handler/test_asm_handler.vader new file mode 100644 index 0000000..4ab9999 --- /dev/null +++ b/test/handler/test_asm_handler.vader @@ -0,0 +1,26 @@ +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_brakeman_handler.vader b/test/handler/test_brakeman_handler.vader new file mode 100644 index 0000000..02eb31b --- /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('../ruby_fixtures/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_checkmake_handler.vader b/test/handler/test_checkmake_handler.vader new file mode 100644 index 0000000..e2e1842 --- /dev/null +++ b/test/handler/test_checkmake_handler.vader @@ -0,0 +1,23 @@ +Before: + runtime ale_linters/make/checkmake.vim + +After: + call ale#linter#Reset() + +Execute(Parsing checkmake errors should work): + silent file Makefile + + AssertEqual + \ [ + \ { + \ 'bufnr': 42, + \ 'lnum': 1, + \ 'type': 'E', + \ 'code': 'woops', + \ 'text': 'an error has occurred', + \ } + \ ], + \ ale_linters#make#checkmake#Handle(42, [ + \ 'This shouldnt match', + \ '1:woops:an error has occurred', + \ ]) diff --git a/test/handler/test_checkstyle_handler.vader b/test/handler/test_checkstyle_handler.vader new file mode 100644 index 0000000..2f1f0f8 --- /dev/null +++ b/test/handler/test_checkstyle_handler.vader @@ -0,0 +1,28 @@ +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', + \ }, + \ { + \ 'lnum': 63, + \ 'col': 3, + \ 'text': 'Missing a Javadoc comment.', + \ 'code': 'JavadocMethod', + \ 'type': 'W', + \ }, + \ ], + \ 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]', + \ ]) diff --git a/test/handler/test_clang_handler.vader b/test/handler/test_clang_handler.vader new file mode 100644 index 0000000..278737a --- /dev/null +++ b/test/handler/test_clang_handler.vader @@ -0,0 +1,19 @@ +Execute(clang errors from included files should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'filename': './b.h', + \ 'type': 'E', + \ 'text': 'expected identifier or ''(''', + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ 'In file included from test.c: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_joker_handler.vader b/test/handler/test_clojure_joker_handler.vader new file mode 100644 index 0000000..460c62e --- /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_coffeelint_handler.vader b/test/handler/test_coffeelint_handler.vader new file mode 100644 index 0000000..a061f3a --- /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 0000000..ee29da3 --- /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_cppcheck_handler.vader b/test/handler/test_cppcheck_handler.vader new file mode 100644 index 0000000..f153b9b --- /dev/null +++ b/test/handler/test_cppcheck_handler.vader @@ -0,0 +1,36 @@ +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': 5, + \ 'type': 'E', + \ 'text': 'Array ''a[10]'' accessed at index 10, which is out of bounds', + \ }, + \ { + \ 'lnum': 7, + \ 'type': 'W', + \ 'text': 'Some other problem', + \ }, + \ ], + \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ + \ '[test.cpp:5]: (error) Array ''a[10]'' accessed at index 10, which is out of bounds', + \ '[test.cpp:7]: (warning) Some other problem', + \ ]) + +Execute(Problems from other files should be ignored by cppcheck): + call ale#test#SetFilename('test.cpp') + + AssertEqual + \ [ + \ ], + \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ + \ '[bar.cpp:5]: (error) Array ''a[10]'' accessed at index 10, which is out of bounds', + \ ]) diff --git a/test/handler/test_cpplint_handler.vader b/test/handler/test_cpplint_handler.vader new file mode 100644 index 0000000..d8d7b8b --- /dev/null +++ b/test/handler/test_cpplint_handler.vader @@ -0,0 +1,29 @@ +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 0000000..5eb0e96 --- /dev/null +++ b/test/handler/test_credo_handler.vader @@ -0,0 +1,29 @@ +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': 4, + \ 'text': 'There is no whitespace around parentheses/brackets most of the time, but here there is.', + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 26, + \ 'col': 0, + \ 'text': 'If/else blocks should not have a negated condition in `if`.', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#elixir#credo#Handle(347, [ + \ 'This line should be ignored completely', + \ 'lib/filename.ex:1:4: C: There is no whitespace around parentheses/brackets most of the time, but here there is.', + \ 'lib/phoenix/channel.ex:26: R: If/else blocks should not have a negated condition in `if`.', + \ ]) diff --git a/test/handler/test_crystal_handler.vader b/test/handler/test_crystal_handler.vader new file mode 100644 index 0000000..a7b7f3a --- /dev/null +++ b/test/handler/test_crystal_handler.vader @@ -0,0 +1,18 @@ +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"}]' + \ ]) diff --git a/test/handler/test_cuda_nvcc_handler.vader b/test/handler/test_cuda_nvcc_handler.vader new file mode 100644 index 0000000..40e3192 --- /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_dafny_handler.vader b/test/handler/test_dafny_handler.vader new file mode 100644 index 0000000..674f691 --- /dev/null +++ b/test/handler/test_dafny_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/dafny/dafny.vim + +After: + call ale#linter#Reset() + +Execute(The Dafny handler should parse output correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 0, + \ 'col': 45, + \ 'lnum': 123, + \ 'text': 'A precondition for this call might not hold.', + \ 'type': 'E' + \ }, + \ { + \ 'bufnr': 0, + \ 'col': 90, + \ 'lnum': 678, + \ 'text': 'This is the precondition that might not hold.', + \ 'type': 'W' + \ } + \ ], + \ 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.' + \ ]) diff --git a/test/handler/test_dartanalyzer_handler.vader b/test/handler/test_dartanalyzer_handler.vader new file mode 100644 index 0000000..954850c --- /dev/null +++ b/test/handler/test_dartanalyzer_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/dart/dartanalyzer.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, + \ }, + \ ], + \ ale_linters#dart#dartanalyzer#Handle(bufnr(''), [ + \ 'Analyzing main.dart...', + \ ' error • Expected to find ''}'' at main.dart:5:1 • expected_token', + \ ' warning • A value of type ''String'' can''t be assigned to a variable of type ''int'' at main.dart:2:16 • invalid_assignment', + \ '1 error and 1 warning found.', + \ ]) diff --git a/test/handler/test_dogma_handler.vader b/test/handler/test_dogma_handler.vader new file mode 100644 index 0000000..ead6d09 --- /dev/null +++ b/test/handler/test_dogma_handler.vader @@ -0,0 +1,30 @@ +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 0000000..1524dde --- /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 0000000..f3424b4 --- /dev/null +++ b/test/handler/test_elmmake_handler.vader @@ -0,0 +1,80 @@ +Before: + let b:tmp = has('win32') ? substitute($TMP, '\\', '\\\\', 'g') : $TMPDIR + + runtime ale_linters/elm/make.vim + +After: + unlet! b:tmp + unlet! g:config_error_lines + + call ale#linter#Reset() + +Execute(The elm-make handler should parse lines 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, [ + \ '[{"tag":"unused import","overview":"warning overview","details":"warning details","region":{"start":{"line":33,"column":1},"end":{"line":33,"column":19}},"type":"warning","file":"' . b:tmp . 'Module.elm"}]', + \ '[{"tag":"TYPE MISMATCH","overview":"error overview 1","subregion":{"start":{"line":406,"column":5},"end":{"line":408,"column":18}},"details":"error details 1","region":{"start":{"line":404,"column":1},"end":{"line":408,"column":18}},"type":"error","file":"' . b:tmp . 'Module.elm"},{"tag":"TYPE MISMATCH","overview":"error overview 2","subregion":{"start":{"line":407,"column":12},"end":{"line":407,"column":17}},"details":"error details 2","region":{"start":{"line":406,"column":5},"end":{"line":407,"column":17}},"type":"error","file":"' . b:tmp . 'Module.elm"},{"tag":"TYPE MISMATCH","overview":"error overview 3","subregion":{"start":{"line":406,"column":88},"end":{"line":406,"column":93}},"details":"error details 3","region":{"start":{"line":406,"column":5},"end":{"line":406,"column":93}},"type":"error","file":"' . b:tmp . 'Module.elm"}]' + \ ]) + +Execute(The elm-make handler should put an error on the first line if a line cannot be parsed): + AssertEqual + \ [ + \ { + \ 'lnum': 33, + \ 'col': 1, + \ 'end_lnum': 33, + \ 'end_col': 19, + \ 'type': 'W', + \ 'text': 'warning overview', + \ 'detail': "warning overview\n\nwarning details", + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': 'Not JSON', + \ 'detail': "Not JSON\nAlso not JSON", + \ }, + \ ], + \ ale_linters#elm#make#Handle(347, [ + \ '[{"tag":"unused import","overview":"warning overview","details":"warning details","region":{"start":{"line":33,"column":1},"end":{"line":33,"column":19}},"type":"warning","file":"' . b:tmp . 'Module.elm"}]', + \ "Not JSON", + \ "Also not JSON", + \ ]) diff --git a/test/handler/test_embertemplatelint_handler.vader b/test/handler/test_embertemplatelint_handler.vader new file mode 100644 index 0000000..97ca439 --- /dev/null +++ b/test/handler/test_embertemplatelint_handler.vader @@ -0,0 +1,81 @@ +" Author: Adrian Zalewski +Before: + runtime ale_linters/handlebars/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_linters#handlebars#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_linters#handlebars#embertemplatelint#Handle(347, input_lines) + +Execute(The ember-template-lint handler should handle no lint errors/warnings): + AssertEqual + \ [], + \ ale_linters#handlebars#embertemplatelint#Handle(347, []) + AssertEqual + \ [], + \ ale_linters#handlebars#embertemplatelint#Handle(347, ['{}']) diff --git a/test/handler/test_eslint_handler.vader b/test/handler/test_eslint_handler.vader new file mode 100644 index 0000000..2e8bfd2 --- /dev/null +++ b/test/handler/test_eslint_handler.vader @@ -0,0 +1,367 @@ +Before: + Save g:ale_javascript_eslint_suppress_eslintignore + Save g:ale_javascript_eslint_suppress_missing_config + + let g:ale_javascript_eslint_suppress_eslintignore = 0 + let g:ale_javascript_eslint_suppress_missing_config = 0 + +After: + Restore + + unlet! b:ale_javascript_eslint_suppress_eslintignore + unlet! b:ale_javascript_eslint_suppress_missing_config + 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]', + \ ]) + +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]', + \ ]) + +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]', + \ ]) diff --git a/test/handler/test_fish_handler.vader b/test/handler/test_fish_handler.vader new file mode 100644 index 0000000..567952e --- /dev/null +++ b/test/handler/test_fish_handler.vader @@ -0,0 +1,39 @@ +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'", + \ '^', + \ ]) diff --git a/test/handler/test_flake8_handler.vader b/test/handler/test_flake8_handler.vader new file mode 100644 index 0000000..efacdfb --- /dev/null +++ b/test/handler/test_flake8_handler.vader @@ -0,0 +1,246 @@ +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, + \ 'type': 'E', + \ 'text': 'indentation is not a multiple of four', + \ 'code': 'E111', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 6, + \ 'type': 'W', + \ 'text': 'some warning', + \ 'code': 'W123', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 3, + \ '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, + \ 'type': 'E', + \ 'end_col': 3, + \ 'text': 'undefined name ''foo''', + \ 'code': 'F821', + \ }, + \ { + \ 'lnum': 28, + \ 'col': 5, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': 'hello may be undefined, or defined from star imports: x', + \ 'code': 'F405', + \ }, + \ { + \ 'lnum': 104, + \ 'col': 5, + \ 'type': 'E', + \ 'end_col': 12, + \ 'text': '''continue'' not properly in loop', + \ 'code': 'F999', + \ }, + \ { + \ 'lnum': 106, + \ 'col': 5, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': '''break'' outside loop', + \ 'code': 'F999', + \ }, + \ { + \ 'lnum': 109, + \ 'col': 5, + \ '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': 'An exception was thrown. 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, + \ '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, + \ '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#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, + \ '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, + \ 'code': 'F401', + \ 'type': 'W', + \ 'text': 'module imported but unused', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: F401 module imported but unused', + \ ]) + +Execute(E112 should be a syntax error): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'code': 'E112', + \ 'type': 'E', + \ 'text': 'expected an indented block', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(bufnr(''), [ + \ 'foo.py:6:1: E112 expected an indented block', + \ ]) diff --git a/test/handler/test_flow_handler.vader b/test/handler/test_flow_handler.vader new file mode 100644 index 0000000..3a575a0 --- /dev/null +++ b/test/handler/test_flow_handler.vader @@ -0,0 +1,506 @@ +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': 'Property `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 0000000..67cb6ca --- /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 0000000..c55a4c6 --- /dev/null +++ b/test/handler/test_fortran_handler.vader @@ -0,0 +1,95 @@ +Before: + runtime ale_linters/fortran/gcc.vim + +After: + call ale#linter#Reset() + +Execute(The fortran handler should parse lines from GCC 4.1.2 correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 357, + \ 'lnum': 4, + \ 'col': 0, + \ 'text': "Symbol ‘b’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': 357, + \ 'lnum': 3, + \ 'col': 0, + \ 'text': "Symbol ‘a’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#fortran#gcc#Handle(357, [ + \ " In file :4", + \ "", + \ "write(*,*) b", + \ " 1", + \ "Error: Symbol ‘b’ at (1) has no IMPLICIT type", + \ " In file :3", + \ "", + \ "write(*,*) a", + \ " 1", + \ "Error: Symbol ‘a’ at (1) has no IMPLICIT type", + \ ]) + + +Execute(The fortran handler should parse lines from GCC 4.9.3 correctly): + AssertEqual + \ [ + \ { + \ 'bufnr': 357, + \ 'lnum': 3, + \ 'col': 12, + \ 'text': "Symbol ‘a’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': 357, + \ 'lnum': 4, + \ 'col': 12, + \ 'text': "Symbol ‘b’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#fortran#gcc#Handle(357, [ + \ ":3.12:", + \ "", + \ "write(*,*) a", + \ " 1", + \ "Error: Symbol ‘a’ at (1) has no IMPLICIT type", + \ ":4.12:", + \ "", + \ "write(*,*) b", + \ " 1", + \ "Error: Symbol ‘b’ at (1) has no IMPLICIT type", + \ ]) + +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_gcc_handler.vader b/test/handler/test_gcc_handler.vader new file mode 100644 index 0000000..79f1789 --- /dev/null +++ b/test/handler/test_gcc_handler.vader @@ -0,0 +1,150 @@ +Execute(The GCC handler should ignore other lines of output): + AssertEqual + \ [], + \ ale#handlers#gcc#HandleGCCFormat(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', + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ 'In file included from :3:0:', + \ 'broken.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'filename': 'b.h', + \ 'type': 'E', + \ 'text': 'expected identifier or ''('' before ''{'' token', + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ 'In file included from a.h:1:0,', + \ ' from test.c:3:', + \ 'b.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ ]) + + 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''', + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ 'In file included from a.h:1:0,', + \ ' from test.c: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;', + \ ' ^', + \ ]) + +Execute(The GCC handler shouldn't complain about #pragma once for headers): + silent file! test.h + + AssertEqual + \ [], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ ':1:1: warning: #pragma once in main file [enabled by default]', + \ ]) + + silent file! test.hpp + + AssertEqual + \ [], + \ ale#handlers#gcc#HandleGCCFormat(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#HandleGCCFormat(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#HandleGCCFormat(347, [ + \ ':1:1: note: x', + \ ':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#HandleGCCFormat(347, [ + \ '-:6:12: error: Some error', + \ ]) diff --git a/test/handler/test_ghc_handler.vader b/test/handler/test_ghc_handler.vader new file mode 100644 index 0000000..b040a23 --- /dev/null +++ b/test/handler/test_ghc_handler.vader @@ -0,0 +1,132 @@ +Execute(The ghc handler should handle hdevtools output): + call ale#test#SetFilename('foo.hs') + + AssertEqual + \ [ + \ { + \ 'lnum': 147, + \ 'type': 'W', + \ 'col': 62, + \ 'text': '• Couldnt match type ‘a -> T.Text’ with ‘T.Text’ Expected type: [T.Text]', + \ 'detail': join([ + \ '• Couldnt match type ‘a -> T.Text’ with ‘T.Text’', + \ ' Expected type: [T.Text]', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ 'foo.hs:147:62: warning:', + \ '• Couldnt 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', + \ ' | ^^^^^', + \ ]) diff --git a/test/handler/test_ghc_mod_handler.vader b/test/handler/test_ghc_mod_handler.vader new file mode 100644 index 0000000..bed5b13 --- /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_gitlint_handler.vader b/test/handler/test_gitlint_handler.vader new file mode 100644 index 0000000..73ee988 --- /dev/null +++ b/test/handler/test_gitlint_handler.vader @@ -0,0 +1,41 @@ +Before: + runtime ale_linters/gitcommit/gitlint.vim + +After: + 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"' + \ ]) + diff --git a/test/handler/test_glslang_handler.vader b/test/handler/test_glslang_handler.vader new file mode 100644 index 0000000..d51c985 --- /dev/null +++ b/test/handler/test_glslang_handler.vader @@ -0,0 +1,21 @@ +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.', + \ ]) diff --git a/test/handler/test_gobuild_handler.vader b/test/handler/test_gobuild_handler.vader new file mode 100644 index 0000000..17608c3 --- /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_gometalinter_handler.vader b/test/handler/test_gometalinter_handler.vader new file mode 100644 index 0000000..1aade8a --- /dev/null +++ b/test/handler/test_gometalinter_handler.vader @@ -0,0 +1,57 @@ +Before: + runtime ale_linters/go/gometalinter.vim + +After: + call ale#linter#Reset() + +Execute (The gometalinter 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', + \ '12', + \ '3', + \ 'warning', + \ 'expected ''package'', found ''IDENT'' gibberish (staticcheck)', + \ ], + \ [ + \ 'C:\something\file with spaces.go', + \ '37', + \ '5', + \ 'error', + \ 'expected ''package'', found ''IDENT'' gibberish (golint)', + \ ], + \ ], + \ map(ale_linters#go#gometalinter#GetMatches([ + \ 'C:\something\file with spaces.go:12:3:warning: expected ''package'', found ''IDENT'' gibberish (staticcheck)', + \ 'C:\something\file with spaces.go:37:5:error: expected ''package'', found ''IDENT'' gibberish (golint)', + \ ]), 'v:val[1:5]') + +Execute (The gometalinter handler should handle paths correctly): + call ale#test#SetFilename('app/test.go') + + let file = ale#path#GetAbsPath(expand('%:p:h'), 'test.go') + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col': 3, + \ 'text': 'expected ''package'', found ''IDENT'' gibberish (staticcheck)', + \ 'type': 'W', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.go'), + \ }, + \ { + \ 'lnum': 37, + \ 'col': 5, + \ 'text': 'expected ''package'', found ''IDENT'' gibberish (golint)', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.go'), + \ }, + \ ], + \ ale_linters#go#gometalinter#Handler(bufnr(''), [ + \ file . ':12:3:warning: expected ''package'', found ''IDENT'' gibberish (staticcheck)', + \ file . ':37:5:error: expected ''package'', found ''IDENT'' gibberish (golint)', + \ ]) diff --git a/test/handler/test_govet_handler.vader b/test/handler/test_govet_handler.vader new file mode 100644 index 0000000..b4bfdc9 --- /dev/null +++ b/test/handler/test_govet_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/go/govet.vim + +After: + call ale#linter#Reset() + +Execute(The govet handler should return the correct filenames): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'text': 'some error', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/test.go'), + \ }, + \ { + \ 'lnum': 27, + \ 'col': 5, + \ 'text': 'some error with a column', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/other.go'), + \ }, + \ ], + \ ale_linters#go#govet#Handler(bufnr(''), [ + \ 'test.go:27: some error', + \ 'other.go:27:5: some error with a column', + \ ]) diff --git a/test/handler/test_hlint_handler.vader b/test/handler/test_hlint_handler.vader new file mode 100644 index 0000000..915e174 --- /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_idris_handler.vader b/test/handler/test_idris_handler.vader new file mode 100644 index 0000000..6a032ea --- /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_javac_handler.vader b/test/handler/test_javac_handler.vader new file mode 100644 index 0000000..ff4e163 --- /dev/null +++ b/test/handler/test_javac_handler.vader @@ -0,0 +1,87 @@ +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', + \ }, + \ ], + \ 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()', + \ '5 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_jscs_handler.vader b/test/handler/test_jscs_handler.vader new file mode 100644 index 0000000..5566116 --- /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_lessc_handler.vader b/test/handler/test_lessc_handler.vader new file mode 100644 index 0000000..31de559 --- /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 0000000..bbe686f --- /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_luac_handler.vader b/test/handler/test_luac_handler.vader new file mode 100644 index 0000000..3a2e769 --- /dev/null +++ b/test/handler/test_luac_handler.vader @@ -0,0 +1,36 @@ +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 0000000..7cebb01 --- /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_mcs_handler.vader b/test/handler/test_mcs_handler.vader new file mode 100644 index 0000000..3defc32 --- /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 0000000..8ae4735 --- /dev/null +++ b/test/handler/test_mcsc_handler.vader @@ -0,0 +1,69 @@ +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', + \ ]) diff --git a/test/handler/test_mypy_handler.vader b/test/handler/test_mypy_handler.vader new file mode 100644 index 0000000..f3d4cbf --- /dev/null +++ b/test/handler/test_mypy_handler.vader @@ -0,0 +1,112 @@ +Before: + Save g:ale_python_mypy_ignore_invalid_syntax + + 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') + + 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 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': '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_nagelfar_handler.vader b/test/handler/test_nagelfar_handler.vader new file mode 100644 index 0000000..ceaee19 --- /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_nim_handler.vader b/test/handler/test_nim_handler.vader new file mode 100644 index 0000000..e484000 --- /dev/null +++ b/test/handler/test_nim_handler.vader @@ -0,0 +1,45 @@ +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', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 5, + \ 'text': '''NotUsed'' is declared but not used', + \ 'code': 'XDeclaredButNotUsed', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 12, + \ 'col': 2, + \ 'text': 'with : character', + \ 'type': 'E', + \ }, + \ ], + \ 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', + \ ]) diff --git a/test/handler/test_nix_handler.vader b/test/handler/test_nix_handler.vader new file mode 100644 index 0000000..398e1ac --- /dev/null +++ b/test/handler/test_nix_handler.vader @@ -0,0 +1,28 @@ +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': 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', + \ ]) diff --git a/test/handler/test_perl_handler.vader b/test/handler/test_perl_handler.vader new file mode 100644 index 0000000..c5791d7 --- /dev/null +++ b/test/handler/test_perl_handler.vader @@ -0,0 +1,88 @@ +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 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.', + \ ]) diff --git a/test/handler/test_perlcritic_handler.vader b/test/handler/test_perlcritic_handler.vader new file mode 100644 index 0000000..f00b07d --- /dev/null +++ b/test/handler/test_perlcritic_handler.vader @@ -0,0 +1,20 @@ +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 0000000..6fe9b32 --- /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 0000000..2374792 --- /dev/null +++ b/test/handler/test_php_phan_handler.vader @@ -0,0 +1,24 @@ +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', + \ }, + \ { + \ 'lnum': 66, + \ 'type': 'W', + \ 'text': 'Call to method string from undeclared class \Respect\Validation\Validator', + \ }, + \ ], + \ 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 0000000..f161d73 --- /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_phpstan_handler.vader b/test/handler/test_phpstan_handler.vader new file mode 100644 index 0000000..207a775 --- /dev/null +++ b/test/handler/test_phpstan_handler.vader @@ -0,0 +1,77 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + + runtime ale_linters/php/phpstan.vim + +After: + Restore + + 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(''), [" 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%"]) + +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': 'W' + \ }, + \ { + \ 'lnum': 16, + \ 'text': 'Sample message.', + \ 'type': 'W' + \ }, + \ { + \ 'lnum': 192, + \ 'text': 'Invalid command testCommand.', + \ 'type': 'W' + \ } + \ ], + \ ale_linters#php#phpstan#Handle(bufnr(''), [ + \ ' 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%', + \ 'phpstan-test-files/foo/test.php:9:Call to method format() on an unknown class DateTimeImutable.', + \ 'phpstan-test-files/foo/test.php:16:Sample message.', + \ 'phpstan-test-files/foo/test.php:192:Invalid command testCommand.', + \]) + +Execute(Output should be parsed correctly with Windows paths): + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + + AssertEqual + \ [ + \ { + \ 'lnum': 9, + \ 'text': 'Access to an undefined property Test::$var.', + \ 'type': 'W' + \ } + \ ], + \ ale_linters#php#phpstan#Handle(bufnr(''), [ + \ ' 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%', + \ 'D:\phpstan-test-files\foo\test.php:9:Access to an undefined property Test::$var.', + \]) + +Execute(Output for .inc files should be parsed correctly): + call ale#test#SetFilename('phpstan-test-files/test.inc') + + AssertEqual + \ [ + \ { + \ 'lnum': 9, + \ 'text': 'Access to an undefined property Test::$var.', + \ 'type': 'W' + \ } + \ ], + \ ale_linters#php#phpstan#Handle(bufnr(''), [ + \ ' 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%', + \ '/phpstan-test-files/foo/test.inc:9:Access to an undefined property Test::$var.', + \]) diff --git a/test/handler/test_pony_handler.vader b/test/handler/test_pony_handler.vader new file mode 100644 index 0000000..25a8254 --- /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_prospector_handler.vader b/test/handler/test_prospector_handler.vader new file mode 100644 index 0000000..7962f6c --- /dev/null +++ b/test/handler/test_prospector_handler.vader @@ -0,0 +1,158 @@ +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() + + silent file something_else.py + +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', + \ ' }', + \ ' }', + \ ' ]', + \ '}', + \ ]) diff --git a/test/handler/test_puppet_handler.vader b/test/handler/test_puppet_handler.vader new file mode 100644 index 0000000..0d274fd --- /dev/null +++ b/test/handler/test_puppet_handler.vader @@ -0,0 +1,45 @@ +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 ':'" + \ } + \ ], + \ 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", + \ ]) diff --git a/test/handler/test_pycodestyle_handler.vader b/test/handler/test_pycodestyle_handler.vader new file mode 100644 index 0000000..3664455 --- /dev/null +++ b/test/handler/test_pycodestyle_handler.vader @@ -0,0 +1,154 @@ +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() + silent file something_else.py + +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_pyflakes_handler.vader b/test/handler/test_pyflakes_handler.vader new file mode 100644 index 0000000..ab4fab4 --- /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_pylint_handler.vader b/test/handler/test_pylint_handler.vader new file mode 100644 index 0000000..aff4084 --- /dev/null +++ b/test/handler/test_pylint_handler.vader @@ -0,0 +1,96 @@ +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)', + \ ]) 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 0000000..9875e97 --- /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('ruby_fixtures/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 0000000..f28d692 --- /dev/null +++ b/test/handler/test_redpen_handler.vader @@ -0,0 +1,69 @@ +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', + \ }, + \ ], + \ 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\"."', + \ ' }', + \ ' ]', + \ ' }', + \ ']', + \ ]) + +Execute(redpen handler should no error output): + AssertEqual + \ [], + \ ale#handlers#redpen#HandleRedpenOutput(bufnr(''), [ + \ '[', + \ ' {', + \ ' "document": "test.md",', + \ ' "errors": []', + \ ' }', + \ ']', + \ ]) diff --git a/test/handler/test_reek_handler.vader b/test/handler/test_reek_handler.vader new file mode 100644 index 0000000..db0a111 --- /dev/null +++ b/test/handler/test_reek_handler.vader @@ -0,0 +1,81 @@ +Before: + runtime ale_linters/ruby/reek.vim + +After: + call ale#linter#Reset() + +Execute(The reek handler should parse JSON correctly, with only context enabled): + let g:ale_ruby_reek_show_context = 1 + let g:ale_ruby_reek_show_wiki_link = 0 + + 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): + let g:ale_ruby_reek_show_context = 0 + let g:ale_ruby_reek_show_wiki_link = 0 + + 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 0000000..f61da19 --- /dev/null +++ b/test/handler/test_remark_lint_handler.vader @@ -0,0 +1,30 @@ +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', + \ }, + \ ], + \ 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', + \ '', + \ '⚠ 1 warnings', + \ '✘ 1 errors', + \]) diff --git a/test/handler/test_rpmlint_handler.vader b/test/handler/test_rpmlint_handler.vader new file mode 100644 index 0000000..2ea9e5c --- /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 0000000..3b4ac03 --- /dev/null +++ b/test/handler/test_rstcheck_lint_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/rstcheck/rstcheck.vim + +After: + call ale#linter#Reset() + +Execute(Warning and error messages should be handled correctly): + AssertEqual + \ [ + \ { + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/bad_python.rst'), + \ 'lnum': 7, + \ 'col': 0, + \ 'type': 'W', + \ 'text': '(python) unexpected EOF while parsing', + \ }, + \ { + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/bad_cpp.rst'), + \ 'lnum': 9, + \ 'col': 0, + \ 'type': 'W', + \ 'text': '(cpp) error: ''x'' was not declared in this scope', + \ }, + \ { + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/bad_rst.rst'), + \ 'lnum': 1, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Title overline & underline mismatch.', + \ }, + \ ], + \ 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.', + \]) diff --git a/test/handler/test_rubocop_handler.vader b/test/handler/test_rubocop_handler.vader new file mode 100644 index 0000000..ef0137d --- /dev/null +++ b/test/handler/test_rubocop_handler.vader @@ -0,0 +1,76 @@ +Before: + runtime ale_linters/ruby/rubocop.vim + +After: + unlet! g:lines + 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_linters#ruby#rubocop#Handle(347, [ + \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[{"path":"my_great_file.rb","offenses":[{"severity":"convention","message":"Prefer single-quoted strings...","cop_name":"Style/SomeCop","corrected":false,"location":{"line":83,"column":29,"length":7}},{"severity":"fatal","message":"Some error","cop_name":"Style/SomeOtherCop","corrected":false,"location":{"line":12,"column":2,"length":1}},{"severity":"warning","message":"Regular warning","cop_name":"Style/WarningCop","corrected":false,"location":{"line":10,"column":5,"length":8}},{"severity":"error","message":"Another error","cop_name":"Style/SpaceBeforeBlockBraces","corrected":false,"location":{"line":11,"column":1,"length":1}}]}],"summary":{"offense_count":4,"target_file_count":1,"inspected_file_count":1}}' + \ ]) + +Execute(The rubocop handler should handle when files are checked and no offenses are found): + AssertEqual + \ [], + \ ale_linters#ruby#rubocop#Handle(347, [ + \ '{"metadata":{"rubocop_version":"0.47.1","ruby_engine":"ruby","ruby_version":"2.1.5","ruby_patchlevel":"273","ruby_platform":"x86_64-linux-gnu"},"files":[{"path":"my_great_file.rb","offenses":[]}],"summary":{"offense_count":0,"target_file_count":1,"inspected_file_count":1}}' + \ ]) + +Execute(The rubocop handler should handle when no files are checked): + AssertEqual + \ [], + \ ale_linters#ruby#rubocop#Handle(347, [ + \ '{"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 output without any errors): + let g:lines = [ + \ '{"metadata":{"rubocop_version":"0.48.1","ruby_engine":"ruby","ruby_version":"2.4.1","ruby_patchlevel":"111","ruby_platform":"x86_64-darwin16"},"files":[]}', + \] + + AssertEqual + \ [], + \ ale_linters#ruby#rubocop#Handle(347, g:lines) + \ + AssertEqual + \ [], + \ ale_linters#ruby#rubocop#Handle(347, ['{}']) + AssertEqual + \ [], + \ ale_linters#ruby#rubocop#Handle(347, []) diff --git a/test/handler/test_ruby_handler.vader b/test/handler/test_ruby_handler.vader new file mode 100644 index 0000000..824d8c5 --- /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_rust_handler.vader b/test/handler/test_rust_handler.vader new file mode 100644 index 0000000..e3ab3e8 --- /dev/null +++ b/test/handler/test_rust_handler.vader @@ -0,0 +1,287 @@ +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': 8, + \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ }, + \ { + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'type': 'E', + \ 'col': 7, + \ 'end_col': 10, + \ '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': 8, + \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ }, + \ { + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'type': 'E', + \ 'col': 7, + \ 'end_col': 10, + \ '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': 23, + \ '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': 23, + \ '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 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': [ + \ ] + \ }, + \ }), + \ ]) diff --git a/test/handler/test_scalac_handler.vader b/test/handler/test_scalac_handler.vader new file mode 100644 index 0000000..fd222f6 --- /dev/null +++ b/test/handler/test_scalac_handler.vader @@ -0,0 +1,18 @@ +Before: + runtime ale_linters/scala/scalac.vim + +After: + call ale#linter#Reset() + +Given scala(An empty Scala file): + +Execute(The default executable and command should be correct): + AssertEqual 'scalac', ale_linters#scala#scalac#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('scalac') . ' -Ystop-after:parser %t', + \ ale_linters#scala#scalac#GetCommand(bufnr('')) + +Given scala.sbt(An empty SBT file): +Execute(scalac should not be run for sbt files): + AssertEqual '', ale_linters#scala#scalac#GetExecutable(bufnr('')) + AssertEqual '', ale_linters#scala#scalac#GetCommand(bufnr('')) diff --git a/test/handler/test_scalastyle_handler.vader b/test/handler/test_scalastyle_handler.vader new file mode 100644 index 0000000..32da79c --- /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_shell_handler.vader b/test/handler/test_shell_handler.vader new file mode 100644 index 0000000..2465f17 --- /dev/null +++ b/test/handler/test_shell_handler.vader @@ -0,0 +1,42 @@ +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', + \ ]) diff --git a/test/handler/test_shellcheck_handler.vader b/test/handler/test_shellcheck_handler.vader new file mode 100644 index 0000000..bfb73ff --- /dev/null +++ b/test/handler/test_shellcheck_handler.vader @@ -0,0 +1,43 @@ +Before: + runtime ale_linters/shell/shellcheck.vim + +After: + call ale#linter#Reset() + +Execute(The shellcheck handler should handle basic errors or warnings): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'In POSIX sh, ''let'' is not supported.', + \ 'code': 'SC2039', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 3, + \ 'type': 'E', + \ 'text': 'Don''t put spaces around the = in assignments.', + \ 'code': 'SC1068', + \ }, + \ ], + \ ale_linters#sh#shellcheck#Handle(bufnr(''), [ + \ '-: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): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col': 3, + \ 'type': 'I', + \ 'text': 'Double quote to prevent globbing and word splitting.', + \ 'code': 'SC2086', + \ }, + \ ], + \ ale_linters#sh#shellcheck#Handle(bufnr(''), [ + \ '-:3:3: note: Double quote to prevent globbing and word splitting. [SC2086]', + \ ]) diff --git a/test/handler/test_slim_handler.vader b/test/handler/test_slim_handler.vader new file mode 100644 index 0000000..bfd29f3 --- /dev/null +++ b/test/handler/test_slim_handler.vader @@ -0,0 +1,34 @@ +" 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 0000000..90e7c2c --- /dev/null +++ b/test/handler/test_sml_handler.vader @@ -0,0 +1,87 @@ +Execute (Testing on EOF error): + AssertEqual [ + \ { + \ 'bufnr': 42, + \ '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 [ + \ { + \ 'bufnr': 42, + \ 'lnum': 1, + \ 'col': 5, + \ 'type': 'E', + \ 'text': "Error: can't find function arguments in clause", + \ }, + \ { + \ 'bufnr': 42, + \ '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 [ + \ { + \ 'bufnr': 42, + \ 'lnum': 5, + \ 'col': 18, + \ 'type': 'E', + \ 'text': "Error: syntax error found at ID", + \ }, + \ { + \ 'bufnr': 42, + \ '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 [ + \ { + \ 'bufnr': 42, + \ '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", + \ "-", + \]) diff --git a/test/handler/test_solhint_handler.vader b/test/handler/test_solhint_handler.vader new file mode 100644 index 0000000..a3ed5d5 --- /dev/null +++ b/test/handler/test_solhint_handler.vader @@ -0,0 +1,60 @@ +Before: + runtime ale_linters/solidity/solhint.vim + +After: + call ale#linter#Reset() + +Execute(The vint handler should parse 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: line 1, col 17, Warning - Compiler version must be fixed (compiler-fixed)', + \ 'contracts/Bounty.sol: line 4, col 8, Error - Use double quotes for string literals (quotes)', + \ 'contracts/Bounty.sol: line 5, col 8, Error - Use double quotes for string literals (quotes)', + \ 'contracts/Bounty.sol: line 13, col 3, Error - Expected indentation of 4 spaces but found 2 (indent)', + \ 'contracts/Bounty.sol: line 14, col 3, Error - Expected indentation of 4 spaces but found 2 (indent)', + \ 'contracts/Bounty.sol: line 47, col 3, Error - Function order is incorrect, public function can not go after internal function. (func-order)', + \ ]) diff --git a/test/handler/test_sqlint_handler.vader b/test/handler/test_sqlint_handler.vader new file mode 100644 index 0000000..5567ca4 --- /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_standard_handler.vader b/test/handler/test_standard_handler.vader new file mode 100644 index 0000000..59ebe53 --- /dev/null +++ b/test/handler/test_standard_handler.vader @@ -0,0 +1,29 @@ +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_stylelint_handler.vader b/test/handler/test_stylelint_handler.vader new file mode 100644 index 0000000..5cb3460 --- /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 0000000..7ab1043 --- /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 0000000..725ff97 --- /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_syntaxerl_handler.vader b/test/handler/test_syntaxerl_handler.vader new file mode 100644 index 0000000..95f2bfe --- /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_tflint_handler.vader b/test/handler/test_tflint_handler.vader new file mode 100644 index 0000000..099d092 --- /dev/null +++ b/test/handler/test_tflint_handler.vader @@ -0,0 +1,31 @@ +Before: + runtime! ale_linters/terraform/tflint.vim + +After: + call ale#linter#Reset() + +Execute(The tflint handler should parse items correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'text': 'be warned, traveller', + \ 'code': 'aws_db_instance_readable_password', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 9, + \ 'text': 'error message', + \ 'code': 'aws_elasticache_cluster_invalid_type', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 5, + \ 'text': 'just so ya know', + \ 'code': 'aws_instance_not_specified_iam_profile', + \ 'type': 'I', + \ }, + \ ], + \ ale_linters#terraform#tflint#Handle(123, [ + \ '[ { "detector": "aws_db_instance_readable_password", "type": "WARNING", "message": "be warned, traveller", "line": 12, "file": "github.com/wata727/example-module/aws_db_instance.tf", "link": "https://github.com/wata727/tflint/blob/master/docs/aws_db_instance_readable_password.md" }, { "detector": "aws_elasticache_cluster_invalid_type", "type": "ERROR", "message": "error message", "line": 9, "file": "github.com/wata727/example-module/aws_elasticache_cluster.tf", "link": "https://github.com/wata727/tflint/blob/master/docs/aws_elasticache_cluster_invalid_type.md" }, { "detector": "aws_instance_not_specified_iam_profile", "type": "NOTICE", "message": "just so ya know", "line": 5, "file": "github.com/wata727/example-module/aws_instance.tf", "link": "https://github.com/wata727/tflint/blob/master/docs/aws_instance_not_specified_iam_profile.md" } ]' + \ ]) diff --git a/test/handler/test_thrift_handler.vader b/test/handler/test_thrift_handler.vader new file mode 100644 index 0000000..9bdb937 --- /dev/null +++ b/test/handler/test_thrift_handler.vader @@ -0,0 +1,63 @@ +Before: + runtime ale_linters/thrift/thrift.vim + +After: + call ale#linter#Reset() + +Execute(The thrift handler should handle basic warnings and errors): + AssertEqual + \ [ + \ { + \ 'lnum': 17, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'The "byte" type is a compatibility alias for "i8". Use i8" to emphasize the signedness of this type.', + \ }, + \ { + \ 'lnum': 20, + \ 'col': 0, + \ 'type': 'W', + \ 'text': 'Could not find include file include.thrift', + \ }, + \ { + \ 'lnum': 83, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Enum FOO is already defined!', + \ }, + \ ], + \ ale_linters#thrift#thrift#Handle(1, [ + \ '[WARNING:/path/filename.thrift:17] The "byte" type is a compatibility alias for "i8". Use i8" to emphasize the signedness of this type.', + \ '[WARNING:/path/filename.thrift:20] Could not find include file include.thrift', + \ '[FAILURE:/path/filename.thrift:83] Enum FOO is already defined!', + \ ]) + +Execute(The thrift handler should handle multiline errors): + AssertEqual + \ [ + \ { + \ 'lnum': 75, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'This integer is too big: "11111111114213213453243"', + \ }, + \ { + \ 'lnum': 76, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'Implicit field keys are deprecated and not allowed with -strict', + \ }, + \ { + \ 'lnum': 77, + \ 'col': 0, + \ 'type': 'E', + \ 'text': "Unknown error (last token was ';')", + \ }, + \ ], + \ ale_linters#thrift#thrift#Handle(1, [ + \ "[ERROR:/path/filename.thrift:75] (last token was '11111111114213213453243')", + \ 'This integer is too big: "11111111114213213453243"', + \ "[ERROR:/path/filename.thrift:76] (last token was ';')", + \ 'Implicit field keys are deprecated and not allowed with -strict', + \ "[ERROR:/path/filename.thrift:77] (last token was ';')", + \ ]) diff --git a/test/handler/test_tslint_handler.vader b/test/handler/test_tslint_handler.vader new file mode 100644 index 0000000..32036ed --- /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 temporay 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 0000000..fda55d6 --- /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_vale_handler.vader b/test/handler/test_vale_handler.vader new file mode 100644 index 0000000..37badb4 --- /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_vint_handler.vader b/test/handler/test_vint_handler.vader new file mode 100644 index 0000000..c542b4e --- /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_write_good_handler.vader b/test/handler/test_write_good_handler.vader new file mode 100644 index 0000000..8bf4b22 --- /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 0000000..a17d74a --- /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/javascript_files/test.js b/test/javascript_files/test.js new file mode 100644 index 0000000..e69de29 diff --git a/test/json_files/testfile.json b/test/json_files/testfile.json new file mode 100644 index 0000000..fe317eb --- /dev/null +++ b/test/json_files/testfile.json @@ -0,0 +1 @@ +{"answer":42} diff --git a/test/lsp/test_did_save_event.vader b/test/lsp/test_did_save_event.vader new file mode 100644 index 0000000..042a3ce --- /dev/null +++ b/test/lsp/test_did_save_event.vader @@ -0,0 +1,108 @@ +Before: + Save g:ale_lint_on_save + Save g:ale_enabled + Save g:ale_linters + Save g:ale_run_synchronously + + call ale#test#SetDirectory('/testplugin/test/completion') + call ale#test#SetFilename('dummy.txt') + + runtime autoload/ale/lsp.vim + + 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:message_list = [] + let g:Callback = '' + + 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_callback': 'LanguageCallback', + \ 'project_root_callback': 'ProjectRootCallback', + \ }) + let g:ale_linters = {'foobar': ['dummy_linter']} + + function! ale#linter#StartLSP(buffer, linter, callback) abort + let g:Callback = a:callback + + return { + \ 'connection_id': 347, + \ 'project_root': '/foo/bar', + \} + 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 + + unlet! b:ale_enabled + unlet! b:ale_linters + unlet! g:Callback + unlet! g:message_list + + 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 + +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#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}], + \ }], + \ [1, 'textDocument/didSave', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(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#ToURI(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_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader new file mode 100644 index 0000000..053da80 --- /dev/null +++ b/test/lsp/test_lsp_client_messages.vader @@ -0,0 +1,235 @@ +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': {}, + \ } + \ ], + \ ale#lsp#message#Initialize('/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#lsp#message#DidOpen() should return correct messages): + let g:ale_lsp_next_version_id = 12 + AssertEqual + \ [ + \ 1, + \ 'textDocument/didOpen', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(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#ToURI(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#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ } + \ ], + \ ale#lsp#message#DidSave(bufnr('')) + +Execute(ale#lsp#message#DidClose() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'textDocument/didClose', + \ { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(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#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 34}, + \ } + \ ], + \ 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#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 34}, + \ '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#ToURI(g:dir . '/foo/bar.ts'), + \ }, + \ 'position': {'line': 11, 'character': 34}, + \ } + \ ], + \ ale#lsp#message#Definition(bufnr(''), 12, 34) + +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', + \ } + \ ], + \ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12, 'abc') + +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) diff --git a/test/lsp/test_lsp_connections.vader b/test/lsp/test_lsp_connections.vader new file mode 100644 index 0000000..8651d80 --- /dev/null +++ b/test/lsp/test_lsp_connections.vader @@ -0,0 +1,271 @@ +Before: + let g:ale_lsp_next_message_id = 1 + +After: + 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 + \ ) + +Execute(Projects with regular project roots should be registered correctly): + let b:conn = {'projects': {}} + + call ale#lsp#RegisterProject(b:conn, '/foo/bar') + + AssertEqual + \ { + \ 'projects': { + \ '/foo/bar': {'initialized': 0, 'message_queue': [], 'init_request_id': 0}, + \ }, + \ }, + \ b:conn + +Execute(Projects with regular project roots should be fetched correctly): + let b:conn = { + \ 'projects': { + \ '/foo/bar': {'initialized': 0, 'message_queue': [], 'init_request_id': 0}, + \ }, + \} + + AssertEqual + \ {'initialized': 0, 'message_queue': [], 'init_request_id': 0}, + \ ale#lsp#GetProject(b:conn, '/foo/bar') + +Execute(Projects with empty project roots should be registered correctly): + let b:conn = {'projects': {}} + + call ale#lsp#RegisterProject(b:conn, '') + + AssertEqual + \ { + \ 'projects': { + \ '<>': {'initialized': 1, 'message_queue': [], 'init_request_id': 0}, + \ }, + \ }, + \ b:conn + +Execute(Projects with empty project roots should be fetched correctly): + let b:conn = { + \ 'projects': { + \ '<>': {'initialized': 1, 'message_queue': [], 'init_request_id': 0}, + \ }, + \} + + AssertEqual + \ {'initialized': 1, 'message_queue': [], 'init_request_id': 0}, + \ ale#lsp#GetProject(b:conn, '') 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 0000000..3a7c7f6 --- /dev/null +++ b/test/lsp/test_other_initialize_message_handling.vader @@ -0,0 +1,66 @@ +Before: + let b:project = { + \ 'initialized': 0, + \ 'init_request_id': 3, + \ 'message_queue': [], + \} + + let b:conn = { + \ 'projects': { + \ '/foo/bar': b:project, + \ }, + \} + +After: + unlet! b:project + unlet! b:conn + +Execute(publishDiagnostics messages with files inside project directories should initialize projects): + " This is for some other file, ignore this one. + call ale#lsp#HandleOtherInitializeResponses(b:conn, { + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': {'uri': 'file:///xyz/bar/baz.txt'}, + \}) + + AssertEqual + \ { + \ 'initialized': 0, + \ 'init_request_id': 3, + \ 'message_queue': [], + \ }, + \ b:project + + call ale#lsp#HandleOtherInitializeResponses(b:conn, { + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': {'uri': 'file:///foo/bar/baz.txt'}, + \}) + + AssertEqual + \ { + \ 'initialized': 1, + \ 'init_request_id': 3, + \ 'message_queue': [], + \ }, + \ b:project + +Execute(Messages with no method and capabilities should initialize projects): + call ale#lsp#HandleOtherInitializeResponses(b:conn, { + \ 'result': {'capabilities': {}}, + \}) + + AssertEqual + \ { + \ 'initialized': 1, + \ 'init_request_id': 3, + \ 'message_queue': [], + \ }, + \ b:project + +Execute(Other messages should not initialize projects): + call ale#lsp#HandleOtherInitializeResponses(b:conn, {'method': 'lolwat'}) + + AssertEqual 0, b:project.initialized + + call ale#lsp#HandleOtherInitializeResponses(b:conn, {'result': {'x': {}}}) + + AssertEqual 0, b:project.initialized diff --git a/test/lsp/test_read_lsp_diagnostics.vader b/test/lsp/test_read_lsp_diagnostics.vader new file mode 100644 index 0000000..444272a --- /dev/null +++ b/test/lsp/test_read_lsp_diagnostics.vader @@ -0,0 +1,166 @@ +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': 16, + \ 'nr': 'some-error', + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ + \ { + \ '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': 4, + \ 'nr': 'some-warning', + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ + \ { + \ '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': 16, + \ 'nr': 'some-error', + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ + \ { + \ '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': 16, + \ } + \ ], + \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ + \ { + \ 'range': Range(2, 10, 4, 15), + \ 'message': 'Something went wrong!', + \ }, + \ ]}}) + +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': 3, + \ }, + \ { + \ 'type': 'W', + \ 'text': 'A warning', + \ 'lnum': 2, + \ 'col': 5, + \ 'end_lnum': 2, + \ 'end_col': 5, + \ }, + \ ], + \ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [ + \ { + \ '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#ReadTSServerDiagnostics() should handle tsserver responses): + AssertEqual + \ [ + \ { + \ 'type': 'E', + \ 'nr': 2365, + \ 'text': 'Operator ''''+'''' cannot be applied to types ''''3'''' and ''''{}''''.', + \ 'lnum': 1, + \ 'col': 11, + \ 'end_lnum': 1, + \ 'end_col': 17, + \ }, + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"/bar/foo.ts","diagnostics":[{"start":{"line":1,"offset":11},"end":{"line":1,"offset":17},"text":"Operator ''+'' cannot be applied to types ''3'' and ''{}''.","code":2365}]}}) + +Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle warnings from tsserver): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 3, + \ 'nr': 2515, + \ 'end_lnum': 27, + \ 'type': 'W', + \ 'end_col': 14, + \ 'text': 'Calls to ''console.log'' are not allowed. (no-console)', + \ } + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"","diagnostics":[{"start":{"line":27,"offset":3},"end":{"line":27,"offset":14},"text":"Calls to 'console.log' are not allowed. (no-console)","code":2515,"category":"warning","source":"tslint"}]}}) + +Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle suggestions from tsserver): + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 3, + \ 'nr': 2515, + \ 'end_lnum': 27, + \ 'type': 'I', + \ 'end_col': 14, + \ 'text': 'Some info', + \ } + \ ], + \ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"","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 0000000..2bec13d --- /dev/null +++ b/test/lsp/test_reset_lsp.vader @@ -0,0 +1,90 @@ +Before: + Save g:ale_enabled + Save g:ale_set_signs + Save g:ale_set_quickfix + Save g:ale_set_loclist + Save g:ale_set_highlights + Save g:ale_echo_cursor + + let g:ale_enabled = 0 + let g:ale_set_signs = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + + function EmptyString() abort + return '' + endfunction + + call ale#engine#InitBufferInfo(bufnr('')) + + call ale#linter#Define('testft', { + \ 'name': 'lsplinter', + \ 'lsp': 'tsserver', + \ 'executable_callback': 'EmptyString', + \ 'command_callback': 'EmptyString', + \ 'project_root_callback': 'EmptyString', + \ 'language_callback': 'EmptyString', + \}) + + call ale#linter#Define('testft', { + \ 'name': 'otherlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + +After: + Restore + + unlet! b:ale_save_event_fired + + delfunction EmptyString + call ale#linter#Reset() + +Given testft(Some file with an imaginary filetype): +Execute(ALEStopAllLSPs should clear the loclist): + let g:ale_buffer_info[bufnr('')].loclist = [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'lsplinter', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'otherlinter', + \ }, + \] + let g:ale_buffer_info[bufnr('')].active_linter_list = ['lsplinter', 'otherlinter'] + + ALEStopAllLSPs + + " The loclist should be updated. + AssertEqual g:ale_buffer_info[bufnr('')].loclist, [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'otherlinter', + \ }, + \] + + " The LSP linter should be removed from the active linter list. + AssertEqual g:ale_buffer_info[bufnr('')].active_linter_list, ['otherlinter'] diff --git a/test/phpcs-test-files/project-with-phpcs/foo/test.php b/test/phpcs-test-files/project-with-phpcs/foo/test.php new file mode 100644 index 0000000..e69de29 diff --git a/test/phpcs-test-files/project-with-phpcs/vendor/bin/phpcs b/test/phpcs-test-files/project-with-phpcs/vendor/bin/phpcs new file mode 100644 index 0000000..e69de29 diff --git a/test/phpcs-test-files/project-without-phpcs/foo/test.php b/test/phpcs-test-files/project-without-phpcs/foo/test.php new file mode 100644 index 0000000..e69de29 diff --git a/test/prettier-test-files/testfile.css b/test/prettier-test-files/testfile.css new file mode 100644 index 0000000..e69de29 diff --git a/test/prettier-test-files/testfile.js b/test/prettier-test-files/testfile.js new file mode 100644 index 0000000..e69de29 diff --git a/test/prettier-test-files/testfile.json b/test/prettier-test-files/testfile.json new file mode 100644 index 0000000..e69de29 diff --git a/test/prettier-test-files/testfile.scss b/test/prettier-test-files/testfile.scss new file mode 100644 index 0000000..e69de29 diff --git a/test/prettier-test-files/testfile.ts b/test/prettier-test-files/testfile.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/prettier-test-files/with_config/.prettierrc b/test/prettier-test-files/with_config/.prettierrc new file mode 100644 index 0000000..e69de29 diff --git a/test/prettier-test-files/with_config/testfile.js b/test/prettier-test-files/with_config/testfile.js new file mode 100644 index 0000000..e69de29 diff --git a/test/reasonml_files/testfile.re b/test/reasonml_files/testfile.re new file mode 100644 index 0000000..e69de29 diff --git a/test/ruby_fixtures/not_a_rails_app/file.rb b/test/ruby_fixtures/not_a_rails_app/file.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/ruby_fixtures/valid_rails_app/app/dummy.rb b/test/ruby_fixtures/valid_rails_app/app/dummy.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/ruby_fixtures/valid_rails_app/app/models/thing.rb b/test/ruby_fixtures/valid_rails_app/app/models/thing.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/ruby_fixtures/valid_rails_app/app/views/my_great_view.html.erb b/test/ruby_fixtures/valid_rails_app/app/views/my_great_view.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/test/ruby_fixtures/valid_rails_app/config/dummy.rb b/test/ruby_fixtures/valid_rails_app/config/dummy.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/ruby_fixtures/valid_rails_app/db/dummy.rb b/test/ruby_fixtures/valid_rails_app/db/dummy.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/rust_files/testfile.rs b/test/rust_files/testfile.rs new file mode 100644 index 0000000..e69de29 diff --git a/test/script/check-supported-tools-tables b/test/script/check-supported-tools-tables new file mode 100755 index 0000000..32cebb2 --- /dev/null +++ b/test/script/check-supported-tools-tables @@ -0,0 +1,68 @@ +#!/bin/bash -eu + +# This script compares the table of supported tools in both the README file +# and the doc/ale.txt file, so we can complain if they don't match up. + +# Find the start and end lines for the help section. +ale_help_start_line="$( \ + grep -m1 -n '^[0-9][0-9]*\. *Supported Languages' doc/ale.txt \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +ale_help_section_size="$( \ + tail -n +"$ale_help_start_line" doc/ale.txt \ + | grep -m1 -n '================' \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +# -- shellcheck complains about expr, but it works better. +# shellcheck disable=SC2003 +ale_help_end_line="$(expr "$ale_help_start_line" + "$ale_help_section_size")" + +# Find the start and end lines for the same section in the README. +readme_start_line="$( \ + grep -m1 -n '^.*[0-9][0-9]*\. *Supported Languages' README.md \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +readme_section_size="$( \ + tail -n +"$readme_start_line" README.md \ + | grep -m1 -n '^##.*Usage' \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +# shellcheck disable=SC2003 +readme_end_line="$(expr "$readme_start_line" + "$readme_section_size")" + +doc_file="$(mktemp -t doc.XXXXXXXX)" +readme_file="$(mktemp -t readme.XXXXXXXX)" + +sed -n "$ale_help_start_line,$ale_help_end_line"p doc/ale.txt \ + | grep '\* .*: ' \ + | sed 's/^*//' \ + | sed 's/[`!^]//g;s/([^)]*)//g' \ + | sed 's/ *\([,:]\)/\1/g' \ + | sed 's/ */ /g' \ + | sed 's/^ *//;s/ *$//' \ + | sed 's/^/ /' \ + > "$doc_file" + +sed -n "$readme_start_line,$readme_end_line"p README.md \ + | grep '| .* |' \ + | sed '/^| Language/d;/^| ---/d' \ + | sed 's/^|//' \ + | sed 's/ \{0,1\}|/:/' \ + | sed 's/[`!^|]//g;s/([^)]*)//g' \ + | sed 's/\[//g;s/\]//g' \ + | sed 's/see[^,]*//g' \ + | sed 's/ *\([,:]\)/\1/g' \ + | sed 's/ */ /g' \ + | sed 's/^ *//;s/ *$//' \ + | sed 's/^/ /' \ + | sed 's/ *-n flag//g' \ + > "$readme_file" + +exit_code=0 + +diff -U0 "$readme_file" "$doc_file" || exit_code=$? + +rm "$doc_file" +rm "$readme_file" + +exit "$exit_code" diff --git a/test/script/check-toc b/test/script/check-toc new file mode 100755 index 0000000..cc2d2b9 --- /dev/null +++ b/test/script/check-toc @@ -0,0 +1,81 @@ +#!/bin/bash -eu + +# This script checks that the table of contents for the supported tools is +# sorted, and that the table matches the files. + +toc_start_line="$( \ + grep -m1 -n 'Integration Documentation.*|ale-integrations|' doc/ale.txt \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +# shellcheck disable=SC2003 +toc_start_line="$(expr "$toc_start_line" + 1)" +toc_section_size="$( \ + tail -n +"$toc_start_line" doc/ale.txt \ + | grep -m1 -n '^ [0-9]\+\.' \ + | sed 's/\([0-9]*\).*/\1/' \ +)" +# shellcheck disable=SC2003 +toc_end_line="$(expr "$toc_start_line" + "$toc_section_size" - 2)" + +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="$(/bin/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 \(.*\) 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 0000000..791053d --- /dev/null +++ b/test/script/custom-checks @@ -0,0 +1,68 @@ +#!/bin/bash -eu + +exit_code=0 +image=w0rp/ale +docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$image") + +echo '========================================' +echo 'Running custom linting rules' +echo '========================================' +echo 'Custom warnings/errors follow:' +echo + +set -o pipefail +docker run -a stdout "${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 + +grep --exclude=tags -roh '\*.*\*$' doc | sort | uniq -d || exit_code=$? + +echo '========================================' +echo 'Checking for invalid tag references' +echo '========================================' +echo 'Invalid tag references tags follow:' +echo + +tag_regex='[gb]\?:\?\(ale\|ALE\)[a-zA-Z_\-]\+' + +# Grep for tags and references, and complain if we find a reference without +# a tag for the reference. Only our tags will be included. +diff -u \ + <(grep --exclude=tags -roh "\*$tag_regex\*" doc | sort -u | sed 's/*//g') \ + <(grep --exclude=tags -roh "|$tag_regex|" doc | sort -u | sed 's/|//g') \ + | grep '^+[^+]' && exit_code=1 + +echo '========================================' +echo 'diff README.md and doc/ale.txt tables' +echo '========================================' +echo 'Differences follow:' +echo + +test/script/check-supported-tools-tables || exit_code=$? + +echo '========================================' +echo 'Look for badly aligned doc tags' +echo '========================================' +echo 'Badly aligned tags follow:' +echo + +# Documentation tags need to be aligned to the right margin, so look for +# tags which aren't at the right margin. +grep ' \*[^*]\+\*$' doc/ -r \ + | awk '{ sep = index($0, ":"); if (length(substr($0, sep + 1 )) < 79) { print } }' \ + | grep . && exit_code=1 + +echo '========================================' +echo 'Look for table of contents issues' +echo '========================================' +echo + +test/script/check-toc || exit_code=$? + +exit $exit_code diff --git a/test/script/custom-linting-rules b/test/script/custom-linting-rules new file mode 100755 index 0000000..ef6d792 --- /dev/null +++ b/test/script/custom-linting-rules @@ -0,0 +1,95 @@ +#!/bin/bash -eu + +# 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" + + for directory in "${directories[@]}"; do + while IFS= read -r match; do + RETURN_CODE=1 + echo "$match $message" + done < <(grep -n "$regex" "$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 + done +fi + +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 "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. Use \`execute echo\` if you want to echo something" + +exit $RETURN_CODE diff --git a/test/script/run-vader-tests b/test/script/run-vader-tests new file mode 100755 index 0000000..a10b8ba --- /dev/null +++ b/test/script/run-vader-tests @@ -0,0 +1,115 @@ +#!/bin/bash -eu + +image=w0rp/ale +docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$image") +red='\033[0;31m' +green='\033[0;32m' +nc='\033[0m' +verbose=0 +quiet=0 +exit_code=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" + +function filter-vader-output() { + # When verbose mode is off, suppress output until Vader starts. + local start_output="$verbose" + local filtered_data='' + + while read -r; do + if ((!start_output)); then + if [[ "$REPLY" = *'Starting Vader:'* ]]; 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 +} + +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 + +set -o pipefail +docker run -a stderr -e VADER_OUTPUT_FILE=/dev/stderr "${docker_flags[@]}" \ + "/vim-build/bin/$vim" -u test/vimrc \ + "+Vader! $tests" 2>&1 | filter-vader-output | color-vader-output || exit_code=$? +set +o pipefail + +exit "$exit_code" diff --git a/test/script/run-vint b/test/script/run-vint new file mode 100755 index 0000000..e114030 --- /dev/null +++ b/test/script/run-vint @@ -0,0 +1,18 @@ +#!/bin/bash -eu + +exit_code=0 +image=w0rp/ale +docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$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 0000000..c23b400 --- /dev/null +++ b/test/sign/test_linting_sets_signs.vader @@ -0,0 +1,53 @@ +Given foobar (Some imaginary filetype): + var y = 3+3; + var y = 3 + +Before: + sign unplace * + + function! TestCallback(buffer, output) + return [ + \ {'lnum': 1, 'text': 'foo', 'type': 'W'}, + \ {'lnum': 2, 'text': 'foo', 'type': 'E'}, + \] + endfunction + + function! CollectSigns() + redir => l:output + silent exec 'sign place' + redir END + + let l:actual_sign_list = [] + + for l:line in split(l:output, "\n") + let l:match = matchlist(l:line, '\v^.*\=(\d+).*\=\d+.*\=(ALE[a-zA-Z]+Sign)') + + if len(l:match) > 0 + call add(l:actual_sign_list, [l:match[1], l:match[2]]) + 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 + + sign unplace * + let g:ale_buffer_info = {} + call ale#linter#Reset() + +Execute(The signs should be updated after linting is done): + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + AssertEqual [['1', 'ALEWarningSign'], ['2', 'ALEErrorSign']], CollectSigns() diff --git a/test/sign/test_sign_column_highlighting.vader b/test/sign/test_sign_column_highlighting.vader new file mode 100644 index 0000000..4457a45 --- /dev/null +++ b/test/sign/test_sign_column_highlighting.vader @@ -0,0 +1,55 @@ +Before: + Save g:ale_change_sign_column_color + + function! ParseHighlight(name) abort + redir => l:output + silent execute 'highlight ' . a:name + redir end + + return join(split(l:output)[2:]) + 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 + + sign unplace * + +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') diff --git a/test/sign/test_sign_limits.vader b/test/sign/test_sign_limits.vader new file mode 100644 index 0000000..b8868ae --- /dev/null +++ b/test/sign/test_sign_limits.vader @@ -0,0 +1,57 @@ +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 + + sign unplace * + +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(1 signs should be set when the max is 1): + let g:ale_max_signs = 1 + + AssertEqual [1], 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 0000000..07848af --- /dev/null +++ b/test/sign/test_sign_parsing.vader @@ -0,0 +1,35 @@ +Execute (Parsing English signs should work): + AssertEqual + \ [0, [[9, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([ + \ 'Signs for app.js:', + \ ' line=9 id=1000001 name=ALEWarningSign', + \ ]) + +Execute (Parsing Russian signs should work): + AssertEqual + \ [0, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' строка=1 id=1000001 имя=ALEErrorSign']) + +Execute (Parsing Japanese signs should work): + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' 行=1 識別子=1000001 名前=ALEWarningSign']) + +Execute (Parsing Spanish signs should work): + AssertEqual + \ [0, [[12, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' línea=12 id=1000001 nombre=ALEWarningSign']) + +Execute (Parsing Italian signs should work): + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' riga=1 id=1000001, nome=ALEWarningSign']) + \ +Execute (The sign parser should indicate if the dummy sign is set): + AssertEqual + \ [1, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([ + \ ' строка=1 id=1000001 имя=ALEErrorSign', + \ ' line=1 id=1000000 name=ALEDummySign', + \ ]) diff --git a/test/sign/test_sign_placement.vader b/test/sign/test_sign_placement.vader new file mode 100644 index 0000000..36f34e1 --- /dev/null +++ b/test/sign/test_sign_placement.vader @@ -0,0 +1,282 @@ +Before: + Save g:ale_set_signs + + let g:ale_set_signs = 1 + + call ale#linter#Reset() + sign unplace * + + 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 + silent sign place + redir END + + return map( + \ split(l:output, '\n')[2:], + \ 'matchlist(v:val, ''^.*=\(\d\+\).*=\(\d\+\).*=\(.*\)$'')[1:3]', + \) + endfunction + + call ale#linter#Define('testft', { + \ 'name': 'x', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'true', + \ 'callback': 'GenerateResults', + \}) + +After: + Restore + + unlet! g:loclist + delfunction GenerateResults + delfunction ParseSigns + call ale#linter#Reset() + sign unplace * + +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): + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + 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): + exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 line=15 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000349 line=16 name=ALEWarningSign buffer=' . bufnr('') + + let g:loclist = [ + \ {'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): + let g:loclist = [ + \ {'bufnr': bufnr(''), 'lnum': 16, 'col': 2, 'type': 'E', 'text': 'f'}, + \] + + call ale#sign#SetSigns(bufnr(''), g:loclist) + + AssertEqual + \ [ + \ ['16', '1000001', 'ALEErrorSign'], + \ ], + \ sort(ParseSigns()) + + call ale#sign#SetSigns(bufnr(''), []) + + AssertEqual [], ParseSigns() + +Execute(No exceptions should be thrown when setting signs for invalid buffers): + call ale#sign#SetSigns(123456789, [{'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}]) + +Execute(Signs should be removed when lines have multiple sign IDs on them): + " We can fail to remove signs if there are multiple signs on one line, + " say after deleting lines in Vim, etc. + 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('') + + call ale#sign#SetSigns(bufnr(''), []) + AssertEqual [], ParseSigns() diff --git a/test/slimlint-test-files/.rubocop.yml b/test/slimlint-test-files/.rubocop.yml new file mode 100644 index 0000000..e69de29 diff --git a/test/slimlint-test-files/subdir/file.slim b/test/slimlint-test-files/subdir/file.slim new file mode 100644 index 0000000..e69de29 diff --git a/test/smlnj/cm/foo.sml b/test/smlnj/cm/foo.sml new file mode 100644 index 0000000..e69de29 diff --git a/test/smlnj/cm/path/to/bar.sml b/test/smlnj/cm/path/to/bar.sml new file mode 100644 index 0000000..e69de29 diff --git a/test/smlnj/cm/sources.cm b/test/smlnj/cm/sources.cm new file mode 100644 index 0000000..e69de29 diff --git a/test/smlnj/file/qux.sml b/test/smlnj/file/qux.sml new file mode 100644 index 0000000..e69de29 diff --git a/test/smoke_test.vader b/test/smoke_test.vader new file mode 100644 index 0000000..f6d0be5 --- /dev/null +++ b/test/smoke_test.vader @@ -0,0 +1,150 @@ +Before: + Save g:ale_set_lists_synchronously + Save g:ale_buffer_info + Save &shell + + 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 + + delfunction TestCallback + delfunction TestCallback2 + call ale#linter#Reset() + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(Linters should run with the default options): + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + AssertEqual [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo bar', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }], getloclist(0) + +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#Lint() + call ale#engine#WaitForJobs(2000) + + 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, + \ }, + \], getloclist(0) + endif + +Execute(Previous errors should be removed when linters change): + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + call ale#linter#Reset() + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter2', + \ 'callback': 'TestCallback2', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo baz boz' : '/bin/sh -c ''echo baz boz''', + \}) + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + AssertEqual [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 3, + \ 'vcol': 0, + \ 'col': 4, + \ 'text': 'baz boz', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }], getloclist(0) diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index f8172c0..05c045b 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -1,91 +1,438 @@ Before: + Save g:ale_buffer_info + Save g:ale_cache_executable_check_failures + Save g:ale_completion_enabled + Save g:ale_fixers + Save g:ale_history_log_output + Save g:ale_lint_on_insert_leave + Save g:ale_lint_on_text_changed + Save g:ale_linters + Save g:ale_maximum_file_size + Save g:ale_pattern_options + Save g:ale_pattern_options_enabled + Save g:ale_set_balloons + Save g:ale_warn_about_trailing_whitespace + + unlet! b:ale_history + + let g:ale_buffer_info = {} + let g:ale_cache_executable_check_failures = 0 + let g:ale_completion_enabled = 0 + let g:ale_history_log_output = 1 + let g:ale_lint_on_insert_leave = 0 + let g:ale_lint_on_text_changed = 'always' + let g:ale_maximum_file_size = 0 + let g:ale_pattern_options = {} + let g:ale_pattern_options_enabled = 0 + let g:ale_set_balloons = 0 + let g:ale_warn_about_trailing_whitespace = 1 + let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout'} let g:testlinter2 = {'name': 'testlinter2', 'executable': 'testlinter2', 'command': 'testlinter2', 'callback': 'testCB2', 'output_stream': 'stdout'} + call ale#engine#ResetExecutableCache() call ale#linter#Reset() let g:ale_linters = {} + let g:ale_fixers = {} let g:ale_linter_aliases = {} + let g:ale_buffer_info = {} + let g:globals_lines = [ + \ ' 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_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_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_save = 1', + \ 'let g:ale_lint_on_text_changed = ''always''', + \ 'let g:ale_lint_on_insert_leave = 0', + \ 'let g:ale_linter_aliases = {}', + \ 'let g:ale_linters = {}', + \ 'let g:ale_linters_explicit = 0', + \ 'let g:ale_list_window_size = 10', + \ 'let g:ale_list_vertical = 0', + \ 'let g:ale_loclist_msg_format = ''%code: %%s''', + \ 'let g:ale_max_buffer_history_size = 20', + \ 'let g:ale_max_signs = -1', + \ '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_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_statusline_format = [''%d error(s)'', ''%d warning(s)'', ''OK'']', + \ 'let g:ale_type_map = {}', + \ '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 After: + Restore + + unlet! g:testlinter1 + unlet! g:testlinter2 + + unlet! b:ale_history + unlet! b:ale_linters unlet! g:output + 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 + delfunction CheckInfo Given nolintersft (Empty buffer with no linters): Execute (ALEInfo with no linters should return the right output): - redir => g:output - silent ALEInfo - redir END - AssertEqual "\n - \ Current Filetype: nolintersft\n - \Available Linters: []\n - \ Enabled Linters: []", g:output + call CheckInfo([ + \ ' Current Filetype: nolintersft', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ' Linter Variables:', + \ '', + \] + 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: []', + \ ' Linter Variables:', + \ '', + \] + g:globals_lines + g:command_header) Given (Empty buffer with no filetype): Execute (ALEInfo with no filetype should return the right output): - redir => g:output - silent ALEInfo - redir END - AssertEqual "\n - \ Current Filetype: \n - \Available Linters: []\n - \ Enabled Linters: []", g:output + call CheckInfo([ + \ ' Current Filetype: ', + \ 'Available Linters: []', + \ ' Enabled Linters: []', + \ ' Linter Variables:', + \ '', + \] + 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) - redir => g:output - silent ALEInfo - redir END - AssertEqual "\n - \ Current Filetype: testft\n - \Available Linters: ['testlinter1']\n - \ Enabled Linters: ['testlinter1']", g:output + + call CheckInfo([ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Linter Variables:', + \ '', + \] + 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) - redir => g:output - silent ALEInfo - redir END - AssertEqual "\n - \ Current Filetype: testft\n - \Available Linters: ['testlinter1', 'testlinter2']\n - \ Enabled Linters: ['testlinter1', 'testlinter2']", g:output + + call CheckInfo([ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Linter Variables:', + \ '', + \] + 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'] } - redir => g:output - silent ALEInfo - redir END - AssertEqual "\n - \ Current Filetype: testft\n - \Available Linters: ['testlinter1', 'testlinter2']\n - \ Enabled Linters: ['testlinter2']", g:output + 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'']', + \ ' Linter Variables:', + \ '', + \] + 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) - redir => g:output - silent ALEInfo - redir END - AssertEqual "\n - \ Current Filetype: testft\n - \Available Linters: ['testlinter1']\n - \ Enabled Linters: ['testlinter1']", g:output + + call CheckInfo([ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Linter Variables:', + \ '', + \] + 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) - redir => g:output - silent ALEInfo - redir END - AssertEqual "\n - \ Current Filetype: testft.testft2\n - \Available Linters: ['testlinter1', 'testlinter2']\n - \ Enabled Linters: ['testlinter1', 'testlinter2']", g:output + call CheckInfo([ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Linter Variables:', + \ '', + \] + 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'']', + \ ' 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) + +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'']', + \ ' 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'']', + \ ' 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'']', + \ ' Linter Variables:', + \ '', + \] + 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'']', + \ ' Linter Variables:', + \ '', + \] + 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'']', + \ ' Linter Variables:', + \ '', + \] + 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'']', + \ ' Linter Variables:', + \ '', + \] + 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 + let g:globals_lines[2] = 'let g:ale_cache_executable_check_failures = 1' + + 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'']', + \ ' Linter Variables:', + \ '', + \] + g:globals_lines + g:command_header + [ + \ '', + \ '(executable check - success) ' . (has('win32') ? 'cmd' : 'echo'), + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \]) diff --git a/test/test_ale_lint_command.vader b/test/test_ale_lint_command.vader new file mode 100644 index 0000000..d36b217 --- /dev/null +++ b/test/test_ale_lint_command.vader @@ -0,0 +1,73 @@ +Before: + Save g:ale_buffer_info + + 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', + \ 'ALECursorGroup', + \ 'ALEHighlightBufferGroup', + \ 'ALERunOnEnterGroup', + \ 'ALERunOnTextChangedGroup', + \] + + 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! g:expected_groups + + let g:ale_buffer_info = {} + call ale#linter#Reset() + + delfunction ToggleTestCallback + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALELint should run the linters): + AssertEqual 'foobar', &filetype + + ALELint + call ale#engine#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 + + " Check the loclist + AssertEqual g:expected_loclist, getloclist(0) diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader new file mode 100644 index 0000000..d56f8c2 --- /dev/null +++ b/test/test_ale_toggle.vader @@ -0,0 +1,346 @@ +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 + + let g:ale_set_signs = 1 + let g:ale_set_lists_synchronously = 1 + let g:ale_run_synchronously = 1 + let g:ale_pattern_options = {} + let g:ale_pattern_options_enabled = 1 + + 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', + \ 'ALECursorGroup', + \ 'ALEHighlightBufferGroup', + \ 'ALERunOnEnterGroup', + \ 'ALERunOnFiletypeChangeGroup', + \ 'ALERunOnSaveGroup', + \ 'ALERunOnTextChangedGroup', + \] + + 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]\+Group') + + " 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, + \}) + + sign unplace * + +After: + Restore + + unlet! g:expected_loclist + unlet! g:expected_groups + unlet! b:ale_enabled + unlet! g:output + + 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, []) + sign unplace * + call clearmatches() + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALEToggle should reset everything and then run again): + " Run this test asynchrously. + let g:ale_run_synchronously = 0 + + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + " First check that everything is there... + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual g:expected_groups, ParseAuGroups() + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + + " 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 [], getloclist(0), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + AssertEqual [], getmatches(), 'The highlights were not cleared' + AssertEqual + \ [ + \ 'ALECleanupGroup', + \ 'ALEHighlightBufferGroup', + \ 'ALERunOnSaveGroup', + \ ], + \ ParseAuGroups() + + " Toggle ALE on, everything should be set up and run again. + ALEToggle + call ale#engine#WaitForJobs(2000) + + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual g:expected_groups, ParseAuGroups() + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + +Execute(ALEToggle should skip filename keys and preserve them): + " Run this test asynchrously. + let g:ale_run_synchronously = 0 + + AssertEqual 'foobar', &filetype + + let g:ale_buffer_info['/foo/bar/baz.txt'] = { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \} + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + " Now Toggle ALE off. + ALEToggle + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) + + " Toggle ALE on again. + ALEToggle + call ale#engine#WaitForJobs(2000) + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) + +Execute(ALEDisable should reset everything and stay disabled): + call ale#Lint() + + AssertEqual g:expected_loclist, getloclist(0) + + ALEDisable + + AssertEqual [], getloclist(0) + AssertEqual 0, g:ale_enabled + + ALEDisable + + AssertEqual [], getloclist(0) + AssertEqual 0, g:ale_enabled + +Execute(ALEEnable should enable ALE and lint again): + let g:ale_enabled = 0 + + ALEEnable + + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual 1, g:ale_enabled + +Execute(ALEReset should reset everything for a buffer): + AssertEqual 'foobar', &filetype + + call ale#Lint() + + " First check that everything is there... + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual [{'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 + + " Everything should be cleared. + Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' + AssertEqual [], getloclist(0), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + AssertEqual [], getmatches(), 'The highlights were not cleared' + + AssertEqual 1, g:ale_enabled + +Execute(ALEToggleBuffer should reset everything and then run again): + " Run this test asynchrously. + let g:ale_run_synchronously = 0 + + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + " First check that everything is there... + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual [{'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 [], getloclist(0), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + AssertEqual [], getmatches(), 'The highlights were not cleared' + + " Toggle ALE on, everything should be set up and run again. + ALEToggleBuffer + call ale#engine#WaitForJobs(2000) + + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual g:expected_groups, ParseAuGroups() + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist + +Execute(ALEDisableBuffer should reset everything and stay disabled): + call ale#Lint() + + AssertEqual g:expected_loclist, getloclist(0) + + ALEDisableBuffer + + AssertEqual [], getloclist(0) + AssertEqual 0, b:ale_enabled + +Execute(ALEEnableBuffer should enable ALE and lint again): + let b:ale_enabled = 0 + + ALEEnableBuffer + + AssertEqual g:expected_loclist, getloclist(0) + 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 [], getloclist(0) + 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 + + call ale#Lint() + + " First check that everything is there... + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual [{'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 + + " Everything should be cleared. + Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' + AssertEqual [], getloclist(0), 'The loclist was not cleared' + AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' + AssertEqual [], getmatches(), 'The highlights were not cleared' + + AssertEqual 1, g:ale_enabled + AssertEqual 1, get(b:, 'ale_enabled', 1) diff --git a/test/test_ale_var.vader b/test/test_ale_var.vader new file mode 100644 index 0000000..fb674d9 --- /dev/null +++ b/test/test_ale_var.vader @@ -0,0 +1,41 @@ +Before: + let g:ale_some_variable = 'abc' + +After: + unlet! g:ale_some_variable + unlet! b:undefined_variable_name + + let g:ale_fix_buffer_data = {} + +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') + +Execute(ale#Var return variables from deleted buffers, saved for fixing things): + let g:ale_fix_buffer_data[1347347] = {'vars': {'ale_some_variable': 'def'}} + + AssertEqual 'def', ale#Var(1347347, 'some_variable') + +Execute(ale#Var should return the global variable for unknown variables): + let g:ale_fix_buffer_data = {} + + AssertEqual 'abc', ale#Var(1347347, 'some_variable') + +Execute(ale#Var should return the global variables when the ALE fix variable is undefined): + unlet! g:ale_fix_buffer_data + + AssertEqual 'abc', ale#Var(1347347, 'some_variable') diff --git a/test/test_alelint_autocmd.vader b/test/test_alelint_autocmd.vader index 22fe193..d51694f 100644 --- a/test/test_alelint_autocmd.vader +++ b/test/test_alelint_autocmd.vader @@ -1,20 +1,38 @@ Before: - let g:success = 0 - augroup VaderTest - autocmd! - autocmd User ALELint let g:success = 1 - augroup end + let g:pre_success = 0 + let g:post_success = 0 + let g:ale_run_synchronously = 1 + + unlet! b:ale_linted After: - augroup! VaderTest + let g:ale_run_synchronously = 0 let g:ale_buffer_info = {} -Given vim (Some vimscript): - set nocompatible + try + augroup! VaderTest + catch + endtry + +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 -Execute (Lint it): call ale#Lint() - call ale#engine#WaitForJobs(2000) -Then (Autocommands should have run): - AssertEqual g:success, 1 + AssertEqual g:pre_success, 1 + AssertEqual g:post_success, 1 + +Execute(b:ale_linted should be increased after each lint cycle): + AssertEqual get(b:, 'ale_linted'), 0 + + call ale#Lint() + + AssertEqual get(b:, 'ale_linted'), 1 + + call ale#Lint() + + AssertEqual get(b:, 'ale_linted'), 2 diff --git a/test/test_autocmd_commands.vader b/test/test_autocmd_commands.vader new file mode 100644 index 0000000..c03e8fb --- /dev/null +++ b/test/test_autocmd_commands.vader @@ -0,0 +1,214 @@ +Before: + function! CheckAutocmd(group) + call ale#toggle#InitAuGroups() + + 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 .= ' ' + else + call add(l:matches, join(split(l:header . l:line))) + let l:header = '' + endif + endif + endfor + + call sort(l:matches) + + return l:matches + endfunction + + Save g:ale_enabled + Save g:ale_lint_on_text_changed + Save g:ale_lint_on_insert_leave + Save g:ale_pattern_options_enabled + Save g:ale_lint_on_enter + Save g:ale_lint_on_filetype_changed + Save g:ale_lint_on_save + Save g:ale_echo_cursor + Save g:ale_fix_on_save + Save g:ale_completion_enabled + +After: + delfunction CheckAutocmd + Restore + + if g:ale_completion_enabled + call ale#completion#Enable() + else + call ale#completion#Disable() + endif + + call ale#toggle#InitAuGroups() + +Execute (g:ale_lint_on_text_changed = 0 should bind no events): + let g:ale_lint_on_text_changed = 0 + + AssertEqual [], CheckAutocmd('ALERunOnTextChangedGroup') + +Execute (g:ale_lint_on_text_changed = 1 bind both events): + let g:ale_lint_on_text_changed = 1 + + AssertEqual [ + \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)' + \], CheckAutocmd('ALERunOnTextChangedGroup') + +Execute (g:ale_lint_on_text_changed = 'always' should bind both events): + let g:ale_lint_on_text_changed = 'always' + + AssertEqual [ + \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)' + \], CheckAutocmd('ALERunOnTextChangedGroup') + +Execute (g:ale_lint_on_text_changed = 'normal' should bind only TextChanged): + let g:ale_lint_on_text_changed = 'normal' + + AssertEqual [ + \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \], CheckAutocmd('ALERunOnTextChangedGroup') + +Execute (g:ale_lint_on_text_changed = 'insert' should bind only TextChangedI): + let g:ale_lint_on_text_changed = 'insert' + + AssertEqual [ + \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \], CheckAutocmd('ALERunOnTextChangedGroup') + +Execute (g:ale_lint_on_insert_leave = 1 should bind InsertLeave): + let g:ale_lint_on_insert_leave = 1 + + AssertEqual [ + \ 'InsertLeave * call ale#Queue(0)', + \], CheckAutocmd('ALERunOnInsertLeave') + +Execute (g:ale_lint_on_insert_leave = 0 should bind no events): + let g:ale_lint_on_insert_leave = 0 + + AssertEqual [], CheckAutocmd('ALERunOnInsertLeave') + +Execute (g:ale_pattern_options_enabled = 1 should bind BufReadPost and BufEnter): + let g:ale_pattern_options_enabled = 1 + + AssertEqual [ + \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand('''')))', + \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand('''')))', + \], CheckAutocmd('ALEPatternOptionsGroup') + +Execute (g:ale_pattern_options_enabled = 0 should still bind events): + let g:ale_pattern_options_enabled = 0 + + AssertEqual [ + \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand('''')))', + \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand('''')))', + \], CheckAutocmd('ALEPatternOptionsGroup') + +Execute (g:ale_enabled = 0 should still bind pattern events): + let g:ale_enabled = 0 + + AssertEqual [ + \ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand('''')))', + \ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand('''')))', + \], CheckAutocmd('ALEPatternOptionsGroup') + +Execute (g:ale_lint_on_enter = 0 should bind only the BufEnter event): + let g:ale_lint_on_enter = 0 + + AssertEqual + \ ['BufEnter * call ale#events#EnterEvent(str2nr(expand('''')))'], + \ CheckAutocmd('ALERunOnEnterGroup') + +Execute (g:ale_lint_on_enter = 1 should bind the required events): + let g:ale_lint_on_enter = 1 + + AssertEqual [ + \ 'BufEnter * call ale#events#EnterEvent(str2nr(expand('''')))', + \ 'BufReadPost * call ale#Queue(0, ''lint_file'', str2nr(expand('''')))', + \ 'BufWinEnter * call ale#Queue(0, ''lint_file'', str2nr(expand('''')))', + \ 'FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand('''')))', + \], CheckAutocmd('ALERunOnEnterGroup') + +Execute (g:ale_lint_on_filetype_changed = 0 should bind no events): + let g:ale_lint_on_filetype_changed = 0 + + AssertEqual [], CheckAutocmd('ALERunOnFiletypeChangeGroup') + +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('''')' + \ . ')', + \ ], + \ CheckAutocmd('ALERunOnFiletypeChangeGroup') + +Execute (The SaveEvent should always be bound): + let g:ale_enabled = 0 + let g:ale_lint_on_save = 0 + let g:ale_fix_on_save = 0 + + AssertEqual [ + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand('''')))', + \], CheckAutocmd('ALERunOnSaveGroup') + +Execute (g:ale_echo_cursor = 0 should bind no events): + let g:ale_echo_cursor = 0 + + AssertEqual [], CheckAutocmd('ALECursorGroup') + +Execute (g:ale_echo_cursor = 1 should bind cursor events): + let g:ale_echo_cursor = 1 + + AssertEqual [ + \ 'CursorHold * call ale#cursor#EchoCursorWarningWithDelay()', + \ 'CursorMoved * call ale#cursor#EchoCursorWarningWithDelay()', + \ 'InsertLeave * call ale#cursor#EchoCursorWarning()', + \], CheckAutocmd('ALECursorGroup') + +Execute (ALECleanupGroup should include the right commands): + AssertEqual [ + \ 'BufDelete * call ale#engine#Cleanup(str2nr(expand('''')))', + \ 'QuitPre * call ale#events#QuitEvent(str2nr(expand('''')))', + \], CheckAutocmd('ALECleanupGroup') + +Execute(Enabling completion should set up autocmd events correctly): + let g:ale_completion_enabled = 0 + call ale#completion#Enable() + + AssertEqual [ + \ 'CompleteDone * call ale#completion#Done()', + \ 'TextChangedI * call ale#completion#Queue()', + \], CheckAutocmd('ALECompletionGroup') + AssertEqual 1, g:ale_completion_enabled + +Execute(Disabling completion should remove autocmd events correctly): + let g:ale_completion_enabled = 1 + call ale#completion#Enable() + call ale#completion#Disable() + + AssertEqual [], CheckAutocmd('ALECompletionGroup') + AssertEqual 0, g:ale_completion_enabled diff --git a/test/test_backwards_compatibility.vader b/test/test_backwards_compatibility.vader new file mode 100644 index 0000000..e4e3756 --- /dev/null +++ b/test/test_backwards_compatibility.vader @@ -0,0 +1,19 @@ +" These tests, and the code that it covers, may be removed upon a major release. + +After: + unlet! g:ale_linters_sh_shellcheck_exclusions + unlet! g:ale_sh_shellcheck_exclusions + unlet! g:ale_linters_sh_shell_default_shell + unlet! g:ale_sh_shell_default_shell + +Execute(Old variable name for the 'shellcheck' linter should still work): + let g:ale_linters_sh_shellcheck_exclusions = 'SC1234' + runtime ale_linters/sh/shellcheck.vim + + AssertEqual 'SC1234', g:ale_sh_shellcheck_exclusions + +Execute (Old variable name for the 'shell' linter should still work): + let g:ale_linters_sh_shell_default_shell = 'woosh' + runtime ale_linters/sh/shell.vim + + AssertEqual 'woosh', g:ale_sh_shell_default_shell diff --git a/test/test_balloon_messages.vader b/test/test_balloon_messages.vader new file mode 100644 index 0000000..ec09fe2 --- /dev/null +++ b/test/test_balloon_messages.vader @@ -0,0 +1,41 @@ +Before: + Save g:ale_buffer_info + + let g:ale_buffer_info[347] = {'loclist': [ + \ { + \ 'bufnr': 347, + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'Missing semicolon. (semi)', + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 2, + \ 'col': 10, + \ 'text': 'Infix operators must be spaced. (space-infix-ops)' + \ }, + \ { + \ 'bufnr': 347, + \ 'lnum': 2, + \ 'col': 15, + \ 'text': 'Missing radix parameter (radix)' + \ }, + \]} + +After: + Restore + +Execute(Balloon messages should be shown for the correct lines): + AssertEqual + \ 'Missing semicolon. (semi)', + \ ale#balloon#MessageForPos(347, 1, 1) + +Execute(Balloon messages should be shown for earlier columns): + AssertEqual + \ 'Infix operators must be spaced. (space-infix-ops)', + \ ale#balloon#MessageForPos(347, 2, 1) + +Execute(Balloon messages should be shown for later columns): + AssertEqual + \ 'Missing radix parameter (radix)', + \ ale#balloon#MessageForPos(347, 2, 16) diff --git a/test/test_c_import_paths.vader b/test/test_c_import_paths.vader new file mode 100644 index 0000000..6080779 --- /dev/null +++ b/test/test_c_import_paths.vader @@ -0,0 +1,281 @@ +Before: + Save g:ale_c_gcc_options + Save g:ale_c_clang_options + Save g:ale_cpp_gcc_options + Save g:ale_cpp_clang_options + + call ale#test#SetDirectory('/testplugin/test') + + let g:ale_c_gcc_options = '' + let g:ale_c_clang_options = '' + let g:ale_cpp_gcc_options = '' + let g:ale_cpp_clang_options = '' + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +" Run this only once for this series of tests. The cleanup Execute step +" will run at the bottom of this file. +" +" We need to move .git/HEAD away so we don't match it, as we need to test +" functions which look for .git/HEAD. +Execute(Move .git/HEAD to a temp dir): + let g:temp_head_filename = tempname() + let g:head_filename = findfile('.git/HEAD', ';') + + if !empty(g:head_filename) + call writefile(readfile(g:head_filename, 'b'), g:temp_head_filename, 'b') + call delete(g:head_filename) + endif + +Execute(The C GCC handler should include 'include' directories for projects with a Makefile): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' + \ . ' -' + \ , ale_linters#c#gcc#GetCommand(bufnr('')) + +Execute(The C GCC handler should include 'include' directories for projects with a configure file): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/configure_project/subdir/file.c') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/include')) . ' ' + \ . ' -' + \ , ale_linters#c#gcc#GetCommand(bufnr('')) + +Execute(The C GCC handler should include root directories for projects with .h files in them): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/h_file_project/subdir/file.c') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' + \ . ' -' + \ , ale_linters#c#gcc#GetCommand(bufnr('')) + +Execute(The C GCC handler should include root directories for projects with .hpp files in them): + runtime! ale_linters/c/gcc.vim + + call ale#test#SetFilename('test_c_projects/hpp_file_project/subdir/file.c') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' + \ . ' -' + \ , ale_linters#c#gcc#GetCommand(bufnr('')) + +Execute(The C Clang handler should include 'include' directories for projects with a Makefile): + runtime! ale_linters/c/clang.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ale#Escape('clang') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' + \ . ' -' + \ , ale_linters#c#clang#GetCommand(bufnr('')) + +Execute(The C Clang handler should include 'include' directories for projects with a configure file): + runtime! ale_linters/c/clang.vim + + call ale#test#SetFilename('test_c_projects/h_file_project/subdir/file.c') + + AssertEqual + \ ale#Escape('clang') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' + \ . ' -' + \ , ale_linters#c#clang#GetCommand(bufnr('')) + +Execute(The C Clang handler should include root directories for projects with .h files in them): + runtime! ale_linters/c/clang.vim + + call ale#test#SetFilename('test_c_projects/h_file_project/subdir/file.c') + + AssertEqual + \ ale#Escape('clang') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' + \ . ' -' + \ , ale_linters#c#clang#GetCommand(bufnr('')) + +Execute(The C Clang handler should include root directories for projects with .hpp files in them): + runtime! ale_linters/c/clang.vim + + call ale#test#SetFilename('test_c_projects/hpp_file_project/subdir/file.c') + + AssertEqual + \ ale#Escape('clang') + \ . ' -S -x c -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' + \ . ' -' + \ , ale_linters#c#clang#GetCommand(bufnr('')) + +Execute(The C++ GCC handler should include 'include' directories for projects with a Makefile): + runtime! ale_linters/cpp/gcc.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' + \ . ' -' + \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + +Execute(The C++ GCC handler should include 'include' directories for projects with a configure file): + runtime! ale_linters/cpp/gcc.vim + + call ale#test#SetFilename('test_c_projects/configure_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/include')) . ' ' + \ . ' -' + \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + +Execute(The C++ GCC handler should include root directories for projects with .h files in them): + runtime! ale_linters/cpp/gcc.vim + + call ale#test#SetFilename('test_c_projects/h_file_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' + \ . ' -' + \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + +Execute(The C++ GCC handler should include root directories for projects with .hpp files in them): + runtime! ale_linters/cpp/gcc.vim + + call ale#test#SetFilename('test_c_projects/hpp_file_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('gcc') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' + \ . ' -' + \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) + +Execute(The C++ Clang handler should include 'include' directories for projects with a Makefile): + runtime! ale_linters/cpp/clang.vim + + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('clang++') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' + \ . ' -' + \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + +Execute(The C++ Clang handler should include 'include' directories for projects with a configure file): + runtime! ale_linters/cpp/clang.vim + + call ale#test#SetFilename('test_c_projects/configure_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('clang++') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/configure_project/include')) . ' ' + \ . ' -' + \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + +Execute(The C++ Clang handler should include root directories for projects with .h files in them): + runtime! ale_linters/cpp/clang.vim + + call ale#test#SetFilename('test_c_projects/h_file_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('clang++') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/h_file_project')) . ' ' + \ . ' -' + \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + +Execute(The C++ Clang handler should include root directories for projects with .hpp files in them): + runtime! ale_linters/cpp/clang.vim + + call ale#test#SetFilename('test_c_projects/hpp_file_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('clang++') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project/subdir')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/hpp_file_project')) . ' ' + \ . ' -' + \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + +Execute(The C++ Clang handler shoud use the include directory based on the .git location): + runtime! ale_linters/cpp/clang.vim + + if !isdirectory(g:dir . '/test_c_projects/git_and_nested_makefiles/.git') + call mkdir(g:dir . '/test_c_projects/git_and_nested_makefiles/.git') + endif + + if !filereadable(g:dir . '/test_c_projects/git_and_nested_makefiles/.git/HEAD') + call writefile([], g:dir . '/test_c_projects/git_and_nested_makefiles/.git/HEAD') + endif + + call ale#test#SetFilename('test_c_projects/git_and_nested_makefiles/src/file.cpp') + + AssertEqual + \ ale#Escape('clang++') + \ . ' -S -x c++ -fsyntax-only ' + \ . '-iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/src')) . ' ' + \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/git_and_nested_makefiles/include')) . ' ' + \ . ' -' + \ , ale_linters#cpp#clang#GetCommand(bufnr('')) + +Execute(The C++ ClangTidy handler should include json folders for projects with suitable build directory in them): + runtime! ale_linters/cpp/clangtidy.vim + + call ale#test#SetFilename('test_c_projects/json_project/subdir/file.cpp') + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s ' + \ . '-p ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/json_project/build')) + \ , ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + +Execute(Move .git/HEAD back): + if !empty(g:head_filename) + call writefile(readfile(g:temp_head_filename, 'b'), g:head_filename, 'b') + call delete(g:temp_head_filename) + endif + + unlet! g:temp_head_filename + unlet! g:head_filename diff --git a/test/test_c_projects/build/bad_folder_to_test_priority b/test/test_c_projects/build/bad_folder_to_test_priority new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/build/compile_commands.json b/test/test_c_projects/build/compile_commands.json new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/configure_project/Makefile b/test/test_c_projects/configure_project/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/configure_project/configure b/test/test_c_projects/configure_project/configure new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/configure_project/include/test.h b/test/test_c_projects/configure_project/include/test.h new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/configure_project/subdir/Makefile b/test/test_c_projects/configure_project/subdir/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/git_and_nested_makefiles/include/test.h b/test/test_c_projects/git_and_nested_makefiles/include/test.h new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/git_and_nested_makefiles/src/Makefile b/test/test_c_projects/git_and_nested_makefiles/src/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/h_file_project/Makefile b/test/test_c_projects/h_file_project/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/h_file_project/subdir/dummy b/test/test_c_projects/h_file_project/subdir/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/h_file_project/test.h b/test/test_c_projects/h_file_project/test.h new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/hpp_file_project/Makefile b/test/test_c_projects/hpp_file_project/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/hpp_file_project/subdir/dummy b/test/test_c_projects/hpp_file_project/subdir/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/hpp_file_project/test.hpp b/test/test_c_projects/hpp_file_project/test.hpp new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/json_project/build/compile_commands.json b/test/test_c_projects/json_project/build/compile_commands.json new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/json_project/include/test.h b/test/test_c_projects/json_project/include/test.h new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/json_project/subdir/dummy b/test/test_c_projects/json_project/subdir/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/makefile_project/Makefile b/test/test_c_projects/makefile_project/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/makefile_project/include/test.h b/test/test_c_projects/makefile_project/include/test.h new file mode 100644 index 0000000..e69de29 diff --git a/test/test_c_projects/makefile_project/subdir/dummy b/test/test_c_projects/makefile_project/subdir/dummy new file mode 100644 index 0000000..e69de29 diff --git a/test/test_cleanup.vader b/test/test_cleanup.vader index ec2b38a..232874a 100644 --- a/test/test_cleanup.vader +++ b/test/test_cleanup.vader @@ -1,15 +1,14 @@ -Before: - let g:buffer = bufnr('%') - - let g:ale_buffer_info = { - \ g:buffer : {}, - \ 10347: {}, - \} - After: - unlet! g:buffer - let g:ale_buffer_info = {} + unlet! g:buffer + let g:ale_buffer_info = {} -Execute('ALE globals should be cleared when the buffer is closed.'): - :q! - AssertEqual {10347: {}}, 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_command_chain.vader b/test/test_command_chain.vader index f471e15..9059d63 100644 --- a/test/test_command_chain.vader +++ b/test/test_command_chain.vader @@ -1,4 +1,11 @@ Before: + Save &shell, g:ale_run_synchronously + let g:ale_run_synchronously = 1 + + if !has('win32') + set shell=/bin/sh + endif + let g:linter_output = [] let g:first_echo_called = 0 let g:second_echo_called = 0 @@ -6,7 +13,7 @@ Before: function! CollectResults(buffer, output) let g:final_callback_called = 1 - let g:linter_output = a:output + let g:linter_output = map(copy(a:output), 'join(split(v:val))') return [] endfunction function! RunFirstEcho(buffer) @@ -23,20 +30,23 @@ Before: call ale#linter#Define('foobar', { \ 'name': 'testlinter', \ 'callback': 'CollectResults', - \ 'executable': 'echo', + \ 'executable': has('win32') ? 'cmd' : 'echo', \ 'command_chain': [ \ { \ 'callback': 'RunFirstEcho', \ 'output_stream': 'stdout', + \ 'read_buffer': 0, \ }, \ { \ 'callback': 'RunSecondEcho', \ 'output_stream': 'stdout', + \ 'read_buffer': 0, \ }, \ ], \}) After: + Restore unlet! g:first_echo_called unlet! g:second_echo_called unlet! g:final_callback_called @@ -53,7 +63,6 @@ Given foobar (Some imaginary filetype): Execute(Check the results of running the chain): AssertEqual 'foobar', &filetype call ale#Lint() - call ale#engine#WaitForJobs(2000) Assert g:first_echo_called, 'The first chain item was not called' Assert g:second_echo_called, 'The second chain item was not called' diff --git a/test/test_common_handlers.vader b/test/test_common_handlers.vader deleted file mode 100644 index ed3b164..0000000 --- a/test/test_common_handlers.vader +++ /dev/null @@ -1,198 +0,0 @@ -Execute (Run HandleCSSLintFormat): - let g:loclist = ale#handlers#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)", - \]) - -Then (The loclist should be correct): - AssertEqual [ - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 2, - \ 'col': 1, - \ 'type': 'E', - \ 'text': '(errors) Expected RBRACE at line 2, col 1.', - \ }, - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 2, - \ 'col': 5, - \ 'type': 'W', - \ 'text': "(known-properties) Expected ... but found 'wat'.", - \ }, - \], g:loclist - -Execute (Run HandlePEP8Format): - let g:loclist = ale#handlers#HandlePEP8Format(42, [ - \ "stdin:6:6: E111 indentation is not a multiple of four", - \ "test.yml:35: [EANSIBLE0002] Trailing whitespace", - \]) - -Then (The loclist should be correct): - AssertEqual [ - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 6, - \ 'col': 6, - \ 'type': 'E', - \ 'text': 'E111: indentation is not a multiple of four', - \ }, - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 35, - \ 'col': 0, - \ 'type': 'E', - \ 'text': "EANSIBLE0002: Trailing whitespace", - \ }, - \], g:loclist - -Execute (Run HandleGCCFormat): - let g:loclist = ale#handlers#HandleGCCFormat(42, [ - \ ':8:5: warning: conversion lacks type at end of format [-Wformat=]', - \ ':10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’)', - \]) - -Then (The loclist should be correct): - AssertEqual [ - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 8, - \ 'col': 5, - \ 'type': 'W', - \ 'text': 'conversion lacks type at end of format [-Wformat=]', - \ }, - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 10, - \ 'col': 27, - \ 'type': 'E', - \ 'text': 'invalid operands to binary - (have ‘int’ and ‘char *’)', - \ }, - \], g:loclist - -Execute (Run HandleUnixFormatAsError): - let g:loclist = ale#handlers#HandleUnixFormatAsError(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)', - \]) - -Then (The loclist should be correct): - AssertEqual [ - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 27, - \ 'col': 0, - \ 'type': 'E', - \ 'text': 'missing argument for Printf("%s"): format reads arg 2, have only 1 args', - \ }, - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ '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)', - \ }, - \], g:loclist - -Execute (Run HandleUnixFormatAsWarning): - let g:loclist = ale#handlers#HandleUnixFormatAsWarning(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)', - \]) - -Then (The loclist should be correct): - AssertEqual [ - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 27, - \ 'col': 0, - \ 'type': 'W', - \ 'text': 'missing argument for Printf("%s"): format reads arg 2, have only 1 args', - \ }, - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ '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)', - \ }, - \], g:loclist - -Execute (Run a Unix format function with a Windows path): - let g:loclist = ale#handlers#HandleUnixFormatAsError(42, [ - \ 'C:\Users\w0rp\AppData\Local\Temp\Xyz123.go:27: foo', - \ 'C:\Users\w0rp\AppData\Local\Temp\Xyz123.go:53:10: foo', - \]) - -Then (The loclist should be correct): - AssertEqual [ - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 27, - \ 'col': 0, - \ 'type': 'E', - \ 'text': 'foo', - \ }, - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 53, - \ 'col': 10, - \ 'type': 'E', - \ 'text': 'foo', - \ }, - \], g:loclist - -Execute (Run HandleCppCheckFormat): - let g:loclist = ale#handlers#HandleCppCheckFormat(42, [ - \ '[/tmp/test.c:5]: (style) Variable a is assigned a value that is never used.', - \ '[/tmp/test.c:12]: (error) Array a[10] accessed at index 10, which is out of bounds.' - \]) - -Then (The loclist should be correct): - AssertEqual [ - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 5, - \ 'col': 0, - \ 'type': 'W', - \ 'text': 'Variable a is assigned a value that is never used. (style)', - \ }, - \ { - \ 'bufnr': 42, - \ 'vcol': 0, - \ 'nr': -1, - \ 'lnum': 12, - \ 'col': 0, - \ 'type': 'E', - \ 'text': 'Array a[10] accessed at index 10, which is out of bounds. (error)', - \ }, - \], g:loclist - -After: - unlet g:loclist diff --git a/test/test_conflicting_plugin_warnings.vader b/test/test_conflicting_plugin_warnings.vader new file mode 100644 index 0000000..08a4c41 --- /dev/null +++ b/test/test_conflicting_plugin_warnings.vader @@ -0,0 +1,74 @@ +Execute(The after file should have been loaded for real): + " FIXME: Fix these tests in NeoVim. + if !has('nvim') + Assert has_key(g:, 'loaded_ale_after'), 'g:loaded_ale_after was not set!' + Assert g:loaded_ale_after + endif + +Before: + silent! cd /testplugin/test + cd .. + unlet! g:loaded_ale_after + +After: + cd test + let g:loaded_ale_after = 1 + let g:ale_emit_conflict_warnings = 1 + unlet! g:loaded_syntastic_plugin + unlet! g:loaded_neomake + unlet! g:loaded_validator_plugin + +Execute(ALE should not warn when nothing extra is installed): + " Nothing should be thrown when loading the after file. + source after/plugin/ale.vim + +Execute(ALE should warn users when Syntastic is installed): + let g:loaded_syntastic_plugin = 1 + + AssertThrows source after/plugin/ale.vim + AssertEqual + \ 'ALE conflicts with Syntastic' + \ . '. Uninstall it, or disable this warning with ' + \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' + \ . '*before* plugins are loaded.', + \ g:vader_exception + +Execute(ALE should not warn about Syntastic when the flag is set): + let g:loaded_syntastic_plugin = 1 + let g:ale_emit_conflict_warnings = 0 + + source after/plugin/ale.vim + +Execute(ALE should warn users when Neomake is installed): + let g:loaded_neomake = 1 + + AssertThrows source after/plugin/ale.vim + AssertEqual + \ 'ALE conflicts with Neomake' + \ . '. Uninstall it, or disable this warning with ' + \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' + \ . '*before* plugins are loaded.', + \ g:vader_exception + +Execute(ALE should not warn about Neomake when the flag is set): + let g:loaded_neomake = 1 + let g:ale_emit_conflict_warnings = 0 + + source after/plugin/ale.vim + +Execute(ALE should warn users when Validator is installed): + let g:loaded_validator_plugin = 1 + + AssertThrows source after/plugin/ale.vim + AssertEqual + \ 'ALE conflicts with Validator' + \ . '. Uninstall it, or disable this warning with ' + \ . '`let g:ale_emit_conflict_warnings = 0` in your vimrc file, ' + \ . '*before* plugins are loaded.', + \ g:vader_exception + +Execute(ALE should not warn about Validator when the flag is set): + let g:loaded_validator_plugin = 1 + let g:ale_emit_conflict_warnings = 0 + + source after/plugin/ale.vim diff --git a/test/test_csslint_config_detection.vader b/test/test_csslint_config_detection.vader new file mode 100644 index 0000000..47e80d0 --- /dev/null +++ b/test/test_csslint_config_detection.vader @@ -0,0 +1,29 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + + runtime ale_linters/css/csslint.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(--config should be set when the .csslintrc file is found): + call ale#test#SetFilename('csslint-test-files/some-app/subdir/testfile.js') + + AssertEqual + \ ( + \ 'csslint --format=compact ' + \ . '--config=' . ale#Escape(ale#path#Simplify(g:dir . '/csslint-test-files/some-app/.csslintrc')) + \ . ' %t' + \ ), + \ ale_linters#css#csslint#GetCommand(bufnr('')) + +Execute(--config should not be used when no .csslintrc file exists): + call ale#test#SetFilename('csslint-test-files/other-app/testfile.css') + + AssertEqual + \ ( + \ 'csslint --format=compact ' + \ . ' %t' + \ ), + \ ale_linters#css#csslint#GetCommand(bufnr('')) diff --git a/test/test_cursor_warnings.vader b/test/test_cursor_warnings.vader index b12f245..1959221 100644 --- a/test/test_cursor_warnings.vader +++ b/test/test_cursor_warnings.vader @@ -1,86 +1,235 @@ Before: + Save g:ale_echo_msg_format + Save g:ale_echo_cursor + let g:ale_buffer_info = { \ bufnr('%'): { \ 'loclist': [ \ { \ 'lnum': 1, + \ 'col': 10, \ 'bufnr': bufnr('%'), \ 'vcol': 0, \ 'linter_name': 'eslint', \ 'nr': -1, \ 'type': 'E', - \ 'col': 10, - \ 'text': 'Missing semicolon. (semi)' + \ 'code': 'semi', + \ 'text': 'Missing semicolon.', + \ 'detail': "Every statement should end with a semicolon\nsecond line", + \ }, + \ { + \ 'lnum': 1, + \ 'col': 14, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'I', + \ 'text': 'Some information', \ }, \ { \ 'lnum': 2, + \ 'col': 10, \ 'bufnr': bufnr('%'), \ 'vcol': 0, \ 'linter_name': 'eslint', \ 'nr': -1, \ 'type': 'W', - \ 'col': 10, - \ 'text': 'Infix operators must be spaced. (space-infix-ops)' + \ 'code': 'space-infix-ops', + \ 'text': 'Infix operators must be spaced.', \ }, \ { \ 'lnum': 2, + \ 'col': 15, \ 'bufnr': bufnr('%'), \ 'vcol': 0, \ 'linter_name': 'eslint', \ 'nr': -1, \ 'type': 'E', - \ 'col': 15, - \ 'text': 'Missing radix parameter (radix)' - \ } + \ 'code': 'radix', + \ 'text': 'Missing radix parameter', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ '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 + + function GetLastMessage() + redir => l:output + silent mess + redir END + + let l:lines = split(l:output, "\n") + + return empty(l:lines) ? '' : l:lines[-1] + endfunction + After: - unlet! g:output - unlet! g:lines + Restore + + 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 + + delfunction GetLastMessage + + " 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 + Given javascript(A Javscript file with warnings/errors): - var x = 3 + var x = 3 + 12345678 var x = 5*2 + parseInt("10"); + // comment -Execute(Evaluate the cursor function at line 1): - :1 +Execute(Messages should be shown for the correct lines): + call cursor(1, 1) call ale#cursor#EchoCursorWarning() -Then(Check the cursor output): - redir => g:output - :mess - redir END + AssertEqual 'semi: Missing semicolon.', GetLastMessage() - let g:lines = split(g:output, "\n") - - AssertEqual 'Missing semicolon. (semi)', g:lines[-1] - -Execute(Evaluate the cursor function at line 2): - :2 +Execute(Messages should be shown for earlier columns): + call cursor(2, 1) call ale#cursor#EchoCursorWarning() -Then(Check the cursor output): - redir => g:output - :mess - redir END + AssertEqual 'space-infix-ops: Infix operators must be spaced.', GetLastMessage() - let g:lines = split(g:output, "\n") - - AssertEqual 'Infix operators must be spaced. (space-infix-ops)', g:lines[-1] - -Execute(Evaluate the cursor function later in line 2): - :2 - normal 16l +Execute(Messages should be shown for later columns): + call cursor(2, 16) call ale#cursor#EchoCursorWarning() -Then(Check the cursor output): - redir => g:output - :mess - redir END + AssertEqual 'radix: Missing radix parameter', GetLastMessage() - let g:lines = split(g:output, "\n") +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 'Missing radix parameter (radix)', g:lines[-1] + AssertEqual 'semi: Missing semicolon.', GetLastMessage() + +Execute(The message at the cursor should be shown on InsertLeave): + call cursor(2, 9) + doautocmd InsertLeave + + AssertEqual 'space-infix-ops: Infix operators must be spaced.', GetLastMessage() + +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', GetLastMessage() + +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 + \ 'eslint: Infix operators must be spaced.', + \ GetLastMessage() + +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.', + \ GetLastMessage() + + call cursor(1, 10) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Error: Missing semicolon.', GetLastMessage() + + call cursor(1, 14) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Info: Some information', GetLastMessage() + +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.', + \ GetLastMessage() + +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', GetLastMessage() + +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', GetLastMessage() + +Execute(The cursor message shouldn't be echoed if the option is off): + let g:ale_echo_cursor = 0 + echom 'foo' + + call cursor(1, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'foo', GetLastMessage() diff --git a/test/test_disabling_ale.vader b/test/test_disabling_ale.vader new file mode 100644 index 0000000..6159f79 --- /dev/null +++ b/test/test_disabling_ale.vader @@ -0,0 +1,119 @@ +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 + + let l:lines = split(l:output, "\n") + + 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 + +Execute(Linting shouldn't happen when the file is too large with a global options): + let g:ale_maximum_file_size = 12 + let g:ale_buffer_info = {} + + call ale#Queue(0) + + AssertEqual {}, g:ale_buffer_info + +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 + +Execute(Linting shouldn't happen when the file is too large with a local options): + let b:ale_maximum_file_size = 12 + let g:ale_buffer_info = {} + + call ale#Queue(0) + + AssertEqual {}, g:ale_buffer_info + +Execute(Cursor warnings shouldn't be echoed when ALE is disabled globally): + let g:ale_enabled = 0 + + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() + +Execute(Cursor warnings shouldn't be echoed when the file is too large with global options): + let g:ale_maximum_file_size = 12 + + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() + +Execute(Cursor warnings shouldn't be echoed when ALE is disabled locally): + let b:ale_enabled = 0 + + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() + +Execute(Cursor warnings shouldn't be echoed when the file is too large with local options): + let b:ale_maximum_file_size = 12 + + call SetUpCursorData() + call ale#cursor#EchoCursorWarning() + AssertEqual '', GetLastMessage() diff --git a/test/test_dockerfile_hadolint_linter.vader b/test/test_dockerfile_hadolint_linter.vader new file mode 100644 index 0000000..3edbb2b --- /dev/null +++ b/test/test_dockerfile_hadolint_linter.vader @@ -0,0 +1,90 @@ +" NOTE: We use the 'b:' forms below to ensure that we're properly using +" ale#Var() + +Given dockerfile: + # + +Before: + Save g:ale_dockerfile_hadolint_use_docker + Save g:ale_dockerfile_hadolint_docker_image + silent! unlet g:ale_dockerfile_hadolint_use_docker + silent! unlet g:ale_dockerfile_hadolint_docker_image + + " enable loading inside test container + silent! cd /testplugin + source ale_linters/dockerfile/hadolint.vim + + +After: + Restore + silent! unlet b:ale_dockerfile_hadolint_use_docker + silent! unlet b:ale_dockerfile_hadolint_docker_image + + +Execute(linter honors ..._use_docker correctly): + + " default: never + AssertEqual + \ 'hadolint', + \ ale_linters#dockerfile#hadolint#GetExecutable(bufnr('')) + + " explicit never + let b:ale_dockerfile_hadolint_use_docker = 'never' + AssertEqual + \ 'hadolint', + \ ale_linters#dockerfile#hadolint#GetExecutable(bufnr('')) + + let b:ale_dockerfile_hadolint_use_docker = 'always' + AssertEqual + \ 'docker', + \ ale_linters#dockerfile#hadolint#GetExecutable(bufnr('')) + + " hadolint if present, otherwise docker + let command = 'docker' + if executable('hadolint') + let command = 'hadolint' + endif + + let b:ale_dockerfile_hadolint_use_docker = 'yes' + AssertEqual + \ command, + \ ale_linters#dockerfile#hadolint#GetExecutable(bufnr('')) + + +Execute(command is correct when using docker): + let b:ale_dockerfile_hadolint_use_docker = 'always' + + AssertEqual + \ "docker run --rm -i hadolint/hadolint", + \ ale_linters#dockerfile#hadolint#GetCommand(bufnr('')) + + +Execute(command is correct when not docker): + let b:ale_dockerfile_hadolint_use_docker = 'never' + + AssertEqual + \ "hadolint -", + \ ale_linters#dockerfile#hadolint#GetCommand(bufnr('')) + +Execute(test warnings from hadolint): + AssertEqual + \ [{'lnum': 10, 'col': 0, 'type': 'W', 'text': 'Using latest is prone to errors', 'detail': "DL3007 ( https://github.com/hadolint/hadolint/wiki/DL3007 )\n\nUsing latest is prone to errors"}], + \ ale_linters#dockerfile#hadolint#Handle(bufnr(''), [ + \ '/dev/stdin:10 DL3007 Using latest is prone to errors', + \ ]) + +Execute(test warnings from shellcheck): + AssertEqual + \ [{'lnum': 3, 'col': 0, 'type': 'W', 'text': 'bar is referenced but not assigned.', 'detail': "SC2154 ( https://github.com/koalaman/shellcheck/wiki/SC2154 )\n\nbar is referenced but not assigned."}], + \ ale_linters#dockerfile#hadolint#Handle(bufnr(''), [ + \ '/dev/stdin:3 SC2154 bar is referenced but not assigned.', + \ ]) + +Execute(test errors from dockerfile parser): + AssertEqual + \ [{'lnum': 3, 'col': 4, 'type': 'E', 'text': 'unexpected "A" expecting at least one space after ''RUN''', 'detail': 'unexpected "A" expecting at least one space after ''RUN'''}], + \ ale_linters#dockerfile#hadolint#Handle(bufnr(''), [ + \ "/dev/stdin:3:4 unexpected \"A\" expecting at least one space after 'RUN'", + \ ]) + +" fin... diff --git a/test/test_elm_executable_detection.vader b/test/test_elm_executable_detection.vader new file mode 100644 index 0000000..4227cbf --- /dev/null +++ b/test/test_elm_executable_detection.vader @@ -0,0 +1,36 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/elm/make.vim + +After: + unlet! g:ale_elm_make_use_global + unlet! g:ale_elm_make_executable + + call ale#test#RestoreDirectory() + +Execute(should get valid executable with default params): + call ale#test#SetFilename('elm-test-files/app/testfile.elm') + + AssertEqual + \ ale#path#Simplify(g:dir . '/elm-test-files/app/node_modules/.bin/elm-make'), + \ ale_linters#elm#make#GetExecutable(bufnr('')) + +Execute(should get valid executable with 'use_global' params): + let g:ale_elm_make_use_global = 1 + + call ale#test#SetFilename('elm-test-files/app/testfile.elm') + + AssertEqual + \ 'elm-make', + \ ale_linters#elm#make#GetExecutable(bufnr('')) + +Execute(should get valid executable with 'use_global' and 'executable' params): + let g:ale_elm_make_executable = 'other-elm-make' + let g:ale_elm_make_use_global = 1 + + call ale#test#SetFilename('elm-test-files/app/testfile.elm') + + AssertEqual + \ 'other-elm-make', + \ ale_linters#elm#make#GetExecutable(bufnr('')) + diff --git a/test/test_engine_invocation.vader b/test/test_engine_invocation.vader new file mode 100644 index 0000000..c56895d --- /dev/null +++ b/test/test_engine_invocation.vader @@ -0,0 +1,139 @@ +Before: + function! CollectResults(buffer, output) + return [] + endfunction + + function! FirstChainFunction(buffer) + return 'first' + endfunction + + function! SecondChainFunction(buffer, output) + " We'll skip this command + return '' + endfunction + + function! ThirdChainFunction(buffer, output) + return 'third' + endfunction + + function! FourthChainFunction(buffer, output) + return 'fourth' + endfunction + + let g:linter = { + \ 'name': 'testlinter', + \ 'callback': 'CollectResults', + \ 'executable': 'echo', + \ 'command_chain': [ + \ {'callback': 'FirstChainFunction'}, + \ {'callback': 'SecondChainFunction'}, + \ {'callback': 'ThirdChainFunction'}, + \ {'callback': 'FourthChainFunction'}, + \ ], + \ 'read_buffer': 1, + \} + + function! ProcessIndex(chain_index) + return ale#engine#ProcessChain(347, g:linter, a:chain_index, []) + endfunction + +After: + delfunction CollectResults + delfunction FirstChainFunction + delfunction SecondChainFunction + delfunction ThirdChainFunction + delfunction ProcessIndex + unlet! g:linter + unlet! g:result + +Execute(Engine invocation should return the command for the first item correctly): + let g:result = ProcessIndex(0) + + AssertEqual 'first', g:result.command + AssertEqual 1, g:result.next_chain_index + +Execute(Engine invocation should return the command for the second item correctly): + let g:result = ProcessIndex(1) + + AssertEqual 'third', g:result.command + AssertEqual 3, g:result.next_chain_index + +Execute(Engine invocation should return the command for the fourth item correctly): + let g:result = ProcessIndex(3) + + AssertEqual 'fourth', g:result.command + AssertEqual 4, g:result.next_chain_index + +Execute(Engine invocation should return the command for a single callback correctly): + unlet g:linter.command_chain + let g:linter.command_callback = 'FirstChainFunction' + + let g:result = ProcessIndex(0) + + AssertEqual 'first', g:result.command + +Execute(Engine invocation should return the command for a command string correctly): + unlet g:linter.command_chain + let g:linter.command = 'foo bar' + + let g:result = ProcessIndex(0) + + AssertEqual 'foo bar', g:result.command + +Execute(Engine invocation should process read_buffer correctly for simple commands): + unlet g:linter.command_chain + let g:linter.command = 'foo bar' + let g:linter.read_buffer = 0 + + let g:result = ProcessIndex(0) + + AssertEqual 'foo bar', g:result.command + AssertEqual 0, g:result.read_buffer + + let g:linter.command_callback = 'FirstChainFunction' + unlet g:linter.command + + let g:result = ProcessIndex(0) + + AssertEqual 'first', g:result.command + AssertEqual 0, g:result.read_buffer + +Execute(Engine invocation should allow read_buffer to be enabled for a command in the middle of a chain): + let g:linter.command_chain[2].read_buffer = 1 + + let g:result = ProcessIndex(2) + + AssertEqual g:result.command, 'third' + AssertEqual g:result.read_buffer, 1 + +Execute(Engine invocation should allow read_buffer to be disabled for the end of a chain): + let g:linter.command_chain[3].read_buffer = 0 + + let g:result = ProcessIndex(3) + + AssertEqual g:result.command, 'fourth' + AssertEqual g:result.read_buffer, 0 + +Execute(Engine invocation should not use read_buffer from earlier items in a chain): + let g:linter.command_chain[1].read_buffer = 1 + + let g:result = ProcessIndex(1) + + AssertEqual g:result.command, 'third' + AssertEqual g:result.read_buffer, 0 + +Execute(Engine invocation should allow the output_stream setting to be changed in the middle of a chain): + let g:linter.command_chain[2].output_stream = 'both' + + let g:result = ProcessIndex(2) + + AssertEqual g:result.command, 'third' + AssertEqual g:result.output_stream, 'both' + +Execute(Engine invocation should not use output_stream from earlier items in a chain): + let g:linter.command_chain[1].output_stream = 'both' + + let g:result = ProcessIndex(1) + + AssertEqual g:result.command, 'third' + AssertEqual g:result.output_stream, 'stdout' diff --git a/test/test_engine_lsp_response_handling.vader b/test/test_engine_lsp_response_handling.vader new file mode 100644 index 0000000..b3a45b1 --- /dev/null +++ b/test/test_engine_lsp_response_handling.vader @@ -0,0 +1,155 @@ +Before: + Save g:ale_buffer_info + call ale#test#SetDirectory('/testplugin/test') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(tsserver syntax error responses should be handled correctly): + runtime ale_linters/typescript/tsserver.vim + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + " When we get syntax errors and no semantic errors, we should keep the + " syntax errors. + call ale#engine#HandleLSPResponse(1, { + \ '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#engine#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 14, + \ 'vcol': 0, + \ 'nr': 1005, + \ 'type': 'E', + \ 'text': ''','' expected.', + \ 'valid': 1, + \ 'pattern': '', + \ }, + \ ], + \ getloclist(0) + + " After we get empty syntax errors, we should clear them. + call ale#engine#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ ], + \ getloclist(0) + +Execute(tsserver semantic error responses should be handled correctly): + runtime ale_linters/typescript/tsserver.vim + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + " When we get syntax errors and no semantic errors, we should keep the + " syntax errors. + call ale#engine#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ ], + \ }, + \}) + call ale#engine#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ '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': 'Some semantic error', + \ 'valid': 1, + \ 'pattern': '', + \ }, + \ ], + \ getloclist(0) + + " After we get empty syntax errors, we should clear them. + call ale#engine#HandleLSPResponse(1, { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'semanticDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ ], + \ getloclist(0) 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 0000000..92d248d --- /dev/null +++ b/test/test_errors_removed_after_filetype_changed.vader @@ -0,0 +1,61 @@ +Before: + Save g:ale_run_synchronously + + let b:old_filetype = &filetype + let g:ale_run_synchronously = 1 + + noautocmd let &filetype = 'foobar' + + function! TestCallback(buffer, output) + return [{'text': 'x', 'lnum': 1}] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'buffer_linter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + + call ale#linter#Define('foobar2', { + \ 'name': 'buffer_linter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + +After: + Restore + + noautocmd let &filetype = b:old_filetype + unlet b:old_filetype + delfunction TestCallback + + if has_key(g:ale_buffer_info, bufnr('')) + call remove(g:ale_buffer_info, bufnr('')) + endif + + call ale#Queue(0) + +Execute(Error should be removed when the filetype changes to something else we cannot check): + call ale#Queue(0) + sleep 1ms + + AssertEqual 1, len(getloclist(0)) + + noautocmd let &filetype = 'foobar2' + + call ale#Queue(0) + sleep 1ms + + " We should get some items from the second filetype. + AssertEqual 1, len(getloclist(0)) + + noautocmd let &filetype = 'xxx' + + call ale#Queue(0) + sleep 1ms + + AssertEqual 0, len(getloclist(0)) diff --git a/test/test_eslint_executable_detection.vader b/test/test_eslint_executable_detection.vader new file mode 100644 index 0000000..c1438ed --- /dev/null +++ b/test/test_eslint_executable_detection.vader @@ -0,0 +1,64 @@ +Before: + let g:ale_javascript_eslint_executable = 'eslint_d' + + call ale#test#SetDirectory('/testplugin/test') + + runtime ale_linters/javascript/eslint.vim + +After: + let g:ale_has_override = {} + let g:ale_javascript_eslint_executable = 'eslint' + let g:ale_javascript_eslint_use_global = 0 + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(create-react-app directories should be detected correctly): + call ale#test#SetFilename('eslint-test-files/react-app/subdir/testfile.js') + + AssertEqual + \ ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'), + \ ale#handlers#eslint#GetExecutable(bufnr('')) + +Execute(use-global should override create-react-app detection): + let g:ale_javascript_eslint_use_global = 1 + + call ale#test#SetFilename('eslint-test-files/react-app/subdir/testfile.js') + + AssertEqual + \ 'eslint_d', + \ ale#handlers#eslint#GetExecutable(bufnr('')) + +Execute(other app directories should be detected correctly): + call ale#test#SetFilename('eslint-test-files/other-app/subdir/testfile.js') + + AssertEqual + \ ale#path#Simplify(g:dir . '/eslint-test-files/node_modules/.bin/eslint'), + \ ale#handlers#eslint#GetExecutable(bufnr('')) + +Execute(use-global should override other app directories): + let g:ale_javascript_eslint_use_global = 1 + + call ale#test#SetFilename('eslint-test-files/other-app/subdir/testfile.js') + + AssertEqual + \ 'eslint_d', + \ ale#handlers#eslint#GetExecutable(bufnr('')) + +Execute(eslint_d should be detected correctly): + call ale#test#SetFilename('eslint-test-files/app-with-eslint-d/testfile.js') + + AssertEqual + \ ale#path#Simplify(g:dir . '/eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d'), + \ ale#handlers#eslint#GetExecutable(bufnr('')) + +Execute(eslint.js executables should be run with node on Windows): + call ale#test#SetFilename('eslint-test-files/react-app/subdir/testfile.js') + let g:ale_has_override['win32'] = 1 + + " We have to execute the file with node. + AssertEqual + \ ale#Escape('node.exe') . ' ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -f unix --stdin --stdin-filename %s', + \ ale#handlers#eslint#GetCommand(bufnr('')) diff --git a/test/test_eslint_handler.vader b/test/test_eslint_handler.vader deleted file mode 100644 index 57efa6b..0000000 --- a/test/test_eslint_handler.vader +++ /dev/null @@ -1,43 +0,0 @@ -Execute(The eslint handler should parse lines correctly): - runtime ale_linters/javascript/eslint.vim - - AssertEqual - \ [ - \ { - \ 'bufnr': 347, - \ 'lnum': 47, - \ 'vcol': 0, - \ 'col': 14, - \ 'text': 'Missing trailing comma. [Warning/comma-dangle]', - \ 'type': 'W', - \ 'nr': -1, - \ }, - \ { - \ 'bufnr': 347, - \ 'lnum': 56, - \ 'vcol': 0, - \ 'col': 41, - \ 'text': 'Missing semicolon. [Error/semi]', - \ 'type': 'E', - \ 'nr': -1, - \ }, - \ { - \ 'bufnr': 347, - \ 'lnum': 13, - \ 'vcol': 0, - \ 'col': 3, - \ 'text': 'Parsing error: Unexpected token', - \ 'type': 'E', - \ 'nr': -1, - \ }, - \ ], - \ ale_linters#javascript#eslint#Handle(347, [ - \ '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', - \ ]) - -After: - call ale#linter#Reset() diff --git a/test/test_filetype_linter_defaults.vader b/test/test_filetype_linter_defaults.vader new file mode 100644 index 0000000..ea4a05f --- /dev/null +++ b/test/test_filetype_linter_defaults.vader @@ -0,0 +1,68 @@ +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 map(ale#linter#Get(a:filetype), 'v:val.name') + endfunction + +After: + Restore + + call ale#linter#Reset() + +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 go filetype should be correct): + AssertEqual ['gofmt', 'golint', 'go vet'], GetLinterNames('go') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('go') + +Execute(The defaults for the help filetype should be correct): + AssertEqual [], GetLinterNames('help') + +Execute(The defaults for the python filetype should be correct): + AssertEqual ['flake8', 'mypy', 'pylint'], GetLinterNames('python') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('python') + +Execute(The defaults for the rust filetype should be correct): + AssertEqual ['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 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 ['iverilog', 'verilator'], GetLinterNames('verilog') + + let g:ale_linters_explicit = 1 + + AssertEqual [], GetLinterNames('verilog') diff --git a/test/test_filetype_mapping.vader b/test/test_filetype_mapping.vader new file mode 100644 index 0000000..2d72491 --- /dev/null +++ b/test/test_filetype_mapping.vader @@ -0,0 +1,29 @@ +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: + unlet! g:map + augroup TestFiletypeGroup + autocmd! + augroup END + augroup! TestFiletypeGroup + +Execute(ALE should parse autocmd filetypes correctly): + let g:map = ale#filetypes#LoadExtensionMap() + + AssertEqual '.x', g:map['xfiletype'] + AssertEqual '.y', g:map['yfiletype'] + AssertEqual '.z', g:map['zfiletype'] + AssertEqual '.jsx', g:map['javascript.jsx'] + +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_find_nearest_directory.vader b/test/test_find_nearest_directory.vader index 450826e..2529950 100644 --- a/test/test_find_nearest_directory.vader +++ b/test/test_find_nearest_directory.vader @@ -1,15 +1,17 @@ -Execute(Open a file some directory down): - silent! cd /testplugin/test - :e! top/middle/bottom/dummy.txt +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('top/middle/bottom/dummy.txt') -Then(We should be able to find the right directory): AssertEqual - \ expand('%:p:h:h:h:h') . '/top/ale-special-directory-name-dont-use-this-please/', - \ ale#util#FindNearestDirectory(bufnr('%'), 'ale-special-directory-name-dont-use-this-please') + \ ale#path#Simplify(expand('%:p:h:h:h:h') . '/top/ale-special-directory-name-dont-use-this-please/'), + \ ale#path#FindNearestDirectory(bufnr('%'), 'ale-special-directory-name-dont-use-this-please') -Execute(Do nothing): - -Then(We shouldn't find anything for files which don't match): +Execute(We shouldn't find anything for files which don't match): AssertEqual \ '', - \ ale#util#FindNearestDirectory(bufnr('%'), 'ale-this-should-never-match-anything') + \ ale#path#FindNearestDirectory(bufnr('%'), 'ale-this-should-never-match-anything') diff --git a/test/test_flow_command.vader b/test/test_flow_command.vader new file mode 100644 index 0000000..49546e9 --- /dev/null +++ b/test/test_flow_command.vader @@ -0,0 +1,32 @@ +Before: + runtime ale_linters/javascript/flow.vim + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + call ale#semver#ResetVersionCache() + +Execute(flow should return a command to run if a .flowconfig file exists): + call ale#test#SetFilename('flow/a/sub/dummy') + + AssertEqual + \ ale#Escape('flow') + \ . ' check-contents --respect-pragma --json --from ale %s', + \ ale_linters#javascript#flow#GetCommand(bufnr('%'), []) + +Execute(flow should should not use --respect-pragma for old versions): + call ale#test#SetFilename('flow/a/sub/dummy') + + AssertEqual + \ ale#Escape('flow') + \ . ' check-contents --json --from ale %s', + \ ale_linters#javascript#flow#GetCommand(bufnr('%'), [ + \ 'Warning: `flow --version` is deprecated in favor of `flow version`', + \ 'Flow, a static type checker for JavaScript, version 0.27.0', + \ ]) + +Execute(flow should not return a command to run if no .flowconfig file exists): + call ale#test#SetFilename('flow/b/sub/dummy') + + AssertEqual '', ale_linters#javascript#flow#GetCommand(bufnr('%'), []) diff --git a/test/test_format_command.vader b/test/test_format_command.vader new file mode 100644 index 0000000..f6143a5 --- /dev/null +++ b/test/test_format_command.vader @@ -0,0 +1,77 @@ +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 + +After: + unlet! g:result + unlet! g:match + + delfunction CheckTempFile + +Execute(FormatCommand should do nothing to basic command strings): + AssertEqual ['', 'awesome-linter do something'], ale#command#FormatCommand(bufnr('%'), 'awesome-linter do something', 0) + +Execute(FormatCommand should handle %%, and ignore other percents): + AssertEqual ['', '% %%d %%f %x %'], ale#command#FormatCommand(bufnr('%'), '%% %%%d %%%f %x %', 0) + +Execute(FormatCommand should convert %s to the current filename): + AssertEqual + \ [ + \ '', + \ 'foo ' . ale#Escape(expand('%:p')) . ' bar ' . ale#Escape(expand('%:p')) + \ ], + \ ale#command#FormatCommand(bufnr('%'), 'foo %s bar %s', 0) + +Execute(FormatCommand should convert %t to a new temporary filename): + let g:result = ale#command#FormatCommand(bufnr('%'), 'foo %t bar %t', 0) + + 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 let you combine %s and %t): + let g:result = ale#command#FormatCommand(bufnr('%'), 'foo %t bar %s', 0) + + 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(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) + + 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) + + 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] diff --git a/test/test_format_temporary_file_creation.vader b/test/test_format_temporary_file_creation.vader new file mode 100644 index 0000000..1afaba3 --- /dev/null +++ b/test/test_format_temporary_file_creation.vader @@ -0,0 +1,35 @@ +Before: + let g:output = [] + + function! TestCallback(buffer, output) + let g:output = a:output + + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'cat', + \ 'command': has('win32') ? 'type %t' : 'cat %t', + \}) + +After: + unlet! g:output + delfunction TestCallback + 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 + + " Sleep a little so the test passes more. + sleep 100ms + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + 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 0000000..d256c40 --- /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 0000000..4b1c608 --- /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 0000000..7e1b593 --- /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_getmatches.vader b/test/test_getmatches.vader new file mode 100644 index 0000000..e728b57 --- /dev/null +++ b/test/test_getmatches.vader @@ -0,0 +1,148 @@ +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#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 0000000..b77a75a --- /dev/null +++ b/test/test_go_to_definition.vader @@ -0,0 +1,285 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:old_filename = expand('%:p') + let g:Callback = 0 + let g:message_list = [] + let g:expr_list = [] + + runtime autoload/ale/definition.vim + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + + function! ale#linter#StartLSP(buffer, linter, callback) abort + let g:Callback = a:callback + + return { + \ 'connection_id': 347, + \ 'project_root': '/foo/bar', + \} + endfunction + + function! ale#lsp#Send(conn_id, message, root) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#definition#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:old_filename + unlet! g:Callback + unlet! g:message_list + unlet! g:expr_list + unlet! b:ale_linters + + runtime autoload/ale/definition.vim + runtime autoload/ale/linter.vim + runtime autoload/ale/lsp.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#definition#HandleTSServerResponse(1, {'command': 'foo'}) + +Execute(Failed definition responses should be handled correctly): + call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#HandleTSServerResponse( + \ 1, + \ {'command': 'definition', 'request_seq': 3} + \) + 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_tab': 0}}) + 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 ' . 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': 1}}) + 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 ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 7], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(tsserver completion requests should be sent): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEGoToDefinition + + AssertEqual + \ 'function(''ale#definition#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [[0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], + \ g:message_list + AssertEqual {'42': {'open_in_tab': 0}}, ale#definition#GetMap() + +Execute(tsserver tab completion requests should be sent): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEGoToDefinitionInTab + + AssertEqual + \ 'function(''ale#definition#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [[0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}]], + \ g:message_list + AssertEqual {'42': {'open_in_tab': 1}}, 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_tab': 0}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': { + \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ } + \) + + AssertEqual + \ [ + \ 'edit ' . 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 in tabs for LSP definition responses): + call ale#definition#SetMap({3: {'open_in_tab': 1}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': { + \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ } + \) + + AssertEqual + \ [ + \ 'tabedit ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 7], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Definition responses with lists should be handled): + call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#HandleLSPResponse( + \ 1, + \ { + \ 'id': 3, + \ 'result': [ + \ { + \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ 'range': { + \ 'start': {'line': 2, 'character': 7}, + \ }, + \ }, + \ { + \ 'uri': ale#path#ToURI(ale#path#Simplify(g:dir . '/other_file')), + \ 'range': { + \ 'start': {'line': 20, 'character': 3}, + \ }, + \ }, + \ ], + \ } + \) + + AssertEqual + \ [ + \ 'edit ' . fnameescape(ale#path#Simplify(g:dir . '/completion_dummy_file')), + \ ], + \ g:expr_list + AssertEqual [3, 7], getpos('.')[1:2] + AssertEqual {}, ale#definition#GetMap() + +Execute(Definition responses with null response should be handled): + call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#HandleLSPResponse(1, {'id': 3, 'result': v:null}) + + AssertEqual [], g:expr_list + +Execute(LSP completion requests should be sent): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToDefinition + + AssertEqual + \ 'function(''ale#definition#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/definition', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 3}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in_tab': 0}}, ale#definition#GetMap() + +Execute(LSP tab completion requests should be sent): + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALEGoToDefinitionInTab + + AssertEqual + \ 'function(''ale#definition#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/definition', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 3}, + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'open_in_tab': 1}}, 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 0000000..3c1eceb --- /dev/null +++ b/test/test_gradle_build_classpath_command.vader @@ -0,0 +1,49 @@ +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('gradle-test-files/wrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/gradle-test-files/wrapped-project')) + \ . ' && ' . ale#Escape(ale#path#Simplify(g:dir . '/gradle-test-files/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('gradle-test-files/unwrapped-project/src/main/kotlin/dummy.kt') + let $PATH .= (has('win32') ? ';' : ':') + \ . ale#path#Simplify(g:dir . '/gradle-test-files') + + AssertEqual + \ 'cd ' . ale#Escape(ale#path#Simplify(g:dir . '/gradle-test-files/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('gradle-test-files/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 0000000..5daa490 --- /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('gradle-test-files/wrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/gradle-test-files/wrapped-project/gradlew'), + \ ale#gradle#FindExecutable(bufnr('')) + +Execute(Should return 'gradle' if 'gradlew' not found in parent directory): + call ale#test#SetFilename('gradle-test-files/unwrapped-project/src/main/kotlin/dummy.kt') + let $PATH .= (has('win32') ? ';': ':') . ale#path#Simplify(g:dir . '/gradle-test-files') + + 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('gradle-test-files/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 0000000..8305bba --- /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('gradle-test-files/wrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/gradle-test-files/wrapped-project'), + \ ale#gradle#FindProjectRoot(bufnr('')) + +Execute(Should return directory for 'settings.gradle' if found in parent directory): + call ale#test#SetFilename('gradle-test-files/settings-gradle-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/gradle-test-files/settings-gradle-project'), + \ ale#gradle#FindProjectRoot(bufnr('')) + +Execute(Should return directory for 'build.gradle' if found in parent directory): + call ale#test#SetFilename('gradle-test-files/build-gradle-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ ale#path#Simplify(g:dir . '/gradle-test-files/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('gradle-test-files/non-gradle-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ '', + \ ale#gradle#FindProjectRoot(bufnr('')) diff --git a/test/test_highlight_placement.vader b/test/test_highlight_placement.vader new file mode 100644 index 0000000..725faff --- /dev/null +++ b/test/test_highlight_placement.vader @@ -0,0 +1,281 @@ +Before: + Save g:ale_enabled + Save g:ale_set_signs + + 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 + + " We don't care what the IDs are, just that we have some matches. + " The IDs are generated. + function! GetMatchesWithoutIDs() abort + let l:list = getmatches() + + for l:item in l:list + call remove(l:item, 'id') + endfor + + return l:list + 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:items + unlet! b:ale_enabled + + delfunction GenerateResults + call ale#linter#Reset() + let g:ale_buffer_info = {} + call clearmatches() + highlight clear SomeOtherGroup + +Given testft(A Javscript file with warnings/errors): + foo + bar + baz wat + line four + +Execute(Highlights should be set when a linter runs): + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + 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}, + \]) + + AssertEqual + \ [{'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}], + \ GetMatchesWithoutIDs() + + 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. + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ ], + \ GetMatchesWithoutIDs() + + call ale#highlight#BufferHidden(bufnr('%')) + + " We should remove our highlight, but not the other one. + AssertEqual + \ [ + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]} + \ ], + \ GetMatchesWithoutIDs() + + call ale#highlight#UpdateHighlights() + + " Our highlight should apper again. + AssertEqual + \ [ + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ ], + \ GetMatchesWithoutIDs() + +Execute(Higlight 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(Higlight end columns should set an appropriate size): + call ale#highlight#SetHighlights(bufnr('%'), [ + \ {'bufnr': bufnr('%') - 1, 'type': 'E', 'lnum': 1, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 1, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 2, 'col': 1}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'sub_type': 'style', 'lnum': 3, 'col': 1}, + \ {'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) + + " 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() + +Execute(Highlights should always be cleared when the buffer highlight list is empty): + " 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() + + " 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. + AssertEqual + \ [ + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 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): + 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}, + \]) + + 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() diff --git a/test/test_highlight_position_chunking.vader b/test/test_highlight_position_chunking.vader new file mode 100644 index 0000000..cd9161b --- /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 0000000..7dabcd9 --- /dev/null +++ b/test/test_history_saving.vader @@ -0,0 +1,157 @@ +Before: + Save g:ale_max_buffer_history_size + Save g:ale_history_log_output + Save g:ale_run_synchronously + + 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! 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 + let g:ale_history_enabled = 1 + let g:ale_history_log_output = 0 + unlet g:history + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + call ale#linter#Reset() + delfunction TestFixer + delfunction CollectResults + +Given foobar (Some imaginary filetype): + anything + +Execute(History should be set when commands are run): + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + let g:history = filter( + \ copy(ale#history#Get(bufnr(''))), + \ 'v:val.job_id isnot# ''executable''', + \) + + AssertEqual 1, len(g:history) + AssertEqual sort(['status', 'exit_code', 'job_id', 'command']), 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 + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + 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 + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + let g:history = ale#history#Get(bufnr('')) + + AssertEqual 1, len(g:history) + AssertEqual ['command history test'], g:history[0].output + +Execute(History items should be popped after going over the max): + let 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 + let g:ale_run_synchronously = 1 + + ALEFix + + 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_line_join.vader b/test/test_line_join.vader index 26abb7c..25cefbc 100644 --- a/test/test_line_join.vader +++ b/test/test_line_join.vader @@ -1,23 +1,84 @@ Before: - let g:test_output = [ - \ ['one', 'two', 'thr'], - \ ['ee', ''], - \ ['fou'], - \ [''], - \ ['r', 'five'], - \ [], - \ ['', 'six'] - \] + let g:lines = [] + let g:data = '' - let g:expected_result = ['one', 'two', 'three', 'four', 'five', 'six'] + 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:test_output - unlet g:expected_result + unlet! g:last_line + unlet! g:lines + unlet! g:data + delfunction LineCallback + delfunction RawCallback -Execute (Join the lines): - let joined_result = [] - for item in g:test_output - call ale#engine#JoinNeovimOutput(joined_result, item) - endfor - AssertEqual g:expected_result, joined_result +Execute (ALE should handle empty Lists for the lines): + let g:last_line = ale#job#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#job#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#job#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#job#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#job#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#job#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#job#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#job#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#job#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#job#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#job#JoinNeovimOutput(1, '', ['y', 'z', 'a'], 'raw', function('RawCallback')) + + AssertEqual "y\nz\na", g:data + AssertEqual '', g:last_line diff --git a/test/test_lint_error_delay.vader b/test/test_lint_error_delay.vader new file mode 100644 index 0000000..7f08179 --- /dev/null +++ b/test/test_lint_error_delay.vader @@ -0,0 +1,22 @@ +Before: + Save g:ale_filetype_blacklist + + " Delete some variable which should be defined. + unlet! g:ale_filetype_blacklist + +After: + Restore + + call ale#ResetErrorDelays() + +Execute(ALE should stop queuing for a while after exceptions are thrown): + AssertThrows call ale#Queue(100) + call ale#Queue(100) + +Execute(ALE should stop linting for a while after exceptions are thrown): + AssertThrows call ale#Lint() + call ale#Lint() + +Execute(ALE should stop echoing messages for a while after exceptions are thrown): + AssertThrows call ale#cursor#EchoCursorWarning() + call ale#cursor#EchoCursorWarning() diff --git a/test/test_lint_file_linters.vader b/test/test_lint_file_linters.vader new file mode 100644 index 0000000..2e992e1 --- /dev/null +++ b/test/test_lint_file_linters.vader @@ -0,0 +1,304 @@ +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 + let g:ale_set_lists_synchronously = 1 + let b:ale_save_event_fired = 0 + call ale#ResetLintFileMarkers() + + 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 getloclist(0) + 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#WaitForJobs(2000) + endif + + Restore + + unlet! b:ale_save_event_fired + unlet! b:ale_enabled + unlet g:buffer_result + let g:ale_buffer_info = {} + call ale#linter#Reset() + 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) + + 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') + + 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') + + " 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) + + 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') + + 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) + + " We shouldn't get any prblems yet. + AssertEqual [], GetSimplerLoclist() + + call ale#events#SaveEvent(bufnr('')) + + " 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('')) + + " This flag needs to be set so windows can be opened, etc. + AssertEqual 1, b:ale_save_event_fired + +Execute(b:ale_save_event_fired should be set to 0 when results are set): + let b:ale_save_event_fired = 1 + + call ale#engine#SetResults(bufnr(''), []) + + AssertEqual 0, b:ale_save_event_fired + +Execute(lint_file linters should stay running after checking without them): + let g:ale_run_synchronously = 0 + + " Run all linters, then just the buffer linters. + call ale#Queue(0, 'lint_file') + call ale#Queue(0) + + " The lint_file linter should still be running. + AssertEqual + \ ['lint_file_linter', 'buffer_linter'], + \ g:ale_buffer_info[bufnr('')].active_linter_list + " We should have 1 job for each linter. + AssertEqual 2, len(g:ale_buffer_info[bufnr('')].job_list) + + call ale#engine#WaitForJobs(2000) + +Execute(The save event should not lint the buffer when ALE is disabled): + let g:ale_enabled = 0 + call ale#events#SaveEvent(bufnr('')) + + 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 0000000..d2b38e0 --- /dev/null +++ b/test/test_lint_on_enter_when_file_changed.vader @@ -0,0 +1,82 @@ +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') + 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('')) + + AssertEqual [{ + \ 'bufnr': bufnr(''), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'baz boz', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }], getloclist(0) + +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#EnterEvent(bufnr('')) + + AssertEqual [{ + \ 'bufnr': bufnr(''), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'baz boz', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }], getloclist(0) diff --git a/test/test_lint_on_filetype_changed.vader b/test/test_lint_on_filetype_changed.vader new file mode 100644 index 0000000..591a512 --- /dev/null +++ b/test/test_lint_on_filetype_changed.vader @@ -0,0 +1,74 @@ +Before: + Save &filetype + + let g:queue_calls = [] + + unlet! b:ale_lint_on_enter + + function! ale#Queue(...) + call add(g:queue_calls, a:000) + endfunction + +After: + Restore + + unlet! b:ale_lint_on_enter + unlet! g:queue_calls + + " Reload the ALE code to load the real function again. + runtime autoload/ale.vim + + unlet! b:ale_original_filetype + +Execute(The original filetype should be set on BufEnter): + let &filetype = 'foobar' + + call ale#events#EnterEvent(bufnr('')) + + AssertEqual 'foobar', b:ale_original_filetype + + let &filetype = 'bazboz' + + call ale#events#EnterEvent(bufnr('')) + + AssertEqual 'bazboz', b:ale_original_filetype + +Execute(Linting should not be queued when the filetype is the same): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'foobar') + + AssertEqual [], g:queue_calls + +Execute(Linting should be queued when the filetype changes): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls + +Execute(Linting shouldn't be done when the original filetype was blank and linting on enter is off): + let b:ale_lint_on_enter = 0 + let b:ale_original_filetype = '' + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [], g:queue_calls + +Execute(Linting should be done when the original filetype was blank and linting on enter is on): + let b:ale_lint_on_enter = 1 + let b:ale_original_filetype = '' + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls + +Execute(The new filetype should become the "original" one if the original was blank and linting on enter is off): + let b:ale_lint_on_enter = 0 + let b:ale_original_filetype = '' + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual 'bazboz', b:ale_original_filetype diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index 4b5c6a7..d946a60 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -211,3 +211,233 @@ Execute(PreProcess should complain about invalid chain items at higher indices): \} AssertThrows call ale#linter#PreProcess(g:linter) AssertEqual 'The `command_chain` item 1 must define a `callback` function', g:vader_exception + +Execute(PreProcess should complain when conflicting command options are used): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command': 'foo', + \ 'command_chain': [{'callback': 'foo'}], + \} + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'Only one of `command`, `command_callback`, or `command_chain` should be set', g:vader_exception + + unlet g:linter.command + let g:linter.command_callback = 'foo' + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'Only one of `command`, `command_callback`, or `command_chain` should be set', g:vader_exception + + let g:linter.command = 'foo' + unlet g:linter.command_chain + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'Only one of `command`, `command_callback`, or `command_chain` should be set', g:vader_exception + +Execute(PreProcess should process the read_buffer option correctly): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [{'callback': 'foo'}, {'callback': 'bar'}], + \ 'read_buffer': '0', + \} + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual '`read_buffer` must be `0` or `1`', g:vader_exception + + let g:linter.read_buffer = 0 + + call ale#linter#PreProcess(g:linter) + + let g:linter.read_buffer = 1 + + call ale#linter#PreProcess(g:linter) + + unlet g:linter.read_buffer + let g:linter.command_chain[0].read_buffer = '0' + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'The `command_chain` item 0 value for `read_buffer` must be `0` or `1`', g:vader_exception + + let g:linter.command_chain[0].read_buffer = 0 + + call ale#linter#PreProcess(g:linter) + + let g:linter.command_chain[1].read_buffer = '0' + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'The `command_chain` item 1 value for `read_buffer` must be `0` or `1`', g:vader_exception + + let g:linter.command_chain[1].read_buffer = 1 + + call ale#linter#PreProcess(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(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(g:linter) + AssertEqual '`lint_file` must be `0` or `1`', g:vader_exception + + let g:linter.lint_file = 0 + + AssertEqual 0, ale#linter#PreProcess(g:linter).lint_file + " The default for read_buffer should be 1 when lint_file is 0 + AssertEqual 1, ale#linter#PreProcess(g:linter).read_buffer + + let g:linter.lint_file = 1 + + AssertEqual 1, ale#linter#PreProcess(g:linter).lint_file + " The default for read_buffer should change to 0 when lint_file is 1. + AssertEqual 0, ale#linter#PreProcess(g:linter).read_buffer + + let g:linter.read_buffer = 1 + + " We shouldn't be able to set both options to 1 at the same time. + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'Only one of `lint_file` or `read_buffer` can be `1`', g:vader_exception + +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(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(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(g:linter) + AssertEqual '`aliases` must be a List of String values', g:vader_exception + + let g:linter.aliases = [1] + + AssertThrows call ale#linter#PreProcess(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(g:linter).aliases + + let g:linter.aliases = ['foo', 'bar'] + + AssertEqual ['foo', 'bar'], ale#linter#PreProcess(g:linter).aliases + +Execute(PreProcess should accept tsserver LSP configuration): + let g:linter = { + \ 'name': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'lsp': 'tsserver', + \ 'language_callback': 'x', + \ 'project_root_callback': 'x', + \} + + AssertEqual 'tsserver', ale#linter#PreProcess(g:linter).lsp + + call remove(g:linter, 'executable') + let g:linter.executable_callback = 'X' + + call ale#linter#PreProcess(g:linter) + + call remove(g:linter, 'command') + let g:linter.command_callback = 'X' + + call ale#linter#PreProcess(g:linter) + +Execute(PreProcess should accept stdio LSP configuration): + let g:linter = { + \ 'name': 'x', + \ 'executable': 'x', + \ 'command': 'x', + \ 'lsp': 'stdio', + \ 'language_callback': 'x', + \ 'project_root_callback': 'x', + \} + + AssertEqual 'stdio', ale#linter#PreProcess(g:linter).lsp + + call remove(g:linter, 'executable') + let g:linter.executable_callback = 'X' + + call ale#linter#PreProcess(g:linter) + + call remove(g:linter, 'command') + let g:linter.command_callback = 'X' + + call ale#linter#PreProcess(g:linter) + +Execute(PreProcess should accept LSP server configurations): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language_callback': 'x', + \ 'project_root_callback': 'x', + \} + + AssertEqual 'socket', ale#linter#PreProcess(g:linter).lsp + +Execute(PreProcess should require an address_callback for LSP socket configurations): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \} + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual '`address_callback` must be defined for getting the LSP address', g:vader_exception + +Execute(PreProcess should complain about address_callback for non-LSP linters): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'address_callback': 'X', + \} + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual '`address_callback` cannot be used when lsp != ''socket''', g:vader_exception diff --git a/test/test_linter_retrieval.vader b/test/test_linter_retrieval.vader index 5dadf6b..5d1ee45 100644 --- a/test/test_linter_retrieval.vader +++ b/test/test_linter_retrieval.vader @@ -1,43 +1,167 @@ Before: - 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'} + Save g:ale_linters + Save g:ale_linter_aliases + let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout', 'read_buffer': 1, 'lint_file': 0, 'aliases': [], 'lsp': '', 'add_newline': 0} + let g:testlinter2 = {'name': 'testlinter2', 'executable': 'testlinter2', 'command': 'testlinter2', 'callback': 'testCB2', 'output_stream': 'stdout', 'read_buffer': 0, 'lint_file': 1, 'aliases': [], 'lsp': '', 'add_newline': 0} call ale#linter#Reset() - let g:ale_linters = {} - let g:ale_linter_aliases = {} -Execute (Define a linter): +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) -Then (Get the defined linter): AssertEqual [g:testlinter1], ale#linter#Get('testft') -Execute (Define a couple linters, filtering one): +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']} -Then (Only the configured linter should be returned): + AssertEqual [g:testlinter1], ale#linter#Get('testft') -Execute (Define a linter for a filetype, and create a filetype alias): +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'} -Then (Linters should be transparently aliased): + AssertEqual [g:testlinter1], ale#linter#Get('testft2') -Execute (Define multiple linters, with filters and aliases): +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'} -Then (Linters should be transparently filtered and aliased): + AssertEqual [g:testlinter1], ale#linter#Get('testft1') AssertEqual [g:testlinter2], ale#linter#Get('testft2') -Execute (Define multiple linters for different filetypes): +Execute (Dot-separated filetypes should be handled correctly): call ale#linter#Define('testft1', g:testlinter1) call ale#linter#Define('testft2', g:testlinter2) -Then (Linters for dot-seperated filetypes should be properly handled): + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1.testft2') -Execute (Try to load a linter from disk): - AssertEqual [{'name': 'testlinter', 'output_stream': 'stdout', 'executable': 'testlinter', 'command': 'testlinter', 'callback': 'testCB'}], ale#linter#Get('testft') +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']} + +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 (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 should be loaded from disk appropriately): + AssertEqual [{'name': 'testlinter', 'output_stream': 'stdout', 'executable': 'testlinter', 'command': 'testlinter', 'callback': 'testCB', 'read_buffer': 1, 'lint_file': 0, 'aliases': [], 'lsp': '', 'add_newline': 0}], ale#linter#Get('testft') + + +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', 'add_newline': 0} + \], 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 0000000..0131b5f --- /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', [ + \ {'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', [ + \ {'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', [ + \ {'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', [ + \ {'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', [ + \ {'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', [ + \ {'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 index 918209d..9960264 100644 --- a/test/test_linting_blacklist.vader +++ b/test/test_linting_blacklist.vader @@ -1,9 +1,12 @@ -Given unite (A Unite.vim file): - anything +Before: + let g:ale_buffer_info = {} After: 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#Lint() call ale#engine#WaitForJobs(2000) diff --git a/test/test_linting_sets_signs.vader b/test/test_linting_sets_signs.vader deleted file mode 100644 index e96e490..0000000 --- a/test/test_linting_sets_signs.vader +++ /dev/null @@ -1,39 +0,0 @@ -Given javascript (Some JavaScript with problems): - var y = 3+3; - var y = 3 - -Before: - sign unplace * - let g:actual_sign_list = [] - let g:expected_sign_list = [ - \ ['1', 'ALEWarningSign'], - \ ['2', 'ALEErrorSign'], - \] - - function! g:CollectSigns() - redir => l:output - silent exec 'sign place' - redir END - - for l:line in split(l:output, "\n") - let l:match = matchlist(l:line, 'line=\(\d\+\).*name=\(ALE[a-zA-Z]\+\)') - - if len(l:match) > 0 - call add(g:actual_sign_list, [l:match[1], l:match[2]]) - endif - endfor - endfunction - -After: - sign unplace * - let g:ale_buffer_info = {} - delfunction g:CollectSigns - unlet g:actual_sign_list - unlet g:expected_sign_list - -Execute(The signs should be updated after linting is done): - call ale#Lint() - call ale#engine#WaitForJobs(2000) - call g:CollectSigns() - - AssertEqual g:expected_sign_list, g:actual_sign_list diff --git a/test/test_linting_updates_loclist.vader b/test/test_linting_updates_loclist.vader index 06f11d8..29ca05d 100644 --- a/test/test_linting_updates_loclist.vader +++ b/test/test_linting_updates_loclist.vader @@ -1,33 +1,71 @@ -Given javascript (Some JavaScript with problems): - var y = 3+3; - var y = 3 - Before: + Save g:ale_set_signs + let g:ale_set_signs = 1 + let g:expected_data = [ \ { \ 'lnum': 1, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'testlinter', \ 'nr': -1, \ 'type': 'W', \ 'col': 10, \ 'text': 'Infix operators must be spaced. [Warning/space-infix-ops]', + \ 'sign_id': 1000001, \ }, \ { \ 'lnum': 2, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'testlinter', \ 'nr': -1, \ 'type': 'E', \ 'col': 10, \ 'text': 'Missing semicolon. [Error/semi]', + \ 'sign_id': 1000002, \ } \] + 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 * + After: - unlet g:expected_data + Restore + + delfunction TestCallback + + unlet! g:expected_data + let g:ale_buffer_info = {} + call ale#linter#Reset() + +Given foobar (Some JavaScript with problems): + var y = 3+3; + var y = 3 Execute(The loclist should be updated after linting is done): call ale#Lint() diff --git a/test/test_list_formatting.vader b/test/test_list_formatting.vader new file mode 100644 index 0000000..0c52f10 --- /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'}) + 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', + \ }, + \ ], + \ getloclist(0) + + 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', + \ }, + \ ], + \ getloclist(0) + +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'}) + 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', + \ }, + \ ], + \ getqflist() + + 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', + \ }, + \ ], + \ getqflist() + +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', + \ }, + \ ], + \ getloclist(0) + +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', + \ }, + \ ], + \ getqflist() + +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', + \ }, + \ ], + \ getloclist(0) diff --git a/test/test_list_opening.vader b/test/test_list_opening.vader index e3517bf..a24e8de 100644 --- a/test/test_list_opening.vader +++ b/test/test_list_opening.vader @@ -1,25 +1,69 @@ " Author: Yann Fery - Before: - let g:loclist = [ - \ {'lnum': 5, 'col': 5}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, - \] + 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 -After: - " Close quickfix window after every execute block - lcl - ccl - unlet g:loclist - call setloclist(0, []) - call setqflist([]) - " Reset options to their default values. 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() @@ -35,46 +79,98 @@ Execute(IsQuickfixOpen should return the right output): AssertEqual 0, ale#list#IsQuickfixOpen() Execute(The quickfix window should not open by default for the loclist): - call ale#list#SetLists(g: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([]) + call ale#list#SetLists(bufnr('%'), []) Assert !ale#list#IsQuickfixOpen() " With a non-empty loclist, the window must open. - call ale#list#SetLists(g:loclist) + 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 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(b:ale_list_window_size) + +Execute(The quickfix window should be horizontal for the loclist with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 0 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 0, GetQuickfixIsVertical(b:ale_list_window_size) + Execute(The quickfix window should stay open for just the loclist): let g:ale_open_list = 1 let g:ale_keep_list_window_open = 1 " The window should stay open after even after it is made blank again. - call ale#list#SetLists(g:loclist) - call ale#list#SetLists([]) + 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(g:loclist) + 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([]) - Assert !ale#list#IsQuickfixOpen() + 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(g:loclist) - Assert ale#list#IsQuickfixOpen() + 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 @@ -82,6 +178,85 @@ Execute(The quickfix window should stay open for the quickfix list): 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(g:loclist) - call ale#list#SetLists([]) + 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(b:ale_list_window_size) + +Execute(The quickfix window should be horizontal for the quickfix with appropriate variables): + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + let b:ale_list_vertical = 0 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 0, GetQuickfixIsVertical(b:ale_list_window_size) + +Execute(The buffer ale_open_list option should be respected): + let b:ale_open_list = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen() + +Execute(The buffer ale_keep_list_window_open option should be respected): + let b:ale_open_list = 1 + let b:ale_keep_list_window_open = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + call ale#list#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 0000000..d521906 --- /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': '', + \}], getloclist(0) + + 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': '', + \}], getqflist() + + 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 0000000..6806719 --- /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 index e0b2c65..5558191 100644 --- a/test/test_loclist_binary_search.vader +++ b/test/test_loclist_binary_search.vader @@ -1,26 +1,49 @@ Before: let g:loclist = [ - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, - \ {'lnum': 3, 'col': 10}, - \ {'lnum': 3, 'col': 12}, - \ {'lnum': 3, 'col': 25}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 5, 'col': 5}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 3, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 12}, + \ {'bufnr': 1, 'lnum': 3, 'col': 25}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 1, 'lnum': 9, 'col': 5}, + \ {'bufnr': 1, 'lnum': 10, 'col': 1}, + \ {'bufnr': 2, 'lnum': 7, 'col': 10}, + \ {'bufnr': 2, 'lnum': 9, 'col': 2}, + \ {'bufnr': 2, 'lnum': 10, 'col': 2}, + \ {'bufnr': 2, 'lnum': 11, 'col': 2}, \] -Execute (Exact column matches should be correct): - AssertEqual 1, ale#util#BinarySearch(g:loclist, 3, 2) - -Execute (Off lines, there should be no match): - AssertEqual -1, ale#util#BinarySearch(g:loclist, 4, 2) - -Execute (Near column matches should be taken): - AssertEqual 2, ale#util#BinarySearch(g:loclist, 3, 11) - AssertEqual 4, ale#util#BinarySearch(g:loclist, 3, 13) - -Execute (Columns before should be taken when the cursor is far ahead): - AssertEqual 4, ale#util#BinarySearch(g:loclist, 3, 300) - 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) diff --git a/test/test_loclist_corrections.vader b/test/test_loclist_corrections.vader new file mode 100644 index 0000000..6224d60 --- /dev/null +++ b/test/test_loclist_corrections.vader @@ -0,0 +1,350 @@ +After: + unlet! b:temp_name + unlet! b:other_bufnr + +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', + \ [{'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': 1, + \ 'type': 'W', + \ 'nr': 42, + \ 'linter_name': 'foobar', + \ }, + \], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ [{ + \ '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, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ [{'text': 'a', 'lnum': 11}], + \ ) + +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', + \ [{'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', + \ [{'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', + \ [ + \ {'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': 10, + \ 'col': 10, + \ 'end_lnum': 13, + \ 'end_col': 12, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 11, + \ 'end_lnum': 13, + \ 'end_col': 12, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ [ + \ {'text': 'a', 'lnum': '010', 'col': '010', 'end_col': '012', 'end_lnum': '013'}, + \ {'text': 'a', 'lnum': '010', 'col': '011', 'end_col': 12, 'end_lnum': 13}, + \ ], + \ ) + +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', + \ [{'text': 'a', 'lnum': 11, 'sub_type': 'style'}], + \ ) + +Execute(FixLocList should accept filenames): + let b:other_bufnr = bufnr('/foo/bar/baz', 1) + + " Make sure we actually get another buffer number, or the test is invalid. + AssertNotEqual -1, b:other_bufnr + + call ale#test#SetFilename('test.txt') + + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'filename': expand('%:p'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 0, + \ 'bufnr': bufnr('%'), + \ 'filename': expand('%:p'), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 4, + \ 'col': 0, + \ 'bufnr': b:other_bufnr, + \ 'filename': '/foo/bar/baz', + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 5, + \ 'col': 0, + \ 'bufnr': b:other_bufnr, + \ 'filename': '/foo/bar/baz', + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \], + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'foobar', + \ [ + \ {'text': 'a', 'lnum': 2, 'filename': expand('%:p')}, + \ {'text': 'a', 'lnum': 3, 'filename': expand('%:p')}, + \ {'text': 'a', 'lnum': 4, 'filename': '/foo/bar/baz'}, + \ {'text': 'a', 'lnum': 5, 'filename': '/foo/bar/baz'}, + \ ], + \ ) + +Execute(FixLocList should interpret temporary filenames as being the current buffer): + let b:temp_name = tempname() + + AssertEqual + \ [ + \ { + \ 'text': 'a', + \ 'lnum': 2, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 3, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'foobar', + \ }, + \], + \ ale#engine#FixLocList( + \ bufnr(''), + \ 'foobar', + \ [ + \ {'text': 'a', 'lnum': 2, 'filename': b:temp_name}, + \ {'text': 'a', 'lnum': 3, 'filename': b:temp_name}, + \ ], + \ ) + +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', + \ [{'text': 'a', 'lnum': 11, 'code': 'some-code'}], + \ ) diff --git a/test/test_loclist_jumping.vader b/test/test_loclist_jumping.vader new file mode 100644 index 0000000..5e18499 --- /dev/null +++ b/test/test_loclist_jumping.vader @@ -0,0 +1,90 @@ +Before: + let g:ale_buffer_info = { + \ bufnr(''): { + \ 'loclist': [ + \ {'bufnr': bufnr('') - 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 2}, + \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 3}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 1}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 2}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 3}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 6}, + \ {'bufnr': bufnr(''), 'lnum': 2, 'col': 700}, + \ {'bufnr': bufnr('') + 1, 'lnum': 3, 'col': 2}, + \ ], + \ }, + \} + + function! TestJump(position, wrap, 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) + 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, [2, 2]) + AssertEqual [1, 3], TestJump('before', 0, [2, 1]) + AssertEqual [2, 3], TestJump('after', 0, [2, 2]) + AssertEqual [2, 1], TestJump('after', 0, [1, 3]) + AssertEqual [2, 6], TestJump('after', 0, [2, 4]) + AssertEqual [2, 8], TestJump('after', 0, [2, 6]) + +Execute(loclist jumping should jump correctly when wrapping): + AssertEqual [2, 1], TestJump('before', 1, [2, 2]) + AssertEqual [1, 3], TestJump('before', 1, [2, 1]) + AssertEqual [2, 3], TestJump('after', 1, [2, 2]) + AssertEqual [2, 1], TestJump('after', 1, [1, 3]) + AssertEqual [2, 6], TestJump('after', 1, [2, 4]) + + AssertEqual [1, 2], TestJump('after', 1, [2, 8]) + AssertEqual [2, 8], TestJump('before', 1, [1, 2]) + +Execute(loclist jumping not jump when the loclist is empty): + let g:ale_buffer_info[bufnr('%')].loclist = [] + + AssertEqual [1, 6], TestJump('before', 0, [1, 6]) + AssertEqual [1, 6], TestJump('before', 1, [1, 6]) + AssertEqual [1, 6], TestJump('after', 0, [1, 6]) + AssertEqual [1, 6], TestJump('after', 1, [1, 6]) + +Execute(We should be able to jump to the last item): + AssertEqual [2, 8], TestJump(-1, 0, [1, 6]) + +Execute(We shouldn't move when jumping to the last item where there are none): + let g:ale_buffer_info[bufnr('%')].loclist = [] + + AssertEqual [1, 6], TestJump(-1, 0, [1, 6]) + +Execute(We should be able to jump to the first item): + AssertEqual [1, 2], TestJump(0, 0, [1, 6]) + +Execute(We shouldn't move when jumping to the first item where there are none): + let g:ale_buffer_info[bufnr('%')].loclist = [] + + AssertEqual [1, 6], TestJump(0, 0, [1, 6]) + +Execute(We should be able to jump when the error line is blank): + " Add a blank line at the end. + call setline(1, getline('.', '$') + ['']) + " Add a problem on the blank line. + call add(g:ale_buffer_info[bufnr('%')].loclist, {'bufnr': bufnr(''), 'lnum': 3, 'col': 1}) + + AssertEqual 0, len(getline(3)) + AssertEqual [2, 8], TestJump('before', 0, [3, 1]) + AssertEqual [2, 8], TestJump('before', 1, [3, 1]) + AssertEqual [3, 1], TestJump('after', 0, [3, 1]) + AssertEqual [1, 2], TestJump('after', 1, [3, 1]) diff --git a/test/test_loclist_sorting.vader b/test/test_loclist_sorting.vader index 6e52be6..157b2a2 100644 --- a/test/test_loclist_sorting.vader +++ b/test/test_loclist_sorting.vader @@ -1,21 +1,27 @@ -Before: - let g:loclist = [ - \ {'lnum': 5, 'col': 5}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, - \] - -Execute (Sort loclist with comparison function): - call sort(g:loclist, 'ale#util#LocItemCompare') - -Then (loclist item should be sorted): +Execute(loclist item should be sorted): AssertEqual [ - \ {'lnum': 2, 'col': 10}, - \ {'lnum': 3, 'col': 2}, - \ {'lnum': 5, 'col': 4}, - \ {'lnum': 5, 'col': 5}, - \], g:loclist - -After: - unlet g:loclist + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ ], + \ sort([ + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \], 'ale#util#LocItemCompare') diff --git a/test/test_mypy_handler.vader b/test/test_mypy_handler.vader deleted file mode 100644 index f886501..0000000 --- a/test/test_mypy_handler.vader +++ /dev/null @@ -1,32 +0,0 @@ -Execute(The mypy handler should parse lines correctly): - runtime ale_linters/python/mypy.vim - - AssertEqual - \ [ - \ { - \ 'bufnr': 347, - \ 'lnum': 4, - \ 'vcol': 0, - \ 'col': 0, - \ 'text': "No library stub file for module 'django.db'", - \ 'type': 'E', - \ 'nr': -1, - \ }, - \ { - \ 'bufnr': 347, - \ 'lnum': 40, - \ 'vcol': 0, - \ 'col': 5, - \ 'text': "Some other problem", - \ 'type': 'E', - \ 'nr': -1, - \ }, - \ ], - \ ale_linters#python#mypy#Handle(347, [ - \ "file.py:4: error: No library stub file for module 'django.db'", - \ 'file.py:4: note: (Stub files are from https://github.com/python/typeshed)', - \ "file.py:40:5: error: Some other problem", - \ ]) - -After: - call ale#linter#Reset() diff --git a/test/test_nearest_file_search.vader b/test/test_nearest_file_search.vader index c59bfce..10d2cb3 100644 --- a/test/test_nearest_file_search.vader +++ b/test/test_nearest_file_search.vader @@ -1,11 +1,15 @@ -Execute(Open a file some directory down): - silent! cd /testplugin/test - :e! top/middle/bottom/dummy.txt +Before: + call ale#test#SetDirectory('/testplugin/test') -Then(We should be able to find a configuration file further up): - AssertEqual expand('%:p:h:h:h:h') . '/top/example.ini', ale#util#FindNearestFile(bufnr('%'), 'example.ini') +After: + call ale#test#RestoreDirectory() -Execute(Do nothing): +Execute(We should be able to find a configuration file further up): + call ale#test#SetFilename('top/middle/bottom/dummy.txt') -Then(We shouldn't find anything for files which don't match): - AssertEqual '', ale#util#FindNearestFile(bufnr('%'), 'cantfindthis') + AssertEqual + \ ale#path#Simplify(expand('%:p:h:h:h:h') . '/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_no_linting_on_write_quit.vader b/test/test_no_linting_on_write_quit.vader new file mode 100644 index 0000000..d3baeaa --- /dev/null +++ b/test/test_no_linting_on_write_quit.vader @@ -0,0 +1,97 @@ +Before: + Save g:ale_echo_cursor + Save g:ale_fix_on_save + Save g:ale_fixers + Save g:ale_lint_on_save + + let g:ale_echo_cursor = 0 + let g:ale_run_synchronously = 1 + + 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#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('')) + + AssertEqual 1, len(getloclist(0)) + + " 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('')) + + AssertEqual [], getloclist(0) + +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('')) + + " Simulate 2 seconds passing. + let b:ale_quitting -= 1000 + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual 1, len(getloclist(0)) + +Execute(No linting should be done on :wq or :x after fixing files): + let g:ale_lint_on_save = 0 + let g:ale_fix_on_save = 1 + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual 1, len(getloclist(0)) + + " 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('')) + + AssertEqual [], getloclist(0) + +Execute(Linting should be done after :q fails and fixing files): + let g:ale_lint_on_save = 0 + let g:ale_fix_on_save = 1 + + call ale#events#QuitEvent(bufnr('')) + + " Simulate 2 seconds passing. + let b:ale_quitting -= 1000 + + call ale#events#SaveEvent(bufnr('')) + + AssertEqual 1, len(getloclist(0)) diff --git a/test/test_path_equality.vader b/test/test_path_equality.vader new file mode 100644 index 0000000..4ec9bd6 --- /dev/null +++ b/test/test_path_equality.vader @@ -0,0 +1,55 @@ +Before: + function! CheckPath(path) abort + return ale#path#IsBufferPath(bufnr(''), ale#path#Simplify(a:path)) + endfunction + +After: + delfunction CheckPath + +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') diff --git a/test/test_path_upwards.vader b/test/test_path_upwards.vader new file mode 100644 index 0000000..cd461a2 --- /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 0000000..a3e68d9 --- /dev/null +++ b/test/test_path_uri.vader @@ -0,0 +1,19 @@ +Execute(ale#path#ToURI should work for Windows paths): + AssertEqual 'file:///C:/foo/bar/baz.tst', ale#path#ToURI('C:\foo\bar\baz.tst') + AssertEqual 'foo/bar/baz.tst', ale#path#ToURI('foo\bar\baz.tst') + +Execute(ale#path#FromURI should work for Windows paths): + AssertEqual 'C:\foo\bar\baz.tst', ale#path#FromURI('file:///C:/foo/bar/baz.tst') + +Execute(ale#path#ToURI should work for Unix paths): + AssertEqual 'file:///foo/bar/baz.tst', ale#path#ToURI('/foo/bar/baz.tst') + AssertEqual 'foo/bar/baz.tst', ale#path#ToURI('foo/bar/baz.tst') + +Execute(ale#path#ToURI should keep safe characters): + AssertEqual '//a-zA-Z0-9$-_.!*''(),', ale#path#ToURI('\/a-zA-Z0-9$-_.!*''(),') + +Execute(ale#path#ToURI should percent encode unsafe characters): + AssertEqual '%20%2b%3a%3f%26%3d', ale#path#ToURI(' +:?&=') + +Execute(ale#path#FromURI should decode percent encodings): + AssertEqual ' +:?&=', ale#path#FromURI('%20%2b%3a%3f%26%3d') diff --git a/test/test_pattern_options.vader b/test/test_pattern_options.vader new file mode 100644 index 0000000..0e26eaa --- /dev/null +++ b/test/test_pattern_options.vader @@ -0,0 +1,92 @@ +Before: + Save g:ale_pattern_options + Save g:ale_pattern_options_enabled + Save &filetype + + 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#pattern_options#SetOptions(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#pattern_options#SetOptions(bufnr('')) + + AssertEqual 0, b:ale_enabled + AssertEqual 0, b:some_option + + call ale#test#SetFilename('bazboz.js') + call ale#pattern_options#SetOptions(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#pattern_options#SetOptions(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#pattern_options#SetOptions(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#pattern_options#SetOptions(bufnr('')) + + AssertEqual 0, b:some_option + + let g:ale_pattern_options['foo'] = {'some_option': 666} + + call ale#pattern_options#SetOptions(bufnr('')) + + AssertEqual 666, b:some_option diff --git a/test/test_phpcs_executable_detection.vader b/test/test_phpcs_executable_detection.vader new file mode 100644 index 0000000..020bfac --- /dev/null +++ b/test/test_phpcs_executable_detection.vader @@ -0,0 +1,39 @@ +Before: + Save g:ale_php_phpcs_executable + Save g:ale_php_phpcs_use_global + + let g:ale_php_phpcs_executable = 'phpcs_test' + let g:ale_php_phpcs_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test') + + runtime ale_linters/php/phpcs.vim + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(project with phpcs should use local by default): + call ale#test#SetFilename('phpcs-test-files/project-with-phpcs/foo/test.php') + + AssertEqual + \ ale#path#Simplify(g:dir . '/phpcs-test-files/project-with-phpcs/vendor/bin/phpcs'), + \ ale_linters#php#phpcs#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_phpcs_use_global = 1 + + call ale#test#SetFilename('phpcs-test-files/project-with-phpcs/foo/test.php') + + AssertEqual + \ 'phpcs_test', + \ ale_linters#php#phpcs#GetExecutable(bufnr('')) + +Execute(project without phpcs should use global): + call ale#test#SetFilename('phpcs-test-files/project-without-phpcs/foo/test.php') + + AssertEqual + \ 'phpcs_test', + \ ale_linters#php#phpcs#GetExecutable(bufnr('')) diff --git a/test/test_phpcs_include_code.vader b/test/test_phpcs_include_code.vader new file mode 100644 index 0000000..1cff191 --- /dev/null +++ b/test/test_phpcs_include_code.vader @@ -0,0 +1,7 @@ +Execute(errors should include code): + AssertEqual + \ [{'lnum': 18, 'col': 3, 'type': 'E', 'text': 'Line indented incorrectly; expected 4 spaces, found 2 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)'}], + \ 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)', + \ ]) + diff --git a/test/test_prepare_command.vader b/test/test_prepare_command.vader new file mode 100644 index 0000000..ed9272a --- /dev/null +++ b/test/test_prepare_command.vader @@ -0,0 +1,39 @@ +Before: + Save &shell + Save &shellcmdflag + +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(Other shells should be used when set): + if !has('win32') + let &shell = '/bin/bash' + let &shellcmdflag = '-c' + + 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 diff --git a/test/test_python_virtualenv.vader b/test/test_python_virtualenv.vader new file mode 100644 index 0000000..b44c5fa --- /dev/null +++ b/test/test_python_virtualenv.vader @@ -0,0 +1,12 @@ +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' diff --git a/test/test_quickfix_deduplication.vader b/test/test_quickfix_deduplication.vader new file mode 100644 index 0000000..0dff3f2 --- /dev/null +++ b/test/test_quickfix_deduplication.vader @@ -0,0 +1,50 @@ +Before: + Save g:ale_buffer_info + +After: + Restore + +Execute: + " Results from multiple buffers should be gathered together. + " Equal problems should be de-duplicated. + let g:ale_buffer_info = { + \ '1': {'loclist': [ + \ {'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1, 'text': 'foo'}, + \ ]}, + \ '2': {'loclist': [ + \ {'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'another error'}, + \ ]}, + \} + + AssertEqual + \ [ + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2, 'text': 'x'}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4, 'text': 'x'}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5, 'text': 'x'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2, 'text': 'foo'}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5, 'text': 'bar'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'another error'}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5, 'text': 'foo'}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1, 'text': 'foo'}, + \ ], + \ ale#list#GetCombinedList() diff --git a/test/test_quitting_variable.vader b/test/test_quitting_variable.vader new file mode 100644 index 0000000..bef344a --- /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#util#ClockMilliseconds() + + call ale#events#QuitEvent(bufnr('')) + + Assert b:ale_quitting >= b:time_before + Assert b:ale_quitting <= ale#util#ClockMilliseconds() + +Execute(EnterEvent should set b:ale_quitting to 0): + let b:ale_quitting = 1 + + call ale#events#EnterEvent(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#util#ClockMilliseconds() + + AssertEqual 1, ale#events#QuitRecently(bufnr('')) + +Execute(The QuitRecently function should return 0 when a second has passed): + let b:ale_quitting = ale#util#ClockMilliseconds() - 1001 + + AssertEqual 0, ale#events#QuitRecently(bufnr('')) diff --git a/test/test_regex_escaping.vader b/test/test_regex_escaping.vader new file mode 100644 index 0000000..b79b8c5 --- /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_resolve_local_path.vader b/test/test_resolve_local_path.vader index e269221..3f0fb20 100644 --- a/test/test_resolve_local_path.vader +++ b/test/test_resolve_local_path.vader @@ -1,15 +1,17 @@ -Execute(Open a file some directory down): - silent! cd /testplugin/test - :e! top/middle/bottom/dummy.txt +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('top/middle/bottom/dummy.txt') -Then(We should be able to find the local version of a file): AssertEqual - \ expand('%:p:h:h:h:h') . '/top/example.ini', - \ ale#util#ResolveLocalPath(bufnr('%'), 'example.ini', '/global/config.ini') + \ ale#path#Simplify(expand('%:p:h:h:h:h') . '/top/example.ini'), + \ ale#path#ResolveLocalPath(bufnr('%'), 'example.ini', '/global/config.ini') -Execute(Do nothing): - -Then(We shouldn't find anything for files which don't match): +Execute(We shouldn't find anything for files which don't match): AssertEqual \ '/global/config.ini', - \ ale#util#ResolveLocalPath(bufnr('%'), 'missing.ini', '/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 0000000..c983a89 --- /dev/null +++ b/test/test_results_not_cleared_when_opening_loclist.vader @@ -0,0 +1,49 @@ +Before: + Save g:ale_run_synchronously + + let g:ale_run_synchronously = 1 + + function! TestCallback(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'text': 'Something is wrong', + \ }, + \] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + +After: + Restore + + delfunction TestCallback + let g:ale_buffer_info = {} + call ale#linter#Reset() + call setloclist(0, []) + call clearmatches() + sign unplace * + +Given foobar (Some file): + abc + +Execute(The loclist shouldn't be cleared when opening the loclist): + call ale#Lint() + sleep 1ms + + AssertEqual 1, len(getloclist(0)), 'The loclist was never set' + + " 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(getloclist(0)), 'The loclist was cleared' diff --git a/test/test_rust_handler.vader b/test/test_rust_handler.vader deleted file mode 100644 index d4d54d3..0000000 --- a/test/test_rust_handler.vader +++ /dev/null @@ -1,28 +0,0 @@ -Execute(The Rust handler should handle rustc output): - AssertEqual - \ [ - \ {'lnum': 15, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 418, 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`'}, - \ {'lnum': 13, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 407, 'text': 'no method named `wat` found for type `std::string::String` in the current scope'}, - \ ], - \ ale#handlers#rust#HandleRustErrorsForFile(347, 'src/playpen.rs', [ - \ '', - \ 'ignore this', - \ '{"message":"expected one of `.`, `;`, `?`, `}`, or an operator, found `for`","code":null,"level":"error","spans":[{"file_name":"","byte_start":418,"byte_end":421,"line_start":15,"line_end":15,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" for chr in source.trim().chars() {","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"expansion":null}],"children":[],"rendered":null}', - \ '{"message":"main function not found","code":null,"level":"error","spans":[],"children":[],"rendered":null}', - \ '{"message":"no method named `wat` found for type `std::string::String` in the current scope","code":null,"level":"error","spans":[{"file_name":"","byte_start":407,"byte_end":410,"line_start":13,"line_end":13,"column_start":7,"column_end":10,"is_primary":true,"text":[{"text":" s.wat()","highlight_start":7,"highlight_end":10}],"label":null,"suggested_replacement":null,"expansion":null}],"children":[],"rendered":null}', - \ '{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":null}', - \ ]) - -Execute(The Rust handler should handle cargo output): - AssertEqual - \ [ - \ {'lnum': 15, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 11505, 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`'}, - \ {'lnum': 13, 'bufnr': 347, 'vcol': 0, 'nr': -1, 'type': 'E', 'col': 11494, 'text': 'no method named `wat` found for type `std::string::String` in the current scope'}, - \ ], - \ ale#handlers#rust#HandleRustErrorsForFile(347, 'src/playpen.rs', [ - \ '', - \ 'ignore this', - \ '{"message":{"children":[],"code":null,"level":"error","message":"expected one of `.`, `;`, `?`, `}`, or an operator, found `for`","rendered":null,"spans":[{"byte_end":11508,"byte_start":11505,"column_end":8,"column_start":5,"expansion":null,"file_name":"src/playpen.rs","is_primary":true,"label":null,"line_end":15,"line_start":15,"suggested_replacement":null,"text":[{"highlight_end":8,"highlight_start":5,"text":" for chr in source.trim().chars() {"}]}]},"package_id":"update 0.0.1 (path+file:///home/w0rp/Downloads/rust-by-example)","reason":"compiler-message","target":{"kind":["bin"],"name":"update","src_path":"/home/w0rp/Downloads/rust-by-example/src/main.rs"}}', - \ '{"message":{"children":[],"code":null,"level":"error","message":"no method named `wat` found for type `std::string::String` in the current scope","rendered":null,"spans":[{"byte_end":11497,"byte_start":11494,"column_end":10,"column_start":7,"expansion":null,"file_name":"src/playpen.rs","is_primary":true,"label":null,"line_end":13,"line_start":13,"suggested_replacement":null,"text":[{"highlight_end":10,"highlight_start":7,"text":" s.wat()"}]}]},"package_id":"update 0.0.1 (path+file:///home/w0rp/Downloads/rust-by-example)","reason":"compiler-message","target":{"kind":["bin"],"name":"update","src_path":"/home/w0rp/Downloads/rust-by-example/src/main.rs"}}', - \ '{"message":{"children":[],"code":null,"level":"error","message":"aborting due to previous error","rendered":null,"spans":[]},"package_id":"update 0.0.1 (path+file:///home/w0rp/Downloads/rust-by-example)","reason":"compiler-message","target":{"kind":["bin"],"name":"update","src_path":"/home/w0rp/Downloads/rust-by-example/src/main.rs"}}', - \ ]) diff --git a/test/test_sandbox_execution.vader b/test/test_sandbox_execution.vader new file mode 100644 index 0000000..7f4941f --- /dev/null +++ b/test/test_sandbox_execution.vader @@ -0,0 +1,63 @@ +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: + delfunction TestCallback + call ale#linter#Reset() + let g:ale_buffer_info = {} + unlet! b:in_sandbox + +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) + sandbox call ale#Lint() + +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#engine#RemoveManagedFiles(3) + + AssertEqual ['/tmp/foo'], g:ale_buffer_info[3].temporary_file_list + AssertEqual ['/tmp/bar'], g:ale_buffer_info[3].temporary_directory_list diff --git a/test/test_semver_utils.vader b/test/test_semver_utils.vader index 9730b74..30e9e81 100644 --- a/test/test_semver_utils.vader +++ b/test/test_semver_utils.vader @@ -1,16 +1,50 @@ -Execute(ParseSemver should return the correct results): - " We should be able to parse the semver string from flake8 - AssertEqual [3, 0, 4], ale#semver#Parse('3.0.4 (mccabe: 0.5.2, pyflakes: 1.2.3, pycodestyle: 2.0.0) CPython 2.7.12 on Linux') +After: + call ale#semver#ResetVersionCache() -Execute(GreaterOrEqual should compare triples correctly): - Assert ale#semver#GreaterOrEqual([3, 0, 4], [3, 0, 0]) - Assert ale#semver#GreaterOrEqual([3, 0, 0], [3, 0, 0]) - Assert ale#semver#GreaterOrEqual([3, 0, 0], [2, 0, 0]) - Assert ale#semver#GreaterOrEqual([3, 1, 0], [3, 1, 0]) - Assert ale#semver#GreaterOrEqual([3, 2, 0], [3, 1, 0]) - Assert ale#semver#GreaterOrEqual([3, 2, 2], [3, 1, 6]) - Assert ale#semver#GreaterOrEqual([3, 2, 5], [3, 2, 5]) - Assert ale#semver#GreaterOrEqual([3, 2, 6], [3, 2, 5]) - Assert !ale#semver#GreaterOrEqual([2, 9, 1], [3, 0, 0]) - Assert !ale#semver#GreaterOrEqual([3, 2, 3], [3, 3, 3]) - Assert !ale#semver#GreaterOrEqual([3, 3, 2], [3, 3, 3]) +Execute(GetVersion should return the version from the lines of output): + " We should be able to parse the semver string from flake8 + AssertEqual [3, 0, 4], ale#semver#GetVersion('dummy', [ + \ '3.0.4 (mccabe: 0.5.2, pyflakes: 1.2.3, pycodestyle: 2.0.0) CPython 2.7.12 on Linux', + \ '1.2.3', + \]) + +Execute(GetVersion should return an empty list when no vesrion can be found): + AssertEqual [], ale#semver#GetVersion('dummy', ['x']) + AssertEqual [], ale#semver#GetVersion('dummy', []) + +Execute(GetVersion should cache the version): + AssertEqual [], ale#semver#GetVersion('dummy', []) + AssertEqual [3, 4, 7], ale#semver#GetVersion('dummy', ['Version 3.4.7']) + AssertEqual [3, 4, 7], ale#semver#GetVersion('dummy', []) + +Execute(HasVersion should return 1 when the version has been cached): + call ale#semver#GetVersion('dummy', []) + AssertEqual 0, ale#semver#HasVersion('dummy') + call ale#semver#GetVersion('dummy', ['3.4.7']) + AssertEqual 1, ale#semver#HasVersion('dummy') + +Execute(GTE should compare triples correctly): + Assert ale#semver#GTE([3, 0, 4], [3, 0, 0]) + Assert ale#semver#GTE([3, 0, 0], [3, 0, 0]) + 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 0000000..f8fcb6a --- /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': '', + \}], getloclist(0) 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 0000000..10a44cc --- /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 [], getloclist(0) + 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 0000000..45dfa66 --- /dev/null +++ b/test/test_setting_problems_found_in_previous_buffers.vader @@ -0,0 +1,98 @@ +Before: + Save g:ale_buffer_info + Save &filetype + Save g:ale_set_lists_synchronously + + let g:ale_set_lists_synchronously = 1 + + " Set up items in other buffers which should set in this one. + let g:ale_buffer_info = {} + call ale#engine#InitBufferInfo(bufnr('') + 1) + let g:ale_buffer_info[bufnr('') + 1].loclist = + \ ale#engine#FixLocList(bufnr('') + 1, 'linter_one', [ + \ {'lnum': 1, 'filename': expand('%:p'), 'text': 'foo'}, + \ {'lnum': 2, 'filename': expand('%:p'), 'text': 'bar'}, + \ {'lnum': 2, 'text': 'ignore this one'}, + \ ]) + call ale#engine#InitBufferInfo(bufnr('') + 2) + let g:ale_buffer_info[bufnr('') + 2].loclist = + \ ale#engine#FixLocList(bufnr('') + 2, 'linter_one', [ + \ {'lnum': 1, 'filename': expand('%:p'), 'text': 'foo'}, + \ {'lnum': 3, 'filename': expand('%:p'), 'text': 'baz'}, + \ {'lnum': 5, 'text': 'ignore this one'}, + \ ]) + + call ale#linter#Define('foobar', { + \ 'name': 'linter_one', + \ 'callback': 'WhoCares', + \ 'executable': 'echo', + \ 'command': 'sleep 1000', + \ 'lint_file': 1, + \}) + +After: + call ale#engine#Cleanup(bufnr('')) + Restore + call ale#linter#Reset() + + " Items and markers, etc. + call setloclist(0, []) + call clearmatches() + sign unplace * + +Given foobar(A file with some lines): + foo + bar + baz + +Execute(Problems found from previously opened buffers should be set when linting for the first time): + call ale#engine#RunLinters(bufnr(''), ale#linter#Get(&filetype), 0) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'foo', + \ 'sign_id': 1000001, + \ }, + \ { + \ 'lnum': 2, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'bar', + \ 'sign_id': 1000002, + \ }, + \ { + \ 'lnum': 3, + \ 'bufnr': bufnr(''), + \ 'col': 0, + \ 'filename': expand('%:p'), + \ 'linter_name': 'linter_one', + \ 'nr': -1, + \ 'type': 'E', + \ 'vcol': 0, + \ 'text': 'baz', + \ 'sign_id': 1000003, + \ }, + \ ], + \ g:ale_buffer_info[bufnr('')].loclist + + AssertEqual + \ [ + \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'foo'}, + \ {'lnum': 2, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'bar'}, + \ {'lnum': 3, 'bufnr': bufnr(''), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'E', 'pattern': '', 'text': 'baz'}, + \ ], + \ getloclist(0) diff --git a/test/test_shell_detection.vader b/test/test_shell_detection.vader new file mode 100644 index 0000000..adb8d70 --- /dev/null +++ b/test/test_shell_detection.vader @@ -0,0 +1,103 @@ +Before: + runtime ale_linters/sh/shell.vim + runtime ale_linters/sh/shellcheck.vim + +After: + call ale#linter#Reset() + + unlet! b:is_bash + unlet! b:is_sh + unlet! b:is_kornshell + +Given(A file with a Bash hashbang): + #!/bin/bash + +Execute(/bin/bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with /bin/sh): + #!/usr/bin/env sh -eu --foobar + +Execute(/bin/sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with bash as an argument to env): + #!/usr/bin/env bash + +Execute(/usr/bin/env bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a tcsh hash bang and arguments): + #!/usr/bin/env tcsh -eu --foobar + +Execute(tcsh should be detected appropriately): + AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'tcsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'tcsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a zsh hash bang and arguments): + #!/usr/bin/env zsh -eu --foobar + +Execute(zsh should be detected appropriately): + AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'zsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'zsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a csh hash bang and arguments): + #!/usr/bin/env csh -eu --foobar + +Execute(zsh should be detected appropriately): + AssertEqual 'csh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'csh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'csh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a sh hash bang and arguments): + #!/usr/bin/env sh -eu --foobar + +Execute(sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file without a hashbang): + +Execute(The bash dialect should be used for shellcheck if b:is_bash is 1): + let b:is_bash = 1 + + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The sh dialect should be used for shellcheck if b:is_sh is 1): + let b:is_sh = 1 + + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The ksh dialect should be used for shellcheck if b:is_kornshell is 1): + let b:is_kornshell = 1 + + AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +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_linters#sh#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_linters#sh#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 0000000..23ebd92 --- /dev/null +++ b/test/test_should_do_nothing_conditions.vader @@ -0,0 +1,41 @@ +Before: + Save &l:statusline + + call ale#test#SetDirectory('/testplugin/test') + + let b:funky_command_created = 0 + + " 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: + call ale#test#RestoreDirectory() + + if b:funky_command_created + delcommand CtrlPFunky + let b:funky_command_created = 0 + endif + + unlet! b:funky_command_created + + Restore + +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('')) diff --git a/test/test_sign_parsing.vader b/test/test_sign_parsing.vader deleted file mode 100644 index f08024a..0000000 --- a/test/test_sign_parsing.vader +++ /dev/null @@ -1,11 +0,0 @@ -Execute (Parsing English signs should work): - AssertEqual [1000001], ale#sign#ParseSigns(['Signs for app.js:', ' line=9 id=1000001 name=ALEWarningSign']) - -Execute (Parsing Russian signs should work): - AssertEqual [1000001], ale#sign#ParseSigns([' строка=1 id=1000001 имя=ALEErrorSign']) - -Execute (Parsing Japanese signs should work): - AssertEqual [1000001], ale#sign#ParseSigns([' 行=1 識別子=1000001 名前=ALEWarningSign']) - -Execute (Parsing Spanish signs should work): - AssertEqual [1000001], ale#sign#ParseSigns([' línea=12 id=1000001 nombre=ALEWarningSign']) diff --git a/test/test_sign_placement.vader b/test/test_sign_placement.vader deleted file mode 100644 index dbec27e..0000000 --- a/test/test_sign_placement.vader +++ /dev/null @@ -1,68 +0,0 @@ -Before: - function! GenerateResults(buffer, output) - return [ - \ { - \ 'lnum': 1, - \ 'col': 1, - \ 'bufnr': bufnr('%'), - \ 'vcol': 0, - \ 'nr': -1, - \ 'type': 'E', - \ 'text': 'foo', - \ }, - \ { - \ 'lnum': 2, - \ 'col': 1, - \ 'bufnr': bufnr('%'), - \ 'vcol': 0, - \ 'nr': -1, - \ 'type': 'W', - \ 'text': 'bar', - \ }, - \ { - \ 'lnum': 3, - \ 'col': 1, - \ 'bufnr': bufnr('%'), - \ 'vcol': 0, - \ 'nr': -1, - \ 'type': 'E', - \ 'text': 'baz', - \ }, - \] - endfunction - - call ale#linter#Define('testft', { - \ 'name': 'x', - \ 'executable': 'echo', - \ 'command': 'echo', - \ 'callback': 'GenerateResults', - \}) - -After: - call ale#linter#Reset() - delfunction GenerateResults - unlet! g:output - -Given testft(A Javscript file with warnings/errors): - foo - bar - baz - -Execute: - call ale#Lint() - call ale#engine#WaitForJobs(2000) - - redir => g:output - :sign place - redir END - - AssertEqual - \ [ - \ ['1', '1000001', 'ALEErrorSign'], - \ ['2', '1000002', 'ALEWarningSign'], - \ ['3', '1000003', 'ALEErrorSign'], - \ ], - \ map( - \ split(g:output, '\n')[2:], - \ 'matchlist(v:val, "[^=]*=\\(\\d\\+\\)[^=]*=\\(\\d\\+\\).*\\(ALE.*\\)$")[1:3]' - \ ) diff --git a/test/test_sml_command.vader b/test/test_sml_command.vader new file mode 100644 index 0000000..d26f650 --- /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('smlnj/cm/foo.sml') + + AssertEqual + \ ale#path#Simplify(g:dir . '/smlnj/cm/sources.cm'), + \ ale#handlers#sml#GetCmFile(bufnr('%')) + +Execute(smlnj finds CM file by searching upwards): + call ale#test#SetFilename('smlnj/cm/path/to/bar.sml') + + AssertEqual + \ ale#path#Simplify(g:dir . '/smlnj/cm/sources.cm'), + \ ale#handlers#sml#GetCmFile(bufnr('%')) + +Execute(smlnj returns '' when no CM file found): + call ale#test#SetFilename('smlnj/file/qux.sml') + + AssertEqual '', ale#handlers#sml#GetCmFile(bufnr('%')) + +Execute(CM-project mode enabled when CM file found): + call ale#test#SetFilename('smlnj/cm/foo.sml') + + AssertEqual 'sml', ale#handlers#sml#GetExecutableSmlnjCm(bufnr('%')) + +Execute(single-file mode disabled when CM file found): + call ale#test#SetFilename('smlnj/cm/foo.sml') + + AssertEqual '', ale#handlers#sml#GetExecutableSmlnjFile(bufnr('%')) + +Execute(CM-project mode disabled when CM file not found): + call ale#test#SetFilename('smlnj/file/qux.sml') + + AssertEqual '', ale#handlers#sml#GetExecutableSmlnjCm(bufnr('%')) + +Execute(single-file mode enabled when CM file found): + call ale#test#SetFilename('smlnj/file/qux.sml') + + AssertEqual 'sml', ale#handlers#sml#GetExecutableSmlnjFile(bufnr('%')) diff --git a/test/test_statusline.vader b/test/test_statusline.vader index 05e6047..0ce1d36 100644 --- a/test/test_statusline.vader +++ b/test/test_statusline.vader @@ -1,60 +1,141 @@ Before: - let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + Save g:ale_statusline_format + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + + " A function for conveniently creating expected count objects. + function! Counts(data) abort + 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 After: - let g:ale_buffer_info = {} + Restore + + delfunction Counts Execute (Count should be 0 when data is empty): - let g:ale_buffer_info = {} - AssertEqual [0, 0], ale#statusline#Count(bufnr('%')) + AssertEqual Counts({}), ale#statusline#Count(bufnr('')) Execute (Count should read data from the cache): - let g:ale_buffer_info = {'44': {'count': [1, 2]}} - AssertEqual [1, 2], ale#statusline#Count(44) + let g:ale_buffer_info = {'44': {'count': Counts({'error': 1, 'warning': 2})}} + AssertEqual Counts({'error': 1, 'warning': 2}), ale#statusline#Count(44) Execute (The count should be correct after an update): let g:ale_buffer_info = {'44': {}} call ale#statusline#Update(44, []) - AssertEqual [0, 0], ale#statusline#Count(44) + AssertEqual Counts({}), ale#statusline#Count(44) Execute (Count should be match the loclist): let g:ale_buffer_info = { - \ bufnr('%'): { + \ bufnr(''): { \ 'loclist': [ - \ { - \ 'lnum': 1, - \ 'bufnr': 1, - \ 'vcol': 0, - \ 'linter_name': 'testlinter', - \ 'nr': -1, - \ 'type': 'E', - \ 'col': 1, - \ 'text': 'Test Error', - \ }, + \ {'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'}, \ ], \ }, \} - AssertEqual [1, 0], ale#statusline#Count(bufnr('%')) + AssertEqual { + \ 'error': 1, + \ 'style_error': 2, + \ 'warning': 3, + \ 'style_warning': 4, + \ 'info': 5, + \ '0': 3, + \ '1': 12, + \ 'total': 15, + \}, ale#statusline#Count(bufnr('')) -Execute (Output should be empty for non-existant buffer): - AssertEqual [0, 0], ale#statusline#Count(9001) +Execute (Output should be empty for non-existent buffer): + AssertEqual Counts({}), ale#statusline#Count(9001) -Execute (Statusline is formatted to the users preference for just errors): - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), [{'type': 'E'}, {'type': 'E'}]) +Execute (Status() should return just errors for the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \]) AssertEqual '2E', ale#statusline#Status() -Execute (Statusline is formatted to the users preference for just warnings): - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), [{'type': 'W'}, {'type': 'W'}, {'type': 'W'}]) +Execute (Status() should return just warnings for the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \]) AssertEqual '3W', ale#statusline#Status() -Execute (Statusline is formatted to the users preference for errors and warnings): - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), [{'type': 'E'}, {'type': 'W'}, {'type': 'W'}]) - AssertEqual '1E 2W', ale#statusline#Status() +Execute (Status() should return errors and warnings for the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \]) + AssertEqual '2E 3W', ale#statusline#Status() -Execute (Statusline is formatted to the users preference for no errors or warnings): - let g:ale_buffer_info = {bufnr('%'): {}} - call ale#statusline#Update(bufnr('%'), []) +Execute (Status() should return the custom 'OK' string with the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), []) AssertEqual 'OKIE', ale#statusline#Status() + +Execute(ale#statusline#Update shouldn't blow up when globals are undefined): + unlet! g:ale_statusline_format + call ale#statusline#Update(1, []) + +Execute(ale#statusline#Count should return 0 counts when globals are undefined): + unlet! g:ale_statusline_format + AssertEqual Counts({}), ale#statusline#Count(1) + +Execute(ale#statusline#Status should return 'OK' when globals are undefined): + unlet! g:ale_statusline_format + AssertEqual 'OK', ale#statusline#Status() diff --git a/test/test_stdin_wrapper_path_can_be_found.vader b/test/test_stdin_wrapper_path_can_be_found.vader deleted file mode 100644 index cf13bca..0000000 --- a/test/test_stdin_wrapper_path_can_be_found.vader +++ /dev/null @@ -1,4 +0,0 @@ -Execute(We should be able to find the stdin-wrapper script): - silent! cd /testplugin/test - - AssertEqual expand('%:p:h:h') . '/stdin-wrapper', ale#util#stdin_wrapper diff --git a/test/test_temporary_file_management.vader b/test/test_temporary_file_management.vader new file mode 100644 index 0000000..ae2bf25 --- /dev/null +++ b/test/test_temporary_file_management.vader @@ -0,0 +1,113 @@ +Before: + 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#engine#ManageFile(a:buffer, g:filename) + + " We are registering this directory appropriately, so we should delete + " the whole thing. + let g:directory = tempname() + call mkdir(g:directory) + call writefile(['foo'], g:directory . '/bar') + call ale#engine#ManageDirectory(a:buffer, g:directory) + + " We are registering this directory as temporary file, so we + " shouldn't delete it. + let g:preserved_directory = tempname() + call mkdir(g:preserved_directory) + call writefile(['foo'], g:preserved_directory . '/bar') + call ale#engine#ManageFile(a:buffer, g:preserved_directory) + + 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_callback': 'TestCommandCallback', + \}) + +After: + 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() + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALE should delete managed files/directories appropriately after linting): + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#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 even if no command is run): + AssertEqual 'foobar', &filetype + + let g:command = '' + + call ale#Lint() + call ale#engine#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#engine#CreateDirectory()): + call ale#engine#InitBufferInfo(bufnr('%')) + + let b:dir = ale#engine#CreateDirectory(bufnr('%')) + let b:dir2 = ale#engine#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' diff --git a/test/test_tflint_config_detection.vader b/test/test_tflint_config_detection.vader new file mode 100644 index 0000000..3500869 --- /dev/null +++ b/test/test_tflint_config_detection.vader @@ -0,0 +1,18 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/terraform/tflint.vim + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(adjacent config file should be found): + call ale#test#SetFilename('tflint-test-files/foo/bar.tf') + AssertEqual + \ ( + \ ale#Escape('tflint') + \ . ' --config ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/tflint-test-files/foo/.tflint.hcl')) + \ . ' -f json %t' + \ ), + \ ale_linters#terraform#tflint#GetCommand(bufnr('')) diff --git a/test/test_tmpdir_init.vader b/test/test_tmpdir_init.vader new file mode 100644 index 0000000..23326dc --- /dev/null +++ b/test/test_tmpdir_init.vader @@ -0,0 +1,4 @@ +Execute($TMPDIR should be set to a default value if unset): + if has('unix') + AssertEqual '/tmp', $TMPDIR + endif diff --git a/test/test_verilog_verilator_options.vader b/test/test_verilog_verilator_options.vader new file mode 100644 index 0000000..561786e --- /dev/null +++ b/test/test_verilog_verilator_options.vader @@ -0,0 +1,25 @@ +Before: + Save g:ale_verilog_verilator_options + let g:ale_verilog_verilator_options = '' + +After: + Restore + call ale#linter#Reset() + +Execute(Set Verilog Verilator linter additional options to `-sv --default-language "1800-2012"`): + runtime! ale_linters/verilog/verilator.vim + + " Additional args for the linter + let g:ale_verilog_verilator_options = '-sv --default-language "1800-2012"' + + call ale#Lint() + + let g:run_cmd = ale_linters#verilog#verilator#GetCommand(bufnr('')) + let g:matched = match(g:run_cmd, '\s' . g:ale_verilog_verilator_options . '\s') + + " match returns -1 if not found + AssertNotEqual + \ g:matched , + \ -1 , + \ 'Additionnal arguments not found in the run command' + diff --git a/test/test_vim8_processid_parsing.vader b/test/test_vim8_processid_parsing.vader new file mode 100644 index 0000000..26416b1 --- /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_windows_escaping.vader b/test/test_windows_escaping.vader new file mode 100644 index 0000000..22cad88 --- /dev/null +++ b/test/test_windows_escaping.vader @@ -0,0 +1,42 @@ +Before: + Save &shell + let &shell = 'cmd.exe' + +After: + Restore + +Execute(ale#Escape for cmd.exe should allow not escape paths without special characters): + AssertEqual 'C:', ale#Escape('C:') + AssertEqual 'C:\', ale#Escape('C:\') + AssertEqual 'python', ale#Escape('python') + AssertEqual 'C:\foo\bar', ale#Escape('C:\foo\bar') + AssertEqual '/bar/baz', ale#Escape('/bar/baz') + AssertEqual 'nul', ale#Escape('nul') + AssertEqual '''foo''', ale#Escape('''foo''') + +Execute(ale#Escape for cmd.exe should escape Windows paths with spaces appropriately): + AssertEqual '"C:\foo bar\baz"', ale#Escape('C:\foo bar\baz') + AssertEqual '"^foo bar^"', ale#Escape('^foo bar^') + AssertEqual '"&foo bar&"', ale#Escape('&foo bar&') + AssertEqual '"|foo bar|"', ale#Escape('|foo bar|') + AssertEqual '"foo bar>"', ale#Escape('>foo bar>') + AssertEqual '"^foo bar^"', ale#Escape('^foo bar^') + AssertEqual '"''foo'' ''bar''"', ale#Escape('''foo'' ''bar''') + +Execute(ale#Escape for cmd.exe should use caret escapes on special characters): + AssertEqual '^^foo^^', ale#Escape('^foo^') + AssertEqual '^&foo^&', ale#Escape('&foo&') + AssertEqual '^|foo^|', ale#Escape('|foo|') + AssertEqual '^foo^>', ale#Escape('>foo>') + AssertEqual '^^foo^^', ale#Escape('^foo^') + AssertEqual '''foo''^^''bar''', ale#Escape('''foo''^''bar''') + +Execute(ale#Escape for cmd.exe should escape percent characters): + AssertEqual '%%foo%%', ale#Escape('%foo%') + AssertEqual 'C:\foo%%\bar\baz%%', ale#Escape('C:\foo%\bar\baz%') + AssertEqual '"C:\foo bar%%\baz%%"', ale#Escape('C:\foo bar%\baz%') + AssertEqual '^^%%foo%%', ale#Escape('^%foo%') + AssertEqual '"^%%foo%% %%bar%%"', ale#Escape('^%foo% %bar%') + AssertEqual '"^%%foo%% %%bar%% """""', ale#Escape('^%foo% %bar% ""') diff --git a/test/test_wrap_comand.vader b/test/test_wrap_comand.vader new file mode 100644 index 0000000..7ddb06a --- /dev/null +++ b/test/test_wrap_comand.vader @@ -0,0 +1,48 @@ +Before: + Save g:ale_command_wrapper + + let g:ale_command_wrapper = '' + + function! TestCommand(expected_part, input) abort + let l:expected = has('win32') + \ ? 'cmd /s/c "' . a:expected_part . '"' + \ : split(&shell) + split(&shellcmdflag) + [a:expected_part] + + AssertEqual l:expected, ale#job#PrepareCommand(bufnr(''), a:input) + endfunction + +After: + Restore + + unlet! b:ale_command_wrapper + + delfunction TestCommand + +Execute(The command wrapper should work with a nice command): + let b:ale_command_wrapper = 'nice -n 5' + + call TestCommand('nice -n 5 foo bar', 'foo bar') + +Execute(The command wrapper should work with a nice command with an explicit marker): + let b:ale_command_wrapper = 'nice -n 5 %*' + + call TestCommand('nice -n 5 foo bar', 'foo bar') + +Execute(Wrappers with spread arguments in the middle should be suppported): + let b:ale_command_wrapper = 'wrap %* --' + + call TestCommand('wrap foo bar --', 'foo bar') + +Execute(Wrappers with the command as one argument should be supported): + let b:ale_command_wrapper = 'wrap -c %@ -x' + + call TestCommand('wrap -c ' . ale#Escape('foo bar') . ' -x', 'foo bar') + +Execute(&& and ; should be moved to the front): + let b:ale_command_wrapper = 'wrap -c %@ -x' + + call TestCommand('foo && bar; wrap -c ' . ale#Escape('baz') . ' -x', 'foo && bar;baz') + + let b:ale_command_wrapper = 'nice -n 5' + + call TestCommand('foo && bar; nice -n 5 baz -z', 'foo && bar;baz -z') diff --git a/test/test_writefile_function.vader b/test/test_writefile_function.vader new file mode 100644 index 0000000..4e4aab5 --- /dev/null +++ b/test/test_writefile_function.vader @@ -0,0 +1,48 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + noautocmd :e! ++ff=unix + setlocal buftype=nofile + + if filereadable('.newline-test') + call delete('.newline-test') + endif + + call ale#test#RestoreDirectory() + +Given(A file with Windows line ending characters): + first + second + third + +Execute(Carriage returns should be included for ale#util#Writefile): + call ale#test#SetFilename('.newline-test') + + setlocal buftype= + noautocmd :w + noautocmd :e! ++ff=dos + + call ale#util#Writefile(bufnr(''), getline(1, '$'), '.newline-test') + + AssertEqual + \ ["first\r", "second\r", "third\r", ''], + \ readfile('.newline-test', 'b') + \ +Given(A file with Unix line ending characters): + first + second + third + +Execute(Unix file lines should be written as normal): + call ale#test#SetFilename('.newline-test') + + setlocal buftype= + noautocmd :w + noautocmd :e! ++ff=unix + + call ale#util#Writefile(bufnr(''), getline(1, '$'), '.newline-test') + + AssertEqual + \ ['first', 'second', 'third', ''], + \ readfile('.newline-test', 'b') diff --git a/test/tflint-test-files/foo/.tflint.hcl b/test/tflint-test-files/foo/.tflint.hcl new file mode 100644 index 0000000..e69de29 diff --git a/test/tflint-test-files/foo/bar.tf b/test/tflint-test-files/foo/bar.tf new file mode 100644 index 0000000..e69de29 diff --git a/test/util/test_cd_string_commands.vader b/test/util/test_cd_string_commands.vader new file mode 100644 index 0000000..5f0e92f --- /dev/null +++ b/test/util/test_cd_string_commands.vader @@ -0,0 +1,18 @@ +Before: + silent! cd /testplugin/test/util + let g:dir = getcwd() + +After: + silent execute 'cd ' . fnameescape(g:dir) + unlet! g:dir + +Execute(CdString should output the correct command string): + " We will check that escaping is done correctly for each platform. + AssertEqual + \ has('unix') ? 'cd ''/foo bar/baz'' && ' : 'cd "/foo bar/baz" && ', + \ ale#path#CdString('/foo bar/baz') + +Execute(BufferCdString should output the correct command string): + call ale#test#SetFilename('foo.txt') + + AssertEqual 'cd ' . ale#Escape(g:dir) . ' && ', ale#path#BufferCdString(bufnr('')) diff --git a/test/vimrc b/test/vimrc index 77be5e2..970e20e 100644 --- a/test/vimrc +++ b/test/vimrc @@ -1,12 +1,25 @@ " vint: -ProhibitSetNoCompatible +" Make most tests just set lists synchronously when run in Docker, etc. +let g:ale_set_lists_synchronously = 1 + " Load builtin plugins " We need this because run_vim.sh sets -i NONE -set runtimepath=/home/vim,$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after,/testplugin,/vader +if has('win32') + set runtimepath=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after,C:\vader,C:\testplugin +else + set runtimepath=/home/vim,$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after,/testplugin,/vader +endif " The following is just an example filetype plugin indent on syntax on + +if !has('win32') + set shell=/bin/sh + set shellcmdflag=-c +endif + set nocompatible set tabstop=4 set softtabstop=4 @@ -18,5 +31,11 @@ set foldmethod=syntax set foldlevelstart=10 set foldnestmax=10 set ttimeoutlen=0 +" The encoding must be explicitly set for tests for Windows. +execute 'set encoding=utf-8' let g:mapleader=',' + +" Clear the TMPDIR value for tests. +" The plugin should set this to /tmp by default, which we will test. +let $TMPDIR = ''