commit 3761b4b6da50fee4bdfedd7ad5bcda91d2354278 Author: Buddy Sandidge Date: Wed Sep 27 16:46:41 2017 -0700 Squashed 'vim/bundle/ale/' content from commit b8dcdc984 git-subtree-dir: vim/bundle/ale git-subtree-split: b8dcdc984bfb1e8cc1adbb77c47c650db2d3caf0 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4da669b --- /dev/null +++ b/.gitattributes @@ -0,0 +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 +/custom-checks export-ignore +/img export-ignore +/run-tests export-ignore +/test export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30ab9ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/init.vim +/doc/tags +.* +*.obj +tags diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2423732 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +--- +sudo: required +services: + - docker +language: python +script: | + ./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 new file mode 100644 index 0000000..a32a596 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,173 @@ +# Contributing to ALE + +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) +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 + +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 +install this plugin (ALE) and install [Vint](https://github.com/Kuniwak/vint), it +will check your code while you type. + + + +## 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 +issue has already been reported, but you have some new insight, please add +a comment to the existing issue. + +Please read the FAQ in the README before creating any issues. A feature +you desire may already exist and be documented, or the FAQ might explain +how to solve a problem you have already. + +Please try and describe any issues reported with as much detail as you can +provide about your Vim version, the linter you were trying to run, your +operating system, or any other information you think might be helpful. + +Please describe your issue in clear, grammatically correct, and easy to +understand English. You are more likely to see an issue resolved if others +can understand you. + + + +## 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 +VIM files. + +```vim +" Author: John Smith +" Description: This file adds support for awesomelinter for the best language ever. +``` + +If you want to credit multiple authors, you can comma separate them. + +```vim +" Author: John Smith , Jane Doe +``` + + + +### 3.i. Adding a New Linter + +If you add a new linter, look for existing handlers first in the +[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](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 +itself are present in the table in the [README.md](README.md) file and in the +Vim [help file](doc/ale.txt). The programs and linters should be sorted +alphabetically in the table and list. + + + +### 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). +Follow the format of other options in each. Global options should appear in the +README file, and in the relevant section in the help file. Options specific +to a particular linter should appear in the section for that linter. + +Linter options for customizing general argument lists should be named +`g:ale___options`, so that all linters can have similar +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 guidlines 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 adduction, 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 new file mode 100644 index 0000000..eba9a1f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM tweekmonster/vim-testbed:latest + +RUN install_vim -tag v8.0.0027 -build \ + -tag neovim:v0.1.7 -build + +ENV PACKAGES="\ + bash \ + git \ + 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 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..45d5350 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,8 @@ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..650050f --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2016-2017, w0rp +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/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 new file mode 100644 index 0000000..cbfef30 --- /dev/null +++ b/README.md @@ -0,0 +1,559 @@ +# Asynchronous Lint Engine [![Build Status](https://travis-ci.org/w0rp/ale.svg?branch=master)](https://travis-ci.org/w0rp/ale) + +![ALE Logo by Mark Grealish - https://www.bhalash.com/](img/logo.jpg?raw=true) + +ALE (Asynchronous Lint Engine) is a plugin for providing linting in NeoVim +and Vim 8 while you edit your text files. + +![linting example](img/example.gif?raw=true) + +ALE makes use of NeoVim and Vim 8 job control functions and timers to +run linters on the contents of text buffers and return errors as +text is changed in Vim. This allows for displaying warnings and +errors in files being edited in Vim before files have been saved +back to a filesystem. + +In other words, this plugin allows you to lint while you type. + +In addition to linting support, ALE offers some support for fixing code with +formatting tools, and completion via Language Server Protocol servers, or +servers with similar enough protocols, like `tsserver`. + +## Table of Contents + +1. [Supported Languages and Tools](#supported-languages) +2. [Usage](#usage) + 1. [Linting](#usage-linting) + 2. [Fixing](#usage-fixing) + 3. [Completion](#usage-completion) +3. [Installation](#installation) + 1. [Installation with Vim package management](#standard-installation) + 2. [Installation with Pathogen](#installation-with-pathogen) + 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 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) + 10. [How can I check JSX files with both stylelint and eslint?](#faq-jsx-stylelint-eslint) + 11. [Will this plugin eat all of my laptop battery power?](#faq-my-battery-is-sad) + + + +## 1. Supported Languages and Tools + +This plugin supports the following languages and tools. All available +tools will be run in combination, so they can be complementary. + + + +| Language | Tools | +| -------- | ----- | +| ASM | [gcc](https://gcc.gnu.org) | +| Ansible | [ansible-lint](https://github.com/willthames/ansible-lint) | +| AsciiDoc | [proselint](http://proselint.com/)| +| Awk | [gawk](https://www.gnu.org/software/gawk/)| +| Bash | [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/) | +| Bourne Shell | [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/) | +| C | [cppcheck](http://cppcheck.sourceforge.net), [gcc](https://gcc.gnu.org/), [clang](http://clang.llvm.org/), [clang-format](https://clang.llvm.org/docs/ClangFormat.html)| +| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html), [clangtidy](http://clang.llvm.org/extra/clang-tidy/), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [gcc](https://gcc.gnu.org/), [clang-format](https://clang.llvm.org/docs/ClangFormat.html)| +| C# | [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) | +| Chef | [foodcritic](http://www.foodcritic.io/) | +| CMake | [cmakelint](https://github.com/richq/cmake-lint) | +| CoffeeScript | [coffee](http://coffeescript.org/), [coffeelint](https://www.npmjs.com/package/coffeelint) | +| Crystal | [crystal](https://crystal-lang.org/) | +| CSS | [csslint](http://csslint.net/), [stylelint](https://github.com/stylelint/stylelint) | +| Cython (pyrex filetype) | [cython](http://cython.org/) | +| D | [dmd](https://dlang.org/dmd-linux.html) | +| Dart | [dartanalyzer](https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli) | +| Dockerfile | [hadolint](https://github.com/lukasmartinelli/hadolint) | +| Elixir | [credo](https://github.com/rrrene/credo), [dogma](https://github.com/lpil/dogma) | +| Elm | [elm-make](https://github.com/elm-lang/elm-make) | +| Erb | [erb](https://github.com/jeremyevans/erubi), [erubis](https://github.com/kwatch/erubis) | +| Erlang | [erlc](http://erlang.org/doc/man/erlc.html), [SyntaxErl](https://github.com/ten0s/syntaxerl) | +| Fortran | [gcc](https://gcc.gnu.org/) | +| FusionScript | [fusion-lint](https://github.com/RyanSquared/fusionscript) | +| Go | [gofmt -e](https://golang.org/cmd/gofmt/), [go vet](https://golang.org/cmd/vet/), [golint](https://godoc.org/github.com/golang/lint), [gometalinter](https://github.com/alecthomas/gometalinter), [go build](https://golang.org/cmd/go/), [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple), [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) | +| GraphQL | [gqlint](https://github.com/happylinks/gqlint) | +| Haml | [haml-lint](https://github.com/brigade/haml-lint) +| Handlebars | [ember-template-lint](https://github.com/rwjblue/ember-template-lint) | +| Haskell | [ghc](https://www.haskell.org/ghc/), [stack-ghc](https://haskellstack.org/), [stack-build](https://haskellstack.org/), [ghc-mod](https://github.com/DanielG/ghc-mod), [stack-ghc-mod](https://github.com/DanielG/ghc-mod), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools) | +| HTML | [HTMLHint](http://htmlhint.com/), [proselint](http://proselint.com/), [tidy](http://www.html-tidy.org/) | +| Idris | [idris](http://www.idris-lang.org/) | +| Java | [checkstyle](http://checkstyle.sourceforge.net), [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html) | +| JavaScript | [eslint](http://eslint.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [flow](https://flowtype.org/), [standard](http://standardjs.com/), [prettier](https://github.com/prettier/prettier) (and `prettier-eslint`, `prettier-standard`), [xo](https://github.com/sindresorhus/xo) +| JSON | [jsonlint](http://zaa.ch/jsonlint/) | +| Kotlin | [kotlinc](https://kotlinlang.org), [ktlint](https://ktlint.github.io) see `:help ale-integration-kotlin` for configuration instructions +| LaTeX | [chktex](http://www.nongnu.org/chktex/), [lacheck](https://www.ctan.org/pkg/lacheck), [proselint](http://proselint.com/) | +| Lua | [luacheck](https://github.com/mpeterv/luacheck) | +| Markdown | [mdl](https://github.com/mivok/markdownlint), [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale) | +| MATLAB | [mlint](https://www.mathworks.com/help/matlab/ref/mlint.html) | +| Nim | [nim](https://nim-lang.org/docs/nimc.html) | +| nix | [nix-instantiate](http://nixos.org/nix/manual/#sec-nix-instantiate) | +| nroff | [proselint](http://proselint.com/)| +| Objective-C | [clang](http://clang.llvm.org/) | +| Objective-C++ | [clang](http://clang.llvm.org/) | +| OCaml | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-integration-ocaml-merlin` for configuration instructions +| Perl | [perl -c](https://perl.org/), [perl-critic](https://metacpan.org/pod/Perl::Critic) | +| PHP | [hack](http://hacklang.org/), [langserver](https://github.com/felixfbecker/php-language-server), [php -l](https://secure.php.net/), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer), [phpmd](https://phpmd.org), [phpstan](https://github.com/phpstan/phpstan), [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer) | +| Pod | [proselint](http://proselint.com/)| +| Pug | [pug-lint](https://github.com/pugjs/pug-lint) | +| Puppet | [puppet](https://puppet.com), [puppet-lint](https://puppet-lint.com) | +| Python | [autopep8](https://github.com/hhatto/autopep8), [flake8](http://flake8.pycqa.org/en/latest/), [isort](https://github.com/timothycrosley/isort), [mypy](http://mypy-lang.org/), [pycodestyle](https://github.com/PyCQA/pycodestyle), [pylint](https://www.pylint.org/), [yapf](https://github.com/google/yapf) | +| R | [lintr](https://github.com/jimhester/lintr) | +| ReasonML | [merlin](https://github.com/the-lambda-church/merlin) see `:help ale-integration-reason-merlin` for configuration instructions +| reStructuredText | [proselint](http://proselint.com/)| +| 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/) | +| SASS | [sass-lint](https://www.npmjs.com/package/sass-lint), [stylelint](https://github.com/stylelint/stylelint) | +| SCSS | [sass-lint](https://www.npmjs.com/package/sass-lint), [scss-lint](https://github.com/brigade/scss-lint), [stylelint](https://github.com/stylelint/stylelint) | +| Scala | [scalac](http://scala-lang.org), [scalastyle](http://www.scalastyle.org) | +| Slim | [slim-lint](https://github.com/sds/slim-lint) +| SML | [smlnj](http://www.smlnj.org/) | +| 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)| +| Texinfo | [proselint](http://proselint.com/)| +| Text^ | [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale) | +| TypeScript | [eslint](http://eslint.org/), [tslint](https://github.com/palantir/tslint), tsserver, typecheck | +| Verilog | [iverilog](https://github.com/steveicarus/iverilog), [verilator](http://www.veripool.org/projects/verilator/wiki/Intro) | +| Vim | [vint](https://github.com/Kuniwak/vint) | +| Vim help^ | [proselint](http://proselint.com/)| +| XHTML | [proselint](http://proselint.com/)| +| XML | [xmllint](http://xmlsoft.org/xmllint.html/)| +| YAML | [swaglint](https://github.com/byCedric/swaglint), [yamllint](https://yamllint.readthedocs.io/) | + +* *^ No linters for text or Vim help filetypes are enabled by default.* + + + +## 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 +programs for checking the syntax and semantics of your programs. By default, +linters will be re-run in the background to check your syntax when you open +new buffers or as you make edits to your files. + +The behaviour of linting can be configured with a variety of options, +documented in [the Vim help file](doc/ale.txt). For more information on the +options ALE offers, consult `:help ale-options` for global options and `:help +ale-linter-options` for options specified to particular linters. + + + +### 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. + + + +## 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%\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.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 +you have the line `execute pathogen#infect()` in your `~/.vimrc` file. +You can run the following commands in your terminal to do so: + +```bash +cd ~/.vim/bundle +git clone https://github.com/w0rp/ale.git +``` + + + +### 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. + +```vim +Plugin 'w0rp/ale' +``` + +See the Vundle documentation for more information. + + + +## 4. Contributing + +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. + +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. + +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). + + + +## 5. FAQ + + + +### 5.i. How do I disable particular linters? + +By default, all available tools for all supported languages will be run. +If you want to only select a subset of the tools, simply create a +`g:ale_linters` dictionary in your vimrc file mapping filetypes +to lists of linters to run. + +```vim +let g:ale_linters = { +\ 'javascript': ['eslint'], +\} +``` + +For all languages unspecified in the dictionary, all possible linters will +be run for those languages, just as when the dictionary is not defined. +Running many linters should not typically obstruct editing in Vim, +as they will all be executed in separate processes simultaneously. + +This plugin will look for linters in the [`ale_linters`](ale_linters) directory. +Each directory within corresponds to a particular filetype in Vim, and each file +in each directory corresponds to the name of a particular linter. + + + +### 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 + +```vim +let g:ale_sign_column_always = 1 +``` + + + +### 5.iii. How can I change the signs ALE uses? + +Use these options to specify what text should be used for signs: + +```vim +let g:ale_sign_error = '>>' +let g:ale_sign_warning = '--' +``` + +ALE sets some background colors automatically for warnings and errors +in the sign gutter, with the names `ALEErrorSign` and `ALEWarningSign`. +These colors can be customised, or even removed completely: + +```vim +highlight clear ALEErrorSign +highlight clear ALEWarningSign +``` + + + +### 5.iv. How can I show errors or warnings in my statusline? + +[vim-airline](https://github.com/vim-airline/vim-airline) integrates with ALE +for displaying error information in the status bar. If you want to see the +status for ALE in a nice format, it is recommended to use vim-airline with ALE. +The airline extension can be enabled by adding the following to your vimrc: + +```vim +" Set this. Airline will handle the rest. +let g:airline#extensions#ale#enabled = 1 +``` + +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`. + +Say you want to display all errors as one figure, and all non-errors as another +figure. You can do the following: + +```vim +function! LinterStatus() abort + let l:counts = ale#statusline#Count(bufnr('')) + + let l:all_errors = l:counts.error + l:counts.style_error + let l:all_non_errors = l:counts.total - l:all_errors + + return l:counts.total == 0 ? 'OK' : printf( + \ '%dW %dE', + \ all_non_errors, + \ all_errors + \) +endfunction + +set statusline=%{LinterStatus()} +``` + +See `:help ale#statusline#Count()` for more information. + + + +### 5.v. 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 +- `g:ale_echo_msg_error_str` is the string used for error severity. +- `g:ale_echo_msg_warning_str` is the string used for warning severity. + +So for example this: + +```vim +let g:ale_echo_msg_error_str = 'E' +let g:ale_echo_msg_warning_str = 'W' +let g:ale_echo_msg_format = '[%linter%] %s [%severity%]' +``` + +Will give you: + +![Echoed message](img/echo.png) + + + +### 5.vi. How can I execute some code when ALE 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. + +```vim +augroup YourGroup + autocmd! + autocmd User ALELint call YourFunction() +augroup END +``` + + + +### 5.vii. How can I navigate between errors quickly? + +ALE offers some commands with `` keybinds for moving between warnings and +errors quickly. You can map the keys Ctrl+j and Ctrl+k to moving between errors +for example: + +```vim +nmap (ale_previous_wrap) +nmap (ale_next_wrap) +``` + +For more information, consult the online documentation with +`:help ale-navigation-commands`. + + + +### 5.viii. How can I run linters only when I save files? + +ALE offers an option `g:ale_lint_on_save` for enabling running the linters +when files are saved. This option is enabled by default. If you only +wish to run linters when files are saved, you can turn the other +options off. + +```vim +" Write this in your vimrc file +let g:ale_lint_on_text_changed = 'never' +" 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`. + + + +### 5.ix. How can I use the quickfix list instead of the loclist? + +The quickfix list can be enabled by turning the `g:ale_set_quickfix` +option on. If you wish to also disable the loclist, you can disable +the `g:ale_set_loclist` option. + +```vim +" Write this in your vimrc file +let g:ale_set_loclist = 0 +let g:ale_set_quickfix = 1 +``` + +If you wish to show Vim windows for the loclist or quickfix items +when a file contains warnings or errors, `g:ale_open_list` can be +set to `1`. `g:ale_keep_list_window_open` can be set to `1` +if you wish to keep the window open even after errors disappear. + +```vim +let g:ale_open_list = 1 +" Set this if you want to. +" This can be useful if you are combining ALE with +" some other plugin which sets quickfix errors, etc. +let g:ale_keep_list_window_open = 1 +``` + + + +### 5.x. 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.xi. 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`. 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 new file mode 100644 index 0000000..7d68cde --- /dev/null +++ b/ale_linters/ansible/ansible_lint.vim @@ -0,0 +1,48 @@ +" 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# 'EANSIBLE002' + \&& !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:code . ': ' . l:match[5], + \ '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/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/asm/gcc.vim b/ale_linters/asm/gcc.vim new file mode 100644 index 0000000..39b1f7c --- /dev/null +++ b/ale_linters/asm/gcc.vim @@ -0,0 +1,33 @@ +" Author: Lucas Kolstad +" Description: gcc linter for asm files + +let g:ale_asm_gcc_options = get(g:, 'ale_asm_gcc_options', '-Wall') + +function! ale_linters#asm#gcc#GetCommand(buffer) abort + return 'gcc -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': 'gcc', +\ '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 new file mode 100644 index 0000000..7680305 --- /dev/null +++ b/ale_linters/c/clang.vim @@ -0,0 +1,29 @@ +" Author: Masahiro H https://github.com/mshr-h +" Description: clang linter for c files + +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_callback': 'ale_linters#c#clang#GetExecutable', +\ 'command_callback': 'ale_linters#c#clang#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/c/cppcheck.vim b/ale_linters/c/cppcheck.vim new file mode 100644 index 0000000..4db93f7 --- /dev/null +++ b/ale_linters/c/cppcheck.vim @@ -0,0 +1,39 @@ +" Author: Bart Libert +" Description: cppcheck linter for c files + +call ale#Set('c_cppcheck_executable', 'cppcheck') +call ale#Set('c_cppcheck_options', '--enable=style') + +function! ale_linters#c#cppcheck#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_callback': 'ale_linters#c#cppcheck#GetExecutable', +\ 'command_callback': 'ale_linters#c#cppcheck#GetCommand', +\ 'callback': 'ale#handlers#cppcheck#HandleCppCheckFormat', +\}) diff --git a/ale_linters/c/gcc.vim b/ale_linters/c/gcc.vim new file mode 100644 index 0000000..4b241e3 --- /dev/null +++ b/ale_linters/c/gcc.vim @@ -0,0 +1,29 @@ +" Author: w0rp +" Description: gcc linter for c files + +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 + 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_callback': 'ale_linters#c#gcc#GetExecutable', +\ 'command_callback': 'ale_linters#c#gcc#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/chef/foodcritic.vim b/ale_linters/chef/foodcritic.vim new file mode 100644 index 0000000..079e304 --- /dev/null +++ b/ale_linters/chef/foodcritic.vim @@ -0,0 +1,42 @@ +" Author: Edward Larkey +" Author: Jose Junior +" Description: This file adds the foodcritic linter for Chef files. + +" Support options! +let g:ale_chef_foodcritic_options = get(g:, 'ale_chef_foodcritic_options', '') +let g:ale_chef_foodcritic_executable = get(g:, 'ale_chef_foodcritic_executable', 'foodcritic') + +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:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:text = l:match[1] + + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'text': l:text, + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +function! ale_linters#chef#foodcritic#GetCommand(buffer) abort + return printf('%s %s %%t', + \ ale#Var(a:buffer, 'chef_foodcritic_executable'), + \ escape(ale#Var(a:buffer, 'chef_foodcritic_options'), '~') + \) +endfunction + + +call ale#linter#Define('chef', { +\ 'name': 'foodcritic', +\ 'executable': 'foodcritic', +\ 'command_callback': 'ale_linters#chef#foodcritic#GetCommand', +\ 'callback': 'ale_linters#chef#foodcritic#Handle', +\}) 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 new file mode 100644 index 0000000..f253928 --- /dev/null +++ b/ale_linters/coffee/coffee.vim @@ -0,0 +1,23 @@ +" Author: KabbAmine - https://github.com/KabbAmine +" Description: Coffee for checking coffee files + +function! ale_linters#coffee#coffee#GetExecutable(buffer) abort + return ale#path#ResolveLocalPath( + \ a:buffer, + \ 'node_modules/.bin/coffee', + \ 'coffee' + \) +endfunction + +function! ale_linters#coffee#coffee#GetCommand(buffer) abort + return ale_linters#coffee#coffee#GetExecutable(a:buffer) + \ . ' -cp -s' +endfunction + +call ale#linter#Define('coffee', { +\ 'name': 'coffee', +\ 'executable_callback': 'ale_linters#coffee#coffee#GetExecutable', +\ 'command_callback': 'ale_linters#coffee#coffee#GetCommand', +\ 'output_stream': 'stderr', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/coffee/coffeelint.vim b/ale_linters/coffee/coffeelint.vim new file mode 100644 index 0000000..6d3df35 --- /dev/null +++ b/ale_linters/coffee/coffeelint.vim @@ -0,0 +1,43 @@ +" Author: Prashanth Chandra https://github.com/prashcr +" Description: coffeelint linter for coffeescript files + +function! ale_linters#coffee#coffeelint#GetExecutable(buffer) abort + return ale#path#ResolveLocalPath( + \ a:buffer, + \ 'node_modules/.bin/coffeelint', + \ 'coffeelint' + \) +endfunction + +function! ale_linters#coffee#coffeelint#GetCommand(buffer) abort + return ale_linters#coffee#coffeelint#GetExecutable(a:buffer) + \ . ' --stdin --reporter csv' +endfunction + +function! ale_linters#coffee#coffeelint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " path,lineNumber,lineNumberEnd,level,message + " stdin,14,,error,Throwing strings is forbidden + " + " Note that we currently ignore lineNumberEnd for multiline errors + let l:pattern = 'stdin,\(\d\+\),\(\d*\),\(.\{-1,}\),\(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[1]), + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('coffee', { +\ 'name': 'coffeelint', +\ 'executable_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..4b6169c --- /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:user_options) ? ' ' . l:user_options : '') + \ . (!empty(l:build_dir) ? ' -p ' . ale#Escape(l:build_dir) : '') + \ . (empty(l:build_dir) ? ' -extra-arg -Xanalyzer -extra-arg -analyzer-output=text' : '') +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 new file mode 100644 index 0000000..8b2aa80 --- /dev/null +++ b/ale_linters/cpp/cppcheck.vim @@ -0,0 +1,39 @@ +" Author: Bart Libert +" Description: cppcheck linter for cpp files + +call ale#Set('cpp_cppcheck_executable', 'cppcheck') +call ale#Set('cpp_cppcheck_options', '--enable=style') + +function! ale_linters#cpp#cppcheck#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_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/gcc.vim b/ale_linters/cpp/gcc.vim new file mode 100644 index 0000000..40dffc9 --- /dev/null +++ b/ale_linters/cpp/gcc.vim @@ -0,0 +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') + +function! ale_linters#cpp#gcc#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_gcc_executable') +endfunction + +function! ale_linters#cpp#gcc#GetCommand(buffer) abort + let l:paths = ale#c#FindLocalHeaderPaths(a:buffer) + + " -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_callback': 'ale_linters#cpp#gcc#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#gcc#GetCommand', +\ '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 new file mode 100644 index 0000000..3d042f9 --- /dev/null +++ b/ale_linters/cs/mcs.vim @@ -0,0 +1,32 @@ +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:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3] . ': ' . l:match[4], + \ 'type': l:match[3] =~# '^error' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('cs',{ +\ '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/css/csslint.vim b/ale_linters/css/csslint.vim new file mode 100644 index 0000000..98b7fdd --- /dev/null +++ b/ale_linters/css/csslint.vim @@ -0,0 +1,18 @@ +" Author: w0rp +" Description: This file adds support for checking CSS code with csslint. + +function! ale_linters#css#csslint#GetCommand(buffer) abort + let l:csslintrc = ale#path#FindNearestFile(a:buffer, '.csslintrc') + let l:config_option = !empty(l:csslintrc) + \ ? '--config=' . ale#Escape(l:csslintrc) + \ : '' + + return 'csslint --format=compact ' . l:config_option . ' %t' +endfunction + +call ale#linter#Define('css', { +\ 'name': 'csslint', +\ 'executable': 'csslint', +\ 'command_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 new file mode 100644 index 0000000..9f68319 --- /dev/null +++ b/ale_linters/css/stylelint.vim @@ -0,0 +1,24 @@ +" Author: diartyz + +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 + return ale#node#FindExecutable(a:buffer, 'css_stylelint', [ + \ 'node_modules/.bin/stylelint', + \]) +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 + +call ale#linter#Define('css', { +\ 'name': 'stylelint', +\ 'executable_callback': 'ale_linters#css#stylelint#GetExecutable', +\ 'command_callback': 'ale_linters#css#stylelint#GetCommand', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/d/dmd.vim b/ale_linters/d/dmd.vim new file mode 100644 index 0000000..b91238a --- /dev/null +++ b/ale_linters/d/dmd.vim @@ -0,0 +1,79 @@ +" Author: w0rp +" Description: "dmd for D files" + +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#path#FindNearestFile(a:buffer, l:possible_filename) + + if !empty(l:dub_file) + return l:dub_file + endif + endfor + + return '' +endfunction + +function! ale_linters#d#dmd#DUBCommand(buffer) abort + " If we can't run dub, then skip this command. + if !executable('dub') + " Returning an empty string skips to the DMD command. + return '' + endif + + let l:dub_file = s:FindDUBConfig(a:buffer) + + if empty(l:dub_file) + return '' + endif + + " 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 ' . ale#Escape(fnamemodify(l:dub_file, ':h')) + \ . ' && dub describe --import-paths' +endfunction + +function! ale_linters#d#dmd#DMDCommand(buffer, dub_output) abort + let l:import_list = [] + + " Build a list of import paths generated from DUB, if available. + 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' . ale#Escape(l:line)) + endif + endfor + + return 'dmd '. join(l:import_list) . ' -o- -vcolumns -c %t' +endfunction + +function! ale_linters#d#dmd#Handle(buffer, lines) abort + " Matches patterns lines like the following: + " /tmp/tmp.qclsa7qLP7/file.d(1): Error: function declaration without return type. (Note that constructors are always named 'this') + " /tmp/tmp.G1L5xIizvB.d(8,8): Error: module weak_reference is in file 'dstruct/weak_reference.d' which cannot be read + let l:pattern = '^[^(]\+(\([0-9]\+\)\,\?\([0-9]*\)): \([^:]\+\): \(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1], + \ 'col': l:match[2], + \ 'type': l:match[3] is# 'Warning' ? 'W' : 'E', + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('d', { +\ 'name': 'dmd', +\ 'executable': 'dmd', +\ 'command_chain': [ +\ {'callback': 'ale_linters#d#dmd#DUBCommand', 'output_stream': 'stdout'}, +\ {'callback': 'ale_linters#d#dmd#DMDCommand', 'output_stream': 'stderr'}, +\ ], +\ 'callback': 'ale_linters#d#dmd#Handle', +\}) diff --git a/ale_linters/dart/dartanalyzer.vim b/ale_linters/dart/dartanalyzer.vim new file mode 100644 index 0000000..f7b82c4 --- /dev/null +++ b/ale_linters/dart/dartanalyzer.vim @@ -0,0 +1,40 @@ +" 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) : '') + \ . ' %t' +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', +\}) diff --git a/ale_linters/dockerfile/hadolint.vim b/ale_linters/dockerfile/hadolint.vim new file mode 100644 index 0000000..5550d69 --- /dev/null +++ b/ale_linters/dockerfile/hadolint.vim @@ -0,0 +1,77 @@ +" Author: hauleth - https://github.com/hauleth + +" always, yes, never +call ale#Set('dockerfile_hadolint_use_docker', 'never') +call ale#Set('dockerfile_hadolint_docker_image', 'lukasmartinelli/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 = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:lnum = 0 + + if l:match[1] isnot# '' + let l:lnum = l:match[1] + 0 + endif + + let l:type = 'W' + let l:text = l:match[3] + + call add(l:output, { + \ 'lnum': l:lnum, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \ 'nr': l:match[2], + \}) + 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_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 new file mode 100644 index 0000000..3699dd2 --- /dev/null +++ b/ale_linters/elixir/credo.vim @@ -0,0 +1,37 @@ +" Author: hauleth - https://github.com/hauleth + +function! ale_linters#elixir#credo#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " lib/filename.ex:19:7: F: Pipe chain should start with a raw value. + let l:pattern = '\v:(\d+):?(\d+)?: (.): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:type = l:match[3] + let l:text = l:match[4] + + 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': 'credo', +\ 'executable': 'mix', +\ 'command': 'mix credo suggest --format=flycheck --read-from-stdin %s', +\ 'callback': 'ale_linters#elixir#credo#Handle', +\}) diff --git a/ale_linters/elixir/dogma.vim b/ale_linters/elixir/dogma.vim new file mode 100644 index 0000000..b4f32b0 --- /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 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 new file mode 100644 index 0000000..04563a4 --- /dev/null +++ b/ale_linters/elm/make.vim @@ -0,0 +1,77 @@ +" Author: buffalocoder - https://github.com/buffalocoder +" Description: Elm linting in Ale. Closely follows the Syntastic checker in https://github.com/ElmCast/elm-vim. + +function! ale_linters#elm#make#Handle(buffer, lines) abort + let l:output = [] + let l:is_windows = has('win32') + let l:temp_dir = l:is_windows ? $TMP : $TMPDIR + let l:unparsed_lines = [] + for l:line in a:lines + if l:line[0] is# '[' + let l:errors = json_decode(l:line) + + for l:error in l:errors + " Check if file is from the temp directory. + " Filters out any errors not related to the buffer. + if l:is_windows + let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] is? l:temp_dir + else + let l:file_is_buffer = l:error.file[0:len(l:temp_dir) - 1] is# l:temp_dir + endif + + if l:file_is_buffer + call add(l:output, { + \ 'lnum': l:error.region.start.line, + \ 'col': l:error.region.start.column, + \ 'end_lnum': l:error.region.end.line, + \ 'end_col': l:error.region.end.column, + \ 'type': (l:error.type is? 'error') ? 'E' : 'W', + \ 'text': l:error.overview, + \ 'detail': l:error.overview . "\n\n" . l:error.details + \}) + endif + endfor + 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#path#FindNearestFile(a:buffer, 'elm-package.json') + 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 ' . 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. + " Source: https://github.com/elm-lang/elm-make/blob/master/src/Flags.hs + let l:elm_cmd = 'elm-make --report=json --output='.ale#Escape('/dev/null') + + return l:dir_set_cmd . ' ' . l:elm_cmd . ' %t' +endfunction + +call ale#linter#Define('elm', { +\ 'name': 'make', +\ 'executable': 'elm-make', +\ '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 new file mode 100644 index 0000000..559dc67 --- /dev/null +++ b/ale_linters/erlang/erlc.vim @@ -0,0 +1,96 @@ +" Author: Magnus Ottenklinger - https://github.com/evnu + +let g:ale_erlang_erlc_options = get(g:, 'ale_erlang_erlc_options', '') + +function! ale_linters#erlang#erlc#GetCommand(buffer) abort + 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 + " Matches patterns like the following: + " + " error.erl:4: variable 'B' is unbound + " error.erl:3: Warning: function main/0 is unused + " error.erl:4: Warning: variable 'A' is unused + let l:pattern = '\v^([^:]+):(\d+): (Warning: )?(.+)$' + + " parse_transforms are a special case. The error message does not indicate a location: + " error.erl: undefined parse transform 'some_parse_transform' + let l:pattern_parse_transform = '\v(undefined parse transform .*)$' + let l:output = [] + + let l:pattern_no_module_definition = '\v(no module definition)$' + let l:pattern_unused = '\v(.* is unused)$' + + let l:is_hrl = fnamemodify(bufname(a:buffer), ':e') is# 'hrl' + + for l:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + " Determine if the output indicates an error. We distinguish between two cases: + " + " 1) normal errors match l:pattern + " 2) parse_transform errors match l:pattern_parse_transform + " + " If none of the patterns above match, the line can be ignored + if len(l:match) == 0 " not a 'normal' warning or error + let l:match_parse_transform = matchlist(l:line, l:pattern_parse_transform) + + if len(l:match_parse_transform) == 0 " also not a parse_transform error + continue + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': 0, + \ 'col': 0, + \ 'type': 'E', + \ 'text': l:match_parse_transform[0], + \}) + + continue + endif + + let l:line = l:match[2] + let l:warning_or_text = l:match[3] + let l:text = l:match[4] + + " If this file is a header .hrl, ignore the following expected messages: + " - 'no module definition' + " - 'X is unused' + if l:is_hrl && ( + \ match(l:text, l:pattern_no_module_definition) != -1 + \ || match(l:text, l:pattern_unused) != -1 + \) + continue + endif + + if !empty(l:warning_or_text) + let l:type = 'W' + else + let l:type = 'E' + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:line, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('erlang', { +\ 'name': 'erlc', +\ 'executable': 'erlc', +\ 'command_callback': 'ale_linters#erlang#erlc#GetCommand', +\ 'callback': 'ale_linters#erlang#erlc#Handle', +\}) diff --git a/ale_linters/erlang/syntaxerl.vim b/ale_linters/erlang/syntaxerl.vim new file mode 100644 index 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/erubis.vim b/ale_linters/eruby/erubis.vim new file mode 100644 index 0000000..be9332d --- /dev/null +++ b/ale_linters/eruby/erubis.vim @@ -0,0 +1,11 @@ +" Author: Jake Zimmerman +" Description: eruby checker using `erubis`, instead of `erb` + +call ale#linter#Define('eruby', { +\ 'name': 'erubis', +\ 'executable': 'erubis', +\ 'output_stream': 'stderr', +\ 'command': 'erubis -x %t | ruby -c', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) + diff --git a/ale_linters/eruby/erubylint.vim b/ale_linters/eruby/erubylint.vim new file mode 100644 index 0000000..2ff03c3 --- /dev/null +++ b/ale_linters/eruby/erubylint.vim @@ -0,0 +1,11 @@ +" Author: Matthias Guenther - https://wikimatze.de +" Description: erb-lint for eruby/erb files + +call ale#linter#Define('eruby', { +\ 'name': 'erubylint', +\ 'executable': 'erb', +\ 'output_stream': 'stderr', +\ 'command': 'erb -P -x %t | ruby -c', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) + diff --git a/ale_linters/fortran/gcc.vim b/ale_linters/fortran/gcc.vim new file mode 100644 index 0000000..5f2ac01 --- /dev/null +++ b/ale_linters/fortran/gcc.vim @@ -0,0 +1,86 @@ +" 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' +endif + +function! ale_linters#fortran#gcc#Handle(buffer, lines) abort + " We have to match a starting line and a later ending line together, + " like so. + " + " :21.34: + " Error: Expected comma in I/O list at (1) + let l:line_marker_pattern = ':\(\d\+\)[.:]\=\(\d\+\)\=:\=$' + let l:message_pattern = '^\(Error\|Warning\): \(.\+\)$' + let l:looking_for_message = 0 + let l:last_loclist_obj = {} + + let l:output = [] + + for l:line in a:lines + if l:looking_for_message + let l:match = matchlist(l:line, l:message_pattern) + else + let l:match = matchlist(l:line, l:line_marker_pattern) + endif + + if len(l:match) == 0 + continue + endif + + if l:looking_for_message + let l:looking_for_message = 0 + + " Now we have the text, we can set it and add the error. + let l:last_loclist_obj.text = l:match[2] + let l:last_loclist_obj.type = l:match[1] is# 'Warning' ? 'W' : 'E' + call add(l:output, l:last_loclist_obj) + else + let l:last_loclist_obj = { + \ 'bufnr': a:buffer, + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \} + + " Start looking for the message and error type. + let l:looking_for_message = 1 + endif + endfor + + return l:output +endfunction + +function! ale_linters#fortran#gcc#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_callback': 'ale_linters#fortran#gcc#GetExecutable', +\ 'command_callback': 'ale_linters#fortran#gcc#GetCommand', +\ 'callback': 'ale_linters#fortran#gcc#Handle', +\}) 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/go/gobuild.vim b/ale_linters/go/gobuild.vim new file mode 100644 index 0000000..143c2fd --- /dev/null +++ b/ale_linters/go/gobuild.vim @@ -0,0 +1,70 @@ +" Author: Joshua Rubin , Ben Reedy +" Description: go build for Go files + +" inspired by work from dzhou121 + +function! ale_linters#go#gobuild#GoEnv(buffer) abort + if exists('s:go_env') + return '' + endif + + return 'go env GOPATH GOROOT' +endfunction + +function! ale_linters#go#gobuild#GetCommand(buffer, goenv_output) abort + if !exists('s:go_env') + let s:go_env = { + \ 'GOPATH': a:goenv_output[0], + \ 'GOROOT': a:goenv_output[1], + \} + endif + + " Run go test in local directory with relative path + return 'GOPATH=' . s:go_env.GOPATH + \ . ' cd ' . fnamemodify(bufname(a:buffer), ':.:h') + \ . ' && go test -c -o /dev/null ./' +endfunction + +function! ale_linters#go#gobuild#GetMatches(lines) abort + " Matches patterns like the following: + " + " file.go:27: missing argument for Printf("%s"): format reads arg 2, have only 1 args + " file.go:53:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) + " file.go:5:2: expected declaration, found 'STRING' "log" + + " go test returns relative paths so use tail of filename as part of pattern matcher + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? (.+)$' + + return ale#util#GetMatches(a:lines, l:pattern) +endfunction + +function! ale_linters#go#gobuild#Handler(buffer, lines) abort + let l:output = [] + + for l:match in ale_linters#go#gobuild#GetMatches(a:lines) + " Omit errors from imported go packages + if !ale#path#IsBufferPath(a:buffer, l:match[1]) + continue + endif + + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[4], + \ 'type': 'E', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('go', { +\ 'name': 'go build', +\ 'executable': 'go', +\ 'command_chain': [ +\ {'callback': 'ale_linters#go#gobuild#GoEnv', '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 new file mode 100644 index 0000000..337deef --- /dev/null +++ b/ale_linters/go/gofmt.vim @@ -0,0 +1,10 @@ +" Author: neersighted +" Description: gofmt for Go files + +call ale#linter#Define('go', { +\ 'name': 'gofmt', +\ 'output_stream': 'stderr', +\ 'executable': 'gofmt', +\ 'command': 'gofmt -e %t', +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) diff --git a/ale_linters/go/golint.vim b/ale_linters/go/golint.vim new file mode 100644 index 0000000..cc807fe --- /dev/null +++ b/ale_linters/go/golint.vim @@ -0,0 +1,9 @@ +" Author: neersighted +" Description: golint for Go files + +call ale#linter#Define('go', { +\ 'name': 'golint', +\ 'executable': 'golint', +\ '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..f1abfc8 --- /dev/null +++ b/ale_linters/go/gometalinter.vim @@ -0,0 +1,49 @@ +" Author: Ben Reedy +" Description: Adds support for the gometalinter suite for Go files + +call ale#Set('go_gometalinter_options', '') +call ale#Set('go_gometalinter_executable', 'gometalinter') + +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) + let l:options = ale#Var(a:buffer, 'go_gometalinter_options') + + return ale#Escape(l:executable) + \ . ' --include=' . ale#Escape('^' . ale#util#EscapePCRE(l:filename)) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' ' . ale#Escape(fnamemodify(l:filename, ':h')) +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:output = [] + + for l:match in ale_linters#go#gometalinter#GetMatches(a:lines) + call add(l:output, { + \ '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..4b7d340 --- /dev/null +++ b/ale_linters/go/gosimple.vim @@ -0,0 +1,9 @@ +" Author: Ben Reedy +" Description: gosimple for Go files + +call ale#linter#Define('go', { +\ 'name': 'gosimple', +\ 'executable': 'gosimple', +\ 'command': 'gosimple %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/go/govet.vim b/ale_linters/go/govet.vim new file mode 100644 index 0000000..f5bb47a --- /dev/null +++ b/ale_linters/go/govet.vim @@ -0,0 +1,10 @@ +" Author: neersighted +" Description: go vet for Go files + +call ale#linter#Define('go', { +\ 'name': 'go vet', +\ 'output_stream': 'stderr', +\ 'executable': 'go', +\ 'command': 'go vet %t', +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) diff --git a/ale_linters/go/staticcheck.vim b/ale_linters/go/staticcheck.vim new file mode 100644 index 0000000..c78b320 --- /dev/null +++ b/ale_linters/go/staticcheck.vim @@ -0,0 +1,9 @@ +" Author: Ben Reedy +" Description: staticcheck for Go files + +call ale#linter#Define('go', { +\ 'name': 'staticcheck', +\ 'executable': 'staticcheck', +\ 'command': 'staticcheck %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) 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..b1a6aa5 --- /dev/null +++ b/ale_linters/haml/hamllint.vim @@ -0,0 +1,26 @@ +" Author: Patrick Lewis - https://github.com/patricklewis +" Description: haml-lint for Haml files + +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': 'haml-lint %t', +\ '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..963ab56 --- /dev/null +++ b/ale_linters/handlebars/embertemplatelint.vim @@ -0,0 +1,50 @@ +" 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, { + \ 'bufnr': a:buffer, + \ 'lnum': 1, + \ 'col': 1, + \ 'text': l:error.message, + \ 'type': l:error.severity == 1 ? 'W' : 'E', + \}) + else + call add(l:output, { + \ 'bufnr': a:buffer, + \ '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 new file mode 100644 index 0000000..fdf22f9 --- /dev/null +++ b/ale_linters/haskell/ghc.vim @@ -0,0 +1,10 @@ +" Author: w0rp +" Description: ghc for Haskell files + +call ale#linter#Define('haskell', { +\ 'name': 'ghc', +\ 'output_stream': 'stderr', +\ 'executable': 'ghc', +\ 'command': 'ghc -fno-code -v0 %t', +\ '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 new file mode 100644 index 0000000..be40d92 --- /dev/null +++ b/ale_linters/haskell/hlint.vim @@ -0,0 +1,34 @@ +" Author: jparoz +" Description: hlint for Haskell files + +function! ale_linters#haskell#hlint#Handle(buffer, lines) abort + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + if l:error.severity is# 'Error' + let l:type = 'E' + elseif l:error.severity is# 'Suggestion' + let l:type = 'I' + else + let l:type = 'W' + endif + + call add(l:output, { + \ 'lnum': str2nr(l:error.startLine), + \ 'col': str2nr(l:error.startColumn), + \ 'end_lnum': str2nr(l:error.endLine), + \ 'end_col': str2nr(l:error.endColumn), + \ 'text': l:error.severity . ': ' . l:error.hint . '. Found: ' . l:error.from . ' Why not: ' . l:error.to, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('haskell', { +\ 'name': 'hlint', +\ 'executable': 'hlint', +\ 'command': 'hlint --color=never --json -', +\ 'callback': 'ale_linters#haskell#hlint#Handle', +\}) diff --git a/ale_linters/haskell/stack_build.vim b/ale_linters/haskell/stack_build.vim new file mode 100644 index 0000000..525fd3f --- /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/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/html/htmlhint.vim b/ale_linters/html/htmlhint.vim new file mode 100644 index 0000000..e142d22 --- /dev/null +++ b/ale_linters/html/htmlhint.vim @@ -0,0 +1,25 @@ +" Author: KabbAmine , deathmaz <00maz1987@gmail.com>, diartyz +" Description: HTMLHint for checking html files + +call ale#Set('html_htmlhint_options', '--format=unix') +call ale#Set('html_htmlhint_executable', 'htmlhint') +call ale#Set('html_htmlhint_use_global', 0) + +function! ale_linters#html#htmlhint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'html_htmlhint', [ + \ 'node_modules/.bin/htmlhint', + \]) +endfunction + +function! ale_linters#html#htmlhint#GetCommand(buffer) abort + return ale_linters#html#htmlhint#GetExecutable(a:buffer) + \ . ' ' . ale#Var(a:buffer, 'html_htmlhint_options') + \ . ' %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#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 new file mode 100644 index 0000000..4a55d62 --- /dev/null +++ b/ale_linters/html/tidy.vim @@ -0,0 +1,69 @@ +" Author: KabbAmine +" Description: This file adds support for checking HTML code with tidy. + +" CLI options +let g:ale_html_tidy_executable = get(g:, 'ale_html_tidy_executable', 'tidy') +" 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 + " (Idea taken from https://github.com/scrooloose/syntastic/blob/master/syntax_checkers/html/tidy.vim) + let l:file_encoding = get({ + \ 'ascii': '-ascii', + \ 'big5': '-big5', + \ 'cp1252': '-win1252', + \ 'cp850': '-ibm858', + \ 'cp932': '-shiftjis', + \ 'iso-2022-jp': '-iso-2022', + \ 'latin1': '-latin1', + \ 'macroman': '-mac', + \ 'sjis': '-shiftjis', + \ 'utf-16le': '-utf16le', + \ 'utf-16': '-utf16', + \ 'utf-8': '-utf8', + \ }, &fileencoding, '-utf8') + + return printf('%s %s %s -', + \ ale#Var(a:buffer, 'html_tidy_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 + " Matches patterns lines like the following: + " line 7 column 5 - Warning: missing before + + let l:pattern = '^line \(\d\+\) column \(\d\+\) - \(Warning\|Error\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[1] + 0 + let l:col = l:match[2] + 0 + let l:type = l:match[3] is# 'Error' ? 'E' : 'W' + let l:text = l:match[4] + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:col, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('html', { +\ 'name': 'tidy', +\ 'executable_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/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..d3d4884 --- /dev/null +++ b/ale_linters/java/checkstyle.vim @@ -0,0 +1,46 @@ +" Author: Devon Meunier +" Description: checkstyle for Java files + +function! ale_linters#java#checkstyle#Handle(buffer, lines) abort + let l:patterns = [ + \ '\v\[(WARN|ERROR)\] .*:(\d+):(\d+): (.*)', + \ '\v\[(WARN|ERROR)\] .*:(\d+): (.*)', + \] + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:patterns) + let l:args = { + \ 'lnum': l:match[2] + 0, + \ 'type': l:match[1] =~? 'WARN' ? 'W' : 'E' + \ } + + let l:col = l:match[3] + 0 + if l:col > 0 + let l:args['col'] = l:col + let l:args['text'] = l:match[4] + else + let l:args['text'] = l:match[3] + endif + + call add(l:output, l:args) + 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..d4566ab --- /dev/null +++ b/ale_linters/java/javac.vim @@ -0,0 +1,94 @@ +" 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 + + 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') + + if !empty(l:src_dir) + let l:sp_option = '-sourcepath ' . ale#Escape(l:src_dir) + endif + + " Create .class files in a temporary directory, which we will delete later. + let l:class_file_directory = ale#engine#CreateDirectory(a:buffer) + + return '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: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, { + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[2] . ':' . l:match[3], + \ 'type': l:match[2] 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 new file mode 100644 index 0000000..785b8bb --- /dev/null +++ b/ale_linters/javascript/eslint.vim @@ -0,0 +1,9 @@ +" Author: w0rp +" Description: eslint for JavaScript files + +call ale#linter#Define('javascript', { +\ 'name': 'eslint', +\ '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 new file mode 100644 index 0000000..0dd6453 --- /dev/null +++ b/ale_linters/javascript/flow.vim @@ -0,0 +1,117 @@ +" Author: Zach Perrault -- @zperrault +" Description: FlowType checking for JavaScript files + +call ale#Set('javascript_flow_executable', 'flow') +call ale#Set('javascript_flow_use_global', 0) + +function! ale_linters#javascript#flow#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_flow', [ + \ 'node_modules/.bin/flow', + \]) +endfunction + +function! ale_linters#javascript#flow#VersionCheck(buffer) abort + return ale#Escape(ale_linters#javascript#flow#GetExecutable(a:buffer)) + \ . ' --version' +endfunction + +function! ale_linters#javascript#flow#GetCommand(buffer, version_lines) abort + let l:flow_config = ale#path#FindNearestFile(a:buffer, '.flowconfig') + + if empty(l:flow_config) + " Don't run Flow if we can't find a .flowconfig file. + return '' + endif + + let l:use_respect_pragma = 1 + + " If we can parse the version number, then only use --respect-pragma + " if the version is >= 0.36.0, which added the argument. + for l:match in ale#util#GetMatches(a:version_lines, '\v\d+\.\d+\.\d+$') + let l:use_respect_pragma = ale#semver#GreaterOrEqual( + \ ale#semver#Parse(l:match[0]), + \ [0, 36, 0] + \) + endfor + + return ale#Escape(ale_linters#javascript#flow#GetExecutable(a:buffer)) + \ . ' 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! ale_linters#javascript#flow#Handle(buffer, lines) abort + let l:str = join(s:GetJSONLines(a:lines), '') + + if empty(l:str) + return [] + endif + + let l:flow_output = json_decode(l:str) + let l:output = [] + + for l:error in get(l:flow_output, 'errors', []) + " Each error is broken up into parts + let l:text = '' + let l:line = 0 + let l:col = 0 + + for l:message in l:error.message + " Comments have no line of column information, so we skip them. + " In certain cases, `l:message.loc.source` points to a different path + " than the buffer one, thus we skip this loc information too. + if has_key(l:message, 'loc') + \&& l:line is# 0 + \&& ale#path#IsBufferPath(a:buffer, l:message.loc.source) + let l:line = l:message.loc.start.line + 0 + let l:col = l:message.loc.start.column + 0 + endif + + if l:text is# '' + let l:text = l:message.descr . ':' + else + let l:text = l:text . ' ' . l:message.descr + endif + endfor + + if has_key(l:error, 'operation') + let l:text = l:text . ' See also: ' . l:error.operation.descr + endif + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:col, + \ 'text': l:text, + \ 'type': l:error.level is# 'error' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'flow', +\ 'executable_callback': 'ale_linters#javascript#flow#GetExecutable', +\ 'command_chain': [ +\ {'callback': 'ale_linters#javascript#flow#VersionCheck'}, +\ {'callback': 'ale_linters#javascript#flow#GetCommand'}, +\ ], +\ 'callback': 'ale_linters#javascript#flow#Handle', +\ 'add_newline': !has('win32'), +\}) diff --git a/ale_linters/javascript/jscs.vim b/ale_linters/javascript/jscs.vim new file mode 100644 index 0000000..b3f826c --- /dev/null +++ b/ale_linters/javascript/jscs.vim @@ -0,0 +1,63 @@ +" 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 = '^.*:\s\+line \(\d\+\),\s\+col\s\+\(\d\+\),\s\+\(.*\)$' + let l:output = [] + let l:m = ale#util#GetMatches(a:lines, [l:pattern]) + + for l:match in l:m + let l:text = l:match[3] + + let l:obj = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3] + \} + + call add(l:output, l:obj) + endfor + + return l:output +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'jscs', +\ '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 new file mode 100644 index 0000000..93b16a8 --- /dev/null +++ b/ale_linters/javascript/jshint.vim @@ -0,0 +1,38 @@ +" Author: Chris Kyrouac - https://github.com/fijshion +" Description: JSHint for Javascript files + +call ale#Set('javascript_jshint_executable', 'jshint') +call ale#Set('javascript_jshint_use_global', 0) + +function! ale_linters#javascript#jshint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_jshint', [ + \ 'node_modules/.bin/jshint', + \]) +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#path#ResolveLocalPath( + \ a:buffer, + \ '.jshintrc', + \ get(g:, 'ale_jshint_config_loc', '') + \) + + 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 ' . ale#Escape(l:jshint_config) + endif + + let l:command .= ' -' + + return l:command +endfunction + +call ale#linter#Define('javascript', { +\ 'name': 'jshint', +\ 'executable_callback': 'ale_linters#javascript#jshint#GetExecutable', +\ 'command_callback': 'ale_linters#javascript#jshint#GetCommand', +\ 'callback': 'ale#handlers#unix#HandleAsError', +\}) diff --git a/ale_linters/javascript/standard.vim b/ale_linters/javascript/standard.vim new file mode 100644 index 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 new file mode 100644 index 0000000..75f4708 --- /dev/null +++ b/ale_linters/json/jsonlint.vim @@ -0,0 +1,27 @@ +" Author: KabbAmine + +function! ale_linters#json#jsonlint#Handle(buffer, lines) abort + " Matches patterns like the following: + " line 2, col 15, found: 'STRING' - expected: 'EOF', '}', ',', ']'. + + let l:pattern = '^line \(\d\+\), col \(\d*\), \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('json', { +\ 'name': 'jsonlint', +\ 'executable': 'jsonlint', +\ 'output_stream': 'stderr', +\ 'command': 'jsonlint --compact -', +\ 'callback': 'ale_linters#json#jsonlint#Handle', +\}) 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/lua/luacheck.vim b/ale_linters/lua/luacheck.vim new file mode 100644 index 0000000..e15b730 --- /dev/null +++ b/ale_linters/lua/luacheck.vim @@ -0,0 +1,45 @@ +" Author: Sol Bekic https://github.com/s-ol +" Description: luacheck linter for lua files + +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:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[3] . l:match[4] . ': ' . l:match[5], + \ 'type': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('lua', { +\ 'name': 'luacheck', +\ '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/markdown/mdl.vim b/ale_linters/markdown/mdl.vim new file mode 100644 index 0000000..f239025 --- /dev/null +++ b/ale_linters/markdown/mdl.vim @@ -0,0 +1,25 @@ +" Author: Steve Dignam +" Description: Support for mdl, a markdown linter + +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: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('markdown', { +\ 'name': 'mdl', +\ 'executable': 'mdl', +\ 'command': 'mdl', +\ 'callback': 'ale_linters#markdown#mdl#Handle' +\}) diff --git a/ale_linters/markdown/proselint.vim b/ale_linters/markdown/proselint.vim new file mode 100644 index 0000000..289d881 --- /dev/null +++ b/ale_linters/markdown/proselint.vim @@ -0,0 +1,9 @@ +" Author: poohzrn https://github.com/poohzrn +" Description: proselint for Markdown files + +call ale#linter#Define('markdown', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/markdown/vale.vim b/ale_linters/markdown/vale.vim new file mode 100644 index 0000000..43b3d34 --- /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=line %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/matlab/mlint.vim b/ale_linters/matlab/mlint.vim new file mode 100644 index 0000000..3276633 --- /dev/null +++ b/ale_linters/matlab/mlint.vim @@ -0,0 +1,55 @@ +" Author: awlayton +" Description: mlint for MATLAB files + +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: + " + " L 27 (C 1): FNDEF: Terminate statement with semicolon to suppress output. + " L 30 (C 13-15): FNDEF: A quoted string is unterminated. + let l:pattern = '^L \(\d\+\) (C \([0-9-]\+\)): \([A-Z]\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:lnum = l:match[1] + 0 + let l:col = l:match[2] + 0 + let l:code = l:match[3] + let l:text = l:match[4] + + " Suppress erroneous waring about filename + " TODO: Enable this error when copying filename is supported + if l:code is# 'FNDEF' + continue + endif + + call add(l:output, { + \ 'bufnr': a:buffer, + \ 'lnum': l:lnum, + \ 'col': l:col, + \ 'text': l:text, + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('matlab', { +\ 'name': 'mlint', +\ 'executable_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..cdd8c56 --- /dev/null +++ b/ale_linters/nim/nimcheck.vim @@ -0,0 +1,58 @@ +" 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:line = l:match[2] + 0 + let l:column = l:match[3] + 0 + let l:text = l:match[4] + let l:type = 'W' + + " Extract error type from message of type 'Error: Some error message' + let l:textmatch = matchlist(l:match[4], '^\(.\{-}\): .\+$') + + if len(l:textmatch) > 0 + let l:errortype = l:textmatch[1] + if l:errortype is# 'Error' + let l:type = 'E' + endif + endif + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:column, + \ 'text': l:text, + \ 'type': l:type, + \}) + 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/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/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 new file mode 100644 index 0000000..cfec996 --- /dev/null +++ b/ale_linters/ocaml/merlin.vim @@ -0,0 +1,17 @@ +" Author: Andrey Popp -- @andreypopp +" Description: Report errors in OCaml code with Merlin + +if !exists('g:merlin') + finish +endif + +function! ale_linters#ocaml#merlin#Handle(buffer, lines) abort + return merlin#ErrorLocList() +endfunction + +call ale#linter#Define('ocaml', { +\ 'name': 'merlin', +\ 'executable': 'ocamlmerlin', +\ 'command': 'true', +\ 'callback': 'ale_linters#ocaml#merlin#Handle', +\}) diff --git a/ale_linters/perl/perl.vim b/ale_linters/perl/perl.vim new file mode 100644 index 0000000..3328806 --- /dev/null +++ b/ale_linters/perl/perl.vim @@ -0,0 +1,58 @@ +" 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_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:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[3] + let l:text = l:match[1] + let l:type = 'E' + + if ale#path#IsBufferPath(a:buffer, l:match[2]) + \ && ( + \ 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, + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('perl', { +\ 'name': 'perl', +\ 'executable_callback': 'ale_linters#perl#perl#GetExecutable', +\ 'output_stream': 'both', +\ '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 new file mode 100644 index 0000000..df2f8b2 --- /dev/null +++ b/ale_linters/perl/perlcritic.vim @@ -0,0 +1,76 @@ +" 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 = '\(\d\+\):\(\d\+\) \(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1], + \ 'col': l:match[2], + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('perl', { +\ 'name': 'perlcritic', +\ 'output_stream': 'stdout', +\ '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 new file mode 100644 index 0000000..77d3a58 --- /dev/null +++ b/ale_linters/php/hack.vim @@ -0,0 +1,28 @@ +" Author: Zefei Xuan +" Description: Hack type checking (http://hacklang.org/) + +function! ale_linters#php#hack#Handle(buffer, lines) abort + let l:pattern = '^\(.*\):\(\d\+\):\(\d\+\),\(\d\+\): \(.\+])\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if a:buffer != bufnr(l:match[1]) + continue + endif + + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': 'hack', +\ 'executable': 'hh_client', +\ 'command': 'hh_client --retries 0 --retry-if-init false', +\ 'callback': 'ale_linters#php#hack#Handle', +\}) 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/php.vim b/ale_linters/php/php.vim new file mode 100644 index 0000000..7158c95 --- /dev/null +++ b/ale_linters/php/php.vim @@ -0,0 +1,35 @@ +" 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: syntax error, unexpected ';', expecting ']' in - on line 15 + let l:pattern = '\v^%(Fatal|Parse) error:\s+(.+unexpected ''(.+)%(expecting.+)@, 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:executable = ale_linters#php#phpcs#GetExecutable(a:buffer) + + let l:standard = ale#Var(a:buffer, 'php_phpcs_standard') + let l:standard_option = !empty(l:standard) + \ ? '--standard=' . l:standard + \ : '' + + 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:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:text = l:match[4] + let l:type = l:match[3] + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:text, + \ 'type': l:type is# 'error' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('php', { +\ 'name': '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..29d8103 --- /dev/null +++ b/ale_linters/php/phpmd.vim @@ -0,0 +1,36 @@ +" Author: medains +" Description: phpmd for PHP files + +" Set to change the ruleset +let g:ale_php_phpmd_ruleset = get(g:, 'ale_php_phpmd_ruleset', 'cleancode,codesize,controversial,design,naming,unusedcode') + +function! ale_linters#php#phpmd#GetCommand(buffer) abort + return 'phpmd %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\+\)\t\(.\+\)$' + 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': 'phpmd', +\ '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..b99e4f5 --- /dev/null +++ b/ale_linters/php/phpstan.vim @@ -0,0 +1,46 @@ +" 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') + +function! ale_linters#php#phpstan#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'php_phpstan_executable') +endfunction + +function! ale_linters#php#phpstan#GetCommand(buffer) abort + let l:executable = ale_linters#php#phpstan#GetExecutable(a:buffer) + + return ale#Escape(l:executable) + \ . ' analyze -l' + \ . ale#Var(a:buffer, 'php_phpstan_level') + \ . ' --errorFormat raw' + \ . ' %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/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/pug/puglint.vim b/ale_linters/pug/puglint.vim new file mode 100644 index 0000000..6c29efe --- /dev/null +++ b/ale_linters/pug/puglint.vim @@ -0,0 +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_callback': 'ale_linters#pug#puglint#GetExecutable', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 0000000..47e89d3 --- /dev/null +++ b/ale_linters/puppet/puppet.vim @@ -0,0 +1,27 @@ +" Author: Alexander Olofsson + +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 + + let l:pattern = '^Error: .*: \(.\+\) at .\+:\(\d\+\):\(\d\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'text': l:match[1], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('puppet', { +\ 'name': 'puppet', +\ 'executable': 'puppet', +\ 'output_stream': 'stderr', +\ '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 new file mode 100644 index 0000000..13da511 --- /dev/null +++ b/ale_linters/puppet/puppetlint.vim @@ -0,0 +1,26 @@ +" 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_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 new file mode 100644 index 0000000..bd5a447 --- /dev/null +++ b/ale_linters/pyrex/cython.vim @@ -0,0 +1,10 @@ +" Author: w0rp +" Description: cython syntax checking for cython files. + +call ale#linter#Define('pyrex', { +\ 'name': 'cython', +\ 'output_stream': 'stderr', +\ 'executable': 'cython', +\ '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 new file mode 100644 index 0000000..8aa4c4d --- /dev/null +++ b/ale_linters/python/flake8.vim @@ -0,0 +1,165 @@ +" Author: w0rp +" Description: flake8 for python files + +let g:ale_python_flake8_executable = +\ get(g:, 'ale_python_flake8_executable', 'flake8') + +" 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 + 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#ClearVersionCache() abort + let s:version_cache = {} +endfunction + +function! ale_linters#python#flake8#VersionCheck(buffer) abort + let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) + + " If we have previously stored the version number in a cache, then + " don't look it up again. + if has_key(s:version_cache, l:executable) + " Returning an empty string skips this command. + return '' + endif + + let l:executable = ale#Escape(ale_linters#python#flake8#GetExecutable(a:buffer)) + let l:module_string = s:UsingModule(a:buffer) ? ' -m flake8' : '' + + return l:executable . l:module_string . ' --version' +endfunction + +" 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]) +endfunction + +function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort + let l:version = s:GetVersion(a:buffer, 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:options = ale#Var(a:buffer, 'python_flake8_options') + + return ale#Escape(ale_linters#python#flake8#GetExecutable(a:buffer)) + \ . (!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 + + let l:item = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:code . ': ' . l:match[4], + \ 'type': 'W', + \} + + if l:code[:0] is# 'F' || l:code is# 'E999' + let l:item.type = 'E' + elseif l:code[:0] is# 'E' + let l:item.type = 'E' + let l:item.sub_type = 'style' + elseif l:code[:0] is# 'W' + let l:item.sub_type = 'style' + endif + + let l:end_col_pattern = get(s:end_col_pattern_map, l:code, '') + + if !empty(l:end_col_pattern) + let l:end_col_match = matchlist(l:match[4], l:end_col_pattern) + + if !empty(l:end_col_match) + let l:item.end_col = l:item.col + len(l:end_col_match[1]) - 1 + endif + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'flake8', +\ 'executable_callback': 'ale_linters#python#flake8#GetExecutable', +\ 'command_chain': [ +\ {'callback': 'ale_linters#python#flake8#VersionCheck'}, +\ {'callback': 'ale_linters#python#flake8#GetCommand', 'output_stream': 'both'}, +\ ], +\ 'callback': 'ale_linters#python#flake8#Handle', +\}) diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim new file mode 100644 index 0000000..6884a9a --- /dev/null +++ b/ale_linters/python/mypy.vim @@ -0,0 +1,65 @@ +" Author: Keith Smiley , w0rp +" Description: mypy support for optional python typechecking + +let g:ale_python_mypy_executable = +\ get(g:, 'ale_python_mypy_executable', 'mypy') +let g:ale_python_mypy_options = get(g:, 'ale_python_mypy_options', '') +let g:ale_python_mypy_use_global = get(g:, 'ale_python_mypy_use_global', 0) + +function! ale_linters#python#mypy#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'python_mypy', ['mypy']) +endfunction + +" The directory to change to before running mypy +function! s:GetDir(buffer) abort + let l:project_root = ale#python#FindProjectRoot(a:buffer) + + return !empty(l:project_root) + \ ? l:project_root + \ : expand('#' . a:buffer . ':p:h') +endfunction + +function! ale_linters#python#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' + " + " Lines like these should be ignored below: + " + " file.py:4: note: (Stub files are from https://github.com/python/typeshed) + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?: (error|warning): (.+)$' + let l:output = [] + + 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', + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': '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/pycodestyle.vim b/ale_linters/python/pycodestyle.vim new file mode 100644 index 0000000..ad89599 --- /dev/null +++ b/ale_linters/python/pycodestyle.vim @@ -0,0 +1,42 @@ +" 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) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'type': l:match[6], + \ 'text': l:match[4], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pycodestyle', +\ 'executable_callback': 'ale_linters#python#pycodestyle#GetExecutable', +\ 'command_callback': 'ale_linters#python#pycodestyle#GetCommand', +\ 'callback': 'ale_linters#python#pycodestyle#Handle', +\}) diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim new file mode 100644 index 0000000..befc51a --- /dev/null +++ b/ale_linters/python/pylint.vim @@ -0,0 +1,62 @@ +" Author: keith +" Description: pylint for python files + +let g:ale_python_pylint_executable = +\ get(g:, 'ale_python_pylint_executable', 'pylint') + +let g:ale_python_pylint_options = +\ get(g:, 'ale_python_pylint_options', '') + +let g:ale_python_pylint_use_global = get(g:, 'ale_python_pylint_use_global', 0) + +function! ale_linters#python#pylint#GetExecutable(buffer) abort + return ale#python#FindExecutable(a:buffer, 'python_pylint', ['pylint']) +endfunction + +function! ale_linters#python#pylint#GetCommand(buffer) abort + return ale#Escape(ale_linters#python#pylint#GetExecutable(a:buffer)) + \ . ' ' . 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:code . ': ' . l:match[5] . ' (' . 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_linters#python#pylint#Handle', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/r/lintr.vim b/ale_linters/r/lintr.vim new file mode 100644 index 0000000..9375b8a --- /dev/null +++ b/ale_linters/r/lintr.vim @@ -0,0 +1,15 @@ +" Author: Michel Lang , w0rp +" Description: This file adds support for checking R code with lintr. + +function! ale_linters#r#lintr#GetCommand(buffer) abort + return ale#path#BufferCdString(a:buffer) + \ . 'Rscript -e ' . ale#Escape('lintr::lint(commandArgs(TRUE))') . ' %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/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/ruby/brakeman.vim b/ale_linters/ruby/brakeman.vim new file mode 100644 index 0000000..790eb56 --- /dev/null +++ b/ale_linters/ruby/brakeman.vim @@ -0,0 +1,51 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: Brakeman, a static analyzer for Rails security + +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, {}) + + for l:warning in get(l:json, 'warnings', []) + " Brakeman always outputs paths relative to the Rails app root + let l:rails_root = ale#ruby#FindRailsRoot(a:buffer) + let l:warning_file = l:rails_root . '/' . l:warning.file + + if !ale#path#IsBufferPath(a:buffer, l:warning_file) + continue + endif + + 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, { + \ '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..10bc9a8 --- /dev/null +++ b/ale_linters/ruby/reek.vim @@ -0,0 +1,44 @@ +" 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), + \}) + endfor + endfor + + return l:output +endfunction + +function! s:BuildText(buffer, error) abort + let l:text = a:error.smell_type . ':' + + if ale#Var(a:buffer, 'ruby_reek_show_context') + let l:text .= ' ' . a:error.context + endif + + let l:text .= ' ' . a:error.message + + if ale#Var(a:buffer, 'ruby_reek_show_wiki_link') + let l:text .= ' [' . a:error.wiki_link . ']' + endif + + return l:text +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 new file mode 100644 index 0000000..2a4388f --- /dev/null +++ b/ale_linters/ruby/rubocop.vim @@ -0,0 +1,60 @@ +" 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 + try + let l:errors = json_decode(a:lines[0]) + catch + return [] + endtry + + if !has_key(l:errors, 'summary') + \|| l:errors['summary']['offense_count'] == 0 + \|| empty(l:errors['files']) + return [] + endif + + let l:output = [] + + for l:error in l:errors['files'][0]['offenses'] + let l:start_col = l:error['location']['column'] + 0 + call add(l:output, { + \ 'lnum': l:error['location']['line'] + 0, + \ 'col': l:start_col, + \ 'end_col': l:start_col + l:error['location']['length'] - 1, + \ 'text': printf('%s [%s]', l:error['message'], l:error['cop_name']), + \ 'type': ale_linters#ruby#rubocop#GetType(l:error['severity']), + \}) + endfor + + return l:output +endfunction + +function! ale_linters#ruby#rubocop#GetType(severity) abort + if a:severity is? 'convention' + \|| a:severity is? 'warning' + \|| a:severity is? 'refactor' + return 'W' + endif + + return 'E' +endfunction + +call ale#linter#Define('ruby', { +\ 'name': '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..a9f7b51 --- /dev/null +++ b/ale_linters/ruby/ruby.vim @@ -0,0 +1,10 @@ +" Author: Brandon Roehl - https://github.com/BrandonRoehl +" Description: Ruby MRI for Ruby files + +call ale#linter#Define('ruby', { +\ 'name': 'ruby', +\ 'executable': 'ruby', +\ 'output_stream': 'stderr', +\ 'command': 'ruby -w -c -T1 %t', +\ 'callback': 'ale#handlers#ruby#HandleSyntaxErrors', +\}) diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim new file mode 100644 index 0000000..f19061a --- /dev/null +++ b/ale_linters/rust/cargo.vim @@ -0,0 +1,31 @@ +" Author: Daniel Schemala +" Description: rustc invoked by cargo for rust files + +let g:ale_rust_cargo_use_check = get(g:, 'ale_rust_cargo_use_check', 0) + +function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort + if ale#path#FindNearestFile(a:bufnr, 'Cargo.toml') isnot# '' + return 'cargo' + else + " if there is no Cargo.toml file, we don't use cargo even if it exists, + " so we return '', because executable('') apparently always fails + return '' + endif +endfunction + +function! ale_linters#rust#cargo#GetCommand(buffer) abort + let l:command = ale#Var(a:buffer, 'rust_cargo_use_check') + \ ? 'check' + \ : 'build' + + return 'cargo ' . l:command . ' --frozen --message-format=json -q' +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'cargo', +\ 'executable_callback': 'ale_linters#rust#cargo#GetCargoExecutable', +\ 'command_callback': 'ale_linters#rust#cargo#GetCommand', +\ 'callback': 'ale#handlers#rust#HandleRustErrors', +\ 'output_stream': 'stdout', +\ 'lint_file': 1, +\}) diff --git a/ale_linters/rust/rls.vim b/ale_linters/rust/rls.vim new file mode 100644 index 0000000..c49d268 --- /dev/null +++ b/ale_linters/rust/rls.vim @@ -0,0 +1,33 @@ +" Author: w0rp +" Description: A language server for Rust + +call ale#Set('rust_rls_executable', 'rls') + +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) + + return ale#Escape(l:executable) . ' +nightly' +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 new file mode 100644 index 0000000..e792faa --- /dev/null +++ b/ale_linters/rust/rustc.vim @@ -0,0 +1,27 @@ +" Author: Daniel Schemala +" Description: rustc for rust files + +function! ale_linters#rust#rustc#RustcCommand(buffer_number) abort + " Try to guess the library search path. If the project is managed by cargo, + " it's usually /target/debug/deps/ or + " /target/release/deps/ + let l:cargo_file = ale#path#FindNearestFile(a:buffer_number, 'Cargo.toml') + + if l:cargo_file isnot# '' + let l:project_root = fnamemodify(l:cargo_file, ':h') + let l:dependencies = '-L ' . l:project_root . '/target/debug/deps -L ' . + \ l:project_root . '/target/release/deps' + else + let l:dependencies = '' + endif + + return 'rustc --error-format=json -Z no-trans ' . l:dependencies . ' -' +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'rustc', +\ 'executable': 'rustc', +\ 'command_callback': 'ale_linters#rust#rustc#RustcCommand', +\ 'callback': 'ale#handlers#rust#HandleRustErrors', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/sass/sasslint.vim b/ale_linters/sass/sasslint.vim new file mode 100644 index 0000000..bbe7125 --- /dev/null +++ b/ale_linters/sass/sasslint.vim @@ -0,0 +1,8 @@ +" Author: KabbAmine - https://github.com/KabbAmine + +call ale#linter#Define('sass', { +\ 'name': 'sasslint', +\ 'executable': 'sass-lint', +\ '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 new file mode 100644 index 0000000..98c3725 --- /dev/null +++ b/ale_linters/sass/stylelint.vim @@ -0,0 +1,22 @@ +" Author: diartyz + +call ale#Set('sass_stylelint_executable', 'stylelint') +call ale#Set('sass_stylelint_use_global', 0) + +function! ale_linters#sass#stylelint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'sass_stylelint', [ + \ 'node_modules/.bin/stylelint', + \]) +endfunction + +function! ale_linters#sass#stylelint#GetCommand(buffer) abort + return ale_linters#sass#stylelint#GetExecutable(a:buffer) + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('sass', { +\ 'name': 'stylelint', +\ 'executable_callback': 'ale_linters#sass#stylelint#GetExecutable', +\ 'command_callback': 'ale_linters#sass#stylelint#GetCommand', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/scala/scalac.vim b/ale_linters/scala/scalac.vim new file mode 100644 index 0000000..584aee7 --- /dev/null +++ b/ale_linters/scala/scalac.vim @@ -0,0 +1,65 @@ +" 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: + " + " /var/folders/5q/20rgxx3x1s34g3m14n5bq0x80000gn/T/vv6pSsy/0:26: error: expected class or object definition + let l:pattern = '^.\+:\(\d\+\): \(\w\+\): \(.\+\)' + let l:output = [] + let l:ln = 0 + + for l:line in a:lines + let l:ln = l:ln + 1 + let l:match = matchlist(l:line, l:pattern) + + if len(l:match) == 0 + continue + endif + + let l:text = l:match[3] + let l:type = l:match[2] is# 'error' ? 'E' : 'W' + let l:col = 0 + + if l:ln + 1 < len(a:lines) + let l:col = stridx(a:lines[l:ln + 1], '^') + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:col + 1, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('scala', { +\ 'name': 'scalac', +\ 'executable_callback': 'ale_linters#scala#scalac#GetExecutable', +\ 'command_callback': 'ale_linters#scala#scalac#GetCommand', +\ 'callback': 'ale_linters#scala#scalac#Handle', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/scala/scalastyle.vim b/ale_linters/scala/scalastyle.vim new file mode 100644 index 0000000..ea56c0e --- /dev/null +++ b/ale_linters/scala/scalastyle.vim @@ -0,0 +1,83 @@ +" 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 + " 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 new file mode 100644 index 0000000..bd01646 --- /dev/null +++ b/ale_linters/scss/sasslint.vim @@ -0,0 +1,8 @@ +" Author: KabbAmine - https://github.com/KabbAmine + +call ale#linter#Define('scss', { +\ 'name': 'sasslint', +\ 'executable': 'sass-lint', +\ '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 new file mode 100644 index 0000000..7ce5724 --- /dev/null +++ b/ale_linters/scss/scsslint.vim @@ -0,0 +1,34 @@ +" Author: w0rp +" Description: This file add scsslint support for SCSS support + +function! ale_linters#scss#scsslint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " test.scss:2:1 [W] Indentation: Line should be indented 2 spaces, but was indented 4 spaces + let l:pattern = '^.*:\(\d\+\):\(\d*\) \[\([^\]]\+\)\] \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + \&& l:match[4] =~# '^TrailingWhitespace' + " Skip trailing whitespace warnings if that option is off. + continue + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4], + \ 'type': l:match[3] is# 'E' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('scss', { +\ 'name': 'scsslint', +\ 'executable': 'scss-lint', +\ 'command': 'scss-lint --stdin-file-path=%s', +\ 'callback': 'ale_linters#scss#scsslint#Handle', +\}) diff --git a/ale_linters/scss/stylelint.vim b/ale_linters/scss/stylelint.vim new file mode 100644 index 0000000..00189a8 --- /dev/null +++ b/ale_linters/scss/stylelint.vim @@ -0,0 +1,22 @@ +" Author: diartyz + +call ale#Set('scss_stylelint_executable', 'stylelint') +call ale#Set('scss_stylelint_use_global', 0) + +function! ale_linters#scss#stylelint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'scss_stylelint', [ + \ 'node_modules/.bin/stylelint', + \]) +endfunction + +function! ale_linters#scss#stylelint#GetCommand(buffer) abort + return ale_linters#scss#stylelint#GetExecutable(a:buffer) + \ . ' --stdin-filename %s' +endfunction + +call ale#linter#Define('scss', { +\ 'name': 'stylelint', +\ 'executable_callback': 'ale_linters#scss#stylelint#GetExecutable', +\ 'command_callback': 'ale_linters#scss#stylelint#GetCommand', +\ 'callback': 'ale#handlers#css#HandleStyleLintFormat', +\}) diff --git a/ale_linters/sh/shell.vim b/ale_linters/sh/shell.vim new file mode 100644 index 0000000..cf5e4e6 --- /dev/null +++ b/ale_linters/sh/shell.vim @@ -0,0 +1,57 @@ +" 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_sh_shell_default_shell') + let g:ale_sh_shell_default_shell = fnamemodify($SHELL, ':t') + + if g:ale_sh_shell_default_shell is# '' || g:ale_sh_shell_default_shell is# 'fish' + let g:ale_sh_shell_default_shell = 'bash' + endif +endif + +function! ale_linters#sh#shell#GetExecutable(buffer) abort + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) + + if !empty(l:shell_type) + return l:shell_type + endif + + return ale#Var(a:buffer, 'sh_shell_default_shell') +endfunction + +function! ale_linters#sh#shell#GetCommand(buffer) abort + return ale_linters#sh#shell#GetExecutable(a:buffer) . ' -n %t' +endfunction + +function! ale_linters#sh#shell#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " bash: line 13: syntax error near unexpected token `d' + " sh: 11: Syntax error: "(" unexpected + let l:pattern = '\v(line |: ?)(\d+): (.+)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[2]), + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('sh', { +\ 'name': 'shell', +\ 'output_stream': 'stderr', +\ 'executable_callback': 'ale_linters#sh#shell#GetExecutable', +\ 'command_callback': 'ale_linters#sh#shell#GetCommand', +\ 'callback': 'ale_linters#sh#shell#Handle', +\}) diff --git a/ale_linters/sh/shellcheck.vim b/ale_linters/sh/shellcheck.vim new file mode 100644 index 0000000..3a2d33f --- /dev/null +++ b/ale_linters/sh/shellcheck.vim @@ -0,0 +1,58 @@ +" Author: w0rp +" 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 +" codes to exclude from shellcheck. For example: +" +" 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', '')) + +let g:ale_sh_shellcheck_executable = +\ get(g:, 'ale_sh_shellcheck_executable', 'shellcheck') + +let g:ale_sh_shellcheck_options = +\ get(g:, 'ale_sh_shellcheck_options', '') + +function! ale_linters#sh#shellcheck#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'sh_shellcheck_executable') +endfunction + +function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) + + if !empty(l:shell_type) + return l:shell_type + endif + + " If there's no hashbang, try using Vim's buffer variables. + if get(b:, 'is_bash') + return 'bash' + elseif get(b:, 'is_sh') + return 'sh' + elseif get(b:, 'is_kornshell') + return 'ksh' + endif + + return '' +endfunction + +function! ale_linters#sh#shellcheck#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'sh_shellcheck_options') + let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions') + let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer) + + return ale_linters#sh#shellcheck#GetExecutable(a:buffer) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '') + \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '') + \ . ' -f gcc -' +endfunction + +call ale#linter#Define('sh', { +\ 'name': 'shellcheck', +\ 'executable_callback': 'ale_linters#sh#shellcheck#GetExecutable', +\ 'command_callback': 'ale_linters#sh#shellcheck#GetCommand', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) diff --git a/ale_linters/slim/slimlint.vim b/ale_linters/slim/slimlint.vim new file mode 100644 index 0000000..74796b2 --- /dev/null +++ b/ale_linters/slim/slimlint.vim @@ -0,0 +1,26 @@ +" Author: Markus Doits - https://github.com/doits +" Description: slim-lint for Slim files, based on hamllint.vim + +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) + 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('slim', { +\ 'name': 'slimlint', +\ 'executable': 'slim-lint', +\ 'command': 'slim-lint %t', +\ '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..4acfc9e --- /dev/null +++ b/ale_linters/sml/smlnj.vim @@ -0,0 +1,47 @@ +" Author: Paulo Alem +" Description: Rudimentary SML checking with smlnj compiler + +function! ale_linters#sml#smlnj#Handle(buffer, lines) abort + " Try to match basic sml errors + + 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 + +call ale#linter#Define('sml', { +\ 'name': 'smlnj', +\ 'executable': 'sml', +\ 'command': 'sml', +\ 'callback': 'ale_linters#sml#smlnj#Handle', +\}) 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 new file mode 100644 index 0000000..b7dcf93 --- /dev/null +++ b/ale_linters/swift/swiftlint.vim @@ -0,0 +1,9 @@ +" Author: David Mohundro +" Description: swiftlint for swift files + +call ale#linter#Define('swift', { +\ 'name': 'swiftlint', +\ 'executable': 'swiftlint', +\ 'command': 'swiftlint lint --use-stdin', +\ 'callback': 'ale#handlers#gcc#HandleGCCFormat', +\}) 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/testft/testlinter.vim b/ale_linters/testft/testlinter.vim new file mode 100644 index 0000000..65e0b20 --- /dev/null +++ b/ale_linters/testft/testlinter.vim @@ -0,0 +1,10 @@ +" Author: neersighted +" Description: dummy linter to use in tests + +call ale#linter#Define('testft', { +\ 'name': 'testlinter', +\ 'output_stream': 'stdout', +\ 'executable': 'testlinter', +\ 'command': 'testlinter', +\ 'callback': 'testCB', +\}) diff --git a/ale_linters/tex/chktex.vim b/ale_linters/tex/chktex.vim new file mode 100644 index 0000000..7f1b0c7 --- /dev/null +++ b/ale_linters/tex/chktex.vim @@ -0,0 +1,54 @@ +" Author: Andrew Balmos - +" Description: chktex for LaTeX files + +let g:ale_tex_chktex_executable = +\ get(g:, 'ale_tex_chktex_executable', 'chktex') + +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#path#FindNearestFile( + \ a:buffer, + \ '.chktexrc') + + 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 ' . ale#Escape(l:chktex_config) + endif + + let l:command .= ' ' . ale#Var(a:buffer, 'tex_chktex_options') + + 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 = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4] . ' (' . (l:match[3]+0) . ')', + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('tex', { +\ 'name': 'chktex', +\ 'executable': 'chktex', +\ 'command_callback': 'ale_linters#tex#chktex#GetCommand', +\ 'callback': 'ale_linters#tex#chktex#Handle' +\}) diff --git a/ale_linters/tex/lacheck.vim b/ale_linters/tex/lacheck.vim new file mode 100644 index 0000000..e5a9632 --- /dev/null +++ b/ale_linters/tex/lacheck.vim @@ -0,0 +1,47 @@ +" Author: Andrew Balmos - +" Description: lacheck for LaTeX files + +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." + + let l:pattern = '^".\+", line \(\d\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + " lacheck follows `\input{}` commands. If the cwd is not the same as the + " file in the buffer then it will fail to find the inputed items. We do not + " want warnings from those items anyway + if !empty(matchstr(l:match[2], '^Could not open ".\+"$')) + continue + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'text': l:match[2], + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('tex', { +\ 'name': 'lacheck', +\ '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 new file mode 100644 index 0000000..35e764e --- /dev/null +++ b/ale_linters/tex/proselint.vim @@ -0,0 +1,9 @@ +" Author: poohzrn https://github.com/poohzrn +" Description: proselint for TeX files + +call ale#linter#Define('tex', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/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/text/proselint.vim b/ale_linters/text/proselint.vim new file mode 100644 index 0000000..281b4ff --- /dev/null +++ b/ale_linters/text/proselint.vim @@ -0,0 +1,9 @@ +" Author: poohzrn https://github.com/poohzrn +" Description: proselint for text files + +call ale#linter#Define('text', { +\ 'name': 'proselint', +\ 'executable': 'proselint', +\ 'command': 'proselint %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/text/vale.vim b/ale_linters/text/vale.vim new file mode 100644 index 0000000..60bd799 --- /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=line %t', +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) 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 new file mode 100644 index 0000000..26d26c8 --- /dev/null +++ b/ale_linters/typescript/tslint.vim @@ -0,0 +1,58 @@ +" Author: Prashanth Chandra https://github.com/prashcr +" Description: tslint for TypeScript files + +call ale#Set('typescript_tslint_executable', 'tslint') +call ale#Set('typescript_tslint_config_path', '') +call ale#Set('typescript_tslint_use_global', 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 + let l:dir = expand('#' . a:buffer . ':p:h') + let l:output = [] + + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + call add(l:output, { + \ 'filename': ale#path#GetAbsPath(l:dir, l:error.name), + \ 'type': (get(l:error, 'ruleSeverity', '') is# 'WARNING' ? 'W' : 'E'), + \ 'text': has_key(l:error, 'ruleName') + \ ? l:error.ruleName . ': ' . l:error.failure + \ : l:error.failure, + \ 'lnum': l:error.startPosition.line + 1, + \ 'col': l:error.startPosition.character + 1, + \ 'end_lnum': l:error.endPosition.line + 1, + \ 'end_col': l:error.endPosition.character + 1, + \}) + endfor + + return l:output +endfunction + +function! ale_linters#typescript#tslint#GetCommand(buffer) abort + let l:tslint_config_path = ale#path#ResolveLocalPath( + \ a:buffer, + \ 'tslint.json', + \ ale#Var(a:buffer, 'typescript_tslint_config_path') + \) + + let l:tslint_config_option = !empty(l:tslint_config_path) + \ ? ' -c ' . ale#Escape(l:tslint_config_path) + \ : '' + + return ale#path#BufferCdString(a:buffer) + \ . ale_linters#typescript#tslint#GetExecutable(a:buffer) + \ . ' --format json' + \ . l:tslint_config_option + \ . ' %t' +endfunction + +call ale#linter#Define('typescript', { +\ 'name': 'tslint', +\ '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 new file mode 100644 index 0000000..2f18691 --- /dev/null +++ b/ale_linters/typescript/typecheck.vim @@ -0,0 +1,33 @@ +" Author: Prashanth Chandra https://github.com/prashcr, Aleh Kashnikau https://github.com/mkusher +" Description: type checker for TypeScript files + +function! ale_linters#typescript#typecheck#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " hello.ts[7, 41]: Property 'a' does not exist on type 'A' + " hello.ts[16, 7]: Type 'A' is not assignable to type 'B' + " + let l:pattern = '.\+\.ts\[\(\d\+\), \(\d\+\)\]: \(.\+\)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[1] + 0 + let l:column = l:match[2] + 0 + let l:text = l:match[3] + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:column, + \ 'text': l:text, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('typescript', { +\ 'name': 'typecheck', +\ 'executable': 'typecheck', +\ 'command': 'typecheck %s', +\ 'callback': 'ale_linters#typescript#typecheck#Handle', +\}) diff --git a/ale_linters/verilog/iverilog.vim b/ale_linters/verilog/iverilog.vim new file mode 100644 index 0000000..18769d5 --- /dev/null +++ b/ale_linters/verilog/iverilog.vim @@ -0,0 +1,35 @@ +" Author: Masahiro H https://github.com/mshr-h +" Description: iverilog for verilog files + +function! ale_linters#verilog#iverilog#Handle(buffer, lines) abort + " Look for lines like the following. + " + " tb_me_top.v:37: warning: Instantiating module me_top with dangling input port 1 (rst_n) floating. + " tb_me_top.v:17: syntax error + " memory_single_port.v:2: syntax error + " tb_me_top.v:17: error: Invalid module instantiation + let l:pattern = '^[^:]\+:\(\d\+\): \(warning\|error\|syntax error\)\(: \(.\+\)\)\?' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[1] + 0 + let l:type = l:match[2] =~# 'error' ? 'E' : 'W' + let l:text = l:match[2] is# 'syntax error' ? 'syntax error' : l:match[4] + + call add(l:output, { + \ 'lnum': l:line, + \ 'text': l:text, + \ 'type': l:type, + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'iverilog', +\ 'output_stream': 'stderr', +\ 'executable': 'iverilog', +\ 'command': 'iverilog -t null -Wall %t', +\ 'callback': 'ale_linters#verilog#iverilog#Handle', +\}) diff --git a/ale_linters/verilog/verilator.vim b/ale_linters/verilog/verilator.vim new file mode 100644 index 0000000..6053da0 --- /dev/null +++ b/ale_linters/verilog/verilator.vim @@ -0,0 +1,59 @@ +" 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. + " + " %Error: addr_gen.v:3: syntax error, unexpected IDENTIFIER + " %Warning-WIDTH: addr_gen.v:26: Operator ASSIGNDLY expects 12 bits on the Assign RHS, but Assign RHS's CONST '20'h0' generates 20 bits. + " %Warning-UNUSED: test.v:3: Signal is not used: a + " %Warning-UNDRIVEN: test.v:3: Signal is not driven: clk + " %Warning-UNUSED: test.v:4: Signal is not used: dout + " %Warning-BLKSEQ: test.v:10: Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=). + let l:pattern = '^%\(Warning\|Error\)[^:]*:\([^:]\+\):\(\d\+\): \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[3] + 0 + 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, { + \ 'lnum': l:line, + \ 'text': l:text, + \ 'type': l:type, + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('verilog', { +\ 'name': 'verilator', +\ 'output_stream': 'stderr', +\ 'executable': 'verilator', +\ '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 new file mode 100644 index 0000000..adf2b4a --- /dev/null +++ b/ale_linters/vim/vint.vim @@ -0,0 +1,73 @@ +" Author: w0rp , KabbAmine +" Description: This file adds support for checking Vim code with Vint. + +" This flag can be used to change enable/disable style issues. +let g:ale_vim_vint_show_style_issues = +\ get(g:, 'ale_vim_vint_show_style_issues', 1) +let s:enable_neovim = has('nvim') ? ' --enable-neovim ' : '' +let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})"' +let s:vint_version = [] + +function! ale_linters#vim#vint#VersionCommand(buffer) abort + if empty(s:vint_version) + " Check the Vint version if we haven't checked it already. + return 'vint --version' + endif + + return '' +endfunction + +function! ale_linters#vim#vint#GetCommand(buffer, version_output) abort + if empty(s:vint_version) && !empty(a:version_output) + " Parse the version out of the --version output. + let s:vint_version = ale#semver#Parse(join(a:version_output, "\n")) + endif + + let l:can_use_no_color_flag = empty(s:vint_version) + \ || ale#semver#GreaterOrEqual(s:vint_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_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/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/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..454cad0 --- /dev/null +++ b/ale_linters/yaml/swaglint.vim @@ -0,0 +1,41 @@ +" 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], + \} + + 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 new file mode 100644 index 0000000..731f801 --- /dev/null +++ b/ale_linters/yaml/yamllint.vim @@ -0,0 +1,48 @@ +" 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) + " something.yml:2:1: [error] syntax error: expected the node content, but found '' + let l:pattern = '^.*:\(\d\+\):\(\d\+\): \[\(error\|warning\)\] \(.\+\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:line = l:match[1] + 0 + let l:col = l:match[2] + 0 + let l:type = l:match[3] + let l:text = l:match[4] + + call add(l:output, { + \ 'lnum': l:line, + \ 'col': l:col, + \ 'text': l:text, + \ 'type': l:type is# 'error' ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('yaml', { +\ 'name': 'yamllint', +\ '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 new file mode 100644 index 0000000..6941a9a --- /dev/null +++ b/autoload/ale.vim @@ -0,0 +1,219 @@ +" 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 + +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 + +" 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 + " Do nothing for blacklisted files + " OR if ALE is running in the sandbox + return index(g:ale_filetype_blacklist, &filetype) >= 0 + \ || (exists('*getcmdwintype') && !empty(getcmdwintype())) + \ || ale#util#InSandbox() + \ || !ale#Var(a:buffer, 'enabled') + \ || ale#FileTooLarge() + \ || getbufvar(a:buffer, '&l:statusline') =~# 'CtrlPMode.*funky' +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[bufnr('%')] = 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(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(-1, a:buffer) + endif +endfunction + +function! ale#Lint(...) abort + 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 + + " Use the filetype from the buffer + let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype')) + let l:should_lint_file = 0 + + " Check if we previously requested checking the file. + if has_key(s:should_lint_file_for_buffer, 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 + + 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 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..b9f9439 --- /dev/null +++ b/autoload/ale/c.vim @@ -0,0 +1,91 @@ +" Author: gagbo , w0rp +" Description: Functions for integrating with C-family linters. + +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 . '/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 . '/' . 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/command.vim b/autoload/ale/command.vim new file mode 100644 index 0000000..f8d04ff --- /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 occurences 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..9f4e3c2 --- /dev/null +++ b/autoload/ale/completion.vim @@ -0,0 +1,339 @@ +" Author: w0rp +" Description: Completion support for LSP linters + +let s:timer_id = -1 + +function! s:GetRegex(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 + + return '' +endfunction + +" 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 = { +\ 'javascript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$', +\ 'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$|\.$', +\} + +" Check if we should look for completions for a language. +function! ale#completion#GetPrefix(filetype, line, column) abort + let l:regex = s:GetRegex(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 + +" Regular expressions for finding the start column to replace with completion. +let s:omni_start_map = { +\ 'javascript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$', +\ 'typescript': '\v[a-zA-Z$_][a-zA-Z$_0-9]*$', +\} + +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:GetRegex(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#HandleTSServerLSPResponse(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! s:GetLSPCompletions(linter) abort + let l:buffer = bufnr('') + let l:lsp_details = ale#linter#StartLSP( + \ l:buffer, + \ a:linter, + \ function('ale#completion#HandleTSServerLSPResponse'), + \) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + let l:command = l:lsp_details.command + let l:root = l:lsp_details.project_root + + 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, + \) + 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 + 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 b:ale_completion_info = { + \ 'line': l:line, + \ 'column': l:column, + \ 'prefix': l:prefix, + \ 'conn_id': 0, + \ 'request_id': 0, + \} + + for l:linter in ale#linter#Get(&filetype) + if l:linter.lsp is# 'tsserver' + call s:GetLSPCompletions(l:linter) + 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 + +function! ale#completion#Queue() abort + let s:timer_pos = getcurpos()[1:2] + + " 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 + + if s:timer_id != -1 + call timer_stop(s:timer_id) + endif + + 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') + let &l:omnifunc = b:ale_old_omnifunc + unlet b:ale_old_omnifunc + endif + + if exists('b:ale_old_completopt') + let &l:completeopt = b:ale_old_completopt + unlet b:ale_old_completopt + endif +endfunction + +function! s:Setup(enabled) abort + 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 new file mode 100644 index 0000000..340432f --- /dev/null +++ b/autoload/ale/cursor.vim @@ -0,0 +1,159 @@ +" Author: w0rp +" Description: Echoes lint message for the current line, if any + +let s:cursor_timer = -1 +let s:last_pos = [0, 0, 0] +let s:error_delay_ms = 1000 * 60 * 2 + +if !exists('s:dont_queue_until') + let s:dont_queue_until = -1 +endif + +if !exists('s:dont_echo_until') + let s:dont_echo_until = -1 +endif + +" Return a formatted message according to g:ale_echo_msg_format variable +function! s:GetMessage(linter, type, text) abort + let l:msg = g:ale_echo_msg_format + let l:type = a:type is# 'E' + \ ? g:ale_echo_msg_error_str + \ : g:ale_echo_msg_warning_str + + " 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, a:text) +endfunction + +function! s:EchoWithShortMess(setting, message) abort + " We need to remember the setting for shormess and reset it again. + let l:shortmess_options = getbufvar('%', '&shortmess') + + try + " Turn shortmess on or off. + if a:setting is# 'on' + setlocal shortmess+=T + " echomsg is needed for the message to get truncated and appear in + " the message history. + exec "norm! :echomsg a:message\n" + elseif a:setting is# 'off' + setlocal shortmess-=T + " Regular echo is needed for printing newline characters. + echo a:message + else + throw 'Invalid setting: ' . string(a:setting) + endif + finally + call setbufvar('%', '&shortmess', l:shortmess_options) + endtry +endfunction + +function! ale#cursor#TruncatedEcho(message) abort + let l:message = a: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') + + call s:EchoWithShortMess('on', l:message) +endfunction + +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:index = ale#util#BinarySearch(l:loclist, l:buf, l:pos[1], l:pos[2]) + let l:loc = l:index >= 0 ? l:loclist[l:index] : {} + + return [l:info, l:loc] +endfunction + +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 ale#ShouldDoNothing(bufnr('')) + return + endif + + " Only echo the warnings in normal mode, otherwise we will get problems. + if mode() isnot# 'n' + return + endif + + let [l:info, l:loc] = s:FindItemAtCursor() + + if !empty(l:loc) + let l:msg = s:GetMessage(l:loc.linter_name, l:loc.type, l:loc.text) + 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. + echo + let l:info.echoed = 0 + endif +endfunction + +function! ale#cursor#EchoCursorWarningWithDelay() abort + return ale#CallWithCooldown( + \ 'dont_echo_with_delay_until', + \ function('s:EchoWithDelayImpl'), + \ [], + \) +endfunction + +function! s:EchoWithDelayImpl() abort + if ale#ShouldDoNothing(bufnr('')) + return + endif + + call s:StopCursorTimer() + + let l:pos = getcurpos()[0:2] + + " Check the current buffer, line, and column number against the last + " recorded position. If the position has actually changed, *then* + " we should echo something. Otherwise we can end up doing processing + " the echo message far too frequently. + if l:pos != s:last_pos + let s:last_pos = l:pos + let s:cursor_timer = timer_start(10, function('ale#cursor#EchoCursorWarning')) + endif +endfunction + +function! ale#cursor#ShowCursorDetail() abort + if ale#ShouldDoNothing(bufnr('')) + 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:info, l:loc] = s:FindItemAtCursor() + + if !empty(l:loc) + let l:message = get(l:loc, 'detail', l:loc.text) + + call s:EchoWithShortMess('off', l:message) + + " Set the echo marker, so we can clear it by moving the cursor. + let l:info.echoed = 1 + endif +endfunction diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim new file mode 100644 index 0000000..7454bb1 --- /dev/null +++ b/autoload/ale/debugging.vim @@ -0,0 +1,183 @@ +" Author: w0rp +" Description: This file implements debugging information for ALE + +let s:global_variable_list = [ +\ 'ale_echo_cursor', +\ 'ale_echo_msg_error_str', +\ 'ale_echo_msg_format', +\ 'ale_echo_msg_warning_str', +\ 'ale_enabled', +\ 'ale_fix_on_save', +\ 'ale_fixers', +\ 'ale_keep_list_window_open', +\ 'ale_lint_delay', +\ 'ale_lint_on_enter', +\ 'ale_lint_on_save', +\ 'ale_lint_on_text_changed', +\ 'ale_linter_aliases', +\ 'ale_linters', +\ 'ale_open_list', +\ 'ale_set_highlights', +\ 'ale_set_loclist', +\ 'ale_set_quickfix', +\ 'ale_set_signs', +\ 'ale_sign_column_always', +\ 'ale_sign_error', +\ 'ale_sign_offset', +\ 'ale_sign_warning', +\ 'ale_statusline_format', +\ 'ale_warn_about_trailing_whitespace', +\] + +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 + echom 'let g:' . l:key . ' = ' . string(g:[l:key]) + + if has_key(b:, l:key) + echom 'let b:' . l:key . ' = ' . string(b:[l:key]) + endif + endfor +endfunction + +function! s:EchoGlobalVariables() abort + for l:key in s:global_variable_list + echom 'let g:' . l:key . ' = ' . string(get(g:, l:key, v:null)) + + if has_key(b:, l:key) + echom '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 + + echom '(' . l:status_message . ') ' . string(a:item.command) + + if g:ale_history_log_output && has_key(a:item, 'output') + if empty(a:item.output) + echom '' + echom '<<>>' + echom '' + else + echom '' + echom '<<>>' + + for l:line in a:item.output + echom l:line + endfor + + echom '<<>>' + echom '' + endif + endif +endfunction + +" Echo the results of an executable check. +function! s:EchoExecutable(item) abort + echom printf( + \ '(executable check - %s) %s', + \ a:item.status ? 'success' : 'failure', + \ a:item.command, + \) +endfunction + +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 + echom ' Linter Aliases:' + endif + + let l:first = 0 + + echom 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) + + echom ' Current Filetype: ' . l:filetype + echom 'Available Linters: ' . string(l:all_names) + call s:EchoLinterAliases(l:all_linters) + echom ' Enabled Linters: ' . string(l:enabled_names) + echom ' Linter Variables:' + echom '' + call s:EchoLinterVariables(l:variable_list) + echom ' Global Variables:' + echom '' + call s:EchoGlobalVariables() + echom ' Command History:' + echom '' + call s:EchoCommandHistory() +endfunction + +function! ale#debugging#InfoToClipboard() abort + redir @+> + silent call ale#debugging#Info() + redir END + + echom 'ALEInfo copied to your clipboard' +endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim new file mode 100644 index 0000000..c49bc9b --- /dev/null +++ b/autoload/ale/engine.vim @@ -0,0 +1,907 @@ +" Author: w0rp +" Description: Backend execution and job management +" Executes linters in the background, using NeoVim or Vim 8 jobs + +" Stores information for each job including: +" +" linter: The linter dictionary for the job. +" buffer: The buffer number for the job. +" output: The array of lines for the output of the job. +if !has_key(s:, 'job_info_map') + let s:job_info_map = {} +endif + +" 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 has_key(s:executable_cache_map, a:executable) + return 1 + endif + + let l:result = 0 + + if executable(a:executable) + let s:executable_cache_map[a:executable] = 1 + + let l:result = 1 + 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 job IDs + " active_linter_list will hold the list of active linter names + " loclist holds the loclist items after all jobs have completed. + " temporary_file_list holds temporary files to be cleaned up + " temporary_directory_list holds temporary directories to be cleaned up + let g:ale_buffer_info[a:buffer] = { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \} + + return 1 + endif + + return 0 +endfunction + +" 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 + + " Delete files with a call akin to a plan `rm` command. + if has_key(l:info, 'temporary_file_list') + for l:filename in l:info.temporary_file_list + call delete(l:filename) + endfor + + let l:info.temporary_file_list = [] + endif + + " Delete directories like `rm -rf`. + " Directories are handled differently from files, so paths that are + " intended to be single files can be set up for automatic deletion without + " accidentally deleting entire directories. + if has_key(l:info, 'temporary_directory_list') + for l:directory in l:info.temporary_directory_list + call delete(l:directory, 'rf') + endfor + + let l:info.temporary_directory_list = [] + endif +endfunction + +function! s:GatherOutput(job_id, line) abort + if has_key(s:job_info_map, a:job_id) + call add(s:job_info_map[a:job_id].output, a:line) + endif +endfunction + +function! s:HandleLoclist(linter_name, buffer, loclist) abort + let l:buffer_info = get(g:ale_buffer_info, a:buffer, {}) + + if empty(l:buffer_info) + return + endif + + " Remove this linter from the list of active linters. + " This may have already been done when the job exits. + call filter(l:buffer_info.active_linter_list, 'v:val isnot# a:linter_name') + + " Make some adjustments to the loclists to fix common problems, and also + " to set default values for loclist items. + let l:linter_loclist = ale#engine#FixLocList(a:buffer, a:linter_name, a:loclist) + + " Remove previous items for this linter. + call filter(g:ale_buffer_info[a:buffer].loclist, 'v:val.linter_name isnot# a:linter_name') + " Add the new items. + call extend(g:ale_buffer_info[a:buffer].loclist, l:linter_loclist) + + " Sort the loclist again. + " We need a sorted list so we can run a binary search against it + " for efficient lookup of the messages in the cursor handler. + call sort(g:ale_buffer_info[a:buffer].loclist, 'ale#util#LocItemCompare') + + if ale#ShouldDoNothing(a:buffer) + return + endif + + call ale#engine#SetResults(a:buffer, g:ale_buffer_info[a:buffer].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 + + 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 + + " 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 + + let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output) + + call s:HandleLoclist(l:linter.name, l:buffer, l:loclist) +endfunction + +function! s:HandleLSPDiagnostics(conn_id, response) abort + let l:linter_name = s:lsp_linter_map[a:conn_id] + let l:filename = ale#path#FromURI(a:response.params.uri) + let l:buffer = bufnr(l:filename) + + if l:buffer <= 0 + return + endif + + let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) + + call s:HandleLoclist(l:linter_name, l:buffer, l:loclist) +endfunction + +function! s:HandleTSServerDiagnostics(response, error_type) abort + let l:buffer = bufnr(a:response.body.file) + let l:info = get(g:ale_buffer_info, l:buffer, {}) + + if empty(l:info) + return + endif + + let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) + + " tsserver sends syntax and semantic errors in separate messages, so we + " have to collect the messages separately for each buffer and join them + " back together again. + if a:error_type is# 'syntax' + let l:info.syntax_loclist = l:thislist + else + let l:info.semantic_loclist = l:thislist + endif + + let l:loclist = get(l:info, 'semantic_loclist', []) + \ + get(l:info, 'syntax_loclist', []) + + call s:HandleLoclist('tsserver', l:buffer, l:loclist) +endfunction + +function! s:HandleLSPErrorMessage(error_message) abort + echoerr 'Error from LSP:' + + for l:line in split(a:error_message, "\n") + 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(a:buffer, a:loclist) + endif + + if g:ale_set_quickfix || g:ale_set_loclist + call ale#list#SetLists(a:buffer, a:loclist) + endif + + if exists('*ale#statusline#Update') + " Don't load/run if not already loaded. + call ale#statusline#Update(a:buffer, a:loclist) + endif + + if g:ale_set_highlights + call ale#highlight#SetHighlights(a:buffer, a:loclist) + endif + + if g:ale_echo_cursor + " Try and echo the warning now. + " This will only do something meaningful if we're in normal mode. + call ale#cursor#EchoCursorWarning() + endif + + if l:linting_is_done + " Reset the save event marker, used for opening windows, etc. + call setbufvar(a:buffer, 'ale_save_event_fired', 0) + + " Automatically remove all managed temporary files and directories + " now that all jobs have completed. + call ale#engine#RemoveManagedFiles(a:buffer) + + " Call user autocommands. This allows users to hook into ALE's lint cycle. + silent doautocmd User ALELint + endif +endfunction + +function! s:RemapItemTypes(type_map, loclist) abort + for l:item in a:loclist + let l:key = l:item.type + \ . (get(l:item, 'sub_type', '') is# 'style' ? 'S' : '') + let l:new_key = get(a:type_map, l:key, '') + + if l:new_key is# 'E' + \|| l:new_key is# 'ES' + \|| l:new_key is# 'W' + \|| l:new_key is# 'WS' + \|| l:new_key is# 'I' + let l:item.type = l:new_key[0] + + if l:new_key is# 'ES' || l:new_key is# 'WS' + let l:item.sub_type = 'style' + elseif has_key(l:item, 'sub_type') + call remove(l:item, 'sub_type') + endif + endif + endfor +endfunction + +" Save the temporary directory so we can figure out if files are in it. +let s:temp_dir = fnamemodify(tempname(), ':h') + +function! ale#engine#FixLocList(buffer, linter_name, loclist) abort + let l:bufnr_map = {} + let l:new_loclist = [] + + " Some errors have line numbers beyond the end of the file, + " so we need to adjust them so they set the error at the last line + " of the file instead. + let l:last_line_number = ale#util#GetLineCount(a:buffer) + + for l:old_item in a:loclist + " Copy the loclist item with some default values and corrections. + " + " line and column numbers will be converted to numbers. + " The buffer will default to the buffer being checked. + " The vcol setting will default to 0, a byte index. + " The error type will default to 'E' for errors. + " The error number will default to -1. + " + " The line number and text are the only required keys. + " + " The linter_name will be set on the errors so it can be used in + " output, filtering, etc.. + let l:item = { + \ 'bufnr': a:buffer, + \ 'text': l:old_item.text, + \ 'lnum': str2nr(l:old_item.lnum), + \ 'col': str2nr(get(l:old_item, 'col', 0)), + \ 'vcol': get(l:old_item, 'vcol', 0), + \ 'type': get(l:old_item, 'type', 'E'), + \ 'nr': get(l:old_item, 'nr', -1), + \ 'linter_name': a:linter_name, + \} + + if has_key(l:old_item, 'filename') + \&& l:old_item.filename[:len(s:temp_dir) - 1] isnot# s:temp_dir + " Use the filename given. + " Temporary files are assumed to be for this buffer, + " and the filename is not included then, because it looks bad + " in the loclist window. + let l:filename = l:old_item.filename + let l:item.filename = l:filename + + if has_key(l:old_item, 'bufnr') + " If a buffer number is also given, include that too. + " If Vim detects that he buffer number is valid, it will + " be used instead of the filename. + let l:item.bufnr = l:old_item.bufnr + elseif has_key(l:bufnr_map, l:filename) + " Get the buffer number from the map, which can be faster. + let l:item.bufnr = l:bufnr_map[l:filename] + else + " Look up the buffer number. + let l:item.bufnr = bufnr(l:filename) + let l:bufnr_map[l:filename] = l:item.bufnr + endif + elseif has_key(l:old_item, 'bufnr') + let l:item.bufnr = l:old_item.bufnr + endif + + if has_key(l:old_item, 'detail') + let l:item.detail = l:old_item.detail + endif + + " 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 + + 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: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[l:job_id] = { + \ 'linter': l:linter, + \ 'buffer': l:buffer, + \ 'output': [], + \ 'next_chain_index': l:next_chain_index, + \} + 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 + +" 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, + " so that many programs can be run in a sequence. + let l:chain_item = a:linter.command_chain[l:chain_index] + + if l:chain_index == 0 + " The first callback in the chain takes only a buffer number. + let l:command = ale#util#GetFunction(l:chain_item.callback)( + \ a:buffer + \) + else + " The second callback in the chain takes some input too. + let l:command = ale#util#GetFunction(l:chain_item.callback)( + \ a:buffer, + \ l:input + \) + endif + + if !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 + + " Command chain items can return an empty string to indicate that + " a command should be skipped, so we should try the next item + " with no input. + let l:input = [] + let l:chain_index += 1 + endwhile + else + let l:command = ale#linter#GetCommand(a:buffer, a:linter) + endif + + 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:read_buffer, + \} +endfunction + +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 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) + + 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 + if !has_key(g:ale_buffer_info, a:buffer) + return + endif + + call ale#engine#RunLinters(a:buffer, [], 1) + + call remove(g:ale_buffer_info, a:buffer) +endfunction + +" Given a buffer number, return the warnings and errors for a given buffer. +function! ale#engine#GetLoclist(buffer) abort + if !has_key(g:ale_buffer_info, a:buffer) + return [] + endif + + return g:ale_buffer_info[a:buffer].loclist +endfunction + +" This function can be called with a timeout to wait for all jobs to finish. +" If the jobs to not finish in the given number of milliseconds, +" an exception will be thrown. +" +" The time taken will be a very rough approximation, and more time may be +" permitted than is specified. +function! ale#engine#WaitForJobs(deadline) abort + let l:start_time = ale#util#ClockMilliseconds() + + if l:start_time == 0 + throw 'Failed to read milliseconds from the clock!' + endif + + let l:job_list = [] + + " Gather all of the jobs from every buffer. + for l:info in values(g:ale_buffer_info) + call extend(l:job_list, l:info.job_list) + endfor + + " NeoVim has a built-in API for this, so use that. + if has('nvim') + let l:nvim_code_list = jobwait(l:job_list, a:deadline) + + if index(l:nvim_code_list, -1) >= 0 + throw 'Jobs did not complete on time!' + endif + + return + endif + + let l:should_wait_more = 1 + + while l:should_wait_more + let l:should_wait_more = 0 + + for l:job_id in l:job_list + if ale#job#IsRunning(l:job_id) + let l:now = ale#util#ClockMilliseconds() + + if l:now - l:start_time > a:deadline + " Stop waiting after a timeout, so we don't wait forever. + throw 'Jobs did not complete on time!' + endif + + " Wait another 10 milliseconds + let l:should_wait_more = 1 + sleep 10ms + break + endif + endfor + endwhile + + " Sleep for a small amount of time after all jobs finish. + " This seems to be enough to let handlers after jobs end run, and + " prevents the occasional failure where this function exits after jobs + " end, but before handlers are run. + sleep 10ms + + " We must check the buffer data again to see if new jobs started + " for command_chain linters. + let l:has_new_jobs = 0 + + " Check again to see if any jobs are running. + for l:info in values(g:ale_buffer_info) + for l:job_id in l:info.job_list + if ale#job#IsRunning(l:job_id) + let l:has_new_jobs = 1 + break + endif + endfor + endfor + + if l:has_new_jobs + " We have to wait more. Offset the timeout by the time taken so far. + let l:now = ale#util#ClockMilliseconds() + let l:new_deadline = a:deadline - (l:now - l:start_time) + + if l:new_deadline <= 0 + " Enough time passed already, so stop immediately. + throw 'Jobs did not complete on time!' + endif + + call ale#engine#WaitForJobs(l:new_deadline) + endif +endfunction diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim new file mode 100644 index 0000000..a3b7467 --- /dev/null +++ b/autoload/ale/events.vim @@ -0,0 +1,47 @@ +" Author: w0rp + +function! ale#events#SaveEvent(buffer) abort + call setbufvar(a:buffer, 'ale_save_event_fired', 1) + let l:should_lint = ale#Var(a:buffer, 'enabled') && g:ale_lint_on_save + + if g:ale_fix_on_save + let l:will_fix = ale#fix#Fix('save_file') + let l:should_lint = l:should_lint && !l:will_fix + endif + + if l:should_lint + 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 + let l:filetype = getbufvar(a:buffer, '&filetype') + call setbufvar(a:buffer, 'ale_original_filetype', l:filetype) + + call s:LintOnEnter(a:buffer) +endfunction + +function! ale#events#FileTypeEvent(buffer, new_filetype) abort + let l:filetype = getbufvar(a:buffer, 'ale_original_filetype', '') + + if a:new_filetype isnot# l:filetype + call ale#Queue(300, 'lint_file', a:buffer) + endif +endfunction + +function! ale#events#FileChangedEvent(buffer) abort + call setbufvar(a:buffer, 'ale_file_changed', 1) + + 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..80f46c2 --- /dev/null +++ b/autoload/ale/fix.vim @@ -0,0 +1,377 @@ +" 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 + 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) + 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) + + if has_key(l:job_info, 'file_to_read') + let l:job_info.output = readfile(l:job_info.file_to_read) + 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. + let l:input = !empty(l:job_info.output) + \ ? l:job_info.output + \ : l:job_info.input + + call s:RunFixer({ + \ 'buffer': l:job_info.buffer, + \ 'input': l:input, + \ 'callback_list': l:job_info.callback_list, + \ 'callback_index': l:job_info.callback_index + 1, + \}) +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:temporary_file, l:command] = ale#command#FormatCommand(l:buffer, l:command, 1) + call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input) + + let l:command = ale#job#PrepareCommand(l:command) + let l:job_options = { + \ 'mode': 'nl', + \ 'exit_cb': function('s:HandleExit'), + \} + + let l:job_info = { + \ 'buffer': l:buffer, + \ 'input': l:input, + \ 'output': [], + \ 'callback_list': a:options.callback_list, + \ 'callback_index': a:options.callback_index, + \} + + 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 + + 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 + + while len(a:options.callback_list) > l:index + let l:Function = a:options.callback_list[l:index] + + let l:result = ale#util#FunctionArgCount(l:Function) == 1 + \ ? call(l:Function, [l:buffer]) + \ : call(l:Function, [l:buffer, copy(l:input)]) + + 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: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), + \ 'callback_list': a:options.callback_list, + \ 'callback_index': l:index, + \}) + + 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 + 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 + + 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 + + call add(l:corrected_list, ale#util#GetFunction(l:Item)) + 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 + + let l:callback_list = s:GetCallbacks() + + if empty(l:callback_list) + if l:fixing_flag is# '' + echoerr '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..b77ac03 --- /dev/null +++ b/autoload/ale/fix/registry.vim @@ -0,0 +1,206 @@ +" 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.', +\ }, +\ 'eslint': { +\ 'function': 'ale#fixers#eslint#Fix', +\ 'suggested_filetypes': ['javascript', 'typescript'], +\ 'description': 'Apply eslint --fix 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'], +\ 'description': 'Apply prettier to a file.', +\ }, +\ 'prettier_eslint': { +\ 'function': 'ale#fixers#prettier_eslint#Fix', +\ 'suggested_filetypes': ['javascript'], +\ 'description': 'Apply prettier-eslint to a file.', +\ }, +\ '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.', +\ }, +\ '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.', +\ }, +\ 'clang-format': { +\ 'function': 'ale#fixers#clangformat#Fix', +\ 'suggested_filetypes': ['c', 'cpp'], +\ 'description': 'Fix C/C++ files with clang-format.', +\ }, +\} + +" Reset the function registry to the default entries. +function! ale#fix#registry#ResetToDefaults() abort + let s:entries = deepcopy(s:default_registry) +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 = {} +endfunction + +" Add a function for fixing problems to the registry. +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 s:entries[a:name] = { + \ 'function': a:func, + \ 'suggested_filetypes': a:filetypes, + \ 'description': a:desc, + \} +endfunction + +" Get a function from the registry by its short name. +function! ale#fix#registry#GetFunc(name) abort + return get(s:entries, a: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 + +" 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, + \ printf('%s - %s', string(l:key), s:entries[l:key].description), + \) + 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, + \ printf('%s - %s', string(l:key), s:entries[l:key].description), + \) + 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/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..892b30d --- /dev/null +++ b/autoload/ale/fixers/eslint.vim @@ -0,0 +1,37 @@ +" Author: w0rp +" Description: Fixing files with eslint. + +function! s:FindConfig(buffer) abort + for l:filename in [ + \ '.eslintrc.js', + \ '.eslintrc.yaml', + \ '.eslintrc.yml', + \ '.eslintrc.json', + \ '.eslintrc', + \ 'package.json', + \] + let l:config = ale#path#FindNearestFile(a:buffer, l:filename) + + if !empty(l:config) + return l:config + endif + endfor + + return '' +endfunction + +function! ale#fixers#eslint#Fix(buffer) abort + let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) + let l:config = s:FindConfig(a:buffer) + + if empty(l:config) + return 0 + endif + + return { + \ 'command': ale#node#Executable(a:buffer, l:executable) + \ . ' --config ' . ale#Escape(l:config) + \ . ' --fix %t', + \ '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..fdc8eab --- /dev/null +++ b/autoload/ale/fixers/generic.vim @@ -0,0 +1,12 @@ +" 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 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/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/isort.vim b/autoload/ale/fixers/isort.vim new file mode 100644 index 0000000..00d968f --- /dev/null +++ b/autoload/ale/fixers/isort.vim @@ -0,0 +1,26 @@ +" 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 + + let l:config = ale#path#FindNearestFile(a:buffer, '.isort.cfg') + let l:config_options = !empty(l:config) + \ ? ' --settings-path ' . ale#Escape(l:config) + \ : '' + + return { + \ 'command': ale#Escape(l:executable) . l:config_options . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/phpcbf.vim b/autoload/ale/fixers/phpcbf.vim new file mode 100644 index 0000000..9bff741 --- /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..ae370ac --- /dev/null +++ b/autoload/ale/fixers/prettier.vim @@ -0,0 +1,26 @@ +" Author: tunnckoCore (Charlike Mike Reagent) , +" w0rp +" 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/prettier-cli/index.js', + \ 'node_modules/.bin/prettier', + \]) +endfunction + +function! ale#fixers#prettier#Fix(buffer) abort + let l:options = ale#Var(a:buffer, 'javascript_prettier_options') + + return { + \ 'command': ale#Escape(ale#fixers#prettier#GetExecutable(a:buffer)) + \ . ' %t' + \ . ' ' . 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..ed5dc96 --- /dev/null +++ b/autoload/ale/fixers/prettier_eslint.vim @@ -0,0 +1,26 @@ +" Author: tunnckoCore (Charlike Mike Reagent) , +" w0rp +" Description: Integration between Prettier and ESLint. + +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', '') + +function! ale#fixers#prettier_eslint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_prettier_eslint', [ + \ 'node_modules/prettier-eslint-cli/index.js', + \ 'node_modules/.bin/prettier-eslint', + \]) +endfunction + +function! ale#fixers#prettier_eslint#Fix(buffer, lines) abort + let l:options = ale#Var(a:buffer, 'javascript_prettier_eslint_options') + + return { + \ 'command': ale#Escape(ale#fixers#prettier_eslint#GetExecutable(a:buffer)) + \ . ' %t' + \ . ' ' . 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/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/standard.vim b/autoload/ale/fixers/standard.vim new file mode 100644 index 0000000..443560e --- /dev/null +++ b/autoload/ale/fixers/standard.vim @@ -0,0 +1,19 @@ +" Author: Sumner Evans +" Description: Fixing files with Standard. + +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/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..89b56a8 --- /dev/null +++ b/autoload/ale/gradle.vim @@ -0,0 +1,54 @@ +" Author: Michael Pardo +" Description: Functions for working with Gradle projects. + +let s:script_path = fnamemodify(resolve(expand(':p')), ':h') + +" 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) + \ . l:executable . ' -I ' . s:script_path . '/gradle/init.gradle -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/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..4607863 --- /dev/null +++ b/autoload/ale/handlers/cpplint.vim @@ -0,0 +1,20 @@ +" 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': l:match[2], + \ '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..4c1b81c --- /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:text = l:match[4] + let l:type = l:match[3] + + let l:group_match = matchlist(l:text, '\v^(.+) \((.+)\)$') + + " Put the error group at the front, so we can see what kind of error + " it is on small echo lines. + if !empty(l:group_match) + let l:text = '(' . l:group_match[2] . ') ' . l:group_match[1] + endif + + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:text, + \ 'type': l:type is# 'Warning' ? 'W' : 'E', + \}) + 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] . ' [' . 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..4ef7489 --- /dev/null +++ b/autoload/ale/handlers/eslint.vim @@ -0,0 +1,109 @@ +" Author: w0rp +" Description: Functions for working with eslint, for checking or fixing files. + +call ale#Set('javascript_eslint_options', '') +call ale#Set('javascript_eslint_executable', 'eslint') +call ale#Set('javascript_eslint_use_global', 0) +call ale#Set('javascript_eslint_suppress_eslintignore', 0) + +function! ale#handlers#eslint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'javascript_eslint', [ + \ '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! ale#handlers#eslint#Handle(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] + if len(matchlist(l:line, l:config_error_pattern)) > 0 + return [{ + \ 'lnum': 1, + \ 'text': 'eslint configuration error (type :ALEDetail for more information)', + \ 'detail': join(a:lines, "\n"), + \}] + endif + endfor + + " 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:type = 'Error' + let l:text = l:match[3] + + if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore') + if l:text is# 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.' + continue + endif + endif + + " Take the error type from the output if available. + if !empty(l:match[4]) + let l:type = split(l:match[4], '/')[0] + let l:text .= ' [' . l:match[4] . ']' + endif + + let l:obj = { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:text, + \ 'type': l:type is# 'Warning' ? 'W' : 'E', + \} + + 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..ad5cab3 --- /dev/null +++ b/autoload/ale/handlers/gcc.vim @@ -0,0 +1,121 @@ +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:AddIncludedErrors(output, include_lnum, include_lines) abort + if a:include_lnum > 0 + call add(a:output, { + \ 'lnum': a:include_lnum, + \ 'type': 'E', + \ 'text': 'Problems were found in the header (See :ALEDetail)', + \ 'detail': join(a:include_lines, "\n"), + \}) + endif +endfunction + +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#ParseGCCVersion(lines) abort + for l:line in a:lines + let l:match = matchstr(l:line, '\d\.\d\.\d') + + if !empty(l:match) + return ale#semver#Parse(l:match) + endif + endfor + + return [] +endfunction + +function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort + let l:include_pattern = '\v^(In file included | *)from ([^:]*):(\d+)' + let l:include_lnum = 0 + let l:include_lines = [] + let l:included_filename = '' + " 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:line in a:lines + let l:match = matchlist(l:line, l:pattern) + + if empty(l:match) + " Check for matches in includes. + " We will keep matching lines until we hit the last file, which + " is our file. + let l:include_match = matchlist(l:line, l:include_pattern) + + if empty(l:include_match) + " If this isn't another include header line, then we + " need to collect it. + call add(l:include_lines, l:line) + else + " GCC and clang return the lists of files in different orders, + " so we'll only grab the line number from lines which aren't + " header files. + if !s:IsHeaderFile(l:include_match[2]) + " Get the line number out of the parsed include line, + " and reset the other variables. + let l:include_lnum = str2nr(l:include_match[3]) + endif + + let l:include_lines = [] + let l:included_filename = '' + endif + elseif l:include_lnum > 0 + \&& (empty(l:included_filename) || l:included_filename is# l:match[1]) + " If we hit the first error after an include header, or the + " errors below have the same name as the first filename we see, + " then include these lines, and remember what that filename was. + let l:included_filename = l:match[1] + call add(l:include_lines, l:line) + else + " If we hit a regular error again, then add the previously + " collected lines as one error, and reset the include variables. + call s:AddIncludedErrors(l:output, l:include_lnum, l:include_lines) + let l:include_lnum = 0 + let l:include_lines = [] + let l:included_filename = '' + + if s:IsHeaderFile(bufname(bufnr(''))) + \&& l:match[5][:len(s:pragma_error) - 1] is# s:pragma_error + continue + endif + + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'type': l:match[4] =~# 'error' ? 'E' : 'W', + \ 'text': s:RemoveUnicodeQuotes(l:match[5]), + \} + + if !empty(l:match[3]) + let l:item.col = str2nr(l:match[3]) + endif + + call add(l:output, l:item) + endif + endfor + + " Add remaining include errors after we go beyond the last line. + call s:AddIncludedErrors(l:output, l:include_lnum, l:include_lines) + + return l:output +endfunction diff --git a/autoload/ale/handlers/haskell.vim b/autoload/ale/handlers/haskell.vim new file mode 100644 index 0000000..bac5f4a --- /dev/null +++ b/autoload/ale/handlers/haskell.vim @@ -0,0 +1,63 @@ +" Author: w0rp +" Description: Error handling for the format GHC outputs. + +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:pattern = '\v^([a-zA-Z]?:?[^:]+):(\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 l:line is# '' + 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 + + 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 + + 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 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/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 new file mode 100644 index 0000000..12a5a16 --- /dev/null +++ b/autoload/ale/handlers/rust.vim @@ -0,0 +1,85 @@ +" Author: Daniel Schemala , +" w0rp +" +" Description: This file implements handlers specific to Rust. + +if !exists('g:ale_rust_ignore_error_codes') + let g:ale_rust_ignore_error_codes = [] +endif + +" returns: a list [lnum, col] with the location of the error or [] +function! s:FindErrorInExpansion(span, buffer) abort + if ale#path#IsBufferPath(a:buffer, a:span.file_name) + return [a:span.line_start, a:span.line_end, a:span.byte_start, a:span.byte_end] + endif + + if !empty(a:span.expansion) + return s:FindErrorInExpansion(a:span.expansion.span, a:buffer) + endif + + 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:output = [] + + for l:errorline in a:lines + " ignore everything that is not Json + if l:errorline !~# '^{' + continue + endif + + let l:error = json_decode(l:errorline) + + if has_key(l:error, 'message') && type(l:error.message) == type({}) + let l:error = l:error.message + endif + + if !has_key(l:error, 'code') + continue + endif + + if !empty(l:error.code) && index(g:ale_rust_ignore_error_codes, l:error.code.code) > -1 + continue + endif + + for l:span in l:error.spans + if ( + \ l:span.is_primary + \ && (ale#path#IsBufferPath(a:buffer, l:span.file_name) || l:span.file_name is# '') + \) + call add(l:output, { + \ 'lnum': l:span.line_start, + \ 'end_lnum': l:span.line_end, + \ 'col': l:span.byte_start, + \ 'end_col': l:span.byte_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, a:buffer) + + if !empty(l:root_cause) + call add(l:output, { + \ 'lnum': l:root_cause[0], + \ 'end_lnum': l:root_cause[1], + \ 'col': l:root_cause[2], + \ 'end_col': l:root_cause[3], + \ '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..894879e --- /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', '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/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/highlight.vim b/autoload/ale/highlight.vim new file mode 100644 index 0000000..5c01e7a --- /dev/null +++ b/autoload/ale/highlight.vim @@ -0,0 +1,119 @@ +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 = 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 +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 = 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..1d8b676 --- /dev/null +++ b/autoload/ale/job.vim @@ -0,0 +1,311 @@ +" Author: w0rp +" Deciption: 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 + 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 . a:data[0] + endif + + if a:mode is# 'raw' + if !empty(l:lines) + call a:callback(a:job, join(l:lines, "\n") . "\n") + endif + else + for l:line in l:lines + call a:callback(a:job, l:line) + endfor + endif + + 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! ale#job#PrepareCommand(command) abort + " 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 thes same exact command for both + " versions. + if ale#Has('win32') + return 'cmd /c ' . a:command + endif + + if &shell =~? 'fish$' + return ['/bin/sh', '-c', a:command] + endif + + return split(&shell) + split(&shellcmdflag) + [a: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. + 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 new file mode 100644 index 0000000..2cd773f --- /dev/null +++ b/autoload/ale/linter.vim @@ -0,0 +1,439 @@ +" Author: w0rp +" Description: Linter registration and lazy-loading +" Retrieves linters as requested by the engine, loading them if needed. + +let s:linters = {} + +" Default filetype aliases. +" The user defined aliases will be merged with this Dictionary. +let s:default_ale_linter_aliases = { +\ 'Dockerfile': 'dockerfile', +\ 'csh': 'sh', +\ 'plaintex': 'tex', +\ 'systemverilog': 'verilog', +\ 'zsh': 'sh', +\} + +" Default linters to run for particular filetypes. +" The user defined linter selections will be merged with this Dictionary. +" +" No linters are used for plaintext files by default. +" +" Only cargo is enabled for Rust by default. +" rpmlint is disabled by default because it can result in code execution. +let s:default_ale_linters = { +\ 'csh': ['shell'], +\ 'go': ['gofmt', 'golint', 'go vet'], +\ 'help': [], +\ 'python': ['flake8', 'mypy', 'pylint'], +\ 'rust': ['cargo'], +\ 'spec': [], +\ 'text': [], +\ 'zsh': ['shell'], +\} + +" Testing/debugging helper to unload all linters. +function! ale#linter#Reset() abort + let s:linters = {} +endfunction + +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'), + \ 'lsp': get(a:linter, 'lsp', ''), + \} + + if type(l:obj.name) != type('') + throw '`name` must be defined to name the linter' + endif + + let l:needs_address = l:obj.lsp is# 'socket' + let l:needs_executable = l:obj.lsp isnot# 'socket' + let l:needs_command = l:obj.lsp isnot# 'socket' + let l:needs_lsp_details = !empty(l:obj.lsp) + + if empty(l:obj.lsp) + let l:obj.callback = get(a:linter, 'callback') + + if !s:IsCallback(l:obj.callback) + throw '`callback` must be defined with a callback to accept output' + endif + endif + + if index(['', 'socket', 'stdio', 'tsserver'], l:obj.lsp) < 0 + throw '`lsp` must be either `''lsp''` 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) + throw '`executable_callback` must be a callback if defined' + endif + elseif has_key(a:linter, 'executable') + let l:obj.executable = a:linter.executable + + if type(l:obj.executable) != type('') + throw '`executable` must be a string if defined' + endif + else + throw 'Either `executable` or `executable_callback` must be defined' + endif + + 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([]) + throw '`command_chain` must be a List' + endif + + if empty(l:obj.command_chain) + throw '`command_chain` must contain at least one item' + endif + + let l:link_index = 0 + + for l:link in l:obj.command_chain + let l:err_prefix = 'The `command_chain` item ' . l:link_index . ' ' + + if !s:IsCallback(get(l:link, 'callback')) + throw l:err_prefix . 'must define a `callback` function' + endif + + if has_key(l:link, 'output_stream') + if type(l:link.output_stream) != type('') + \|| index(['stdout', 'stderr', 'both'], l:link.output_stream) < 0 + throw l:err_prefix . '`output_stream` flag must be ' + \ . "'stdout', 'stderr', or 'both'" + 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') + let l:obj.command_callback = a:linter.command_callback + + if !s:IsCallback(l:obj.command_callback) + throw '`command_callback` must be a callback if defined' + endif + elseif has_key(a:linter, 'command') + let l:obj.command = a:linter.command + + if type(l:obj.command) != type('') + throw '`command` must be a string if defined' + endif + else + throw 'Either `command`, `executable_callback`, `command_chain` ' + \ . '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('') + \|| index(['stdout', 'stderr', 'both'], l:obj.output_stream) < 0 + throw "`output_stream` must be 'stdout', 'stderr', or 'both'" + endif + + " An option indicating that this linter should only be run against the + " file on disk. + let l:obj.lint_file = get(a:linter, 'lint_file', 0) + + if !s:IsBoolean(l:obj.lint_file) + 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 + +function! ale#linter#Define(filetype, linter) abort + if !has_key(s:linters, a:filetype) + let s:linters[a:filetype] = [] + endif + + let l:new_linter = ale#linter#PreProcess(a:linter) + + call add(s:linters[a:filetype], l:new_linter) +endfunction + +function! ale#linter#GetAll(filetypes) abort + let l:combined_linters = [] + + for l:filetype in a:filetypes + " Load linter defintions from files if we haven't loaded them yet. + if !has_key(s:linters, l:filetype) + execute 'silent! runtime! ale_linters/' . l:filetype . '/*.vim' + + " Always set an empty List for the loaded linters if we don't find + " any. This will prevent us from executing the runtime command + " many times, redundantly. + if !has_key(s:linters, l:filetype) + let s:linters[l:filetype] = [] + endif + endif + + call extend(l:combined_linters, get(s:linters, l:filetype, [])) + endfor + + return l:combined_linters +endfunction + +function! s:GetAliasedFiletype(original_filetype) abort + " 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 [ + \ get(b:, 'ale_linter_aliases', {}), + \ g:ale_linter_aliases, + \ s:default_ale_linter_aliases, + \] + if has_key(l:dict, a:original_filetype) + return l:dict[a:original_filetype] + endif + endfor + + return a:original_filetype +endfunction + +function! ale#linter#ResolveFiletype(original_filetype) abort + let l:filetype = s:GetAliasedFiletype(a:original_filetype) + + if type(l:filetype) != type([]) + return [l:filetype] + endif + + return l:filetype +endfunction + +function! s:GetLinterNames(original_filetype) abort + for l:dict in [ + \ get(b:, 'ale_linters', {}), + \ g:ale_linters, + \ s:default_ale_linters, + \] + if has_key(l:dict, a:original_filetype) + return l:dict[a:original_filetype] + endif + endfor + + return 'all' +endfunction + +function! ale#linter#Get(original_filetypes) abort + let l:possibly_duplicated_linters = [] + + " Handle dot-seperated 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( + \ 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 new file mode 100644 index 0000000..bc8d411 --- /dev/null +++ b/autoload/ale/list.vim @@ -0,0 +1,171 @@ +" 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') is# 'quickfix' + return 1 + endif + endfor + return 0 +endfunction + +" Check if we should open the list, based on the save event being fired, and +" that setting being on, or the setting just being set to `1`. +function! s:ShouldOpen(buffer) abort + let l:val = ale#Var(a:buffer, 'open_list') + let l:saved = getbufvar(a:buffer, 'ale_save_event_fired', 0) + + return l:val is 1 || (l:val is# 'on_save' && l:saved) +endfunction + +function! ale#list#GetCombinedList() abort + let l:list = [] + + for l:info in values(g:ale_buffer_info) + call extend(l:list, l:info.loclist) + endfor + + call sort(l:list, function('ale#util#LocItemCompareWithText')) + call uniq(l:list, function('ale#util#LocItemCompareWithText')) + + return l:list +endfunction + +function! s:FixList(list) abort + let l:new_list = [] + + for l:item in a:list + if l:item.bufnr == -1 + " If the buffer number is invalid, remove it. + let l:fixed_item = copy(l:item) + call remove(l:fixed_item, 'bufnr') + else + " Don't copy the Dictionary if we do not need to. + let l:fixed_item = l:item + endif + + call add(l:new_list, l:fixed_item) + endfor + + return l:new_list +endfunction + +function! s: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 + let l:quickfix_list = ale#list#GetCombinedList() + + if has('nvim') + call setqflist(s:FixList(l:quickfix_list), ' ', l:title) + else + call setqflist(s:FixList(l:quickfix_list)) + call setqflist([], 'r', {'title': l:title}) + endif + elseif g:ale_set_loclist + " If windows support is off, bufwinid() may not exist. + " We'll set result in the current window, which might not be correct, + " but is better than nothing. + let l:win_id = s:BufWinId(a:buffer) + + if has('nvim') + call setloclist(l:win_id, s:FixList(a:loclist), ' ', l:title) + else + call setloclist(l:win_id, s:FixList(a:loclist)) + call setloclist(l:win_id, [], 'r', {'title': l:title}) + endif + endif + + " 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# "\" + + if g:ale_set_quickfix + if !ale#list#IsQuickfixOpen() + execute 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) + endif + elseif g:ale_set_loclist + execute 'lopen ' . str2nr(ale#Var(a:buffer, 'list_window_size')) + endif + + " If focus changed, restore it (jump to the last window). + if l:winnr isnot# winnr() + wincmd p + endif + + 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 new file mode 100644 index 0000000..7ed9e6b --- /dev/null +++ b/autoload/ale/loclist_jumping.vim @@ -0,0 +1,87 @@ +" Author: w0rp +" Description: This file implements functions for jumping around in a file +" based on ALE's internal loclist. + +" Search for the nearest line either before or after the current position +" in the loclist. The argument 'wrap' can be passed to enable wrapping +" around the end of the list. +" +" If there are no items or we have hit the end with wrapping off, an empty +" List will be returned, otherwise a pair of [line_number, column_number] will +" be returned. +function! ale#loclist_jumping#FindNearest(direction, wrap) abort + let l:buffer = bufnr('') + let l:pos = 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 is# 'before' + call reverse(l:loclist) + endif + + " Look for items before or after the current position. + for l:item in l:loclist + " Compare the cursor with a item where the column number is bounded, + " such that it's possible for the cursor to actually be on the given + " column number, without modifying the cursor number we return. This + " will allow us to move through matches, but still let us move the + " cursor to a line without changing the column, in some cases. + let l:cmp_value = ale#util#LocItemCompare( + \ { + \ 'bufnr': l:buffer, + \ 'lnum': l:item.lnum, + \ 'col': min([ + \ max([l:item.col, 1]), + \ max([len(getline(l:item.lnum)), 1]), + \ ]), + \ }, + \ l:search_item + \) + + if a:direction is# 'before' && l:cmp_value < 0 + return [l:item.lnum, l:item.col] + endif + + if a:direction is# 'after' && l:cmp_value > 0 + return [l:item.lnum, l:item.col] + endif + endfor + + " If we found nothing, and the wrap option is set to 1, then we should + " wrap around the list of warnings/errors + if a:wrap && !empty(l:loclist) + let l:item = l:loclist[0] + + return [l:item.lnum, l:item.col] + endif + + return [] +endfunction + +" As before, find the nearest match, but position the cursor at it. +function! ale#loclist_jumping#Jump(direction, wrap) abort + let l:nearest = ale#loclist_jumping#FindNearest(a:direction, a:wrap) + + if !empty(l:nearest) + 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..b6c890c --- /dev/null +++ b/autoload/ale/lsp.vim @@ -0,0 +1,407 @@ +" 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 = { + \ 'id': v:null, + \ 'jsonrpc': '2.0', + \ 'method': a:message[1], + \} + + 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 + +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..7910247 --- /dev/null +++ b/autoload/ale/lsp/message.vim @@ -0,0 +1,88 @@ +" 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"), + \ }, + \}] +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")}] + \}] +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 diff --git a/autoload/ale/lsp/response.vim b/autoload/ale/lsp/response.vim new file mode 100644 index 0000000..13219ef --- /dev/null +++ b/autoload/ale/lsp/response.vim @@ -0,0 +1,66 @@ +" 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 + + 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..ab18d74 --- /dev/null +++ b/autoload/ale/lsp/tsserver_message.vim @@ -0,0 +1,55 @@ +" 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"), + \}] +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 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..bc026cc --- /dev/null +++ b/autoload/ale/path.vim @@ -0,0 +1,179 @@ +" Author: w0rp +" Description: Functions for working with paths in the filesystem. + +function! ale#path#Simplify(path) abort + " //foo is turned into /foo to stop Windows doing stupid things with + " search paths. + return substitute(simplify(a: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: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: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 + " Check for /foo and C:\foo, etc. + return a:filename[:0] is# '/' || a:filename[1:2] is# ':\' +endfunction + +" Given a filename, return 1 if the file represents some temporary file +" created by Vim. +function! ale#path#IsTempName(filename) abort + let l:prefix_list = [ + \ $TMPDIR, + \ resolve($TMPDIR), + \ '/run/user', + \] + + for l:prefix in l:prefix_list + if a:filename[:len(l:prefix) - 1] is# l:prefix + return 1 + endif + endfor + + return 0 +endfunction + +" Given a base directory, which must not have a trailing slash, and a +" filename, which may have an absolute path a path relative to the base +" directory, return the absolute path to the file. +function! ale#path#GetAbsPath(base_directory, filename) abort + if ale#path#IsAbsolute(a:filename) + return a:filename + endif + + let l:sep = has('win32') ? '\' : '/' + + return ale#path#Simplify(a:base_directory . l:sep . a:filename) +endfunction + +" Given a buffer number and a relative or absolute path, return 1 if the +" two paths represent the same file on disk. +function! ale#path#IsBufferPath(buffer, complex_filename) abort + " If the path is one of many different names for stdin, we have a match. + if a:complex_filename 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 = ale#Has('win32') ? '\v/+|\\+' : '\v/+' + let l:sep = ale#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 ale#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 + + return ale#uri#Decode(l:encoded_path) +endfunction diff --git a/autoload/ale/pattern_options.vim b/autoload/ale/pattern_options.vim new file mode 100644 index 0000000..a603c98 --- /dev/null +++ b/autoload/ale/pattern_options.vim @@ -0,0 +1,22 @@ +" Author: w0rp +" Description: Set options in files based on regex patterns. + +function! ale#pattern_options#SetOptions() abort + let l:filename = expand('%:p') " no-custom-checks + let l:options = {} + + for l:pattern in keys(g:ale_pattern_options) + if match(l:filename, l:pattern) >= 0 + let l:options = g:ale_pattern_options[l:pattern] + break + endif + endfor + + for l:key in keys(l:options) + if l:key[:0] is# '&' + call setbufvar(bufnr(''), l:key, l:options[l:key]) + else + let b:[l:key] = l:options[l:key] + endif + endfor +endfunction diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim new file mode 100644 index 0000000..ed5064d --- /dev/null +++ b/autoload/ale/python.vim @@ -0,0 +1,93 @@ +" Author: w0rp +" Description: Functions for integrating with Python linters. + +" 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', +\]) + + +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') + 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 directorty +" 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(l:path . '/' . l:dirname) + + if filereadable(ale#path#Simplify(l:venv_dir . '/' . s:bin_dir . '/activate')) + return l:venv_dir + endif + endfor + endfor + + return '' +endfunction + +" Given a buffer number and a command name, find the path to the executable. +" First search on a virtualenv for Python, if nothing is found, try the global +" command. Returns an empty string if cannot find the executable +function! ale#python#FindExecutable(buffer, base_var_name, path_list) abort + if ale#Var(a:buffer, a:base_var_name . '_use_global') + return ale#Var(a:buffer, a:base_var_name . '_executable') + endif + + let l:virtualenv = ale#python#FindVirtualenv(a:buffer) + + if !empty(l:virtualenv) + for l:path in a:path_list + let l:ve_executable = ale#path#Simplify(l:virtualenv . '/' . s:bin_dir . '/' . l:path) + + 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 new file mode 100644 index 0000000..b153dd1 --- /dev/null +++ b/autoload/ale/semver.vim @@ -0,0 +1,29 @@ +" Given some text, parse a semantic versioning string from the text +" into a triple of integeers [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 empty(l:match) + return [] + endif + + return [l:match[1] + 0, l:match[2] + 0, l:match[3] + 0] +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 + 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] + endif + endif + + return 0 +endfunction diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim new file mode 100644 index 0000000..7ba8364 --- /dev/null +++ b/autoload/ale/sign.vim @@ -0,0 +1,353 @@ +scriptencoding utf8 +" Author: w0rp +" Description: Draws error and warning signs into signcolumn + +if !hlexists('ALEErrorSign') + highlight link ALEErrorSign error +endif + +if !hlexists('ALEStyleErrorSign') + highlight link ALEStyleErrorSign ALEErrorSign +endif + +if !hlexists('ALEWarningSign') + highlight link ALEWarningSign todo +endif + +if !hlexists('ALEStyleWarningSign') + highlight link ALEStyleWarningSign ALEWarningSign +endif + +if !hlexists('ALEInfoSign') + highlight link ALEInfoSign ALEWarningSign +endif + +if !hlexists('ALESignColumnWithErrors') + highlight link ALESignColumnWithErrors error +endif + +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 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 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 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 [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 + " 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 + if l:match[3] is# 'ALEDummySign' + let l:is_dummy_sign_set = 1 + else + call add(l:result, [ + \ str2nr(l:match[1]), + \ str2nr(l:match[2]), + \ l:match[3], + \]) + endif + endif + endfor + + return [l:is_dummy_sign_set, l:result] +endfunction + +function! ale#sign#FindCurrentSigns(buffer) abort + let l:line_list = ale#sign#ReadSigns(a:buffer) + + return ale#sign#ParseSigns(l:line_list) +endfunction + +" Given a loclist, group the List into with one List per line. +function! s:GroupLoclistItems(buffer, loclist) abort + let l:grouped_items = [] + let l:last_lnum = -1 + + for l:obj in a:loclist + if l:obj.bufnr != a:buffer + continue + endif + + " Create a new sub-List when we hit a new line. + if l:obj.lnum != l:last_lnum + call add(l:grouped_items, []) + endif + + call add(l:grouped_items[-1], l:obj) + let l:last_lnum = l:obj.lnum + endfor + + return l:grouped_items +endfunction + +function! ale#sign#SetSignColumnHighlight(has_problems) abort + highlight clear SignColumn + + if a:has_problems + highlight link SignColumn ALESignColumnWithErrors + else + highlight link SignColumn ALESignColumnWithoutErrors + endif +endfunction + +function! s:UpdateLineNumbers(buffer, current_sign_list, loclist) abort + let l:line_map = {} + let l:line_numbers_changed = 0 + + for [l:line, l:sign_id, l:name] in a:current_sign_list + let l:line_map[l:sign_id] = l:line + endfor + + for l:item in a:loclist + if l:item.bufnr == a:buffer + let l:lnum = get(l:line_map, get(l:item, 'sign_id', 0), 0) + + if l:lnum && l:item.lnum != l:lnum + let l:item.lnum = l:lnum + let l:line_numbers_changed = 1 + endif + endif + endfor + + " When the line numbers change, sort the list again + if l:line_numbers_changed + call sort(a:loclist, 'ale#util#LocItemCompare') + endif +endfunction + +function! s:BuildSignMap(current_sign_list, grouped_items) abort + 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_map[l:line] = { + \ 'current_id': l:sign_id, + \ 'current_name': l:name, + \ 'new_id': 0, + \ 'new_name': '', + \ 'items': [], + \} + + if l:sign_id > l:sign_offset + let l:sign_offset = l:sign_id + endif + endfor + + for l:group in a:grouped_items + let l:line = l:group[0].lnum + let l:sign_info = get(l:sign_map, l:line, { + \ 'current_id': 0, + \ 'current_name': '', + \ 'new_id': 0, + \ 'new_name': '', + \ 'items': [], + \}) + + let l:sign_info.new_name = ale#sign#GetSignName(l:group) + let l:sign_info.items = l:group + + if l:sign_info.current_name isnot# l:sign_info.new_name + let l:sign_info.new_id = l:sign_offset + 1 + let l:sign_offset += 1 + else + let l:sign_info.new_id = l:sign_info.current_id + endif + + 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 l:info.new_id isnot l:info.current_id + call add(l:command_list, 'sign place ' + \ . (l:info.new_id) + \ . ' line=' . l:line_str + \ . ' name=' . (l:info.new_name) + \ . ' buffer=' . a:buffer + \) + endif + endif + endfor + + " Remove signs without new IDs. + for l:info in values(a:sign_map) + if l:info.current_id && l:info.current_id isnot l:info.new_id + call add(l:command_list, 'sign unplace ' + \ . (l:info.current_id) + \ . ' buffer=' . a:buffer + \) + endif + endfor + + " 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 + if !bufexists(str2nr(a:buffer)) + " Stop immediately when attempting to set signs for a buffer which + " does not exist. + return + endif + + " Find the current markers + let [l:is_dummy_sign_set, l:current_sign_list] = + \ ale#sign#FindCurrentSigns(a:buffer) + + " Update the line numbers for items from before which may have moved. + call s:UpdateLineNumbers(a:buffer, l:current_sign_list, a:loclist) + + " Group items after updating the line numbers. + let l:grouped_items = s:GroupLoclistItems(a:buffer, a:loclist) + + " Build a map of current and new signs, with the lines as the keys. + let l:sign_map = s:BuildSignMap(l:current_sign_list, l:grouped_items) + + let l:command_list = ale#sign#GetSignCommands( + \ a:buffer, + \ l:is_dummy_sign_set, + \ l:sign_map, + \) + + for l:command in l:command_list + silent! execute l:command + endfor +endfunction diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim new file mode 100644 index 0000000..a073f7a --- /dev/null +++ b/autoload/ale/statusline.vim @@ -0,0 +1,107 @@ +" 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 + if !exists('g:ale_buffer_info') || !has_key(g:ale_buffer_info, a:buffer) + return + endif + + let l:loclist = filter(copy(a:loclist), 'v:val.bufnr == a:buffer') + let l:count = s:CreateCountDict() + let l:count.total = len(l:loclist) + + 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:count.error += 1 + endif + endfor + + " Set keys for backwards compatibility. + let l:count[0] = l:count.error + l:count.style_error + let l:count[1] = l:count.total - l:count[0] + + let g:ale_buffer_info[a:buffer].count = l:count +endfunction + +" 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. + if !has_key(g:ale_buffer_info[a:buffer], 'count') + call ale#statusline#Update(a:buffer, g:ale_buffer_info[a:buffer].loclist) + endif + + return g:ale_buffer_info[a:buffer].count +endfunction + +" Returns a Dictionary with counts for use in third party integrations. +function! ale#statusline#Count(buffer) abort + " The Dictionary is copied here before exposing it to other plugins. + return copy(s:GetCounts(a:buffer)) +endfunction + +" 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:counts = s:GetCounts(bufnr('')) + + " Build strings based on user formatting preferences. + 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) + let l:res = l:no_errors + elseif !empty(l:errors) && !empty(l:warnings) + let l:res = printf('%s %s', l:errors, l:warnings) + else + let l:res = empty(l:errors) ? l:warnings : l:errors + endif + + 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 !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..c045805 --- /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() +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() + 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/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 new file mode 100644 index 0000000..cf8d5be --- /dev/null +++ b/autoload/ale/util.vim @@ -0,0 +1,299 @@ +" Author: w0rp +" Description: Contains miscellaneous functions + +" A wrapper function for mode() so we can test calls for it. +function! ale#util#Mode(...) abort + return call('mode', a:000) +endfunction + +" A wrapper function for feedkeys so we can test calls for it. +function! ale#util#FeedKeys(...) abort + return call('feedkeys', a:000) +endfunction + +if !exists('g:ale#util#nul_file') + " A null file for sending output to nothing. + let g:ale#util#nul_file = '/dev/null' + + if has('win32') + let g:ale#util#nul_file = 'nul' + endif +endif + +" Return the number of lines for a given buffer. +function! ale#util#GetLineCount(buffer) abort + return len(getbufline(a:buffer, 1, '$')) +endfunction + +function! ale#util#GetFunction(string_or_ref) abort + if type(a:string_or_ref) == type('') + return function(a:string_or_ref) + endif + + 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.bufnr < a:right.bufnr + return -1 + endif + + if a:left.bufnr > a:right.bufnr + return 1 + endif + + if a:left.bufnr == -1 + if a:left.filename < a:right.filename + return -1 + endif + + if a:left.filename > a:right.filename + return 1 + endif + endif + + if a:left.lnum < a:right.lnum + return -1 + endif + + if a:left.lnum > a:right.lnum + return 1 + endif + + if a:left.col < a:right.col + return -1 + endif + + if a:left.col > a:right.col + return 1 + endif + + return 0 +endfunction + +" Compare two loclist items, including the text for the items. +" +" This function can be used for de-duplicating lists. +function! ale#util#LocItemCompareWithText(left, right) abort + let l:cmp_value = ale#util#LocItemCompare(a:left, a:right) + + if l:cmp_value + return l:cmp_value + endif + + if a:left.text < a:right.text + return -1 + endif + + if a:left.text > a:right.text + return 1 + endif + + return 0 +endfunction + +" This function will perform a binary search and a small sequential search +" on the list to find the last problem in the buffer and line which is +" on or before the column. The index of the problem will be returned. +" +" -1 will be returned if nothing can be found. +function! ale#util#BinarySearch(loclist, buffer, line, column) abort + let l:min = 0 + let l:max = len(a:loclist) - 1 + + while 1 + if l:max < l:min + return -1 + endif + + let l:mid = (l:min + l:max) / 2 + let l:item = a:loclist[l:mid] + + " Binary search for equal buffers, equal lines, then near columns. + if l:item.bufnr < a:buffer + let l:min = l:mid + 1 + elseif l:item.bufnr > a:buffer + let l:max = l:mid - 1 + elseif l:item.lnum < a:line + let l:min = l:mid + 1 + elseif l:item.lnum > a:line + let l:max = l:mid - 1 + else + " This part is a small sequential search. + let l:index = l:mid + + " Search backwards to find the first problem on the line. + while l:index > 0 + \&& a:loclist[l:index - 1].bufnr == a:buffer + \&& a:loclist[l:index - 1].lnum == a:line + let l:index -= 1 + endwhile + + " Find the last problem on or before this column. + while l:index < l:max + \&& a:loclist[l:index + 1].bufnr == a:buffer + \&& a:loclist[l:index + 1].lnum == a:line + \&& a:loclist[l:index + 1].col <= a:column + let l:index += 1 + endwhile + + 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 + +" 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 + return json_decode(l:str) + 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 new file mode 100755 index 0000000..aad16c9 --- /dev/null +++ b/custom-checks @@ -0,0 +1,92 @@ +#!/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 + +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 ' \+$' '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 '==#' "Use 'is#' instead of '==#'. 0 ==# 'foobar' is true" +check_errors '==?' "Use 'is?' instead of '==?'. 0 ==? 'foobar' is true" +check_errors '!=#' "Use 'isnot#' instead of '!=#'. 0 !=# 'foobar' is false" +check_errors '!=?' "Use 'isnot?' instead of '!=?'. 0 !=? 'foobar' is false" + +exit $RETURN_CODE diff --git a/doc/ale-asm.txt b/doc/ale-asm.txt new file mode 100644 index 0000000..a72b775 --- /dev/null +++ b/doc/ale-asm.txt @@ -0,0 +1,17 @@ +=============================================================================== +ALE Assembly Integration *ale-asm-options* + + +=============================================================================== +gcc *ale-asm-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..d3f23ac --- /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..1e1322b --- /dev/null +++ b/doc/ale-c.txt @@ -0,0 +1,82 @@ +=============================================================================== +ALE C Integration *ale-c-options* + + +=============================================================================== +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. + + +=============================================================================== +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. + + +=============================================================================== +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. + + +=============================================================================== +clang-format *ale-c-clangformat* + +g:ale_c_clangformat_executable *g:ale_c_clangformat_executable* + *b:ale_c_clangformat_executable* + Type: |String| + Default: `'clang-format'` + + This variable can be changed to use a different executable for clang-format. + + +g:ale_c_clangformat_options *g:ale_c_clangformat_options* + *b:ale_c_clangformat_options* + Type: |String| + Default: `''` + + This variable can be change to modify flags given to clang-format. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-chef.txt b/doc/ale-chef.txt new file mode 100644 index 0000000..026f9b3 --- /dev/null +++ b/doc/ale-chef.txt @@ -0,0 +1,26 @@ +=============================================================================== +ALE Chef Integration *ale-chef-options* + + +=============================================================================== +foodcritc *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-cmake.txt b/doc/ale-cmake.txt new file mode 100644 index 0000000..c1356c9 --- /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_exectuable *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..2ece684 --- /dev/null +++ b/doc/ale-cpp.txt @@ -0,0 +1,200 @@ +=============================================================================== +ALE C++ Integration *ale-cpp-options* + + +=============================================================================== +Global Options + +g:ale_c_build_dir_names *g:ale_c_build_dir_names* + *b:ale_c_build_dir_names* + + Type: |List| + Default: `['build', 'bin']` + + A list of directory names to be used when searching upwards from cpp + files to discover compilation databases with. For directory named `'foo'`, + ALE will search for `'foo/compile_commands.json'` in all directories on and above + the directory containing the cpp file to find path to compilation database. + This feature is useful for the clang tools wrapped around LibTooling (namely + here, clang-tidy) + + +g:ale_c_build_dir *g:ale_c_build_dir* + *b:ale_c_build_dir* + + Type: |String| + Default: `''` + + A path to the directory containing the `compile_commands.json` file to use + with c-family linters. Usually setting this option to a non-empty string + will override the |g:ale_c_build_dir_names| option to impose a compilation + database (it can be useful if multiple builds are in multiple build + subdirectories in the project tree). + This feature is also most useful for the clang tools linters, wrapped + aroung LibTooling (namely clang-tidy here) + +=============================================================================== +clang *ale-cpp-clang* + +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. + + +=============================================================================== +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. + + +=============================================================================== +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. + + +=============================================================================== +clang-format *ale-cpp-clangformat* + +See |ale-c-clangformat| for information about the available options. +Note that the C options are also used for C++. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-css.txt b/doc/ale-css.txt new file mode 100644 index 0000000..effa52a --- /dev/null +++ b/doc/ale-css.txt @@ -0,0 +1,33 @@ +=============================================================================== +ALE CSS Integration *ale-css-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-dart.txt b/doc/ale-dart.txt new file mode 100644 index 0000000..37ba6fa --- /dev/null +++ b/doc/ale-dart.txt @@ -0,0 +1,36 @@ +=============================================================================== +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`. + + +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..288addb --- /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/lukasmartinelli/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: `'lukasmartinelli/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/lukasmartinelli/hadolint/ + + +=============================================================================== + 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..b9cd3cb --- /dev/null +++ b/doc/ale-eruby.txt @@ -0,0 +1,17 @@ +=============================================================================== +ALE Eruby Integration *ale-eruby-options* + +There are two linters for `eruby` files: + +- `erubylint` +- `erubis` + +If you don't know which one your project uses, it's probably `erb`. +To selectively enable one or the other, see |g:ale_linters|. + +(Note that ALE already disables linters if the executable for that linter is +not found; thus, there's probably no need to disable one of these if you're +using the other one.) + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-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-fuse.txt b/doc/ale-fuse.txt new file mode 100644 index 0000000..95e1160 --- /dev/null +++ b/doc/ale-fuse.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE FusionScript Integration *ale-fuse-options* + + +=============================================================================== +4.12. fusionlint *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-go.txt b/doc/ale-go.txt new file mode 100644 index 0000000..935f491 --- /dev/null +++ b/doc/ale-go.txt @@ -0,0 +1,53 @@ +=============================================================================== +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. + +=============================================================================== +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. + + +=============================================================================== + 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..5ceb5ca --- /dev/null +++ b/doc/ale-graphql.txt @@ -0,0 +1,9 @@ +=============================================================================== +ALE GraphQL Integration *ale-graphql-options* + + +=============================================================================== +gqlint *ale-graphql-gqlint* + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-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..0735c6e --- /dev/null +++ b/doc/ale-haskell.txt @@ -0,0 +1,34 @@ +=============================================================================== +ALE Haskell Integration *ale-haskell-options* + +=============================================================================== +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. + +=============================================================================== +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. + +=============================================================================== + 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..e6f3398 --- /dev/null +++ b/doc/ale-html.txt @@ -0,0 +1,60 @@ +=============================================================================== +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: `'--format=unix'` + + 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* + +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 + + +=============================================================================== + 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..13decb4 --- /dev/null +++ b/doc/ale-java.txt @@ -0,0 +1,37 @@ +=============================================================================== +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. + + +=============================================================================== + 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..3adba50 --- /dev/null +++ b/doc/ale-javascript.txt @@ -0,0 +1,274 @@ +=============================================================================== +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 disable the warning that linting is disabled on + the current file due to being covered by `.eslintignore`. + + +=============================================================================== +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* + +ALE supports `prettier-eslint` for easy integration with projects, but it is +not recommended for new projects. ALE instead recommends configuring +|g:ale_fixers| to run `'prettier'` and `'eslint'` in a sequence like so: > + + let g:ale_fixers = {'javascript': ['prettier', 'eslint']} +< + +This is because `prettier-eslint` cannot be configured to use the ESLint +configuration file for input given via stdin, which is how ALE integrates with +the tool. + +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| + + +=============================================================================== +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_global *g:ale_javascript_flow_use_global* + *b:ale_javascript_flow_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== +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| + + +=============================================================================== +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-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-lua.txt b/doc/ale-lua.txt new file mode 100644 index 0000000..f92d5a1 --- /dev/null +++ b/doc/ale-lua.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Lua Integration *ale-lua-options* + + +=============================================================================== +4.12. 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-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..093d911 --- /dev/null +++ b/doc/ale-ocaml.txt @@ -0,0 +1,15 @@ +=============================================================================== +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). + + +=============================================================================== + 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..7611d30 --- /dev/null +++ b/doc/ale-perl.txt @@ -0,0 +1,74 @@ +=============================================================================== +ALE Perl Integration *ale-perl-options* + + +=============================================================================== +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..e2b0de6 --- /dev/null +++ b/doc/ale-php.txt @@ -0,0 +1,128 @@ +=============================================================================== +ALE PHP Integration *ale-php-options* + + +=============================================================================== +hack *ale-php-hack* + +There are no options for this linter. + + +=============================================================================== +langserver *ale-php-langserver* + +g:ale_php_langserver_executable *g:ale_php_langserver_executable* + *b:ale_php_langserver_executable* + Type: |String| + Default: `'php-language-server.php'` + + The variable can be set to configure the executable that will be used for + running the PHP language server. `vendor` directory executables will be + preferred instead of this setting if |g:ale_php_langserver_use_global| is `0`. + + See: |ale-integrations-local-executables| + + +g:ale_php_langserver_use_global *g:ale_php_langserver_use_global* + *b:ale_php_langserver_use_global* + Type: |Number| + Default: `0` + + This variable can be set to `1` to force the language server to be run with + the executable set for |g:ale_php_langserver_executable|. + + See: |ale-integrations-local-executables| + + +=============================================================================== +phpcs *ale-php-phpcs* + +g:ale_php_phpcs_executable *g:ale_php_phpcs_executable* + *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_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. + + +------------------------------------------------------------------------------- +phpcbf *ale-php-phpcbf* + +g:ale_php_phpcbf_executable *g:ale_php_phpcbf_executable* + *b:ale_php_phpcbf_executable* + Type: |String| + Default: `'phpcbf'` + + See |ale-integrations-local-executables| + + +g:ale_php_phpcbf_standard *g:ale_php_phpcbf_standard* + *b:ale_php_phpcbf_standard* + Type: |String| + Default: `''` + + This variable can be set to specify the coding standard used by phpcbf. If no + coding standard is specified, phpcbf will default to fixing against the + PEAR coding standard, or the standard you have set as the default. + + +g:ale_php_phpcbf_use_global *g:ale_php_phpcbf_use_global* + *b:ale_php_phpcbf_use_global* + Type: |Number| + Default: `0` + + See |ale-integrations-local-executables| + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-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..a8d033e --- /dev/null +++ b/doc/ale-python.txt @@ -0,0 +1,213 @@ +=============================================================================== +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_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| + + +=============================================================================== +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| + + +=============================================================================== +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-reasonml.txt b/doc/ale-reasonml.txt new file mode 100644 index 0000000..17a7b2e --- /dev/null +++ b/doc/ale-reasonml.txt @@ -0,0 +1,15 @@ +=============================================================================== +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). + + +=============================================================================== + 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..c710a26 --- /dev/null +++ b/doc/ale-ruby.txt @@ -0,0 +1,79 @@ +=============================================================================== +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. + + +=============================================================================== + 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..d03ab07 --- /dev/null +++ b/doc/ale-rust.txt @@ -0,0 +1,74 @@ +=============================================================================== +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 + compliation of Rust code, and can check Rust files while you type. `rls` + requires Rust files to contained in Cargo projects. + + Only cargo is enabled by default. To switch to using rustc instead of cargo, + configure |g:ale_linters| appropriately: > + + " 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: `0` + + When set to `1`, this option will cause ALE to use "cargo check" instead of + "cargo build". "cargo check" is supported since version 1.16.0 of Rust. + + +=============================================================================== +rls *ale-rust-rls* + +g:ale_rust_rls_executable *g:ale_rust_rls_executable* + *b:ale_rust_rls_executable* + Type: |String| + Default: `'rls'` + + This variable can be modified to change the executable path for `rls`. + + +=============================================================================== +rustc *ale-rust-rustc* + +g:ale_rust_ignore_error_codes *g:ale_rust_ignore_error_codes* + *b:ale_rust_ignore_error_codes* + Type: |List| of |String|s + Default: `[]` + + 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'] + + +=============================================================================== + 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-scalastyle.txt b/doc/ale-scala-scalastyle.txt new file mode 100644 index 0000000..c819e00 --- /dev/null +++ b/doc/ale-scala-scalastyle.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE Scala Integration *ale-scala-options* + + +=============================================================================== +scalastyle *ale-scala-scalastyle* + +g:ale_scalastyle_config_loc *g:ale_scalastyle_config_loc* + + Type: |String| + Default: `''` + + A string containing the location of a global fallback config file. + By default, ALE will look for a config 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..c7b7919 --- /dev/null +++ b/doc/ale-scss.txt @@ -0,0 +1,25 @@ +=============================================================================== +ALE SCSS Integration *ale-scss-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..6fbc9fe --- /dev/null +++ b/doc/ale-sh.txt @@ -0,0 +1,61 @@ +=============================================================================== +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' +< + +=============================================================================== + 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..b7c8f24 --- /dev/null +++ b/doc/ale-spec.txt @@ -0,0 +1,43 @@ +=============================================================================== +ALE RPM 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..29d0dfd --- /dev/null +++ b/doc/ale-stylus.txt @@ -0,0 +1,33 @@ +=============================================================================== +ALE CSS 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-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-typescript.txt b/doc/ale-typescript.txt new file mode 100644 index 0000000..df479c5 --- /dev/null +++ b/doc/ale-typescript.txt @@ -0,0 +1,77 @@ +=============================================================================== +ALE TypeScript Integration *ale-typescript-options* + + +=============================================================================== +eslint *ale-typescript-eslint* + +Becauase 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|. + + +=============================================================================== +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_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.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-xml.txt b/doc/ale-xml.txt new file mode 100644 index 0000000..ddbeb31 --- /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 new file mode 100644 index 0000000..1fd801a --- /dev/null +++ b/doc/ale.txt @@ -0,0 +1,1687 @@ +*ale.txt* For Vim version 8.0. +*ale* + +ALE - Asynchronous Lint Engine + +=============================================================================== +CONTENTS *ale-contents* + + 1. Introduction.........................|ale-introduction| + 2. Supported Languages & Tools..........|ale-support| + 3. Global Options.......................|ale-options| + 3.1 Highlights........................|ale-highlights| + 4. Fixing Problems......................|ale-fix| + 5. Completion...........................|ale-completion| + 6. Integration Documentation............|ale-integrations| + asm...................................|ale-asm-options| + gcc.................................|ale-asm-gcc| + c.....................................|ale-c-options| + clang...............................|ale-c-clang| + cppcheck............................|ale-c-cppcheck| + gcc.................................|ale-c-gcc| + clang-format........................|ale-c-clangformat| + chef..................................|ale-chef-options| + foodcritic..........................|ale-chef-foodcritic| + cpp...................................|ale-cpp-options| + clang...............................|ale-cpp-clang| + clangcheck..........................|ale-cpp-clangcheck| + clangtidy...........................|ale-cpp-clangtidy| + cppcheck............................|ale-cpp-cppcheck| + cpplint.............................|ale-cpp-cpplint| + gcc.................................|ale-cpp-gcc| + clang-format........................|ale-cpp-clangformat| + css...................................|ale-css-options| + stylelint...........................|ale-css-stylelint| + cmake.................................|ale-cmake-options| + cmakelint...........................|ale-cmake-cmakelint| + dart..................................|ale-dart-options| + dartanalyzer........................|ale-dart-dartanalyzer| + erlang................................|ale-erlang-options| + erlc................................|ale-erlang-erlc| + syntaxerl...........................|ale-erlang-syntaxerl| + eruby.................................|ale-eruby-options| + fortran...............................|ale-fortran-options| + gcc.................................|ale-fortran-gcc| + fusionscript..........................|ale-fuse-options| + fusion-lint.........................|ale-fuse-fusionlint| + go....................................|ale-go-options| + gometalinter........................|ale-go-gometalinter| + graphql...............................|ale-graphql-options| + gqlint..............................|ale-graphql-gqlint| + handlebars............................|ale-handlebars-options| + ember-template-lint.................|ale-handlebars-embertemplatelint| + haskell...............................|ale-haskell-options| + stack-build.........................|ale-haskell-stack-build| + html..................................|ale-html-options| + htmlhint............................|ale-html-htmlhint| + tidy................................|ale-html-tidy| + idris.................................|ale-idris-options| + idris...............................|ale-idris-idris| + java..................................|ale-java-options| + checkstyle..........................|ale-java-checkstyle| + javac...............................|ale-java-javac| + javascript............................|ale-javascript-options| + eslint..............................|ale-javascript-eslint| + flow................................|ale-javascript-flow| + 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| + kotlin................................|ale-kotlin-options| + kotlinc.............................|ale-kotlin-kotlinc| + lua...................................|ale-lua-options| + luacheck............................|ale-lua-luacheck| + objc..................................|ale-objc-options| + clang...............................|ale-objc-clang| + objcpp................................|ale-objcpp-options| + clang...............................|ale-objcpp-clang| + ocaml.................................|ale-ocaml-options| + merlin..............................|ale-ocaml-merlin| + perl..................................|ale-perl-options| + perl................................|ale-perl-perl| + perlcritic..........................|ale-perl-perlcritic| + php...................................|ale-php-options| + hack................................|ale-php-hack| + langserver..........................|ale-php-langserver| + phpcs...............................|ale-php-phpcs| + phpmd...............................|ale-php-phpmd| + phpstan.............................|ale-php-phpstan| + phpcbf..............................|ale-php-phpcbf| + pug...................................|ale-pug-options| + puglint.............................|ale-pug-puglint| + python................................|ale-python-options| + autopep8............................|ale-python-autopep8| + flake8..............................|ale-python-flake8| + isort...............................|ale-python-isort| + mypy................................|ale-python-mypy| + pycodestyle.........................|ale-python-pycodestyle| + pylint..............................|ale-python-pylint| + yapf................................|ale-python-yapf| + ruby..................................|ale-ruby-options| + brakeman............................|ale-ruby-brakeman| + rails_best_practices................|ale-ruby-rails_best_practices| + reek................................|ale-ruby-reek| + rubocop.............................|ale-ruby-rubocop| + rust..................................|ale-rust-options| + cargo...............................|ale-rust-cargo| + rls.................................|ale-rust-rls| + rustc...............................|ale-rust-rustc| + sass..................................|ale-sass-options| + stylelint...........................|ale-sass-stylelint| + scala.................................|ale-scala-options| + scalastyle..........................|ale-scala-scalastyle| + scss..................................|ale-scss-options| + stylelint...........................|ale-scss-stylelint| + sh....................................|ale-sh-options| + shell...............................|ale-sh-shell| + shellcheck..........................|ale-sh-shellcheck| + spec..................................|ale-spec-options| + rpmlint.............................|ale-spec-rpmlint| + stylus................................|ale-stylus-options| + stylelint...........................|ale-stylus-stylelint| + tcl...................................|ale-tcl-options| + nagelfar............................|ale-tcl-nagelfar| + tex...................................|ale-tex-options| + chktex..............................|ale-tex-chktex| + lacheck.............................|ale-tex-lacheck| + typescript............................|ale-typescript-options| + eslint..............................|ale-typescript-eslint| + 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| + xml...................................|ale-xml-options| + xmllint.............................|ale-xml-xmllint| + yaml..................................|ale-yaml-options| + swaglint............................|ale-yaml-swaglint| + yamllint............................|ale-yaml-yamllint| + 7. Commands/Keybinds....................|ale-commands| + 8. API..................................|ale-api| + 9. Special Thanks.......................|ale-special-thanks| + 10. Contact.............................|ale-contact| + +=============================================================================== +1. Introduction *ale-introduction* + +ALE provides the means to run linters asynchronously in Vim in a variety of +languages and tools. ALE sends the contents of buffers to linter programs +using the |job-control| features available in Vim 8 and NeoVim. For Vim 8, +Vim must be compiled with the |job| and |channel| and |timer| features +as a minimum. + +ALE supports the following key features for linting: + +1. Running linters when text is changed. +2. Running linters when files are opened. +3. Running linters when files are saved. (When a global flag is set.) +4. Populating the |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. + +* ASM: 'gcc' +* Ansible: 'ansible-lint' +* Asciidoc: 'proselint' +* Bash: 'shell' (-n flag), 'shellcheck' +* Bourne Shell: 'shell' (-n flag), 'shellcheck' +* C: 'cppcheck', 'gcc', 'clang', 'clang-format' +* C++ (filetype cpp): 'clang', 'clangtidy', 'cppcheck', 'cpplint', 'gcc', 'clang-format' +* C#: 'mcs' +* Chef: 'foodcritic' +* CMake: 'cmakelint' +* CoffeeScript: 'coffee', 'coffelint' +* Crystal: 'crystal' +* CSS: 'csslint', 'stylelint' +* Cython (pyrex filetype): 'cython' +* D: 'dmd' +* Dart: 'dartanalyzer' +* Dockerfile: 'hadolint' +* Elixir: 'credo', 'dogma' +* Elm: 'elm-make' +* Erlang: 'erlc' +* Fortran: 'gcc' +* Go: 'gofmt', 'go vet', 'golint', 'go build', 'gosimple', 'staticcheck' +* FusionScript: 'fusion-lint' +* Haml: 'hamllint' +* Handlebars: 'ember-template-lint' +* Haskell: 'ghc', 'stack-ghc', 'stack-build', 'ghc-mod', 'stack-ghc-mod', 'hlint', 'hdevtools' +* HTML: 'HTMLHint', 'proselint', 'tidy' +* Idris: 'idris' +* Java: 'javac' +* JavaScript: 'eslint', 'jscs', 'jshint', 'flow', 'prettier', 'prettier-eslint', 'xo' +* JSON: 'jsonlint' +* Kotlin: 'kotlinc' +* LaTeX (tex): 'chktex', 'lacheck', 'proselint' +* Lua: 'luacheck' +* Markdown: 'mdl', 'proselint', 'vale' +* MATLAB: 'mlint' +* nim: 'nim check' +* nix: 'nix-instantiate' +* nroff: 'proselint' +* Objective-C: 'clang' +* Objective-C++: 'clang' +* OCaml: 'merlin' (see |ale-ocaml-merlin|) +* Perl: 'perl' (-c flag), 'perlcritic' +* PHP: 'hack', 'langserver', 'php' (-l flag), 'phpcs', 'phpmd', 'phpstan', 'phpcbf' +* Pod: 'proselint' +* Pug: 'pug-lint' +* Puppet: 'puppet', 'puppet-lint' +* Python: 'autopep8', 'flake8', 'isort', 'mypy', 'pylint', 'yapf' +* R: 'lintr' +* ReasonML: 'merlin' +* reStructuredText: 'proselint' +* RPM spec: 'spec' +* Rust: 'cargo', 'rls', 'rustc' (see |ale-integration-rust|) +* Ruby: 'reek', 'rubocop' +* SASS: 'sasslint', 'stylelint' +* SCSS: 'sasslint', 'scsslint', 'stylelint' +* Scala: 'scalac', 'scalastyle' +* Slim: 'slim-lint' +* SML: 'smlnj' +* Stylus: 'stylelint' +* SQL: 'sqlint' +* Swift: 'swiftlint', 'swiftformat' +* Texinfo: 'proselint' +* Text: 'proselint', 'vale' +* TypeScript: 'eslint', 'tslint', 'tsserver', 'typecheck' +* Verilog: 'iverilog', 'verilator' +* Vim: 'vint' +* Vim help: 'proselint' +* XHTML: 'proselint' +* XML: 'xmllint' +* YAML: 'swaglint', 'yamllint' + +=============================================================================== +3. Global Options *ale-options* + +g:airline#extensions#ale#enabled *g:airline#extensions#ale#enabled* + + Type: |Number| + Default: `1` + + Enables or disables the |airline|'s native extension for ale, which displays + warnings and errors in the status line, prefixed by + |airline#extensions#ale#error_symbol| and + |airline#extensions#ale#warning_symbol|. + + +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_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| + Default: `1` + + When this option is set to `1`, a truncated message will be echoed when a + cursor is near a warning or error. ALE will attempt to find the warning or + error at a column nearest to the cursor when the cursor is resting on a line + which contains a warning or error. This option can be set to `0` to disable + this behaviour. + The format of the message can be customizable in |g:ale_echo_msg_format|. + + +g:ale_echo_msg_error_str *g:ale_echo_msg_error_str* + + Type: |String| + 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 + + +g:ale_echo_msg_format *g:ale_echo_msg_format* + + Type: |String| + Default: `%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 + + +g:ale_echo_msg_warning_str *g:ale_echo_msg_warning_str* + + Type: |String| + 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 + + +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. + + +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 overriden with variables in each buffer. + + +g:ale_fix_on_save *g:ale_fix_on_save* + + Type: |Number| + Default: `0` + + When set to 1, ALE will fix files when they are saved. + + If |g:ale_lint_on_save| is set to 1, files will be checked with linters + after files are fixed, only when the buffer is open, or re-opened. Changes + to the file will be saved to the file on disk. + + +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 quicfix 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* + + Type: |Number| + Default: `200` + + This variable controls the milliseconds delay after which the linters will + be run after text is changed. This option is only meaningful with the + |g:ale_lint_on_text_changed| variable set to `always`, `insert`, or `normal`. + + +g:ale_lint_on_enter *g:ale_lint_on_enter* + + Type: |Number| + Default: `1` + + 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. + + +g:ale_lint_on_save *g:ale_lint_on_save* + + Type: |Number| + Default: `1` + + This option will make ALE run the linters whenever a file is saved when it + it set to `1` in your vimrc file. This option can be used in combination + with the |g:ale_lint_on_enter| and |g:ale_lint_on_text_changed| options to + make ALE only check files after that have been saved, if that is what is + desired. + + +g:ale_lint_on_text_changed *g:ale_lint_on_text_changed* + + 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 `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. 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` + + This option will make ALE run the linters whenever leaving insert mode when + it is set to `1` in your vimrc file. + + +g:ale_linter_aliases *g:ale_linter_aliases* + *b:ale_linter_aliases* + Type: |Dictionary| + Default: `{}` + + The |g:ale_linter_aliases| option can be used to set aliases from one + filetype to another. A given filetype can be mapped to use the linters + run for another given filetype. + + This |Dictionary| will be merged with a default dictionary containing the + following values: > + + { + \ 'zsh': 'sh', + \ 'csh': 'sh', + \} +< + For example, if you wish to map a new filetype `'foobar'` to run the `'php'` + linters, you could set the following: > + + let g:ale_linter_aliases = {'foobar': 'php'} +< + When combined with the |g:ale_linters| option, the original filetype + (`'foobar'`) will be used for determining which linters to run, + not the aliased type (`'php'`). This allows an aliased type to run a + different set of linters from the type it is being mapped to. + + Passing a list of filetypes is also supported. Say you want to lint + javascript and css embedded in HTML (using linters that support that). + You could alias `html` like so: + + `let g:ale_linter_aliases = {'html': ['html', 'javascript', 'css']}` + + Note that `html` itself was included as an alias. That is because aliases + will override the original linters for the aliased filetepe. + + 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. + + +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. + + This |Dictionary| will be merged with a default dictionary containing the + following values: > + + { + \ 'csh': ['shell'], + \ 'rust': ['cargo'], + \ '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: > + + let g:ale_linters = {'javascript': ['eslint']} +< + If you want to disable all linters for a particular filetype, you can pass + an empty list of linters as the value: > + + let g:ale_linters = {'javascript': []} +< + All linters available for a given filetype can be enabled by using the + string `'all'`: > + + 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 a default Dictionary. + + +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_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. + + The window size can be configured with |g:ale_list_window_size|. + + +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: > + + let g:ale_pattern_options = { + \ '\.foo\.js$': { + \ 'ale_linters': {'javascript': ['eslint']}, + \ }, + \} +< + The above example will match any filename ending in `.foo.js`, and use + only `eslint` for checking those files by setting `b:ale_linters`. + + 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')`. + + +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. + + +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'` + + +g:ale_set_loclist *g:ale_set_loclist* + + Type: |Number| + Default: `1` + + When this option is set to `1`, the |loclist| will be populated with any + warnings and errors which are found by ALE. This feature can be used to + implement jumping between errors through typical use of |lnext| and |lprev|. + + +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 problems which are found by ALE, instead of the |loclist|. The loclist + will never be populated when this option is on. + + Problems from every buffer ALE has checked will be included in the quickfix + list, which can be checked with |:copen|. Problems will be de-duplicated. + + +g:ale_set_signs *g:ale_set_signs* + + Type: |Number| + Default: `has('signs')` + + When this option is set to `1`, the |sign| column will be populated with + 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. + + +g:ale_sign_column_always *g:ale_sign_column_always* + + Type: |Number| + Default: `0` + + By default, the sign gutter will disappear when all warnings and errors have + been fixed for a file. When this option is set to `1`, the sign column will + remain open. This can be preferable if you don't want the text in your file + to move around as you edit a file. + + +g:ale_sign_error *g:ale_sign_error* + + Type: |String| + Default: `'>>'` + + 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* + + Type: |Number| + Default: `1000000` + + This variable controls offset from which numeric IDs will be generated for + new signs. Signs cannot share the same ID values, so when two Vim plugins + set signs at the same time, the IDs have to be configured such that they do + not conflict with one another. If the IDs used by ALE are found to conflict + with some other plugin, this offset value can be changed, and hopefully both + plugins will work together. See |sign-place| for more information on how + signs are set. + + +g:ale_sign_warning *g:ale_sign_warning* + + Type: |String| + Default: `'--'` + + The sign for warnings in the sign gutter. + + +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 error 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 do + 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 use 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: `['.env', 'env', 've-py3', 've', 'virtualenv']` + + 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_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`. + + 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. + + +g:ale_windows_node_executable_path *g:ale_windows_node_executable_path* + *b:ale_windows_node_executable_path* + + Type: |String| + Default: `'node.exe'` + + This variable is used as the path to the executable to use for executing + scripts with Node.js on Windows. + + For Windows, any file with a `.js` file extension needs to be executed with + the node executable explicitly. Otherwise, Windows could try and open the + scripts with other applications, like a text editor. Therefore, these + scripts are executed with whatever executable is configured with this + setting. + + +------------------------------------------------------------------------------- +3.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 lines where error signs appear. See |g:ale_set_signs|. + + +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 lines where info signs appear. See |g:ale_set_signs|. + + +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 lines where warning signs appear. See |g:ale_set_signs|. + + +ALEWarningSign *ALEWarningSign* + + Default: `highlight link ALEWarningSign todo` + + The highlight used for warning signs. See |g:ale_set_signs|. + + +=============================================================================== +4. Fixing Problems *ale-fix* + +ALE can fix problems with files with the |ALEFix| command. When |ALEFix| is +run, the variable |g:ale_fixers| will be read for getting a |List| of commands +for filetypes, split on `.`, and the functions named in |g:ale_fixers| will be +executed for fixing the errors. + +The |ALEFixSuggest| command can be used to suggest tools that be used to +fix problems for the current buffer. + +The values for `g:ale_fixers` can be a list of |String|, |Funcref|, or +|lambda| values. String values must either name a function, or a short name +for a function set in the ALE fixer registry. + +Each function for fixing errors must accept either one argument `(buffer)` or +two arguments `(buffer, lines)`, representing the buffer being fixed and the +lines to fix. The functions must return either `0`, for changing nothing, a +|List| for new lines to set, or a |Dictionary| for describing a command to be +run in the background. + +Functions receiving a variable number of arguments will not receive the second +argument `lines`. Functions should name two arguments if the `lines` argument +is desired. This is required to avoid unnecessary copying of the lines of +the buffers being checked. + +When a |Dictionary| is returned for an |ALEFix| callback, the following keys +are supported for running the commands. + + `command` A |String| for the command to run. This key is required. + + When `%t` is included in a command string, a temporary + file will be created, containing the lines from the file + after previous adjustment have been done. + + `read_temporary_file` When set to `1`, ALE will read the contents of the + temporary file created for `%t`. This option can be used + for commands which need to modify some file on disk in + order to fix files. + + *ale-fix-configuration* + +Synchronous functions and asynchronous jobs will be run in a sequence for +fixing files, and can be combined. For example: +> + let g:ale_fixers = { + \ 'javascript': [ + \ 'DoSomething', + \ 'eslint', + \ {buffer, lines -> filter(lines, 'v:val !=~ ''^\s*//''')}, + \ ], + \} + + ALEFix +< +The above example will call a function called `DoSomething` which could act +upon some lines immediately, then run `eslint` from the ALE registry, and +then call a lambda function which will remove every single line comment +from the file. + +For convenience, a plug mapping is defined for |ALEFix|, so you can set up a +keybind easily for fixing files. > + + " Bind F8 to fixing problems with ALE + nmap (ale_fix) +< +Files can be fixed automatically with the following options, which are all off +by default. + +|g:ale_fix_on_save| - Fix files when they are saved. + + +=============================================================================== +5. Completion *ale-completion* + +ALE offers some limited support for automatic completion of code while you +type. Completion is only supported via Language Server Protocol servers which +ALE can connect to for linting, which can offer good built-in support for +suggesting completion information. ALE will only suggest symbols for +completion for LSP linters that are enabled. + +NOTE: At the moment, only `tsserver` for TypeScript code is supported for +completion. + +Suggestions will be made while you type after completion is enabled. +Completion can be enabled by setting |g:ale_completion_enabled| to `1`. The +delay for completion can be configured with |g:ale_completion_delay|. ALE will +only suggest so many possible matches for completion. The maximum number of +items can be controlled with |g:ale_completion_max_suggestions|. + + +=============================================================================== +6. Integration Documentation *ale-integrations* + +Linter and fixer options are documented in individual help files. See the +table of contents at |ale-contents|. + +Every option for programs can be set globally, or individually for each +buffer. For example, `b:ale_python_flake8_executable` will override any +values set for `g:ale_python_flake8_executable`. + + *ale-integrations-local-executables* + +Some tools will prefer to search for locally-installed executables, unless +configured otherwise. For example, the `eslint` linter will search for +various executable paths in `node_modules`. The `flake8` linter will search +for virtualenv directories. + +If you prefer to use global executables for those tools, set the relevant +`_use_global` and `_executable` options for those linters. > + + " Use the global executable with a special name for eslint. + let g:ale_javascript_eslint_executable = 'special-eslint' + let g:ale_javascript_eslint_use_global = 1 + + " Use the global executable with a special name for flake8. + let g:ale_python_flake8_executable = '/foo/bar/flake8' + let g:ale_python_flake8_use_global = 1 +< + +The option |g:ale_virtualenv_dir_names| controls the local virtualenv paths +ALE will use to search for Python executables. + + +=============================================================================== +7. Commands/Keybinds *ale-commands* + +ALEFix *ALEFix* + + Fix problems with the current buffer. See |ale-fix| for more information. + + A plug mapping `(ale_fix)` is defined for this command. + + +ALEFixSuggest *ALEFixSuggest* + + Suggest tools that can be used to fix problems in the current buffer. + + See |ale-fix| for more information. + + +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. 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: > + + " Map movement through errors without wrapping. + nmap (ale_previous) + nmap (ale_next) + " OR map keys to use wrapping. + nmap (ale_previous_wrap) + nmap (ale_next_wrap) +< + +ALEToggle *ALEToggle* +ALEEnable *ALEEnable* +ALEDisable *ALEDisable* + + Enable or disable ALE, including all of its autocmd events, loclist items, + quickfix items, signs, current jobs, etc. Executing any of those commands + will change the |g:ale_enabled| variable. + + For convenience, a plug mapping `(ale_toggle)` is defined for the + |ALEToggle| command. + + +ALEDetail *ALEDetail* + + Show the full linter message for the current line. This will only have an + effect on lines that contain a linter message. + + A plug mapping `(ale_detail)` is defined for this command. + + +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. + + +=============================================================================== +8. API *ale-api* + +ale#Queue(delay, [linting_flag, buffer_number]) *ale#Queue()* + + Run linters for the current buffer, based on the filetype of the buffer, + with a given `delay`. A `delay` of `0` will run the linters immediately. + 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()|. + + +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) *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. + + +ale#linter#Define(filetype, linter) *ale#linter#Define()* + + Given a |String| for a filetype and a |Dictionary| Describing a linter + configuration, add a linter for the given filetype. The dictionaries each + offer the following options: + + `name` The name of the linter. These names will be used by + |g:ale_linters| option for enabling/disabling + particular linters. + + This argument is required. + + `callback` A |String| or |Funcref| for a callback function + accepting two arguments (buffer, lines), for a + buffer number the output is for, and the lines of + output from a linter. + + This callback function should return a |List| of + |Dictionary| objects in the format accepted by + |setqflist()|. The |List| will be sorted by line and + then column order so it can be searched with a binary + search by in future before being passed on to the + |loclist|, etc. + + This argument is required, unless the linter is an + LSP linter. In which case, this argument must not be + defined, as LSP linters handle 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 + coverted 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 + program requested is installed or not. + + Either this or the `executable_callback` argument + must be provided. + + `executable_callback ` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned for the executable to check. This can be + used in place of `executable` when more complicated + processing is needed. + + `command` A |String| for an executable to run asynchronously. + This command will be fed the lines from the buffer to + check, and will produce the lines of output given to + the `callback`. + + `command_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned for a command to run. This can be used in + place of `command` when more complicated processing + is needed. + + If an empty string is returned from the callback, + no jobs for linting will be run for that linter. + This can be used for skipping a linter call, + say if no configuration file was found. + + *ale-command-chain* + `command_chain` A |List| of |Dictionary| items defining a series + of commands to be run. At least one |Dictionary| + should be provided. Each Dictionary must contain the + key `callback`, defining a |String| or |Funcref| for + a function returning a |String| for a command to run. + + The callback functions for each command after the + first command in in the chain should accept two + arguments `(buffer, output)`, a buffer number and a + |List| of lines of output from the previous command + in the chain. + + The first callback function in a chain accepts only + a `(buffer)` argument, as there are no previous + commands to run which return `output`. + + If an empty string is returned for a command in a + chain, that command in the chain will be skipped, + and the next function in the chain will be called + immediately instead. If the last command in a chain + returns an empty string, then no linting will be + performed. + + Commands in the chain will all use the + `output_stream` value provided in the root + |Dictionary|. Each command in the chain can also + provide an `output_stream` key to override this value. + See the `output_stream` description for more + information. + + Commands in the chain all behave as if `read_buffer` + is set to `0` by default, except for the last command + in the chain, which uses the value set for + `read_buffer` in the root |Dictionary|. Each command + in the chain can also provide a `read_buffer` key + to override these values. + See the `read_buffer` description for more + information. + + `output_stream` A |String| for the output stream the lines of output + should be read from for the command which is run. The + accepted values are `'stdout'`, `'stderr'`, and + `'both'`. This argument defaults to `'stdout'`. This + argument can be set for linter programs which output + their errors and warnings to the stderr stream + instead of stdout. The option `'both'` will read + from both stder and stdout at the same time. + + `read_buffer` A |Number| (`0` or `1`) indicating whether a command + should read the Vim buffer as input via stdin. This + option is set to `1` by default, and can be disabled + if a command manually reads from a temporary file + instead, etc. + + *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 |ALELint-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 diagonstics automatically, so + the `callback` argument must not be defined. + + `project_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned representing the path to the project for the + file being checked with the language server. If an + empty string is returned, the file will not be + checked at all. + + This argument must only be set if the `lsp` argument + is also set to a non-empty string. + + `language_callback` A |String| or |Funcref| for a callback function + accepting a buffer number. A |String| should be + returned representing the name of the language being + checked. + + This argument must only be set if the `lsp` argument + is also set to a non-empty string. + + `aliases` A |List| of aliases for the linter name. + + This 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. + + 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': '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: + 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 + components. + + +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. + + +ALELint *ALELint-autocmd* + + This |User| autocommand is triggered by ALE every time it completes a lint + cycle. It can be used to update statuslines, send notifications, or + complete any other operation that needs to be done after linting has been + performed. + + For example, you can echo a message when linting is complete like so: + > + autocmd User ALELint unsilent echom 'ALE run!' +< + The autocmd commands are run with |:silent|, so |:unsilent| is required for + echoing messges. + +=============================================================================== +9. 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! + +=============================================================================== +10. 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 + +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/img/echo.png b/img/echo.png new file mode 100644 index 0000000..671a66b Binary files /dev/null and b/img/echo.png differ diff --git a/img/example.gif b/img/example.gif new file mode 100644 index 0000000..1e443bf Binary files /dev/null and b/img/example.gif differ diff --git a/img/issues.png b/img/issues.png new file mode 100644 index 0000000..7415d03 Binary files /dev/null and b/img/issues.png differ diff --git a/img/logo.jpg b/img/logo.jpg new file mode 100644 index 0000000..1d8468d Binary files /dev/null and b/img/logo.jpg differ diff --git a/img/no_issues.png b/img/no_issues.png new file mode 100644 index 0000000..397804e Binary files /dev/null and b/img/no_issues.png differ diff --git a/plugin/ale.vim b/plugin/ale.vim new file mode 100644 index 0000000..a9ab88a --- /dev/null +++ b/plugin/ale.vim @@ -0,0 +1,391 @@ +" Author: w0rp +" Description: Main entry point for the plugin: sets up prefs and autocommands +" Preferences can be set in vimrc files and so on to configure ale + +" Sanity Checks + +if exists('g:loaded_ale_dont_use_this_in_other_plugins_please') + finish +endif + +" Set a special flag used only by this plugin for preventing doubly +" loading the script. +let g:loaded_ale_dont_use_this_in_other_plugins_please = 1 + +" A flag for detecting if the required features are set. +if has('nvim') + let s:has_features = has('timers') +else + " Check if Job and Channel functions are available, instead of the + " features. This works better on old MacVim versions. + let s:has_features = has('timers') && exists('*job_start') && exists('*ch_close_in') +endif + +if !s:has_features + " Only output a warning if editing some special files. + if index(['', 'gitcommit'], &filetype) == -1 + echoerr 'ALE requires NeoVim >= 0.1.5 or Vim 8 with +timers +job +channel' + echoerr 'Please update your editor appropriately.' + endif + + " Stop here, as it won't work. + finish +endif + +" Add the after directory to the runtimepath +let &runtimepath .= ',' . expand(':p:h:h') . '/after' + +" 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 flag can be set to 0 to disable emitting conflict warnings. +let g:ale_emit_conflict_warnings = get(g:, 'ale_emit_conflict_warnings', 1) + +" This global variable is used internally by ALE for tracking information for +" each buffer which linters are being run against. +let g:ale_buffer_info = {} + +" User Configuration + +" This option prevents ALE autocmd commands from being run for particular +" filetypes which can cause issues. +let g:ale_filetype_blacklist = [ +\ 'dirvish', +\ 'nerdtree', +\ 'qf', +\ 'tags', +\ 'unite', +\] + +" This Dictionary configures which linters are enabled for which languages. +let g:ale_linters = get(g:, 'ale_linters', {}) + +" This Dictionary configures which functions will be used for fixing problems. +let g:ale_fixers = get(g:, 'ale_fixers', {}) + +" This Dictionary allows users to set up filetype aliases for new filetypes. +let g:ale_linter_aliases = get(g:, 'ale_linter_aliases', {}) + +" This flag can be set with a number of milliseconds for delaying the +" execution of a linter when text is changed. The timeout will be set and +" cleared each time text is changed, so repeated edits won't trigger the +" jobs for linting until enough time has passed after editing is done. +let g:ale_lint_delay = get(g:, 'ale_lint_delay', 200) + +" This flag can be set to 'never' to disable linting when text is changed. +" This flag can also be set to '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) + +" 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', 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). +let g:ale_set_loclist = get(g:, 'ale_set_loclist', 1) +let g:ale_set_quickfix = get(g:, 'ale_set_quickfix', 0) + +" This flag dictates if ale open the configured loclist +let g:ale_open_list = get(g:, 'ale_open_list', 0) + +" This flag dictates if ale keeps open loclist even if there is no error in loclist +let g:ale_keep_list_window_open = get(g:, 'ale_keep_list_window_open', 0) + +" The window size to set for the quickfix and loclist windows +call ale#Set('list_window_size', 10) + +" This flag can be set to 0 to disable setting signs. +" This is enabled by default only if the 'signs' feature exists. +let g:ale_set_signs = get(g:, 'ale_set_signs', has('signs')) + +" This flag can be set to 1 to enable changing the sign column colors when +" there are errors. +call ale#Set('change_sign_column_color', 0) + +" This flag can be set to 0 to disable setting error highlights. +let g:ale_set_highlights = get(g:, 'ale_set_highlights', has('syntax')) + +" These variables dictate what sign is used to indicate errors and warnings. +call ale#Set('sign_error', '>>') +call ale#Set('sign_style_error', g:ale_sign_error) +call ale#Set('sign_warning', '--') +call ale#Set('sign_style_warning', g:ale_sign_warning) +call ale#Set('sign_info', g:ale_sign_warning) + +" This variable sets an offset which can be set for sign IDs. +" This ID can be changed depending on what IDs are set for other plugins. +" The dummy sign will use the ID exactly equal to the offset. +let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) + +" This flag can be set to 1 to keep sign gutter always open +let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0) + +" 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') + +" 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_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) + +" 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) + +" 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 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) + +function! ALEInitAuGroups() 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! + if g:ale_enabled && g:ale_pattern_options_enabled + autocmd BufEnter,BufRead * call ale#pattern_options#SetOptions() + endif + 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! + if (g:ale_enabled && g:ale_lint_on_save) || g:ale_fix_on_save + autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand(''))) + endif + 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 + if !g:ale_fix_on_save + augroup! ALERunOnSaveGroup + endif + + augroup! ALEPatternOptionsGroup + augroup! ALERunOnTextChangedGroup + augroup! ALERunOnEnterGroup + augroup! ALERunOnInsertLeave + augroup! ALECursorGroup + endif +endfunction + +function! s:ALEToggle() abort + let g:ale_enabled = !get(g:, 'ale_enabled') + + if g:ale_enabled + " Set pattern options again, if enabled. + if g:ale_pattern_options_enabled + call ale#pattern_options#SetOptions() + 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 + else + for l:key in keys(g:ale_buffer_info) + " The key could be a filename or a buffer number, so try and + " convert it to a number. We need a number for the other + " functions. + let l:buffer = str2nr(l:key) + + if l:buffer > 0 + " Stop all jobs and clear the results for everything, and delete + " all of the data we stored for the buffer. + call ale#engine#Cleanup(l:buffer) + endif + endfor + + " Remove highlights for the current buffer now. + if g:ale_set_highlights + call ale#highlight#UpdateHighlights() + endif + + if g:ale_set_balloons + call ale#balloon#Disable() + endif + endif + + call ALEInitAuGroups() +endfunction + +call ALEInitAuGroups() + +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! -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) + +" 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 s:ALEToggle() +command! -bar ALEEnable :if !g:ale_enabled | ALEToggle | endif +command! -bar ALEDisable :if g:ale_enabled | ALEToggle | endif + +" 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) + +" 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_lint) :ALELint +nnoremap (ale_detail) :ALEDetail +nnoremap (ale_fix) :ALEFix + +" Housekeeping + +augroup ALECleanupGroup + autocmd! + " Clean up buffers automatically when they are unloaded. + autocmd BufUnload * call ale#engine#Cleanup(str2nr(expand(''))) +augroup END + +" Backwards Compatibility + +function! ALELint(delay) abort + call ale#Queue(a:delay) +endfunction + +function! ALEGetStatusLine() abort + return ale#statusline#Status() +endfunction diff --git a/run-tests b/run-tests new file mode 100755 index 0000000..316eb6c --- /dev/null +++ b/run-tests @@ -0,0 +1,240 @@ +#!/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 + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' +CURRENT_IMAGE_ID=d5a1b5915b09 +IMAGE=w0rp/ale +DOCKER_FLAGS=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$IMAGE") +EXIT=0 + +tests='test/*.vader test/*/*.vader test/*/*/*.vader test/*/*/*.vader' +verbose=0 +quiet=0 +run_neovim_tests=1 +run_vim_tests=1 +run_vint=1 +run_custom_checks=1 + +while [ $# -ne 0 ]; do + case $1 in + -v) + verbose=1 + shift + ;; + -q) + quiet=1 + 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 + ;; + --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" + +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" +} + +if ((run_neovim_tests)); then + for vim in $(docker run --rm "$IMAGE" ls /vim-build/bin | grep '^neovim' ); do + echo + echo '========================================' + echo "Running tests for $vim" + echo '========================================' + echo + + set -o pipefail + docker run -it -e VADER_OUTPUT_FILE=/dev/stderr "${DOCKER_FLAGS[@]}" \ + "/vim-build/bin/$vim" -u test/vimrc \ + --headless "+Vader! $tests" | filter-vader-output | color-vader-output || EXIT=$? + set +o pipefail + done + + echo +fi + +if ((run_vim_tests)); then + for vim in $(docker run --rm "$IMAGE" ls /vim-build/bin | grep '^vim' ); do + 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=$? + set +o pipefail + done + + echo +fi + +if ((run_vint)); then + 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=$? + set +o pipefail + echo +fi + +if ((run_custom_checks)); then + echo '========================================' + echo 'Running custom checks' + echo '========================================' + echo 'Custom warnings/errors follow:' + echo + + set -o pipefail + docker run -a stdout "${DOCKER_FLAGS[@]}" ./custom-checks . || EXIT=$? + 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=$? + + echo '========================================' + echo 'Checking for invalid tag references' + echo '========================================' + echo 'Invalid tag references tags follow:' + echo + + tag_regex='[gb]\?:\?\(ale\|ALE\)[a-zA-Z_\-]\+' + + # Grep for tags and references, and complain if we find a reference without + # a tag for the reference. Only our tags will be included. + diff -u \ + <(grep --exclude=tags -roh "\*$tag_regex\*" doc | sort -u | sed 's/*//g') \ + <(grep --exclude=tags -roh "|$tag_regex|" doc | sort -u | sed 's/|//g') \ + | grep '^+[^+]' && EXIT=1 +fi + +exit $EXIT diff --git a/syntax/ale-fix-suggest.vim b/syntax/ale-fix-suggest.vim new file mode 100644 index 0000000..be3d45e --- /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/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/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/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-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-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/__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/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/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/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/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/__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/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_brakeman_command_callback.vader b/test/command_callback/test_brakeman_command_callback.vader new file mode 100644 index 0000000..b97c580 --- /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(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(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_cppcheck_command_callbacks.vader b/test/command_callback/test_c_cppcheck_command_callbacks.vader new file mode 100644 index 0000000..daf61fb --- /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(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_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_clang_tidy_command_callback.vader b/test/command_callback/test_clang_tidy_command_callback.vader new file mode 100644 index 0000000..f9e5781 --- /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=''*'' %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=''-*,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=''*'' %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=''*'' %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=''*'' %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=''*'' %s -- -Wall', + \ ale_linters#cpp#clangtidy#GetCommand(bufnr('')) + \ + call ale#test#SetFilename('test.hpp') + + AssertEqual + \ ale#Escape('clang-tidy') + \ . ' -checks=''*'' %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=''*'' %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..34b87fc --- /dev/null +++ b/test/command_callback/test_cpp_clangcheck_command_callbacks.vader @@ -0,0 +1,62 @@ +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 -Xanalyzer -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 -Xanalyzer -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 --something' + \ . ' -extra-arg -Xanalyzer -extra-arg -analyzer-output=text', + \ 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..1839118 --- /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(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_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_dartanalyzer_command_callback.vader b/test/command_callback/test_dartanalyzer_command_callback.vader new file mode 100644 index 0000000..c26028d --- /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') . ' %t', + \ 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') . ' %t', + \ 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(g:dir . '/dart_paths/.packages') + \ . ' %t', + \ ale_linters#dart#dartanalyzer#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..1df2be3 --- /dev/null +++ b/test/command_callback/test_erlang_syntaxerl_command_callback.vader @@ -0,0 +1,58 @@ +Before: + Save g:ale_erlang_syntaxerl_executable + + unlet! g:ale_erlang_syntaxerl_executable + unlet! b:ale_erlang_syntaxerl_executable + + runtime ale_linters/erlang/syntaxerl.vim + + +After: + Restore + + call ale#linter#Reset() + + +Execute (The executable should be correct): + AssertEqual 'syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + AssertEqual '/some/other/syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + AssertEqual '/yet/another/syntaxerl', ale_linters#erlang#syntaxerl#GetExecutable(bufnr('')) + + +Execute (The executable should be presented in the feature check command): + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + AssertEqual "'/some/other/syntaxerl' -h", ale_linters#erlang#syntaxerl#FeatureCheck(bufnr('')) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + AssertEqual "'/yet/another/syntaxerl' -h", ale_linters#erlang#syntaxerl#FeatureCheck(bufnr('')) + + +Execute (The executable should be presented in the command): + let g:ale_erlang_syntaxerl_executable = '/some/other/syntaxerl' + AssertEqual "'/some/other/syntaxerl' %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), []) + + let b:ale_erlang_syntaxerl_executable = '/yet/another/syntaxerl' + AssertEqual "'/yet/another/syntaxerl' %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), []) + + +Execute (The -b option should be used when available): + AssertEqual "'syntaxerl' %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), [ + \ 'Syntax checker for Erlang (0.14.0)', + \ 'Usage: syntaxerl [-d | --debug] ', + \ ' syntaxerl <-h | --help>', + \ ' -d, --debug Enable debug output', + \ ' -h, --help Show this message', + \ ]) + + AssertEqual "'syntaxerl' -b %s %t", ale_linters#erlang#syntaxerl#GetCommand(bufnr(''), [ + \ 'Syntax checker for Erlang (0.14.0)', + \ 'Usage: syntaxerl [-b | --base ] [-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_flake8_command_callback.vader b/test/command_callback/test_flake8_command_callback.vader new file mode 100644 index 0000000..c36fe4f --- /dev/null +++ b/test/command_callback/test_flake8_command_callback.vader @@ -0,0 +1,133 @@ +Before: + runtime ale_linters/python/flake8.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + let g:ale_python_flake8_executable = 'flake8' + let g:ale_python_flake8_options = '' + let g:ale_python_flake8_use_global = 0 + + call ale_linters#python#flake8#ClearVersionCache() + +Execute(The flake8 callbacks should return the correct default values): + AssertEqual + \ 'flake8', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ '''flake8'' --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ '''flake8'' --format=default --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) + " Try with older versions. + call ale_linters#python#flake8#ClearVersionCache() + AssertEqual + \ '''flake8'' --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + +Execute(The flake8 command callback should let you set options): + let g:ale_python_flake8_options = '--some-option' + + AssertEqual + \ '''flake8'' --some-option --format=default --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.4']) + call ale_linters#python#flake8#ClearVersionCache() + AssertEqual + \ '''flake8'' --some-option --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 + \ '''executable with spaces'' --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ '''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') + + AssertEqual + \ g:dir . '/python_paths/with_virtualenv/env/bin/flake8', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ '''' . g:dir . '/python_paths/with_virtualenv/env/bin/flake8'' --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ '''' . g:dir . '/python_paths/with_virtualenv/env/bin/flake8''' + \ . ' --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 + \ fnameescape(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 + \ fnameescape(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 + \ fnameescape(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 + \ fnameescape(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 + \ fnameescape(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 + \ '''python'' -m flake8 --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ '''python'' -m flake8 --some-option --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + + call ale_linters#python#flake8#ClearVersionCache() + + " Leading spaces shouldn't matter + let g:ale_python_flake8_options = ' -m flake8 --some-option' + + AssertEqual + \ 'python', + \ ale_linters#python#flake8#GetExecutable(bufnr('')) + AssertEqual + \ '''python'' -m flake8 --version', + \ ale_linters#python#flake8#VersionCheck(bufnr('')) + AssertEqual + \ '''python'' -m flake8 --some-option --format=default -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) 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..5398066 --- /dev/null +++ b/test/command_callback/test_fusionlint_command_callback.vader @@ -0,0 +1,24 @@ +Before: + runtime ale_linters/fuse/fusionlint.vim + +After: + call ale#linter#Reset() + let g:ale_fuse_fusionlint_options = '' + let g:ale_fuse_fusionlint_executable = 'fusion-lint' + +Execute(The fuse fusionlint command callback should return the correct default string): + AssertEqual '''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 '''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 '''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_gometalinter_command_callback.vader b/test/command_callback/test_gometalinter_command_callback.vader new file mode 100644 index 0000000..912396c --- /dev/null +++ b/test/command_callback/test_gometalinter_command_callback.vader @@ -0,0 +1,49 @@ +Before: + Save b:ale_go_gometalinter_executable + Save b:ale_go_gometalinter_options + + let b:ale_go_gometalinter_executable = 'gometalinter' + let b:ale_go_gometalinter_options = '' + + 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 + \ ale#Escape('gometalinter') + \ . ' --include=' . ale#Escape('^' . ale#util#EscapePCRE(expand('%'))) + \ . ' ' . ale#Escape(getcwd()), + \ 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 + \ ale#Escape('something else') + \ . ' --include=' . ale#Escape('^' . ale#util#EscapePCRE(expand('%'))) + \ . ' ' . ale#Escape(getcwd()), + \ ale_linters#go#gometalinter#GetCommand(bufnr('')) + +Execute(The gometalinter callback should use configured options): + let b:ale_go_gometalinter_options = '--foobar' + + AssertEqual + \ ale#Escape('gometalinter') + \ . ' --include=' . ale#Escape('^' . ale#util#EscapePCRE(expand('%'))) + \ . ' --foobar' + \ . ' ' . ale#Escape(getcwd()), + \ ale_linters#go#gometalinter#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_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_javac_command_callback.vader b/test/command_callback/test_javac_command_callback.vader new file mode 100644 index 0000000..706839e --- /dev/null +++ b/test/command_callback/test_javac_command_callback.vader @@ -0,0 +1,97 @@ +Before: + runtime ale_linters/java/javac.vim + call ale#engine#InitBufferInfo(bufnr('')) + + silent! cd /testplugin/test/command_callback + +After: + 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('')) + let g:ale_java_javac_options = '' + let g:ale_java_javac_classpath = '' + +Execute(The javac callback should return the correct default value): + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), []) + + Assert match(b:command, '\v^javac +-Xlint +-d +''/tmp/[0-9a-zA-Z/]+'' +\%t$') >= 0, + \ 'Invalid command string: ' . b:command + +Execute(The javac callback should use g:ale_java_javac_classpath correctly): + let g:ale_java_javac_classpath = 'foo.jar' + + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), []) + + Assert match(b:command, '\v^javac +-Xlint +-cp ''+foo\.jar'' +-d ''+/tmp/[0-9a-zA-Z/]+'' +\%t$') >= 0, + \ 'Invalid command string: ' . b:command + +Execute(The javac callback should include discovered classpaths): + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \]) + + Assert match(b:command, '\v^javac +-Xlint +-cp ''+/foo/bar\.jar:/xyz/abc\.jar'' +-d +''/tmp/[0-9a-zA-Z/]+'' +\%t$') >= 0, + \ 'Invalid command string: ' . b:command + +Execute(The javac callback should combine discovered classpaths and manual ones): + let g:ale_java_javac_classpath = 'configured.jar' + + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \]) + + Assert match(b:command, '\v^javac +-Xlint +-cp +''/foo/bar\.jar:/xyz/abc\.jar:configured\.jar'' +-d ''+/tmp/[0-9a-zA-Z/]+'' +\%t$') >= 0, + \ 'Invalid command string: ' . b:command + + let g:ale_java_javac_classpath = 'configured.jar:configured2.jar' + + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \]) + + Assert match(b:command, '\v^javac +-Xlint +-cp +''/foo/bar\.jar:/xyz/abc\.jar:configured\.jar:configured2\.jar'' +-d +''/tmp/[0-9a-zA-Z/]+'' +\%t$') >= 0, + \ 'Invalid command string: ' . b:command + +Execute(The javac callback should detect source directories): + call ale#engine#Cleanup(bufnr('')) + :e! java_paths/src/main/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), []) + + Assert match(b:command, '\v^javac +-Xlint +-sourcepath ''/.*java_paths/src/main/java/'' +-d +''/tmp/[0-9a-zA-Z/]+'' +\%t$') >= 0, + \ 'Invalid command string: ' . b:command + +Execute(The javac callback should combine detected source directories and classpaths): + call ale#engine#Cleanup(bufnr('')) + :e! java_paths/src/main/java/com/something/dummy + call ale#engine#InitBufferInfo(bufnr('')) + + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \]) + + Assert match(b:command, '\v^javac +-Xlint +-cp +''/foo/bar\.jar:/xyz/abc\.jar'' +-sourcepath ''/.*java_paths/src/main/java/'' +-d +''/tmp/[0-9a-zA-Z/]+'' +\%t$') >= 0, + \ 'Invalid command string: ' . b:command + +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(''), []) + + Assert match(b:command, '\v^javac +-Xlint +-d +''/tmp/[0-9a-zA-Z/]+'' --anything --else +\%t$') >= 0, + \ 'Invalid command string: ' . b:command 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..8245337 --- /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 + \ '''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 + \ '''foobar'' --reporter inline --no-colors -', + \ ale_linters#javascript#jscs#GetCommand(bufnr('')) 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..c4ee98a --- /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 '''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 '''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 '''luacheck.sh'' --formatter plain --codes --filename %s -', + \ join(split(ale_linters#lua#luacheck#GetCommand(1))) 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..8df7193 --- /dev/null +++ b/test/command_callback/test_mypy_command_callback.vader @@ -0,0 +1,73 @@ +Before: + runtime ale_linters/python/mypy.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + let g:ale_python_mypy_executable = 'mypy' + let g:ale_python_mypy_options = '' + let g:ale_python_mypy_use_global = 0 + +Execute(The mypy callbacks should return the correct default values): + AssertEqual + \ 'mypy', + \ ale_linters#python#mypy#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ''' . g:dir . ''' && ''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 ''' . g:dir . ''' && ''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 ''' . g:dir . ''' && ''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 ''' . g:dir . '/python_paths/no_virtualenv/subdir'' && ''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') + + AssertEqual + \ g:dir . '/python_paths/with_virtualenv/env/bin/mypy', + \ ale_linters#python#mypy#GetExecutable(bufnr('')) + AssertEqual + \ 'cd ''' . g:dir . '/python_paths/with_virtualenv/subdir'' && ''' + \ . g:dir . '/python_paths/with_virtualenv/env/bin/mypy'' --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 ''' . g:dir . '/python_paths/with_virtualenv/subdir'' && ''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_php_langserver_callbacks.vader b/test/command_callback/test_php_langserver_callbacks.vader new file mode 100644 index 0000000..38630f4 --- /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 + \ g:dir . '/php-langserver-project/vendor/bin/php-language-server.php', + \ ale_linters#php#langserver#GetExecutable(bufnr('')) + AssertEqual + \ 'php ' . ale#Escape( + \ 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_phpstan_command_callbacks.vader b/test/command_callback/test_phpstan_command_callbacks.vader new file mode 100644 index 0000000..7366df8 --- /dev/null +++ b/test/command_callback/test_phpstan_command_callbacks.vader @@ -0,0 +1,29 @@ +Before: + Save g:ale_php_phpstan_executable + Save g:ale_php_phpstan_level + + unlet! g:ale_php_phpstan_executable + unlet! g:ale_php_phpstan_level + + runtime ale_linters/php/phpstan.vim + +After: + Restore + + call ale#linter#Reset() + +Execute(Custom executables should be used for the executable and command): + let g:ale_php_phpstan_executable = 'phpstan_test' + + AssertEqual 'phpstan_test', ale_linters#php#phpstan#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape('phpstan_test') . ' analyze -l4 --errorFormat raw %s', + \ ale_linters#php#phpstan#GetCommand(bufnr('')) + +Execute(project with level set to 3): + call ale#test#SetFilename('phpstan-test-files/foo/test.php') + let g:ale_php_phpstan_level = 3 + + AssertEqual + \ ale#Escape('phpstan') . ' analyze -l3 --errorFormat raw %s', + \ ale_linters#php#phpstan#GetCommand(bufnr('')) diff --git a/test/command_callback/test_puglint_command_callback.vader b/test/command_callback/test_puglint_command_callback.vader new file mode 100644 index 0000000..6d18989 --- /dev/null +++ b/test/command_callback/test_puglint_command_callback.vader @@ -0,0 +1,69 @@ +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 + \ g:dir . '/puglint_project/node_modules/.bin/pug-lint', + \ ale_linters#pug#puglint#GetExecutable(bufnr('')) + AssertEqual + \ ale#Escape(g:dir . '/puglint_project/node_modules/.bin/pug-lint') + \ . ' -c ' . ale#Escape(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(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(g:dir . '/puglint_project/node_modules/.bin/pug-lint') + \ . ' -c ' . ale#Escape(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(g:dir . '/puglint_project/node_modules/.bin/pug-lint') + \ . ' -c ' . ale#Escape(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(g:dir . '/puglint_project/node_modules/.bin/pug-lint') + \ . ' -c ' . ale#Escape(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..a516346 --- /dev/null +++ b/test/command_callback/test_pycodestyle_command_callback.vader @@ -0,0 +1,23 @@ +Before: + runtime ale_linters/python/pycodestyle.vim + Save g:ale_python_pycodestyle_executable, + \ g:ale_python_pycodestyle_options, + \ g:ale_python_pycodestyle_use_global + +After: + call ale#linter#Reset() + Restore + +Execute(The pycodestyle command callback should return default string): + AssertEqual '''pycodestyle'' -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(The pycodestyle command callback should allow options): + let g:ale_python_pycodestyle_options = '--exclude=test*.py' + AssertEqual '''pycodestyle'' --exclude=test*.py -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(The pycodestyle executable should be configurable): + let g:ale_python_pycodestyle_executable = '~/.local/bin/pycodestyle' + AssertEqual '''~/.local/bin/pycodestyle'' -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pylint_command_callback.vader b/test/command_callback/test_pylint_command_callback.vader new file mode 100644 index 0000000..f8f44ab --- /dev/null +++ b/test/command_callback/test_pylint_command_callback.vader @@ -0,0 +1,68 @@ +Before: + runtime ale_linters/python/pylint.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + + let b:command_tail = ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + let g:ale_python_pylint_executable = 'pylint' + let g:ale_python_pylint_options = '' + let g:ale_python_pylint_use_global = 0 + +Execute(The pylint callbacks should return the correct default values): + AssertEqual + \ 'pylint', + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + AssertEqual + \ '''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 + \ '''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 + \ '''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 + \ '''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') + + AssertEqual + \ g:dir . '/python_paths/with_virtualenv/env/bin/pylint', + \ ale_linters#python#pylint#GetExecutable(bufnr('')) + AssertEqual + \ ''''. g:dir . '/python_paths/with_virtualenv/env/bin/pylint'' ' . 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 + \ '''pylint'' ' . b:command_tail, + \ ale_linters#python#pylint#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..09436be --- /dev/null +++ b/test/command_callback/test_rails_best_practices_command_callback.vader @@ -0,0 +1,42 @@ +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') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(Executable should default to rails_best_practices): + AssertEqual + \ '''rails_best_practices'' --silent -f json --output-file /dev/stdout ' + \ . ale#Escape(simplify(g:dir . '/../ruby_fixtures/valid_rails_app')), + \ 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 + \ '''bin/rails_best_practices'' --silent -f json --output-file /dev/stdout ' + \ . ale#Escape(simplify(g:dir . '/../ruby_fixtures/valid_rails_app')), + \ 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 + \ '''path to/bundle'' exec rails_best_practices --silent -f json --output-file /dev/stdout ' + \ . ale#Escape(simplify(g:dir . '/../ruby_fixtures/valid_rails_app')), + \ 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_rubocop_command_callback.vader b/test/command_callback/test_rubocop_command_callback.vader new file mode 100644 index 0000000..a88d453 --- /dev/null +++ b/test/command_callback/test_rubocop_command_callback.vader @@ -0,0 +1,33 @@ +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#test#RestoreDirectory() + +Execute(Executable should default to rubocop): + AssertEqual + \ '''rubocop'' --format json --force-exclusion --stdin ' + \ . ale#Escape(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 + \ '''bin/rubocop'' --format json --force-exclusion --stdin ' + \ . ale#Escape(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 + \ '''path to/bundle'' exec rubocop --format json --force-exclusion --stdin ' + \ . ale#Escape(g:dir . '/dummy.rb'), + \ ale_linters#ruby#rubocop#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..76e6992 --- /dev/null +++ b/test/command_callback/test_rust_rls_callbacks.vader @@ -0,0 +1,32 @@ +Before: + Save g:ale_rust_rls_executable + + unlet! g:ale_rust_rls_executable + + 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') . ' +nightly', + \ 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 + \ g:dir . '/rust-rls-project', + \ ale_linters#rust#rls#GetProjectRoot(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..f051b02 --- /dev/null +++ b/test/command_callback/test_scalastyle_command_callback.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/scala/scalastyle.vim + +After: + call ale#linter#Reset() + let g:ale_scala_scalastyle_options = '' + let g:ale_scalastyle_conf_loc = '' + +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 ''/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 --config ''/dooper/config.xml'' --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..0d8fef6 --- /dev/null +++ b/test/command_callback/test_shellcheck_command_callback.vader @@ -0,0 +1,47 @@ +Before: + Save g:ale_sh_shellcheck_exclusions + Save g:ale_sh_shellcheck_executable + Save g:ale_sh_shellcheck_options + + unlet! g:ale_sh_shellcheck_exclusions + unlet! g:ale_sh_shellcheck_executable + unlet! g:ale_sh_shellcheck_options + + runtime ale_linters/sh/shellcheck.vim + +After: + Restore + + unlet! b:ale_sh_shellcheck_exclusions + unlet! b:ale_sh_shellcheck_executable + unlet! b:ale_sh_shellcheck_options + unlet! b:is_bash + + call ale#linter#Reset() + +Execute(The default shellcheck command should be correct): + AssertEqual + \ 'shellcheck -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) + +Execute(The shellcheck command should accept options): + let b:ale_sh_shellcheck_options = '--foobar' + + AssertEqual + \ 'shellcheck --foobar -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) + +Execute(The shellcheck command should accept options and exclusions): + let b:ale_sh_shellcheck_options = '--foobar' + let b:ale_sh_shellcheck_exclusions = 'foo,bar' + + AssertEqual + \ 'shellcheck --foobar -e foo,bar -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) + +Execute(The shellcheck command should include the dialect): + let b:is_bash = 1 + + AssertEqual + \ 'shellcheck -s bash -f gcc -', + \ ale_linters#sh#shellcheck#GetCommand(bufnr('')) diff --git a/test/command_callback/test_standard_command_callback.vader b/test/command_callback/test_standard_command_callback.vader new file mode 100644 index 0000000..193ead8 --- /dev/null +++ b/test/command_callback/test_standard_command_callback.vader @@ -0,0 +1,98 @@ +Before: + Save g:ale_javascript_standard_executable + Save g:ale_javascript_standard_use_global + Save g:ale_javascript_standard_options + + unlet! b:executable + unlet! g:ale_javascript_standard_executable + unlet! b:ale_javascript_standard_executable + unlet! g:ale_javascript_standard_use_global + unlet! g:ale_javascript_standard_options + + call ale#test#SetDirectory('/testplugin/test/command_callback') + call ale#test#SetFilename('testfile.js') + + runtime ale_linters/javascript/standard.vim + +After: + Restore + + unlet! b:executable + + let g:ale_has_override = {} + + call ale#test#SetFilename('test.txt') + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(bin/cmd.js paths should be preferred): + call ale#test#SetFilename('standard-test-files/with-cmd/testfile.js') + + let b:executable = g:dir + \ . '/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js' + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(.bin directories should be used too): + call ale#test#SetFilename('standard-test-files/with-bin/testfile.js') + + let b:executable = g:dir + \ . '/standard-test-files/with-bin/node_modules/.bin/standard' + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape(b:executable) . ' --stdin %s', + \ ale_linters#javascript#standard#GetCommand(bufnr('')) + +Execute(.js files should be executed with node on Windows): + let g:ale_has_override['win32'] = 1 + + call ale#test#SetFilename('standard-test-files/with-cmd/testfile.js') + + let b:executable = g:dir + \ . '/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js' + + AssertEqual + \ b:executable, + \ ale_linters#javascript#standard#GetExecutable(bufnr('')) + + AssertEqual + \ ale#Escape('node.exe') . ' ' . 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_swaglint_command_callback.vader b/test/command_callback/test_swaglint_command_callback.vader new file mode 100644 index 0000000..5d04e92 --- /dev/null +++ b/test/command_callback/test_swaglint_command_callback.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/yaml/swaglint.vim + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + call ale#linter#Reset() + let g:ale_yaml_swaglint_executable = 'swaglint' + let g:ale_yaml_swaglint_use_global = 0 + +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 g:dir . '/swaglint_paths/node_modules/.bin/swaglint', + \ ale_linters#yaml#swaglint#GetExecutable(bufnr('')) + AssertEqual g:dir . '/swaglint_paths/node_modules/.bin/swaglint -r compact --stdin', + \ ale_linters#yaml#swaglint#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..694d36d --- /dev/null +++ b/test/command_callback/test_tslint_command_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:typescript_tslint_executable + Save g:typescript_tslint_config_path + Save g:typescript_tslint_use_global + + unlet! g:typescript_tslint_executable + unlet! g:typescript_tslint_config_path + unlet! g:typescript_tslint_use_global + + runtime ale_linters/typescript/tslint.vim + + call ale#test#SetDirectory('/testplugin/test/command_callback') + +After: + Restore + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(The default tslint command should be correct): + AssertEqual + \ 'cd ''' . expand('%:p:h') . ''' && ' + \ . 'tslint --format json %t', + \ ale_linters#typescript#tslint#GetCommand(bufnr('')) 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..7c0b196 --- /dev/null +++ b/test/command_callback/test_xmllint_command_callback.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/xml/xmllint.vim + +After: + call ale#linter#Reset() + let g:ale_xml_xmllint_options = '' + let g:ale_xml_xmllint_executable = 'xmllint' + +Execute(The xml xmllint command callback should return the correct default string): + AssertEqual '''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 '''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 '''~/.local/bin/xmllint'' --noout -', + \ join(split(ale_linters#xml#xmllint#GetCommand(1))) + 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/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/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/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/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..c8c0bd4 --- /dev/null +++ b/test/fixers/test_autopep8_fixer_callback.vader @@ -0,0 +1,35 @@ +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() + +After: + Restore + + 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': "'" . g:dir . "/python_paths/with_virtualenv/env/bin/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': "'" . g:dir . "/python_paths/with_virtualenv/env/bin/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_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_eslint_fixer_callback.vader b/test/fixers/test_eslint_fixer_callback.vader new file mode 100644 index 0000000..218461d --- /dev/null +++ b/test/fixers/test_eslint_fixer_callback.vader @@ -0,0 +1,34 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + let g:ale_has_override = {} + call ale#test#RestoreDirectory() + +Execute(The path to eslint.js should be run on Unix): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --config ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#eslint#Fix(bufnr('')) + +Execute(The eslint fixer with eslint.js 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 + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('node.exe') . ' ' + \ . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' --config ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#eslint#Fix(bufnr('')) diff --git a/test/fixers/test_isort_fixer_callback.vader b/test/fixers/test_isort_fixer_callback.vader new file mode 100644 index 0000000..437e276 --- /dev/null +++ b/test/fixers/test_isort_fixer_callback.vader @@ -0,0 +1,25 @@ +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() + +After: + Restore + + 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': "'" . g:dir . "/python_paths/with_virtualenv/env/bin/isort' -" }, + \ ale#fixers#isort#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..c2fe3a6 --- /dev/null +++ b/test/fixers/test_phpcbf_fixer_callback.vader @@ -0,0 +1,56 @@ +Before: + Save g:ale_php_phpcbf_executable + Save g:ale_php_phpcbf_standard + Save g:ale_php_phpcbf_use_global + + let g:ale_php_phpcbf_executable = 'phpcbf_test' + let g:ale_php_phpcbf_standard = '' + let g:ale_php_phpcbf_use_global = 0 + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(project with phpcbf should use local by default): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(use-global should override local detection): + let g:ale_php_phpcbf_use_global = 1 + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(project without phpcbf should use global): + call ale#test#SetFilename('php_paths/project-without-phpcbf/foo/test.php') + + AssertEqual + \ 'phpcbf_test', + \ ale#fixers#phpcbf#GetExecutable(bufnr('')) + +Execute(The phpcbf callback should return the correct default values): + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf') . ' --stdin-path=%s ' }, + \ ale#fixers#phpcbf#Fix(bufnr('')) + +Execute(The phpcbf callback should include the phpcbf_standard option): + let g:ale_php_phpcbf_standard = 'phpcbf_ruleset.xml' + call ale#test#SetFilename('php_paths/project-with-phpcbf/foo/test.php') + + AssertEqual + \ {'command': ale#Escape(g:dir . '/php_paths/project-with-phpcbf/vendor/bin/phpcbf') . ' --stdin-path=%s ' . '--standard=phpcbf_ruleset.xml'}, + \ ale#fixers#phpcbf#Fix(bufnr('')) + diff --git a/test/fixers/test_puppetlint_fixer_callback.vader b/test/fixers/test_puppetlint_fixer_callback.vader new file mode 100644 index 0000000..04a85e5 --- /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': "'" . 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_rubocop_fixer_callback.vader b/test/fixers/test_rubocop_fixer_callback.vader new file mode 100644 index 0000000..87d56d0 --- /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(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(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_standard_fixer_callback.vader b/test/fixers/test_standard_fixer_callback.vader new file mode 100644 index 0000000..88169bb --- /dev/null +++ b/test/fixers/test_standard_fixer_callback.vader @@ -0,0 +1,32 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + let g:ale_has_override = {} + call ale#test#RestoreDirectory() + +Execute(The path to standard.js should be run on Unix): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/standard/bin/cmd.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#standard#Fix(bufnr('')) + +Execute(The standard fixer with standard.js 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 + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('node.exe') . ' ' + \ . ale#Escape(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..482704d --- /dev/null +++ b/test/fixers/test_stylelint_fixer_callback.vader @@ -0,0 +1,32 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + let g:ale_has_override = {} + call ale#test#RestoreDirectory() + +Execute(The path to stylelint.js should be run on Unix): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.css') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': + \ ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js')) + \ . ' --fix %t', + \ }, + \ ale#fixers#stylelint#Fix(bufnr('')) + +Execute(The stylelint fixer with stylelint.js should be run with node on Windows): + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.css') + let g:ale_has_override['win32'] = 1 + + " We have to execute the file with node. + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('node.exe') . ' ' + \ . ale#Escape(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_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..6edc267 --- /dev/null +++ b/test/fixers/test_yapf_fixer_callback.vader @@ -0,0 +1,38 @@ +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() + +After: + Restore + + 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(g:dir . '/python_paths/with_virtualenv/env/bin/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(g:dir . '/python_paths/with_virtualenv/env/bin/yapf') + \ . ' --no-local-style' + \ . ' --style ' . ale#Escape(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/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/handler/test_ansible_lint_handler.vader b/test/handler/test_ansible_lint_handler.vader new file mode 100644 index 0000000..b14b1f6 --- /dev/null +++ b/test/handler/test_ansible_lint_handler.vader @@ -0,0 +1,42 @@ +Before: + runtime ale_linters/ansible/ansible_lint.vim + call ale#test#SetFilename('main.yml') + +After: + call ale#linter#Reset() + +Execute(The ansible-lint handler should handle basic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 35, + \ 'col': 0, + \ 'type': 'E', + \ 'text': 'EANSIBLE0002: Trailing whitespace', + \ }, + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [ + \ '/tmp/vxepmGL/1/main.yml:35: [EANSIBLE0002] Trailing whitespace', + \ ]) + +Execute (The ansible-lint handler should handle names with spaces): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 6, + \ 'type': 'E', + \ 'text': 'E111: indentation is not a multiple of four', + \ }, + \ ], + \ ale_linters#ansible#ansible_lint#Handle(bufnr(''), [ + \ '/tmp/vxepm GL/1/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..2868628 --- /dev/null +++ b/test/handler/test_asm_handler.vader @@ -0,0 +1,24 @@ +Execute(The asm GCC handler should parse lines from GCC 6.3.1 correctly): + runtime ale_linters/asm/gcc.vim + + 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", + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_brakeman_handler.vader b/test/handler/test_brakeman_handler.vader new file mode 100644 index 0000000..02d7023 --- /dev/null +++ b/test/handler/test_brakeman_handler.vader @@ -0,0 +1,82 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/handler') + cd .. + + 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 + \ [ + \ { + \ 'lnum': 84, + \ 'text': 'SQL Injection Possible SQL injection (Medium)', + \ 'type': 'W', + \ }, + \ { + \ '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": "app/models/thing.rb",', + \ '"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": "app/models/thing.rb",', + \ '"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_checkstyle_handler.vader b/test/handler/test_checkstyle_handler.vader new file mode 100644 index 0000000..0384451 --- /dev/null +++ b/test/handler/test_checkstyle_handler.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/java/checkstyle.vim + +After: + call ale#linter#Reset() + +Execute(The checkstyle handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 101, + \ 'text': "'method def rcurly' has incorrect indentation level 4, expected level should be 2. [Indentation]", + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 63, + \ 'col': 3, + \ 'text': "Missing a Javadoc comment. [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..d28b9eb --- /dev/null +++ b/test/handler/test_clang_handler.vader @@ -0,0 +1,23 @@ +Execute(clang errors from included files should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': 'Problems were found in the header (See :ALEDetail)', + \ 'detail': join([ + \ './b.h:1:1: error: expected identifier or ''(''', + \ '{{{', + \ '^', + \ '1 error generated.', + \ ], "\n"), + \ }, + \ ], + \ 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_coffeelint_handler.vader b/test/handler/test_coffeelint_handler.vader new file mode 100644 index 0000000..4426e44 --- /dev/null +++ b/test/handler/test_coffeelint_handler.vader @@ -0,0 +1,18 @@ +Execute(The coffeelint handler should parse lines correctly): + runtime ale_linters/coffee/coffeelint.vim + + 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.", + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_common_handlers.vader b/test/handler/test_common_handlers.vader new file mode 100644 index 0000000..65026d8 --- /dev/null +++ b/test/handler/test_common_handlers.vader @@ -0,0 +1,179 @@ +Execute(HandleCSSLintFormat should handle CSS errors): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': '(errors) Expected RBRACE at line 2, col 1.', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 5, + \ 'type': 'W', + \ 'text': '(known-properties) Expected ... but found ''wat''.', + \ }, + \ ], + \ 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..6df84cc --- /dev/null +++ b/test/handler/test_cpplint_handler.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/cpp/cpplint.vim + +Execute(cpplint warnings from included files should be parsed correctly): + + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 0, + \ 'text': ' Estra space after ( in function call [whitespace/parents] [4]', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 120, + \ 'col': 0, + \ 'text': ' At least two spaces is best between code and comments [whitespace/comments] [2]', + \ 'type': 'W', + \ }, + \ ], + \ ale#handlers#cpplint#HandleCppLintFormat(347, [ + \ 'test.cpp:5: Estra 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]', + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_credo_handler.vader b/test/handler/test_credo_handler.vader new file mode 100644 index 0000000..73f98ba --- /dev/null +++ b/test/handler/test_credo_handler.vader @@ -0,0 +1,29 @@ +Execute(The credo handler should parse lines correctly): + runtime ale_linters/elixir/credo.vim + + 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`.', + \ ]) + +After: + call ale#linter#Reset() + diff --git a/test/handler/test_crystal_handler.vader b/test/handler/test_crystal_handler.vader new file mode 100644 index 0000000..984b976 --- /dev/null +++ b/test/handler/test_crystal_handler.vader @@ -0,0 +1,16 @@ +Execute(The crystal handler should parse lines correctly and add the column if it can): + runtime ale_linters/crystal/crystal.vim + 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"}]' + \ ]) + +After: + call ale#linter#Reset() 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..ee9795e --- /dev/null +++ b/test/handler/test_dogma_handler.vader @@ -0,0 +1,28 @@ +Execute(The dogma handler should parse lines correctly): + runtime ale_linters/elixir/dogma.vim + + 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', + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_elmmake_handler.vader b/test/handler/test_elmmake_handler.vader new file mode 100644 index 0000000..cbd7ac9 --- /dev/null +++ b/test/handler/test_elmmake_handler.vader @@ -0,0 +1,76 @@ +Before: + runtime ale_linters/elm/make.vim + +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":"' . $TMPDIR . '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":"' . $TMPDIR . '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":"' . $TMPDIR . '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":"' . $TMPDIR . '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":"' . $TMPDIR . 'Module.elm"}]', + \ "Not JSON", + \ "Also not JSON", + \ ]) + +After: + unlet! g:config_error_lines + call ale#linter#Reset() diff --git a/test/handler/test_embertemplatelint_handler.vader b/test/handler/test_embertemplatelint_handler.vader new file mode 100644 index 0000000..8e132d3 --- /dev/null +++ b/test/handler/test_embertemplatelint_handler.vader @@ -0,0 +1,85 @@ +" Author: Adrian Zalewski + +Before: + runtime ale_linters/handlebars/embertemplatelint.vim + +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 + \ [ + \ { + \ 'bufnr': 347, + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'bare-strings: Non-translated string used', + \ 'type': 'E', + \ }, + \ { + \ 'bufnr': 347, + \ '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": 1, + \ "column": 1, + \ "source": "Error: Parse error on line 5 ...", + \ "severity": 2 + \ } + \ ] + \ }', '\n') + + AssertEqual + \ [ + \ { + \ 'bufnr': 347, + \ 'lnum': 1, + \ 'col': 1, + \ '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, ['{}']) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_eslint_handler.vader b/test/handler/test_eslint_handler.vader new file mode 100644 index 0000000..943e177 --- /dev/null +++ b/test/handler/test_eslint_handler.vader @@ -0,0 +1,236 @@ +Before: + Save g:ale_javascript_eslint_suppress_eslintignore + + let g:ale_javascript_eslint_suppress_eslintignore = 0 + +After: + Restore + + unlet! g:config_error_lines + +Execute(The eslint handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 47, + \ 'col': 14, + \ 'text': 'Missing trailing comma. [Warning/comma-dangle]', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 56, + \ 'col': 41, + \ 'text': 'Missing semicolon. [Error/semi]', + \ '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: 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(347, 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(347, 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(347, 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(347, 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'' [Error]', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 70, + \ 'col': 3, + \ 'end_col': 5, + \ 'text': '''foo'' is not defined. [Error/no-undef]', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 71, + \ 'col': 2, + \ 'end_col': 6, + \ 'text': 'Unexpected `await` inside a loop. [Error/no-await-in-loop]', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 72, + \ 'col': 6, + \ 'end_col': 10, + \ 'text': 'Redundant use of `await` on a return value. [Error/no-return-await]', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 73, + \ 'col': 4, + \ 'end_col': 10, + \ 'text': 'Unexpected console statement [Error/no-console]', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 74, + \ 'col': 4, + \ 'end_col': 11, + \ 'text': 'Unexpected ''debugger'' statement. [Error/no-debugger]', + \ 'type': 'E', + \ }, + \ ], + \ ale#handlers#eslint#Handle(347, [ + \ '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 ) [Error]', + \ '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. [Warning]' + \ }], + \ ale#handlers#eslint#Handle(347, [ + \ '/path/to/some/ignored.js:0:0: File ignored because of a matching ignore pattern. Use "--no-ignore" to override. [Warning]', + \ ]) + +Execute(eslint should not warn about ignored files when explicitly disabled): + let g:ale_javascript_eslint_suppress_eslintignore = 1 + + AssertEqual + \ [], + \ ale#handlers#eslint#Handle(347, [ + \ '/path/to/some/ignored.js:0:0: File ignored because of a matching ignore pattern. Use "--no-ignore" to override. [Warning]', + \ ]) diff --git a/test/handler/test_flake8_handler.vader b/test/handler/test_flake8_handler.vader new file mode 100644 index 0000000..0d6d65f --- /dev/null +++ b/test/handler/test_flake8_handler.vader @@ -0,0 +1,134 @@ +Before: + runtime ale_linters/python/flake8.vim + +After: + call ale#linter#Reset() + +Execute(The flake8 handler should handle basic warnings and syntax errors): + AssertEqual + \ [ + \ { + \ 'lnum': 6, + \ 'col': 6, + \ 'type': 'E', + \ 'text': 'E111: indentation is not a multiple of four', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 6, + \ 'type': 'W', + \ 'text': 'W123: some warning', + \ 'sub_type': 'style', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 3, + \ 'type': 'E', + \ 'text': 'E999: SyntaxError: invalid syntax', + \ }, + \ ], + \ 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 should be set for certain errors): + AssertEqual + \ [ + \ { + \ 'lnum': 25, + \ 'col': 1, + \ 'type': 'E', + \ 'end_col': 3, + \ 'text': 'F821: undefined name ''foo''', + \ }, + \ { + \ 'lnum': 28, + \ 'col': 5, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': 'F405: hello may be undefined, or defined from star imports: x', + \ }, + \ { + \ 'lnum': 104, + \ 'col': 5, + \ 'type': 'E', + \ 'end_col': 12, + \ 'text': 'F999: ''continue'' not properly in loop', + \ }, + \ { + \ 'lnum': 106, + \ 'col': 5, + \ 'type': 'E', + \ 'end_col': 9, + \ 'text': 'F999: ''break'' outside loop', + \ }, + \ { + \ 'lnum': 109, + \ 'col': 5, + \ 'type': 'E', + \ 'end_col': 8, + \ 'text': 'F841: local variable ''test'' is assigned to but never used', + \ }, + \ ], + \ 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': 'E111: indentation is not a multiple of four', + \ 'sub_type': 'style', + \ }, + \ ], + \ ale_linters#python#flake8#Handle(42, [ + \ 'C:\something\with spaces.py:6:6: E111 indentation is not a multiple of four', + \ ]) diff --git a/test/handler/test_flow_handler.vader b/test/handler/test_flow_handler.vader new file mode 100644 index 0000000..288610b --- /dev/null +++ b/test/handler/test_flow_handler.vader @@ -0,0 +1,354 @@ +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": "/home/w0rp/Downloads/graphql-js/src/language/parser.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 417, + \ "column": 10, + \ "offset": 9503 + \ }, + \ "end": { + \ "line": 417, + \ "column": 10, + \ "offset": 9504 + \ } + \ }, + \ "path": "/home/w0rp/Downloads/graphql-js/src/language/parser.js", + \ "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": "/home/w0rp/Downloads/graphql-js/src/language/parser.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 416, + \ "column": 43, + \ "offset": 9472 + \ }, + \ "end": { + \ "line": 416, + \ "column": 61, + \ "offset": 9491 + \ } + \ }, + \ "path": "/home/w0rp/Downloads/graphql-js/src/language/parser.js", + \ "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": "/home/w0rp/Downloads/graphql-js/src/language/parser.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 419, + \ "column": 3, + \ "offset": 9508 + \ }, + \ "end": { + \ "line": 421, + \ "column": 7, + \ "offset": 9626 + \ } + \ }, + \ "path": "/home/w0rp/Downloads/graphql-js/src/language/parser.js", + \ "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": "/Users/rav/Projects/vim-ale-flow/index.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": "/Users/rav/Projects/vim-ale-flow/index.js", + \ "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": "/Users/rav/Projects/vim-ale-flow/index.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": "/Users/rav/Projects/vim-ale-flow/index.js", + \ "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": "vim-ale-flow/index.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": "vim-ale-flow/index.js", + \ "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": "vim-ale-flow/index.js", + \ "type": "SourceFile", + \ "start": { + \ "line": 6, + \ "column": 3, + \ "offset": 92 + \ }, + \ "end": { + \ "line": 6, + \ "column": 18, + \ "offset": 108 + \ } + \ }, + \ "path": "vim-ale-flow/index.js", + \ "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 diff --git a/test/handler/test_fortran_handler.vader b/test/handler/test_fortran_handler.vader new file mode 100644 index 0000000..acd83e3 --- /dev/null +++ b/test/handler/test_fortran_handler.vader @@ -0,0 +1,106 @@ +Execute(The fortran handler should parse lines from GCC 4.1.2 correctly): + runtime ale_linters/fortran/gcc.vim + + 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", + \ ]) + +After: + call ale#linter#Reset() + + +Execute(The fortran handler should parse lines from GCC 4.9.3 correctly): + runtime ale_linters/fortran/gcc.vim + + 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", + \ ]) + +After: + call ale#linter#Reset() + + + +Execute(The fortran handler should parse lines from GCC 6.3.1 correctly): + runtime ale_linters/fortran/gcc.vim + + 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", + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_gcc_handler.vader b/test/handler/test_gcc_handler.vader new file mode 100644 index 0000000..2f60390 --- /dev/null +++ b/test/handler/test_gcc_handler.vader @@ -0,0 +1,135 @@ +Execute(GCC errors from included files should be parsed correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': 'Problems were found in the header (See :ALEDetail)', + \ 'detail': join([ + \ 'broken.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ 'In file included from :3:0:', + \ 'broken.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ ]) + + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': 'Problems were found in the header (See :ALEDetail)', + \ 'detail': join([ + \ 'b.h:1:1: error: expected identifier or ''('' before ''{'' token', + \ ' {{{', + \ ' ^', + \ ], "\n"), + \ }, + \ ], + \ 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': 3, + \ 'type': 'E', + \ 'text': 'Problems were found in the header (See :ALEDetail)', + \ 'detail': join([ + \ 'b.h:1:1: error: unknown type name ‘bad_type’', + \ ' bad_type x;', + \ ' ^', + \ 'b.h:2:1: error: unknown type name ‘other_bad_type’', + \ ' other_bad_type y;', + \ ' ^', + \ ], "\n"), + \ }, + \ ], + \ ale#handlers#gcc#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(GCC versions should be parsed correctly): + AssertEqual [4, 9, 1], ale#handlers#gcc#ParseGCCVersion([ + \ 'g++ (GCC) 4.9.1 20140922 (Red Hat 4.9.1-10)', + \]) + AssertEqual [4, 8, 4], ale#handlers#gcc#ParseGCCVersion([ + \ 'gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4', + \ 'Copyright (C) 2013 Free Software Foundation, Inc.', + \ 'This is free software; see the source for copying conditions. There is NO', + \ 'warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.', + \]) + +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''', + \ ]) diff --git a/test/handler/test_ghc_handler.vader b/test/handler/test_ghc_handler.vader new file mode 100644 index 0000000..bf54386 --- /dev/null +++ b/test/handler/test_ghc_handler.vader @@ -0,0 +1,78 @@ +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]', + \ }, + \ ], + \ 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.', + \ }, + \ { + \ '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.', + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ '', + \ '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.', + \ '', + \ '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)', + \ }, + \ { + \ 'lnum': 84, + \ 'col': 1, + \ 'type': 'W', + \ 'text': 'Top-level binding with no type signature:^@ myLayout :: Choose Tall (Choose (Mirror Tall) Full) a', + \ }, + \ { + \ 'lnum': 94, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'Some other error', + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ 'src/Main.hs:168:1:', + \ ' parse error (possibly incorrect indentation or mismatched brackets)', + \ 'src/Main.hs:84:1:Warning: Top-level binding with no type signature:^@ myLayout :: Choose Tall (Choose (Mirror Tall) Full) a', + \ 'src/Main.hs:94:5:Error:', + \ ' Some other error', + \ ]) diff --git a/test/handler/test_ghc_mod_handler.vader b/test/handler/test_ghc_mod_handler.vader new file mode 100644 index 0000000..b8d09a5 --- /dev/null +++ b/test/handler/test_ghc_mod_handler.vader @@ -0,0 +1,30 @@ +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', + \ }, + \ ], + \ 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', + \ ]) diff --git a/test/handler/test_gobuild_handler.vader b/test/handler/test_gobuild_handler.vader new file mode 100644 index 0000000..ce2119c --- /dev/null +++ b/test/handler/test_gobuild_handler.vader @@ -0,0 +1,44 @@ +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): + silent file! /foo/bar/baz.go + + AssertEqual + \ [ + \ { + \ 'lnum': 27, + \ 'col': 0, + \ 'text': 'missing argument for Printf("%s"): format reads arg 2, have only 1 args', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#go#gobuild#Handler(bufnr(''), [ + \ 'baz.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..603ba22 --- /dev/null +++ b/test/handler/test_gometalinter_handler.vader @@ -0,0 +1,53 @@ +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 relative paths correctly): + silent file /foo/bar/baz.go + + AssertEqual + \ [ + \ { + \ 'lnum': 12, + \ 'col': 3, + \ 'text': 'expected ''package'', found ''IDENT'' gibberish (staticcheck)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 37, + \ 'col': 5, + \ 'text': 'expected ''package'', found ''IDENT'' gibberish (golint)', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#go#gometalinter#Handler(bufnr(''), [ + \ 'baz.go:12:3:warning: expected ''package'', found ''IDENT'' gibberish (staticcheck)', + \ 'baz.go:37:5:error: expected ''package'', found ''IDENT'' gibberish (golint)', + \ ]) 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..1c20be7 --- /dev/null +++ b/test/handler/test_idris_handler.vader @@ -0,0 +1,52 @@ +Before: + runtime ale_linters/idris/idris.vim + +Execute(The idris handler should parse messages that reference a single column): + call ale#test#SetFilename('/tmp/foo.idr') + + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 5, + \ 'type': 'E', + \ 'text': 'When checking right hand side of main with expected type IO () When checking an application of function Prelude.Monad.>>=: Type mismatch between IO () (Type of putStrLn _) and _ -> _ (Is putStrLn _ applied to too many arguments?) Specifically: Type mismatch between IO and \uv => _ -> uv' + \ } + \ ], + \ ale_linters#idris#idris#Handle(bufnr(''), [ + \ '/tmp/foo.idr:4:5:', + \ 'When checking right hand side of main with expected type', + \ ' IO ()', + \ '', + \ 'When checking an application of function Prelude.Monad.>>=:', + \ ' Type mismatch between', + \ ' IO () (Type of putStrLn _)', + \ ' and', + \ ' _ -> _ (Is putStrLn _ applied to too many arguments?)', + \ '', + \ ' Specifically:', + \ ' Type mismatch between', + \ ' IO', + \ ' and', + \ ' \uv => _ -> uv', + \ ]) + +Execute(The idris handler should parse messages that reference a column range): + call ale#test#SetFilename('/tmp/foo.idr') + + AssertEqual + \ [ + \ { + \ 'lnum': 11, + \ 'col': 11, + \ 'type': 'E', + \ 'text': 'When checking right hand side of Main.case block in main at /tmp/foo.idr:10:10 with expected type IO () Last statement in do block must be an expression' + \ } + \ ], + \ ale_linters#idris#idris#Handle(bufnr(''), [ + \ '/tmp/foo.idr:11:11-13:', + \ 'When checking right hand side of Main.case block in main at /tmp/foo.idr:10:10 with expected type', + \ ' IO ()', + \ '', + \ 'Last statement in do block must be an expression', + \ ]) diff --git a/test/handler/test_javac_handler.vader b/test/handler/test_javac_handler.vader new file mode 100644 index 0000000..2cf3207 --- /dev/null +++ b/test/handler/test_javac_handler.vader @@ -0,0 +1,57 @@ +Before: + runtime ale_linters/java/javac.vim + +After: + call ale#linter#Reset() + +Execute(The javac handler should handle cannot find symbol errors): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'error: some error', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 5, + \ 'text': 'error: cannot find symbol: BadName', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 34, + \ 'col': 5, + \ 'text': 'error: cannot find symbol: BadName2', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 37, + \ 'text': 'warning: some warning', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 42, + \ 'col': 11, + \ 'text': 'error: cannot find symbol: bar()', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#java#javac#Handle(347, [ + \ '/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/264/foo.java:42: error: cannot find symbol', + \ ' this.bar();', + \ ' ^', + \ ' symbol: method bar()', + \ '5 errors', + \ ]) diff --git a/test/handler/test_jscs_handler.vader b/test/handler/test_jscs_handler.vader new file mode 100644 index 0000000..6247307 --- /dev/null +++ b/test/handler/test_jscs_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/javascript/jscs.vim + +After: + call ale#linter#Reset() + +Execute(jscs should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 7, + \ 'text': 'disallowVar: Variable declarations should use `let` or `const` not `var`', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 21, + \ 'text': 'disallowTrailingWhitespace: Illegal trailing whitespace', + \ }, + \ { + \ 'lnum': 5, + \ 'col': 9, + \ 'text': 'disallowUnusedVariables: Variable `hello` is not used', + \ }, + \ ], + \ 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', + \ ]) diff --git a/test/handler/test_lua_handler.vader b/test/handler/test_lua_handler.vader new file mode 100644 index 0000000..af1c134 --- /dev/null +++ b/test/handler/test_lua_handler.vader @@ -0,0 +1,32 @@ +After: + call ale#linter#Reset() + +Execute(The luacheck handler should parse lines correctly): + runtime ale_linters/lua/luacheck.vim + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 8, + \ 'text': 'W612: line contains trailing whitespace', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'text': 'W213: unused loop variable ''k''', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 19, + \ 'text': 'W113: accessing undefined variable ''x''', + \ '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''', + \ ]) diff --git a/test/handler/test_mypy_handler.vader b/test/handler/test_mypy_handler.vader new file mode 100644 index 0000000..d0cf91e --- /dev/null +++ b/test/handler/test_mypy_handler.vader @@ -0,0 +1,80 @@ +Before: + runtime ale_linters/python/mypy.vim + +After: + call ale#linter#Reset() + silent file something_else.py + +Execute(The mypy handler should parse lines correctly): + silent file foo/bar/__init__.py + + AssertEqual + \ [ + \ { + \ 'lnum': 768, + \ 'col': 38, + \ 'filename': 'foo/bar/foo/bar/baz.py', + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''SOME_SYMBOL''', + \ }, + \ { + \ 'lnum': 821, + \ 'col': 38, + \ 'filename': 'foo/bar/foo/bar/baz.py', + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''SOME_SYMBOL''', + \ }, + \ { + \ 'lnum': 38, + \ 'col': 44, + \ 'filename': 'foo/bar/foo/bar/other.py', + \ 'type': 'E', + \ 'text': 'Cannot determine type of ''ANOTHER_SYMBOL''', + \ }, + \ { + \ 'lnum': 15, + \ 'col': 3, + \ 'filename': 'foo/bar/foo/bar/__init__.py', + \ 'type': 'E', + \ 'text': 'Argument 1 to "somefunc" has incompatible type "int"; expected "str"' + \ }, + \ { + \ 'lnum': 72, + \ 'col': 1, + \ 'filename': 'foo/bar/foo/bar/__init__.py', + \ 'type': 'W', + \ 'text': 'Some warning', + \ }, + \ ], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ 'foo/bar/baz.py: note: In class "SomeClass":', + \ 'foo/bar/baz.py:768:38: error: Cannot determine type of ''SOME_SYMBOL''', + \ 'foo/bar/baz.py: note: In class "AnotherClass":', + \ 'foo/bar/baz.py:821:38: error: Cannot determine type of ''SOME_SYMBOL''', + \ 'foo/bar/__init__.py:92: note: In module imported here:', + \ 'foo/bar/other.py: note: In class "YetAnotherClass":', + \ 'foo/bar/other.py:38:44: error: Cannot determine type of ''ANOTHER_SYMBOL''', + \ 'foo/bar/__init__.py: note: At top level:', + \ 'foo/bar/__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,', + \ 'foo/bar/__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''', + \ ]) diff --git a/test/handler/test_nagelfar_handler.vader b/test/handler/test_nagelfar_handler.vader new file mode 100644 index 0000000..2a31f19 --- /dev/null +++ b/test/handler/test_nagelfar_handler.vader @@ -0,0 +1,171 @@ +Before: + runtime ale_linters/tcl/nagelfar.vim + +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..c9a1b71 --- /dev/null +++ b/test/handler/test_nim_handler.vader @@ -0,0 +1,38 @@ +Execute(Parsing nim errors should work): + runtime ale_linters/nim/nimcheck.vim + silent file foobar.nim + + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'col': 8, + \ 'text': 'Warning: use {.base.} for base methods; baseless methods are deprecated [UseBase]', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 12, + \ 'col': 2, + \ 'text': 'Error: identifier expected, but found ''a.barfoo''', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 5, + \ 'text': 'Hint: ''NotUsed'' is declared but not used [XDeclaredButNotUsed]', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 12, + \ 'col': 2, + \ 'text': 'Error: 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..1555e59 --- /dev/null +++ b/test/handler/test_nix_handler.vader @@ -0,0 +1,27 @@ +Execute(The nix handler should parse nix-instantiate error messages correctly): + runtime ale_linters/nix/nix.vim + + 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', + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_perl_handler.vader b/test/handler/test_perl_handler.vader new file mode 100644 index 0000000..18c5d70 --- /dev/null +++ b/test/handler/test_perl_handler.vader @@ -0,0 +1,49 @@ +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 ' . g:dir . '/foo.pm line 4, near "aklsdfjmy "', + \ 'Compilation failed in require at ' . g:dir . '/bar.pl line 2.', + \ 'BEGIN failed--compilation aborted at ' . 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.', + \ ]) diff --git a/test/handler/test_php_handler.vader b/test/handler/test_php_handler.vader new file mode 100644 index 0000000..0d4d427 --- /dev/null +++ b/test/handler/test_php_handler.vader @@ -0,0 +1,81 @@ +Before: + runtime ale_linters/php/php.vim + +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 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", + \ ]) + +After: + call ale#linter#Reset() 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_pycodestyle_handler.vader b/test/handler/test_pycodestyle_handler.vader new file mode 100644 index 0000000..cc83fc8 --- /dev/null +++ b/test/handler/test_pycodestyle_handler.vader @@ -0,0 +1,48 @@ +Before: + runtime ale_linters/python/pycodestyle.vim + +After: + call ale#linter#Reset() + silent file something_else.py + +Execute(The pycodestyle handler should parse output): + AssertEqual + \ [ + \ { + \ 'lnum': 69, + \ 'col': 11, + \ 'text': 'E401 multiple imports on one line', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 77, + \ 'col': 1, + \ 'text': 'E302 expected 2 blank lines, found 1', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 88, + \ 'col': 5, + \ 'text': 'E301 expected 1 blank line, found 0', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 222, + \ 'col': 34, + \ 'text': 'W602 deprecated form of raising exception', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 544, + \ 'col': 21, + \ 'text': 'W601 .has_key() is deprecated, use ''in''', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#python#pycodestyle#Handle(bufnr(''), [ + \ 'stdin:69:11: E401 multiple imports on one line', + \ 'stdin:77:1: E302 expected 2 blank lines, found 1', + \ 'stdin:88:5: E301 expected 1 blank line, found 0', + \ 'stdin:222:34: W602 deprecated form of raising exception', + \ 'example.py:544:21: W601 .has_key() is deprecated, use ''in''', + \ ]) diff --git a/test/handler/test_pylint_handler.vader b/test/handler/test_pylint_handler.vader new file mode 100644 index 0000000..2314e9b --- /dev/null +++ b/test/handler/test_pylint_handler.vader @@ -0,0 +1,60 @@ +Before: + runtime ale_linters/python/pylint.vim + +After: + call ale#linter#Reset() + silent file something_else.py + +Execute(pylint handler parsing, translating columns to 1-based index): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 1, + \ 'text': 'C0303: Trailing whitespace (trailing-whitespace)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'C0111: Missing module docstring (missing-docstring)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'text': 'C0111: Missing function docstring (missing-docstring)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'text': 'E0103: ''break'' not properly in loop (not-in-loop)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 4, + \ 'col': 5, + \ 'text': 'W0101: Unreachable code (unreachable)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 33, + \ 'text': 'W0702: No exception type(s) specified (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)', + \ ]) 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..11875cb --- /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",', + \ '"filename": "' . g:dir . '/ruby_fixtures/valid_rails_app/app/models/thing.rb"', + \ '},{', + \ '"message": "other advice",', + \ '"line_number": "10",', + \ '"filename": "' . g:dir . '/ruby_fixtures/valid_rails_app/app/models/thing.rb"', + \ '}', + \ ']' + \ ]) + +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_reek_handler.vader b/test/handler/test_reek_handler.vader new file mode 100644 index 0000000..6861428 --- /dev/null +++ b/test/handler/test_reek_handler.vader @@ -0,0 +1,76 @@ +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': 'Rule1: Context#method violates rule number one', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 34, + \ 'text': 'Rule2: Context#method violates rule number two', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 56, + \ 'text': 'Rule2: Context#method violates rule number two', + \ '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': 'Rule1: violates rule number one', + \ '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': 'Rule1: Context#method violates rule number one [https://example.com/Rule1.md]', + \ '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_rpmlint_handler.vader b/test/handler/test_rpmlint_handler.vader new file mode 100644 index 0000000..45f5071 --- /dev/null +++ b/test/handler/test_rpmlint_handler.vader @@ -0,0 +1,29 @@ +Execute(The rpmlint handler should parse error messages correctly): + runtime ale_linters/spec/rpmlint.vim + + 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_rubocop_handler.vader b/test/handler/test_rubocop_handler.vader new file mode 100644 index 0000000..4d3bbe2 --- /dev/null +++ b/test/handler/test_rubocop_handler.vader @@ -0,0 +1,72 @@ +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... [Style/SomeCop]', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 12, + \ 'col': 2, + \ 'end_col': 2, + \ 'text': 'Some error [Style/SomeOtherCop]', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 10, + \ 'col': 5, + \ 'end_col': 12, + \ 'text': 'Regular warning [Style/WarningCop]', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 11, + \ 'col': 1, + \ 'end_col': 1, + \ 'text': 'Another error [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..ba67650 --- /dev/null +++ b/test/handler/test_ruby_handler.vader @@ -0,0 +1,36 @@ +Execute(The ruby handler should parse lines correctly and add the column if it can): + runtime ale_linters/ruby/ruby.vim + " 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", + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_rust_handler.vader b/test/handler/test_rust_handler.vader new file mode 100644 index 0000000..510ae69 --- /dev/null +++ b/test/handler/test_rust_handler.vader @@ -0,0 +1,111 @@ +Execute(The Rust handler should handle rustc output): + call ale#test#SetFilename('src/playpen.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 15, + \ 'end_lnum': 15, + \ 'type': 'E', + \ 'col': 418, + \ 'end_col': 421, + \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ }, + \ { + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'type': 'E', + \ 'col': 407, + \ 'end_col': 410, + \ 'text': 'no method named `wat` found for type `std::string::String` in the current scope', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrorsForFile(bufnr(''), '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): + call ale#test#SetFilename('src/playpen.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 15, + \ 'end_lnum': 15, + \ 'type': 'E', + \ 'col': 11505, + \ 'end_col': 11508, + \ 'text': 'expected one of `.`, `;`, `?`, `}`, or an operator, found `for`', + \ }, + \ { + \ 'lnum': 13, + \ 'end_lnum': 13, + \ 'type': 'E', + \ 'col': 11494, + \ 'end_col': 11497, + \ 'text': 'no method named `wat` found for type `std::string::String` in the current scope', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrorsForFile(bufnr(''), '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"}}', + \ ]) + +" Execute(The Rust handler should handle cargo output on Windows): +" call ale#test#SetFilename('src\nvim.rs') +" +" AssertEqual +" \ [ +" \ { +" \ 'lnum': 467, +" \ 'end_lnum': 467, +" \ 'type': 'E', +" \ 'col': 43198, +" \ 'end_col': 43199, +" \ 'text': 'expected one of `!` or `::`, found `#`: unexpected token', +" \ }, +" \ ], +" \ ale#handlers#rust#HandleRustErrorsForFile(bufnr(''), 'src\nvim.rs', [ +" \ '{"message":{"children":[],"code":null,"level":"error","message":"expected one of `!` or `::`, found `#`","rendered":null,"spans":[{"byte_end":43199,"byte_start":43198,"column_end":2,"column_start":1,"expansion":null,"file_name":"src\\nvim.rs","is_primary":true,"label":"unexpected token","line_end":467,"line_start":467,"suggested_replacement":null,"text":[{"highlight_end":2,"highlight_start":1,"text":"#[cfg(test)]\r"}]}]},"package_id":"nvim-gtk 0.1.2 (path+file:///E:/daa/local/neovim-gtk)","reason":"compiler-message","target":{"crate_types":["bin"],"kind":["bin"],"name":"nvim-gtk","src_path":"E:\\daa\\local\\neovim-gtk\\src\\main.rs"}}', +" \ ]) + +Execute(The Rust handler should show detailed errors): + call ale#test#SetFilename('src/playpen.rs') + + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'end_lnum': 4, + \ 'type': 'E', + \ 'col': 52, + \ 'end_col': 54, + \ 'text': 'mismatched types: expected bool, found integral variable', + \ }, + \ ], + \ ale#handlers#rust#HandleRustErrorsForFile(bufnr(''), 'src/playpen.rs', [ + \ '', + \ 'ignore this', + \ '{"message":{"children":[],"code":null,"level":"error","message":"mismatched types","rendered":null,"spans":[{"byte_end":54,"byte_start":52,"column_end":23,"column_start":21,"expansion":null,"file_name":"src/playpen.rs","is_primary":true,"label":"expected bool, found integral variable","line_end":4,"line_start":4,"suggested_replacement":null,"text":[{"highlight_end":23,"highlight_start":21,"text":" let foo: bool = 42;"}]}]},"package_id":"ale-rust-details 0.1.1 (path+file:///home/jon/tmp/ale-rust-details)","reason":"compiler-message","target":{"crate_types":["bin"],"kind":["bin"],"name":"ale-rust-details","src_path":"/home/jon/tmp/ale-rust-details/src/main.rs"}}', + \ '{"message":{"children":[],"code":null,"level":"error","message":"aborting due to previous error(s)","rendered":null,"spans":[]},"package_id":"ale-rust-details 0.1.1 (path+file:///home/jon/tmp/ale-rust-details)","reason":"compiler-message","target":{"crate_types":["bin"],"kind":["bin"],"name":"ale-rust-details","src_path":"/home/jon/tmp/ale-rust-details/src/main.rs"}}', + \ ]) + +Execute(The Rust handler should find correct files): + call ale#test#SetFilename('src/noerrors/mod.rs') + + AssertEqual + \ [], + \ ale#handlers#rust#HandleRustErrorsForFile(bufnr(''), 'src/noerrors/mod.rs', [ + \ '', + \ 'ignore this', + \ '{"message":{"children":[],"code":null,"level":"error","message":"unresolved import `Undefined`","rendered":null,"spans":[{"byte_end":103,"byte_start":94,"column_end":14,"column_start":5,"expansion":null,"file_name":"src/haserrors/mod.rs","is_primary":true,"label":"no `Undefined` in the root","line_end":1,"line_start":1,"suggested_replacement":null,"text":[{"highlight_end":14,"highlight_start":5,"text":"use Undefined;"}]}]},"package_id":"sample 0.1.0 (path+file:///private/tmp/sample)","reason":"compiler-message","target":{"crate_types":["lib"],"kind":["lib"],"name":"sample","src_path":"/private/tmp/sample/src/lib.rs"}}', + \ '{"message":{"children":[],"code":null,"level":"error","message":"aborting due to previous error","rendered":null,"spans":[]},"package_id":"sample 0.1.0 (path+file:///private/tmp/sample)","reason":"compiler-message","target":{"crate_types":["lib"],"kind":["lib"],"name":"sample","src_path":"/private/tmp/sample/src/lib.rs"}}', + \ ]) diff --git a/test/handler/test_scalac_handler.vader b/test/handler/test_scalac_handler.vader new file mode 100644 index 0000000..a4c7363 --- /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..b03d18e --- /dev/null +++ b/test/handler/test_scalastyle_handler.vader @@ -0,0 +1,38 @@ +Execute(The scalastyle handler should parse lines correctly): + runtime! ale_linters/scala/scalastyle.vim + + 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', + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_shell_handler.vader b/test/handler/test_shell_handler.vader new file mode 100644 index 0000000..ecfbf02 --- /dev/null +++ b/test/handler/test_shell_handler.vader @@ -0,0 +1,41 @@ +After: + call ale#linter#Reset() + +Execute(The shell handler should parse lines correctly): + runtime ale_linters/sh/shell.vim + + 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_slim_handler.vader b/test/handler/test_slim_handler.vader new file mode 100644 index 0000000..21c1ec9 --- /dev/null +++ b/test/handler/test_slim_handler.vader @@ -0,0 +1,31 @@ +" Author: Markus Doits + +Execute(The slim handler should parse lines correctly): + runtime ale_linters/slim/slimlint.vim + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'text': 'RedundantDiv: `div` is redundant when class attribute shortcut is present', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 2, + \ 'text': 'LineLength: Line is too long. [136/80]', + \ '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', + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_sml_handler.vader b/test/handler/test_sml_handler.vader new file mode 100644 index 0000000..26c8571 --- /dev/null +++ b/test/handler/test_sml_handler.vader @@ -0,0 +1,91 @@ +Before: + runtime ale_linters/sml/smlnj.vim + +Execute (Testing on EOF error): + AssertEqual [ + \ { + \ 'bufnr': 42, + \ 'lnum': 2, + \ 'col': 15, + \ 'type': 'E', + \ 'text': 'Error: syntax error found at EOF', + \ }, + \], + \ ale_linters#sml#smlnj#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_linters#sml#smlnj#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_linters#sml#smlnj#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_linters#sml#smlnj#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_sqlint_handler.vader b/test/handler/test_sqlint_handler.vader new file mode 100644 index 0000000..62d2ea7 --- /dev/null +++ b/test/handler/test_sqlint_handler.vader @@ -0,0 +1,33 @@ +Execute(The sqlint handler should parse lines correctly): + runtime! ale_linters/sql/sqlint.vim + + 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', + \ ]) + +After: + call ale#linter#Reset() 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..69de1ee --- /dev/null +++ b/test/handler/test_stylelint_handler.vader @@ -0,0 +1,41 @@ +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 [number-leading-zero]', + \ }, + \ { + \ 'lnum': 116, + \ 'col': 20, + \ 'type': 'E', + \ 'text': 'Expected a trailing semicolon [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..e2c2730 --- /dev/null +++ b/test/handler/test_swaglint_handler.vader @@ -0,0 +1,58 @@ +Before: + runtime ale_linters/yaml/swaglint.vim + +Execute(The swaglint handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'text': 'Missing required property: info (sway_object_missing_required_property)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 9, + \ 'text': 'Not a valid response definition (sway_one_of_missing)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 11, + \ 'text': 'Missing required property: description (sway_object_missing_required_property)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 11, + \ 'text': 'Missing required property: $ref (sway_object_missing_required_property)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'Expected type string but found type integer (sway_invalid_type)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'text': 'No enum match for: 2 (sway_enum_mismatch)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 14, + \ 'col': 3, + \ 'text': 'Definition is not used: #/definitions/Foo (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..b77b442 --- /dev/null +++ b/test/handler/test_swiftlint_handler.vader @@ -0,0 +1,22 @@ +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. (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. (operator_usage_whitespace)', + \ 'type': 'W', + \ }, + \ + \ ], + \ ale#handlers#gcc#HandleGCCFormat(347, [ + \ '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_tslint_handler.vader b/test/handler/test_tslint_handler.vader new file mode 100644 index 0000000..5c8679a --- /dev/null +++ b/test/handler/test_tslint_handler.vader @@ -0,0 +1,135 @@ +Before: + runtime ale_linters/typescript/tslint.vim + + call ale#test#SetDirectory('/testplugin/test/handler') + +After: + 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': expand('%:p:h') . '/test.ts', + \ 'end_lnum': 1, + \ 'type': 'E', + \ 'end_col': 15, + \ 'text': 'semicolon: Missing semicolon' + \ }, + \ { + \ 'lnum': 2, + \ 'col': 8, + \ 'filename': expand('%:p:h') . '/test.ts', + \ 'end_lnum': 3, + \ 'type': 'W', + \ 'end_col': 12, + \ 'text': 'Something else' + \ }, + \ { + \ 'lnum': 2, + \ 'col': 8, + \ 'filename': expand('%:p:h') . '/something-else.ts', + \ 'end_lnum': 3, + \ 'type': 'W', + \ 'end_col': 12, + \ 'text': 'something: Something else' + \ }, + \ { + \ 'lnum': 31, + \ 'col': 9, + \ 'filename': expand('%:p:h') . '/test.ts', + \ 'end_lnum': 31, + \ 'type': 'E', + \ 'end_col': 20, + \ 'text': 'no-console: Calls to console.log are not allowed.' + \ }, + \ ] , + \ 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(''), []) diff --git a/test/handler/test_typecheck_handler.vader b/test/handler/test_typecheck_handler.vader new file mode 100644 index 0000000..cf93798 --- /dev/null +++ b/test/handler/test_typecheck_handler.vader @@ -0,0 +1,23 @@ +Execute(The typecheck handler should parse lines correctly): + runtime ale_linters/typescript/typecheck.vim + + 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'", + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/handler/test_vint_handler.vader b/test/handler/test_vint_handler.vader new file mode 100644 index 0000000..8747979 --- /dev/null +++ b/test/handler/test_vint_handler.vader @@ -0,0 +1,59 @@ +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, + \ 'text': 'Use scriptencoding when multibyte char exists (see :help :script encoding)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 17, + \ 'end_col': 18, + \ 'text': 'Use robust operators ''==#'' or ''==?'' instead of ''=='' (see Google VimScript Style Guide (Matching))', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 8, + \ 'end_col': 15, + \ 'text': 'Make the scope explicit like ''l:filename'' (see Anti-pattern of vimrc (Scope of identifier))', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 7, + \ 'col': 8, + \ 'end_col': 15, + \ 'text': 'Undefined variable: filename (see :help E738)', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 8, + \ 'col': 11, + \ 'end_col': 16, + \ 'text': 'E128: Function name must start with a capital or contain a colon: foobar (see ynkdir/vim-vimlparser)', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 9, + \ 'col': 12, + \ 'end_col': 13, + \ 'text': 'Use robust operators ''=~#'' or ''=~?'' instead of ''=~'' (see Google VimScript Style Guide (Matching))', + \ 'type': 'W', + \ }, + \ ], + \ ale_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_xmllint_handler.vader b/test/handler/test_xmllint_handler.vader new file mode 100644 index 0000000..4a377ab --- /dev/null +++ b/test/handler/test_xmllint_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/xml/xmllint.vim + +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>', + \ '^' + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/lsp/test_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader new file mode 100644 index 0000000..057abad --- /dev/null +++ b/test/lsp/test_lsp_client_messages.vader @@ -0,0 +1,181 @@ +Before: + silent! cd /testplugin/test/lsp + let g:dir = getcwd() + let g:ale_lsp_next_version_id = 1 + + call ale#test#SetFilename('foo/bar.ts') + +After: + silent execute 'cd ' . fnameescape(g:dir) + unlet! g:dir + +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': 'file://' . g:dir . '/foo/bar.ts', + \ 'languageId': 'typescript', + \ 'version': 12, + \ 'text': "foo()\nbar()\nbaz()", + \ }, + \ } + \ ], + \ 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': 'file://' . g:dir . '/foo/bar.ts', + \ 'version': 34, + \ }, + \ 'contentChanges': [{'text': "foo()\nbar()\nbaz()"}], + \ } + \ ], + \ 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': 'file://' . 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': 'file://' . g:dir . '/foo/bar.ts', + \ }, + \ } + \ ], + \ ale#lsp#message#DidClose(bufnr('')) + +Execute(ale#lsp#tsserver_message#Open() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'ts@open', + \ { + \ 'file': 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': 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': g:dir . '/foo/bar.ts', + \ 'line': 1, + \ 'offset': 1, + \ 'endLine': 1073741824, + \ 'endOffset': 1, + \ 'insertString': "foo()\nbar()\nbaz()", + \ } + \ ], + \ ale#lsp#tsserver_message#Change(bufnr('')) + +Execute(ale#lsp#tsserver_message#Geterr() should return correct messages): + AssertEqual + \ [ + \ 1, + \ 'ts@geterr', + \ { + \ 'files': [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': 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': g:dir . '/foo/bar.ts', + \ 'line': 347, + \ 'offset': 12, + \ 'entryNames': ['foo', 'bar'], + \ } + \ ], + \ ale#lsp#tsserver_message#CompletionEntryDetails(bufnr(''), 347, 12, ['foo', 'bar']) diff --git a/test/lsp/test_lsp_connections.vader b/test/lsp/test_lsp_connections.vader new file mode 100644 index 0000000..5549b1f --- /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" + \ . '{"id": 1, "jsonrpc": "2.0", "method": "someMethod", "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" + \ . '{"id": 2, "jsonrpc": "2.0", "method": "someMethod", "params": {"foo": "barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + else + AssertEqual + \ [ + \ 1, + \ "Content-Length: 71\r\n\r\n" + \ . '{"id":1,"jsonrpc":"2.0","method":"someMethod","params":{"foo":"barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + AssertEqual + \ [ + \ 2, + \ "Content-Length: 71\r\n\r\n" + \ . '{"id":2,"jsonrpc":"2.0","method":"someMethod","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" + \ . '{"id": 1, "jsonrpc": "2.0", "method": "someOtherMethod"}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someOtherMethod']) + else + AssertEqual + \ [ + \ 1, + \ "Content-Length: 51\r\n\r\n" + \ . '{"id":1,"jsonrpc":"2.0","method":"someOtherMethod"}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someOtherMethod']) + endif + +Execute(ale#lsp#CreateMessageData() should create notifications): + if has('nvim') + AssertEqual + \ [ + \ 0, + \ "Content-Length: 60\r\n\r\n" + \ . '{"id": null, "jsonrpc": "2.0", "method": "someNotification"}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification']) + AssertEqual + \ [ + \ 0, + \ "Content-Length: 86\r\n\r\n" + \ . '{"id": null, "jsonrpc": "2.0", "method": "someNotification", "params": {"foo": "bar"}}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}]) + else + AssertEqual + \ [ + \ 0, + \ "Content-Length: 55\r\n\r\n" + \ . '{"id":null,"jsonrpc":"2.0","method":"someNotification"}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification']) + AssertEqual + \ [ + \ 0, + \ "Content-Length: 78\r\n\r\n" + \ . '{"id":null,"jsonrpc":"2.0","method":"someNotification","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..3e63741 --- /dev/null +++ b/test/lsp/test_read_lsp_diagnostics.vader @@ -0,0 +1,135 @@ +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}]}}) 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/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/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/sign/test_linting_sets_signs.vader b/test/sign/test_linting_sets_signs.vader new file mode 100644 index 0000000..1530847 --- /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, 'line=\(\d\+\).*name=\(ALE[a-zA-Z]\+\)') + + 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': 'echo', + \ 'command': '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..882b03d --- /dev/null +++ b/test/sign/test_sign_column_highlighting.vader @@ -0,0 +1,33 @@ +Before: + 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: + delfunction ParseHighlight + call SetHighlight('SignColumn', g:sign_highlight) + delfunction SetHighlight + unlet! g:sign_highlight + +Execute(The SignColumn highlight should be set and reset): + call ale#sign#SetSignColumnHighlight(1) + AssertEqual 'links to ALESignColumnWithErrors', ParseHighlight('SignColumn') + + call ale#sign#SetSignColumnHighlight(0) + AssertEqual 'links to ALESignColumnWithoutErrors', ParseHighlight('SignColumn') 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..abae765 --- /dev/null +++ b/test/sign/test_sign_placement.vader @@ -0,0 +1,268 @@ +Before: + Save g:ale_set_signs + + let g:ale_set_signs = 1 + + 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': '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'}]) diff --git a/test/smoke_test.vader b/test/smoke_test.vader new file mode 100644 index 0000000..209b5bb --- /dev/null +++ b/test/smoke_test.vader @@ -0,0 +1,80 @@ +Before: + function! TestCallback(buffer, output) + return [{ + \ 'lnum': 2, + \ 'col': 3, + \ 'text': a:output[0], + \}] + endfunction + function! TestCallback2(buffer, output) + return [{ + \ 'lnum': 3, + \ 'col': 4, + \ 'text': a:output[0], + \}] + endfunction + + " Running the command in another subshell seems to help here. + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': 'echo', + \ 'command': '/bin/sh -c ''echo foo bar''', + \}) + +After: + let g:ale_buffer_info = {} + 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(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': 'echo', + \ 'command': '/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_fix.vader b/test/test_ale_fix.vader new file mode 100644 index 0000000..b5c1672 --- /dev/null +++ b/test/test_ale_fix.vader @@ -0,0 +1,384 @@ +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 + + let g:ale_enabled = 0 + let g:ale_echo_cursor = 0 + let g:ale_run_synchronously = 1 + let g:ale_fix_buffer_data = {} + let g:ale_fixers = { + \ 'testft': [], + \} + let &shell = '/bin/bash' + + 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! SetUpLinters() + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': 'true', + \ 'command': 'true', + \}) + endfunction + +After: + Restore + unlet! g:ale_run_synchronously + unlet! g:ale_emulate_job_failure + unlet! b:ale_fixers + delfunction AddCarets + delfunction AddDollars + delfunction DoNothing + delfunction CatLine + delfunction CatLineOneArg + delfunction ReplaceWithTempFile + delfunction RemoveLastLine + delfunction RemoveLastLineOneArg + delfunction TestCallback + delfunction SetUpLinters + 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 = {} + +Given testft (A file with three lines): + a + b + c + +Execute(ALEFix should complain when there are no functions to call): + AssertThrows ALEFix + AssertEqual 'Vim(echoerr):No fixers have been defined. Try :ALEFixSuggest', g:vader_exception + +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): + let g:ale_fixers.testft = ['CatLine'] + ALEFix + +Expect(An extra line should be added): + a + b + c + d + +Execute(ALEFix should allow temporary files to be read): + let g:ale_fixers.testft = ['ReplaceWithTempFile'] + ALEFix + +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): + let g:ale_fixers.testft = ['ReplaceWithTempFile', 'AddDollars'] + ALEFix + +Expect(The lines from the temporary file should be modified): + $x + +Execute(ALEFix should send lines modified by functions to jobs): + let g:ale_fixers.testft = ['AddDollars', 'CatLine'] + ALEFix + +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 + +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''' + + " 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) + +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' + + " 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) + +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' + + " 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) + +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': 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(ALEFix functions returning jobs should be able to accept one argument): + let g:ale_fixers.testft = ['CatLine'] + ALEFix + +Expect(An extra line should be added): + a + b + c + d diff --git a/test/test_ale_fix_suggest.vader b/test/test_ale_fix_suggest.vader new file mode 100644 index 0000000..97227b4 --- /dev/null +++ b/test/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.') + 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'' - 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/test_ale_info.vader b/test/test_ale_info.vader new file mode 100644 index 0000000..8ab5ad5 --- /dev/null +++ b/test/test_ale_info.vader @@ -0,0 +1,370 @@ +Before: + Save g:ale_warn_about_trailing_whitespace + Save g:ale_linters + Save g:ale_fixers + + unlet! b:ale_history + + let g:ale_warn_about_trailing_whitespace = 1 + + let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout'} + let g:testlinter2 = {'name': 'testlinter2', 'executable': 'testlinter2', 'command': 'testlinter2', 'callback': 'testCB2', 'output_stream': 'stdout'} + + call ale#engine#ResetExecutableCache() + call ale#linter#Reset() + let g:ale_linters = {} + let g:ale_fixers = {} + let g:ale_linter_aliases = {} + let g:ale_buffer_info = {} + let g:globals_lines = [ + \ ' Global Variables:', + \ '', + \ 'let g:ale_echo_cursor = 1', + \ 'let g:ale_echo_msg_error_str = ''Error''', + \ 'let g:ale_echo_msg_format = ''%s''', + \ '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_keep_list_window_open = 0', + \ 'let g:ale_lint_delay = 200', + \ 'let g:ale_lint_on_enter = 1', + \ 'let g:ale_lint_on_save = 1', + \ 'let g:ale_lint_on_text_changed = ''always''', + \ 'let g:ale_linter_aliases = {}', + \ 'let g:ale_linters = {}', + \ 'let g:ale_open_list = 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_offset = 1000000', + \ 'let g:ale_sign_warning = ''--''', + \ 'let g:ale_statusline_format = [''%d error(s)'', ''%d warning(s)'', ''OK'']', + \ '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 + + let g:ale_buffer_info = {} + + 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 + let g:ale_buffer_info = {} + let g:ale_history_log_output = 0 + 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): + 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): + 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) + + 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) + + 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']} + + 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) + + 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) + + 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(''), 'echo') + call ale#engine#IsExecutable(bufnr(''), 'TheresNoWayThisIsExecutable') + + call CheckInfo([ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ' Linter Variables:', + \ '', + \] + g:globals_lines + g:command_header + [ + \ '', + \ '(executable check - success) echo', + \ '(executable check - failure) TheresNoWayThisIsExecutable', + \]) diff --git a/test/test_ale_init_au_groups.vader b/test/test_ale_init_au_groups.vader new file mode 100644 index 0000000..2685f50 --- /dev/null +++ b/test/test_ale_init_au_groups.vader @@ -0,0 +1,218 @@ +Before: + function! CheckAutocmd(group) + call ALEInitAuGroups() + 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 ALEInitAuGroups() + +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 = 0 should bind no events): + let g:ale_pattern_options_enabled = 0 + + AssertEqual [], CheckAutocmd('ALEPatternOptionsGroup') + +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()', + \ 'BufReadPost * call ale#pattern_options#SetOptions()', + \], 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 (g:ale_lint_on_save = 0 should bind no events): + let g:ale_lint_on_save = 0 + let g:ale_fix_on_save = 0 + + AssertEqual [], CheckAutocmd('ALERunOnSaveGroup') + +Execute (g:ale_lint_on_save = 1 should bind no events): + let g:ale_lint_on_save = 1 + let g:ale_fix_on_save = 0 + + AssertEqual [ + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand('''')))', + \], CheckAutocmd('ALERunOnSaveGroup') + +Execute (g:ale_lint_on_save = 0 and g:ale_fix_on_save = 1 should bind events): + let g:ale_lint_on_save = 0 + let g:ale_fix_on_save = 1 + + AssertEqual [ + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand('''')))', + \], CheckAutocmd('ALERunOnSaveGroup') + +Execute (g:ale_fix_on_save = 1 should bind events even when ALE is disabled): + let g:ale_enabled = 0 + let g:ale_lint_on_save = 0 + let g:ale_fix_on_save = 1 + + 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(Enabling completion should set up autocmd events correctly): + let g:ale_completion_enabled = 0 + call ale#completion#Enable() + + AssertEqual [ + \ 'CompleteDone * call ale#completion#Done()', + \ 'TextChangedI * call ale#completion#Queue()', + \], CheckAutocmd('ALECompletionGroup') + AssertEqual 1, g:ale_completion_enabled + +Execute(Disabling completion should remove autocmd events correctly): + let g:ale_completion_enabled = 1 + call ale#completion#Enable() + call ale#completion#Disable() + + AssertEqual [], CheckAutocmd('ALECompletionGroup') + AssertEqual 0, g:ale_completion_enabled diff --git a/test/test_ale_lint_command.vader b/test/test_ale_lint_command.vader new file mode 100644 index 0000000..42554ec --- /dev/null +++ b/test/test_ale_lint_command.vader @@ -0,0 +1,67 @@ +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': a:output[0], + \ 'type': 'E', + \ 'nr': -1, + \}] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'ToggleTestCallback', + \ 'executable': '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) + + " 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..f5d8599 --- /dev/null +++ b/test/test_ale_toggle.vader @@ -0,0 +1,177 @@ +Before: + Save g:ale_buffer_info + Save g:ale_set_signs + Save g:ale_set_lists_synchronously + + let g:ale_set_signs = 1 + let g:ale_set_lists_synchronously = 1 + + let g:ale_buffer_info = {} + let g:expected_loclist = [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ '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': 'echo', + \ 'command': 'echo', + \ 'read_buffer': 0, + \}) + + sign unplace * + +After: + Restore + + unlet! g:expected_loclist + unlet! g:expected_groups + + 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 + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALEToggle should reset everything and then run again): + 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'], 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): + AssertEqual 'foobar', &filetype + + let g:ale_buffer_info['/foo/bar/baz.txt'] = { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \} + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + " Now Toggle ALE off. + ALEToggle + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) + + " Toggle ALE on again. + ALEToggle + call ale#engine#WaitForJobs(2000) + + AssertEqual + \ { + \ 'job_list': [], + \ 'active_linter_list': [], + \ 'loclist': [], + \ 'temporary_file_list': [], + \ 'temporary_directory_list': [], + \ 'history': [], + \ }, + \ get(g:ale_buffer_info, '/foo/bar/baz.txt', {}) diff --git a/test/test_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 new file mode 100644 index 0000000..4503005 --- /dev/null +++ b/test/test_alelint_autocmd.vader @@ -0,0 +1,18 @@ +Before: + let g:success = 0 + let g:ale_run_synchronously = 1 + +After: + let g:ale_run_synchronously = 0 + let g:ale_buffer_info = {} + augroup! VaderTest + +Execute (Run a lint cycle, and check that a variable is set in the autocmd): + augroup VaderTest + autocmd! + autocmd User ALELint let g:success = 1 + augroup end + + call ale#Lint() + + AssertEqual g:success, 1 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..dac73f0 --- /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(g:dir . '/test_c_projects/makefile_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/configure_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/h_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/hpp_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/makefile_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/h_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/h_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/hpp_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/makefile_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/configure_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/h_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/hpp_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/makefile_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/configure_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/h_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/hpp_file_project/subdir') . ' ' + \ . ' -I' . ale#Escape(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(g:dir . '/test_c_projects/git_and_nested_makefiles/src') . ' ' + \ . ' -I' . ale#Escape(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=''*'' %s ' + \ . '-p ' . ale#Escape(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 new file mode 100644 index 0000000..23e5bcf --- /dev/null +++ b/test/test_cleanup.vader @@ -0,0 +1,15 @@ +Before: + let g:buffer = bufnr('%') + + let g:ale_buffer_info = { + \ g:buffer : {'temporary_file_list': [], 'temporary_directory_list': []}, + \ 10347: {'temporary_file_list': [], 'temporary_directory_list': []}, + \} + +After: + unlet! g:buffer + let g:ale_buffer_info = {} + +Execute('ALE globals should be cleared when the buffer is closed.'): + :q! + 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 new file mode 100644 index 0000000..1647204 --- /dev/null +++ b/test/test_command_chain.vader @@ -0,0 +1,66 @@ +Before: + Save &shell, g:ale_run_synchronously + let g:ale_run_synchronously = 1 + set shell=/bin/sh + let g:linter_output = [] + let g:first_echo_called = 0 + let g:second_echo_called = 0 + let g:final_callback_called = 0 + + function! CollectResults(buffer, output) + let g:final_callback_called = 1 + let g:linter_output = a:output + return [] + endfunction + function! RunFirstEcho(buffer) + let g:first_echo_called = 1 + + return 'echo foo' + endfunction + function! RunSecondEcho(buffer, output) + let g:second_echo_called = 1 + + return 'echo bar' + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'CollectResults', + \ 'executable': '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 + unlet! g:linter_output + let g:ale_buffer_info = {} + call ale#linter#Reset() + delfunction CollectResults + delfunction RunFirstEcho + delfunction RunSecondEcho + +Given foobar (Some imaginary filetype): + anything + +Execute(Check the results of running the chain): + AssertEqual 'foobar', &filetype + call ale#Lint() + + Assert g:first_echo_called, 'The first chain item was not called' + Assert g:second_echo_called, 'The second chain item was not called' + Assert g:final_callback_called, 'The final callback was not called' + AssertEqual ['bar'], g:linter_output diff --git a/test/test_completion.vader b/test/test_completion.vader new file mode 100644 index 0000000..811a264 --- /dev/null +++ b/test/test_completion.vader @@ -0,0 +1,328 @@ +Before: + Save g:ale_completion_enabled + Save g:ale_completion_delay + Save g:ale_completion_max_suggestions + Save &l:omnifunc + Save &l:completeopt + + let g:test_vars = { + \ 'feedkeys_calls': [], + \} + + function! ale#util#FeedKeys(string, mode) abort + call add(g:test_vars.feedkeys_calls, [a:string, a:mode]) + endfunction + +After: + Restore + + unlet! g:test_vars + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completopt + unlet! b:ale_completion_info + unlet! b:ale_completion_response + unlet! b:ale_completion_parser + + runtime autoload/ale/completion.vim + runtime autoload/ale/lsp.vim + + if g:ale_completion_enabled + call ale#completion#Enable() + else + call ale#completion#Disable() + endif + +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'}, + \ ], + \ }, + \ ], + \}) + +Execute(Prefix filtering should work for Lists of strings): + AssertEqual + \ ['FooBar', 'foo'], + \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], 'foo') + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(['FooBar', 'FongBar', 'baz', 'foo'], '.') + +Execute(Prefix filtering should work for completion items): + AssertEqual + \ [{'word': 'FooBar'}, {'word': 'foo'}], + \ ale#completion#Filter( + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ 'foo' + \ ) + AssertEqual + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ ale#completion#Filter( + \ [ + \ {'word': 'FooBar'}, + \ {'word': 'FongBar'}, + \ {'word': 'baz'}, + \ {'word': 'foo'}, + \ ], + \ '.' + \ ) + +Execute(The right message sent to the tsserver LSP when the first completion message is received): + " The cursor position needs to match what was saved before. + call setpos('.', [bufnr(''), 1, 1, 0]) + let b:ale_completion_info = { + \ 'conn_id': 123, + \ 'prefix': 'f', + \ 'request_id': 4, + \ 'line': 1, + \ 'column': 1, + \} + " We should only show up to this many suggestions. + let g:ale_completion_max_suggestions = 3 + + " Replace the Send function for LSP, so we can monitor calls to it. + function! ale#lsp#Send(conn_id, message) abort + let g:test_vars.message = a:message + endfunction + + " Handle the response for completions. + call ale#completion#HandleTSServerLSPResponse(123, { + \ 'request_seq': 4, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'Baz'}, + \ {'name': 'dingDong'}, + \ {'name': 'Foo'}, + \ {'name': 'FooBar'}, + \ {'name': 'frazzle'}, + \ {'name': 'FFS'}, + \ ], + \}) + + " The entry details messages should have been sent. + AssertEqual + \ [ + \ 0, + \ 'ts@completionEntryDetails', + \ { + \ 'file': expand('%:p'), + \ 'entryNames': ['Foo', 'FooBar', 'frazzle'], + \ 'offset': 1, + \ 'line': 1, + \ }, + \ ], + \ g:test_vars.message + +Given typescript(): + let abc = y. + let foo = ab + 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(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:test_vars.feedkeys_calls + +Execute(ale#completion#Show() should set up the response and parser): + call ale#completion#Show('Response', 'Parser') + + AssertEqual 'Response', b:ale_completion_response + AssertEqual 'Parser', b:ale_completion_parser + +Execute(ale#completion#Done() should restore old omnifunc values): + let b:ale_old_omnifunc = 'FooBar' + + call ale#completion#Done() + + " We reset the old omnifunc setting and remove the buffer variable. + AssertEqual 'FooBar', &l:omnifunc + Assert !has_key(b:, 'ale_old_omnifunc') + +Execute(ale#completion#Done() should restore the old completeopt setting): + let b:ale_old_completopt = 'menu' + let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' + + call ale#completion#Done() + + AssertEqual 'menu', &l:completeopt + Assert !has_key(b:, 'ale_old_completopt') + +Execute(ale#completion#Done() should leave settings alone when none were remembered): + let &l:omnifunc = 'BazBoz' + let &l:completeopt = 'menu' + + call ale#completion#Done() + + AssertEqual 'BazBoz', &l:omnifunc + AssertEqual 'menu', &l:completeopt + +Execute(The completion request_id should be reset when queuing again): + let b:ale_completion_info = {'request_id': 123} + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + sleep 1m + + AssertEqual 0, b:ale_completion_info.request_id + +Execute(b:ale_completion_info should be set up correctly when requesting completions): + call setpos('.', [bufnr(''), 3, 14, 0]) + call ale#completion#GetCompletions() + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ }, + \ b:ale_completion_info + +Execute(ale#completion#GetCompletions should be called when the cursor position stays the same): + let g:test_vars.get_completions_called = 0 + + " We just want to check if the function is called. + function! ale#completion#GetCompletions() + let g:test_vars.get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + sleep 1m + + Assert g:test_vars.get_completions_called + +Execute(ale#completion#GetCompletions should not be called when the cursor position changes): + call setpos('.', [bufnr(''), 1, 2, 0]) + + let g:test_vars.get_completions_called = 0 + + " We just want to check if the function is called. + function! ale#completion#GetCompletions() + let g:test_vars.get_completions_called = 1 + endfunction + + let g:ale_completion_delay = 0 + call ale#completion#Queue() + + " Change the cursor position before the callback is triggered. + call setpos('.', [bufnr(''), 2, 2, 0]) + + sleep 1m + + Assert !g:test_vars.get_completions_called diff --git a/test/test_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..b4707dc --- /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=' . shellescape(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 new file mode 100644 index 0000000..586cc13 --- /dev/null +++ b/test/test_cursor_warnings.vader @@ -0,0 +1,143 @@ +Before: + let g:ale_buffer_info = { + \ bufnr('%'): { + \ 'loclist': [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'Missing semicolon. (semi)', + \ 'detail': "Every statement should end with a semicolon\nsecond line" + \ }, + \ { + \ 'lnum': 2, + \ 'col': 10, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'Infix operators must be spaced. (space-infix-ops)' + \ }, + \ { + \ 'lnum': 2, + \ 'col': 15, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'Missing radix parameter (radix)' + \ }, + \ { + \ '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 + + 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: + 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 + + 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 '' + +Given javascript(A Javscript file with warnings/errors): + var x = 3 + var x = 5*2 + parseInt("10"); + // comment + +Execute(Messages should be shown for the correct lines): + call cursor(1, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Missing semicolon. (semi)', GetLastMessage() + +Execute(Messages should be shown for earlier columns): + call cursor(2, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Infix operators must be spaced. (space-infix-ops)', GetLastMessage() + +Execute(Messages should be shown for later columns): + call cursor(2, 16) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'Missing radix parameter (radix)', GetLastMessage() + +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 semicolon. (semi)', GetLastMessage() + +Execute(The message at the cursor should be shown on InsertLeave): + call cursor(2, 9) + doautocmd InsertLeave + + AssertEqual 'Infix operators must be spaced. (space-infix-ops)', GetLastMessage() + +Execute(ALEDetail should print 'detail' attributes): + call cursor(1, 1) + + redir => g:output + ALEDetail + redir END + + AssertEqual "\nEvery statement should end with a semicolon\nsecond line", g:output + +Execute(ALEDetail should print regular 'text' attributes): + call cursor(2, 10) + + redir => g:output + ALEDetail + redir END + + AssertEqual "\nInfix operators must be spaced. (space-infix-ops)", g:output + +Execute(ALEDetail should not capitlise cursor messages): + call cursor(3, 1) + call ale#cursor#EchoCursorWarning() + + AssertEqual 'lowercase error', 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..7262c5b --- /dev/null +++ b/test/test_dockerfile_hadolint_linter.vader @@ -0,0 +1,69 @@ +" 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 lukasmartinelli/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('')) + +" fin... 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..0498a50 --- /dev/null +++ b/test/test_errors_removed_after_filetype_changed.vader @@ -0,0 +1,58 @@ +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': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + + call ale#linter#Define('foobar2', { + \ 'name': 'buffer_linter', + \ 'callback': 'TestCallback', + \ 'executable': '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) + + AssertEqual 1, len(getloclist(0)) + + noautocmd let &filetype = 'foobar2' + + call ale#Queue(0) + + " We should get some items from the second filetype. + AssertEqual 1, len(getloclist(0)) + + noautocmd let &filetype = 'xxx' + + call ale#Queue(0) + + 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..411fa13 --- /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 + \ 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 + \ 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 + \ 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(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_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 new file mode 100644 index 0000000..03d3886 --- /dev/null +++ b/test/test_find_nearest_directory.vader @@ -0,0 +1,17 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(We should be able to find a directory some directory down): + call ale#test#SetFilename('top/middle/bottom/dummy.txt') + + AssertEqual + \ 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(We shouldn't find anything for files which don't match): + AssertEqual + \ '', + \ ale#path#FindNearestDirectory(bufnr('%'), 'ale-this-should-never-match-anything') diff --git a/test/test_flow_command.vader b/test/test_flow_command.vader new file mode 100644 index 0000000..d984286 --- /dev/null +++ b/test/test_flow_command.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/javascript/flow.vim + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(flow should return a command to run if a .flowconfig file exists): + call ale#test#SetFilename('flow/a/sub/dummy') + + AssertEqual '''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 + \ '''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_foodcritic_command_callback.vader b/test/test_foodcritic_command_callback.vader new file mode 100644 index 0000000..a5b02e4 --- /dev/null +++ b/test/test_foodcritic_command_callback.vader @@ -0,0 +1,18 @@ +Before: + let g:ale_chef_foodcritic_options = '-t ~F011' + let g:ale_chef_foodcritic_executable = 'foodcritic' + + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/chef/foodcritic.vim + +After: + let g:ale_chef_foodcritic_options = '' + let g:ale_chef_foodcritic_executable = '' + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + +Execute(command line should be assembled correctly): + AssertEqual + \ 'foodcritic -t \~F011 %t', + \ ale_linters#chef#foodcritic#GetCommand(bufnr('')) diff --git a/test/test_format_command.vader b/test/test_format_command.vader new file mode 100644 index 0000000..156ced9 --- /dev/null +++ b/test/test_format_command.vader @@ -0,0 +1,52 @@ +Before: + silent! cd /testplugin/test + silent file top/middle/bottom/dummy.txt + +After: + unlet! g:result + unlet! g:match + +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 ' . shellescape(expand('%:p')) . ' bar ' . shellescape(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) + let g:match = matchlist(g:result[1], '\v^foo (''/tmp/[^'']*/dummy.txt'') bar (''/tmp/[^'']*/dummy.txt'')$') + + 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 shellescape(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) + let g:match = matchlist(g:result[1], '\v^foo (''/tmp/.*/dummy.txt'') bar (''.*/dummy.txt'')$') + + 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 shellescape(g:result[0]), g:match[1] + " The second item should be equal to the original filename. + AssertEqual shellescape(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) + let g:match = matchlist(g:result[1], '\v^foo bar \< (''/tmp/[^'']*/dummy.txt'')$') + Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] + AssertEqual shellescape(g:result[0]), g:match[1] + + let g:result = ale#command#FormatCommand(bufnr('%'), 'foo bar %t', 1) + let g:match = matchlist(g:result[1], '\v^foo bar (''/tmp/[^'']*/dummy.txt'')$') + Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] + AssertEqual shellescape(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..0639c59 --- /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': 'cat', + \ 'command': '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..4ac0ca1 --- /dev/null +++ b/test/test_fuzzy_json_decode.vader @@ -0,0 +1,21 @@ +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 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..2def377 --- /dev/null +++ b/test/test_get_abspath.vader @@ -0,0 +1,15 @@ +Execute(Relative paths should be resolved correctly): + AssertEqual + \ '/foo/bar/baz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', '../baz/whatever.txt') + AssertEqual + \ '/foo/bar/xyz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', './whatever.txt') + AssertEqual + \ '/foo/bar/xyz/whatever.txt', + \ ale#path#GetAbsPath('/foo/bar/xyz', 'whatever.txt') + +Execute(Absolute paths should be resolved correctly): + AssertEqual + \ '/ding/dong', + \ ale#path#GetAbsPath('/foo/bar/xyz', '/ding/dong') diff --git a/test/test_get_loclist.vader b/test/test_get_loclist.vader new file mode 100644 index 0000000..1469699 --- /dev/null +++ b/test/test_get_loclist.vader @@ -0,0 +1,31 @@ +Before: + let g:loclist = [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'E', + \ 'col': 10, + \ 'text': 'Missing semicolon. (semi)' + \ }, + \ { + \ 'lnum': 2, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'eslint', + \ 'nr': -1, + \ 'type': 'W', + \ 'col': 10, + \ 'text': 'Infix operators must be spaced. (space-infix-ops)' + \ }, + \] + let g:ale_buffer_info = {'1': {'loclist': g:loclist}} + +After: + unlet g:loclist + let g:ale_buffer_info = {} + +Execute(GetLoclist should return the loclist): + AssertEqual g:loclist, ale#engine#GetLoclist(1) diff --git a/test/test_getmatches.vader b/test/test_getmatches.vader new file mode 100644 index 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_gradle_build_classpath_command.vader b/test/test_gradle_build_classpath_command.vader new file mode 100644 index 0000000..8413526 --- /dev/null +++ b/test/test_gradle_build_classpath_command.vader @@ -0,0 +1,42 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/kotlin/kotlinc.vim + let g:ale_gradle_path = $PATH + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + let $PATH = g:ale_gradle_path + +Execute(Should return 'gradlew' command if project includes gradle wapper): + call ale#test#SetFilename('gradle-test-files/wrapped-project/src/main/kotlin/dummy.kt') + + let g:project_root = '/testplugin/test/gradle-test-files/wrapped-project' + let g:gradle_executable = '/testplugin/test/gradle-test-files/wrapped-project/gradlew' + let g:gradle_init_path = '/testplugin/autoload/ale/gradle/init.gradle' + let g:gradle_options = '-I ' . g:gradle_init_path . ' -q printClasspath' + + + AssertEqual + \ "cd '" . g:project_root . "' && " . g:gradle_executable . " " . g:gradle_options, + \ 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 .= ':' . g:dir . '/gradle-test-files' + + let g:project_root = '/testplugin/test/gradle-test-files/unwrapped-project' + let g:gradle_executable = 'gradle' + let g:gradle_init_path = '/testplugin/autoload/ale/gradle/init.gradle' + let g:gradle_options = '-I ' . g:gradle_init_path . ' -q printClasspath' + + AssertEqual + \ "cd '" . g:project_root . "' && " . g:gradle_executable . " " . g:gradle_options, + \ 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..2ae2b46 --- /dev/null +++ b/test/test_gradle_find_executable.vader @@ -0,0 +1,31 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + runtime ale_linters/kotlin/kotlinc.vim + let g:ale_gradle_path = $PATH + +After: + call ale#test#RestoreDirectory() + call ale#linter#Reset() + let $PATH = g:ale_gradle_path + +Execute(Should return 'gradlew' if found in parent directory): + call ale#test#SetFilename('gradle-test-files/wrapped-project/src/main/kotlin/dummy.kt') + + AssertEqual + \ 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 .= ':' . 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..bd1b8d7 --- /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 + \ 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 + \ 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 + \ 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..c1909c4 --- /dev/null +++ b/test/test_highlight_placement.vader @@ -0,0 +1,234 @@ +Before: + 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': 'echo', + \ 'command': 'echo', + \ 'callback': 'GenerateResults', + \}) + highlight link SomeOtherGroup SpellBad + +After: + unlet! g:items + + 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() 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..3b8fb2a --- /dev/null +++ b/test/test_history_saving.vader @@ -0,0 +1,110 @@ +Before: + Save g:ale_max_buffer_history_size + Save g:ale_history_log_output + + unlet! b:ale_history + + " Temporarily set the shell to /bin/sh, if it isn't already set that way. + " This will make it so the test works when running it directly. + let g:current_shell = &shell + let &shell = '/bin/sh' + let g:history = [] + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + let g:ale_history_log_output = 0 + + function! CollectResults(buffer, output) + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'CollectResults', + \ 'executable': 'echo', + \ 'command': '/bin/sh -c ''echo command history test''', + \ 'read_buffer': 0, + \}) + +After: + Restore + + " Clear the history we changed. + unlet! b:ale_history + + " Reset the shell back to what it was before. + let &shell = g:current_shell + unlet g:current_shell + 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 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 = ale#history#Get(bufnr('')) + + AssertEqual 1, len(g:history) + AssertEqual sort(['status', 'exit_code', 'job_id', 'command']), sort(keys(g:history[0])) + AssertEqual ['/bin/sh', '-c', '/bin/sh -c ''echo command history test'''], g:history[0].command + 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('')) diff --git a/test/test_line_join.vader b/test/test_line_join.vader new file mode 100644 index 0000000..0426429 --- /dev/null +++ b/test/test_line_join.vader @@ -0,0 +1,90 @@ +Before: + let g:lines = [] + let g:data = '' + + function! LineCallback(job_id, line) abort + call add(g:lines, a:line) + endfunction + + function! RawCallback(job_id, some_data) abort + let g:data .= a:some_data + endfunction + +After: + unlet! g:last_line + unlet! g:lines + unlet! g:data + delfunction LineCallback + delfunction RawCallback + +Execute (ALE should 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 '', g:data + AssertEqual 'x', 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 join two incomplete pieces of large lines together): + let g:last_line = ale#job#JoinNeovimOutput(1, 'x', ['y'], 'raw', function('RawCallback')) + + AssertEqual '', g:data + 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'], 'raw', function('RawCallback')) + + AssertEqual "xy\nz\n", g:data + 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'], 'raw', function('RawCallback')) + + AssertEqual "xy\n", g:data + AssertEqual 'z', 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..4c7f094 --- /dev/null +++ b/test/test_lint_error_delay.vader @@ -0,0 +1,26 @@ +Before: + Save g:ale_filetype_blacklist + + " Delete some variable which should be defined. + unlet! g:ale_filetype_blacklist + +After: + Restore + + call ale#ResetErrorDelays() + +Execute(ALE should stop queuing for a while after exceptions are thrown): + AssertThrows call ale#Queue(100) + call ale#Queue(100) + +Execute(ALE should stop linting for a while after exceptions are thrown): + AssertThrows call ale#Lint() + call ale#Lint() + +Execute(ALE should stop queuing echo messages for a while after exceptions are thrown): + AssertThrows call ale#cursor#EchoCursorWarningWithDelay() + call ale#cursor#EchoCursorWarningWithDelay() + +Execute(ALE should stop echoing messages for a while after exceptions are thrown): + AssertThrows call ale#cursor#EchoCursorWarning() + call ale#cursor#EchoCursorWarning() diff --git a/test/test_lint_file_linters.vader b/test/test_lint_file_linters.vader new file mode 100644 index 0000000..cb85979 --- /dev/null +++ b/test/test_lint_file_linters.vader @@ -0,0 +1,289 @@ +Before: + Save g:ale_run_synchronously + Save g:ale_buffer_info + Save g:ale_linters + + let g:ale_buffer_info = {} + let g:ale_run_synchronously = 1 + 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': 'echo', + \ 'command': 'echo', + \ 'lint_file': 1, + \}) + + call ale#linter#Define('foobar', { + \ 'name': 'buffer_linter', + \ 'callback': 'BufferCallback', + \ 'executable': '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 b:ale_enabled = 0 + call ale#events#SaveEvent(bufnr('')) + + " This flag needs to be set so windows can be opened, etc. + AssertEqual 1, b:ale_save_event_fired + +Execute(b:ale_save_event_fired should be set to 0 when results are set): + let b:ale_save_event_fired = 1 + + call ale#engine#SetResults(bufnr(''), []) + + AssertEqual 0, b:ale_save_event_fired + +Execute(lint_file linters should stay running after checking without them): + let g:ale_run_synchronously = 0 + + " Run all linters, then just the buffer linters. + call ale#Queue(0, 'lint_file') + call ale#Queue(0) + + " The lint_file linter should still be running. + AssertEqual + \ ['lint_file_linter', 'buffer_linter'], + \ g:ale_buffer_info[bufnr('')].active_linter_list + " We should have 1 job for each linter. + AssertEqual 2, len(g:ale_buffer_info[bufnr('')].job_list) + + call ale#engine#WaitForJobs(2000) diff --git a/test/test_lint_on_enter_when_file_changed.vader b/test/test_lint_on_enter_when_file_changed.vader new file mode 100644 index 0000000..4d4f19c --- /dev/null +++ b/test/test_lint_on_enter_when_file_changed.vader @@ -0,0 +1,79 @@ +Before: + Save &filetype + Save g:ale_buffer_info + Save g:ale_lint_on_enter + let g:buf = bufnr('') + let g:ale_lint_on_enter = 1 + let g:ale_run_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': 'true', + \ 'command': '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..44446ef --- /dev/null +++ b/test/test_lint_on_filetype_changed.vader @@ -0,0 +1,47 @@ +Before: + Save &filetype + + let g:queue_calls = [] + + function! ale#Queue(...) + call add(g:queue_calls, a:000) + endfunction + +After: + Restore + + unlet! g:queue_calls + + " Reload the ALE code to load the real function again. + runtime autoload/ale.vim + + unlet! b:ale_original_filetype + +Execute(The original filetype should be set on BufEnter): + let &filetype = 'foobar' + + call ale#events#EnterEvent(bufnr('')) + + AssertEqual 'foobar', b:ale_original_filetype + + let &filetype = 'bazboz' + + call ale#events#EnterEvent(bufnr('')) + + AssertEqual 'bazboz', b:ale_original_filetype + +Execute(Linting should not be queued when the filetype is the same): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'foobar') + + AssertEqual [], g:queue_calls + +Execute(Linting should be queued when the filetype changes): + let b:ale_original_filetype = 'foobar' + let g:queue_calls = [] + + call ale#events#FileTypeEvent(bufnr(''), 'bazboz') + + AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader new file mode 100644 index 0000000..d946a60 --- /dev/null +++ b/test/test_linter_defintion_processing.vader @@ -0,0 +1,443 @@ +Before: + let g:linter = {} + +After: + unlet g:linter + +Execute (PreProcess should throw when the linter object is not a Dictionary): + AssertThrows call ale#linter#PreProcess('') + AssertEqual 'The linter object must be a Dictionary', g:vader_exception + +Execute (PreProcess should throw when there is no name): + AssertThrows call ale#linter#PreProcess({ + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + AssertEqual '`name` must be defined to name the linter', g:vader_exception + +Execute (PreProcess should throw when there is no callback): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + AssertEqual '`callback` must be defined with a callback to accept output', g:vader_exception + +Execute (PreProcess should throw when then callback is not a function): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 1, + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + AssertEqual '`callback` must be defined with a callback to accept output', g:vader_exception + +Execute (PreProcess should throw when there is no executable or executable_callback): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'command': 'echo', + \}) + AssertEqual 'Either `executable` or `executable_callback` must be defined', g:vader_exception + +Execute (PreProcess should throw when executable is not a string): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 123, + \ 'command': 'echo', + \}) + AssertEqual '`executable` must be a string if defined', g:vader_exception + +Execute (PreProcess should throw when executable_callback is not a callback): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable_callback': 123, + \ 'command': 'echo', + \}) + AssertEqual '`executable_callback` must be a callback if defined', g:vader_exception + +Execute (PreProcess should throw when there is no command): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \}) + AssertEqual 'Either `command`, `executable_callback`, `command_chain` must be defined', g:vader_exception + +Execute (PreProcess should throw when command is not a string): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': [], + \}) + AssertEqual '`command` must be a string if defined', g:vader_exception + +Execute (PreProcess should throw when command_callback is not a callback): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command_callback': 123, + \}) + AssertEqual '`command_callback` must be a callback if defined', g:vader_exception + +Execute (PreProcess should when the output stream isn't a valid string): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'xxx', + \}) + AssertEqual "`output_stream` must be 'stdout', 'stderr', or 'both'", g:vader_exception + +Execute (PreProcess should not throw when everything is correct): + call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + +Execute (PreProcess should accept an stdout output_stream): + call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'stdout', + \}) + +Execute (PreProcess should accept an stderr output_stream): + call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'stderr', + \}) + +Execute (PreProcess should accept a 'both' output_stream): + call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'output_stream': 'both', + \}) + +Execute(PreProcess should complain if the command_chain is not a List): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': 'x', + \} + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual '`command_chain` must be a List', g:vader_exception + +Execute(PreProcess should complain if the command_chain is empty): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [], + \} + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual '`command_chain` must contain at least one item', g:vader_exception + +Execute(PreProcess should complain if the command_chain has no callback): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [{}], + \} + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'The `command_chain` item 0 must define a `callback` function', g:vader_exception + +Execute(PreProcess should complain if the command_chain callback is not a function): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [{'callback': 2}], + \} + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'The `command_chain` item 0 must define a `callback` function', g:vader_exception + +Execute(PreProcess should accept a chain with one callback): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [{'callback': 'foo'}], + \} + call ale#linter#PreProcess(g:linter) + +Execute(PreProcess should complain about invalid output_stream values in the chain): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [{'callback': 'foo', 'output_stream': ''}], + \} + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual "The `command_chain` item 0 `output_stream` flag must be 'stdout', 'stderr', or 'both'", g:vader_exception + +Execute(PreProcess should complain about valid output_stream values in the chain): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [{'callback': 'foo', 'output_stream': 'stdout'}], + \} + call ale#linter#PreProcess(g:linter) + let g:linter.command_chain[0].output_stream = 'stderr' + call ale#linter#PreProcess(g:linter) + let g:linter.command_chain[0].output_stream = 'both' + call ale#linter#PreProcess(g:linter) + +Execute(PreProcess should complain about invalid chain items at higher indices): + let g:linter = { + \ 'name': 'x', + \ 'callback': 'x', + \ 'executable': 'x', + \ 'command_chain': [{'callback': 'foo'}, {'callback': 123}], + \} + 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 new file mode 100644 index 0000000..afb540d --- /dev/null +++ b/test/test_linter_retrieval.vader @@ -0,0 +1,127 @@ +Before: + Save g:ale_linters, 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() + +After: + Restore + + unlet! g:testlinter1 + unlet! g:testlinter2 + unlet! b:ale_linters + unlet! b:ale_linter_aliases + call ale#linter#Reset() + +Execute (You should be able to get a defined linter): + call ale#linter#Define('testft', g:testlinter1) + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able get select a single linter): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able to select a linter by an alias): + let g:testlinter1.aliases = ['foo', 'linter1alias'] + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['linter1alias']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able to select linters with a buffer option): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1', 'testlinter2']} + let b:ale_linters = {'testft': ['testlinter1']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (Buffer settings shouldn't completely replace global settings): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + let g:ale_linters = {'testft': ['testlinter1']} + let b:ale_linters = {'testft2': ['testlinter1', 'testlinter2']} + + AssertEqual [g:testlinter1], ale#linter#Get('testft') + +Execute (You should be able to alias linters from one filetype to another): + call ale#linter#Define('testft1', g:testlinter1) + let g:ale_linter_aliases = {'testft2': 'testft1'} + + AssertEqual [g:testlinter1], ale#linter#Get('testft2') + +Execute (You should be able to filter aliased linters): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft1', g:testlinter2) + let g:ale_linters = {'testft1': ['testlinter1'], 'testft2': ['testlinter2']} + let g:ale_linter_aliases = {'testft2': 'testft1'} + + AssertEqual [g:testlinter1], ale#linter#Get('testft1') + AssertEqual [g:testlinter2], ale#linter#Get('testft2') + +Execute (Dot-separated filetypes should be handled correctly): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1.testft2') + +Execute (Linters for multiple aliases should be loaded): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let ale_linter_aliases = {'testft3': ['testft1', 'testft2']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft3') + +Execute (You should be able to alias filetypes to themselves and another): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') + +Execute (Buffer-local overrides for aliases should be used): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let g:ale_linter_aliases = {'testft1': ['testft2']} + let b:ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') + +Execute (The local alias option shouldn't completely replace the global one): + call ale#linter#Define('testft1', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + let g:ale_linter_aliases = {'testft1': ['testft1', 'testft2']} + " This is a key set for a differnt filetype. + " We should look for a key in this Dictionary first, and then check the + " global Dictionary. + let b:ale_linter_aliases = {'testft3': ['testft1']} + + AssertEqual [g:testlinter1, g:testlinter2], ale#linter#Get('testft1') + +Execute (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 new file mode 100644 index 0000000..9960264 --- /dev/null +++ b/test/test_linting_blacklist.vader @@ -0,0 +1,14 @@ +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) + + AssertEqual {}, g:ale_buffer_info diff --git a/test/test_linting_updates_loclist.vader b/test/test_linting_updates_loclist.vader new file mode 100644 index 0000000..a1daf28 --- /dev/null +++ b/test/test_linting_updates_loclist.vader @@ -0,0 +1,75 @@ +Before: + Save g:ale_set_signs + let g:ale_set_signs = 1 + + let g:expected_data = [ + \ { + \ 'lnum': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'linter_name': 'testlinter', + \ 'nr': -1, + \ 'type': 'W', + \ 'col': 10, + \ 'text': 'Infix operators must be spaced. [Warning/space-infix-ops]', + \ 'sign_id': 1000001, + \ }, + \ { + \ 'lnum': 2, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ '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': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + + sign unplace * + +After: + 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() + call ale#engine#WaitForJobs(2000) + + AssertEqual ['' . bufnr('%')], keys(g:ale_buffer_info) + AssertEqual g:expected_data, g:ale_buffer_info[bufnr('%')].loclist diff --git a/test/test_list_opening.vader b/test/test_list_opening.vader new file mode 100644 index 0000000..63b30ef --- /dev/null +++ b/test/test_list_opening.vader @@ -0,0 +1,211 @@ +" Author: Yann Fery +Before: + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_open_list + Save g:ale_keep_list_window_open + Save g:ale_list_window_size + Save g:ale_buffer_info + Save g:ale_set_lists_synchronously + + let g:ale_set_loclist = 1 + let g:ale_set_quickfix = 0 + let g:ale_open_list = 0 + let g:ale_keep_list_window_open = 0 + let g:ale_list_window_size = 10 + let g:ale_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 + +After: + Restore + + unlet! g:loclist + unlet! b:ale_list_window_size + unlet! b:ale_open_list + unlet! b:ale_keep_list_window_open + unlet! b:ale_save_event_fired + + delfunction GetQuickfixHeight + + " Close quickfix window after every execute block + lcl + ccl + call setloclist(0, []) + call setqflist([]) + +Execute(IsQuickfixOpen should return the right output): + AssertEqual 0, ale#list#IsQuickfixOpen() + call setloclist(0, g:loclist) + lopen + AssertEqual 1, ale#list#IsQuickfixOpen() + lcl + AssertEqual 0, ale#list#IsQuickfixOpen() + call setqflist(g:loclist) + copen + AssertEqual 1, ale#list#IsQuickfixOpen() + ccl + AssertEqual 0, ale#list#IsQuickfixOpen() + +Execute(The quickfix window should not open by default for the loclist): + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert !ale#list#IsQuickfixOpen() + +Execute(The quickfix window should open for just the loclist): + let g:ale_open_list = 1 + + " It should not open for an empty list. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen() + + " With a non-empty loclist, the window must open. + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen() + + " Clear the list and it should close again. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen() + +Execute(The quickfix window 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 stay open for just the loclist): + let g:ale_open_list = 1 + let g:ale_keep_list_window_open = 1 + + " The window should stay open after even after it is made blank again. + call ale#list#SetLists(bufnr('%'), g:loclist) + call ale#list#SetLists(bufnr('%'), []) + Assert ale#list#IsQuickfixOpen() + +Execute(The quickfix window should not open by default when quickfix is on): + let g:ale_set_quickfix = 1 + + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert !ale#list#IsQuickfixOpen() + +Execute(The quickfix window should open for the quickfix list): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + + let g:ale_buffer_info[bufnr('') + 1] = { + \ 'loclist': [{'bufnr': -1, 'filename': '/foo/bar', 'lnum': 5, 'col': 5, 'text': 'x'}], + \} + + " It should not open for an empty list. + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was opened when the list was empty' + + " With a non-empty quickfix list, the window must open. + call ale#list#SetLists(bufnr('%'), g:loclist) + Assert ale#list#IsQuickfixOpen(), 'The quickfix window was closed when the list was not empty' + + " Clear this List. The window should stay open, as there are other items. + let g:ale_buffer_info[bufnr('')].loclist = [] + call ale#list#SetLists(bufnr('%'), []) + Assert ale#list#IsQuickfixOpen(), 'The quickfix window closed even though there are items in another buffer' + + " Clear the other List now. Now the window should close. + call remove(g:ale_buffer_info, bufnr('') + 1) + call ale#list#SetLists(bufnr('%'), []) + Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was not closed' + +Execute(The quickfix window should stay open for the quickfix list): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + let g:ale_keep_list_window_open = 1 + + " The window should stay open after even after it is made blank again. + call ale#list#SetLists(bufnr('%'), g:loclist) + call ale#list#SetLists(bufnr('%'), []) + Assert ale#list#IsQuickfixOpen() + +Execute(The quickfix window height should be correct for the quickfix list): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + let g:ale_list_window_size = 7 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 7, GetQuickfixHeight() + +Execute(The quickfix window height should be correct for the quickfix list with buffer variables): + let g:ale_set_quickfix = 1 + let g:ale_open_list = 1 + let b:ale_list_window_size = 8 + + call ale#list#SetLists(bufnr('%'), g:loclist) + + AssertEqual 8, GetQuickfixHeight() + +Execute(The 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..74cb4bc --- /dev/null +++ b/test/test_list_titles.vader @@ -0,0 +1,69 @@ +Before: + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + let g:ale_set_loclist = 0 + let g:ale_set_quickfix = 0 + + silent! cd /testplugin/test + +After: + Restore + + call setloclist(0, []) + call setqflist([]) + +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': 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': 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 new file mode 100644 index 0000000..5558191 --- /dev/null +++ b/test/test_loclist_binary_search.vader @@ -0,0 +1,49 @@ +Before: + let g:loclist = [ + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 3, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 12}, + \ {'bufnr': 1, 'lnum': 3, 'col': 25}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 1, 'lnum': 9, 'col': 5}, + \ {'bufnr': 1, 'lnum': 10, 'col': 1}, + \ {'bufnr': 2, 'lnum': 7, 'col': 10}, + \ {'bufnr': 2, 'lnum': 9, 'col': 2}, + \ {'bufnr': 2, 'lnum': 10, 'col': 2}, + \ {'bufnr': 2, 'lnum': 11, 'col': 2}, + \] + +After: + unlet g:loclist + +Execute(Exact column matches should be correct): + AssertEqual 1, ale#util#BinarySearch(g:loclist, 1, 3, 2) + +Execute(Off lines, there should be no match): + AssertEqual -1, ale#util#BinarySearch(g:loclist, 1, 4, 2) + +Execute(Near column matches should be taken): + AssertEqual 2, ale#util#BinarySearch(g:loclist, 1, 3, 11) + AssertEqual 3, ale#util#BinarySearch(g:loclist, 1, 3, 13) + +Execute(Columns before should be taken when the cursor is far ahead): + AssertEqual 4, ale#util#BinarySearch(g:loclist, 1, 3, 300) + +Execute(The only problems on lines in later columns should be matched): + AssertEqual 7, ale#util#BinarySearch(g:loclist, 1, 9, 1) + +Execute(The only problems on lines in earlier columns should be matched): + AssertEqual 8, ale#util#BinarySearch(g:loclist, 1, 10, 30) + +Execute(Lines for other buffers should not be matched): + AssertEqual -1, ale#util#BinarySearch(g:loclist, 1, 7, 10) + +Execute(Searches for buffers later in the list should work): + AssertEqual 10, ale#util#BinarySearch(g:loclist, 2, 9, 10) + +Execute(Searches should work with just one item): + let g:loclist = [{'bufnr': 1, 'lnum': 3, 'col': 10}] + + AssertEqual 0, ale#util#BinarySearch(g:loclist, 1, 3, 2) diff --git a/test/test_loclist_corrections.vader b/test/test_loclist_corrections.vader new file mode 100644 index 0000000..e6844d8 --- /dev/null +++ b/test/test_loclist_corrections.vader @@ -0,0 +1,329 @@ +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}, + \ ], + \ ) 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 new file mode 100644 index 0000000..157b2a2 --- /dev/null +++ b/test/test_loclist_sorting.vader @@ -0,0 +1,27 @@ +Execute(loclist item should be sorted): + AssertEqual [ + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ ], + \ sort([ + \ {'bufnr': 3, 'lnum': 1, 'col': 1}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 5, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 5}, + \ {'bufnr': 2, 'lnum': 5, 'col': 5}, + \ {'bufnr': -1, 'filename': 'b', 'lnum': 4, 'col': 2}, + \ {'bufnr': 1, 'lnum': 5, 'col': 4}, + \ {'bufnr': 1, 'lnum': 2, 'col': 10}, + \ {'bufnr': 2, 'lnum': 1, 'col': 5}, + \ {'bufnr': 1, 'lnum': 3, 'col': 2}, + \ {'bufnr': 2, 'lnum': 1, 'col': 2}, + \ {'bufnr': -1, 'filename': 'c', 'lnum': 3, 'col': 2}, + \], 'ale#util#LocItemCompare') diff --git a/test/test_nearest_file_search.vader b/test/test_nearest_file_search.vader new file mode 100644 index 0000000..71b7d10 --- /dev/null +++ b/test/test_nearest_file_search.vader @@ -0,0 +1,13 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(We should be able to find a configuration file further up): + call ale#test#SetFilename('top/middle/bottom/dummy.txt') + + AssertEqual 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_path_equality.vader b/test/test_path_equality.vader new file mode 100644 index 0000000..54d9bf9 --- /dev/null +++ b/test/test_path_equality.vader @@ -0,0 +1,44 @@ +Execute(ale#path#IsBufferPath should match simple relative paths): + call ale#test#SetFilename('app/foo.txt') + + Assert ale#path#IsBufferPath(bufnr(''), 'app/foo.txt'), 'No match for foo.txt' + Assert !ale#path#IsBufferPath(bufnr(''), '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') + + Assert ale#path#IsBufferPath(bufnr(''), '../../app/foo.txt'), 'No match for ../../app/foo.txt' + +Execute(ale#path#IsBufferPath should match absolute paths): + silent file! foo.txt + + Assert ale#path#IsBufferPath(bufnr(''), getcwd() . '/foo.txt'), 'No match for foo.txt' + Assert !ale#path#IsBufferPath(bufnr(''), getcwd() . '/bar.txt'), 'Bad match for bar.txt' + +Execute(ale#path#IsBufferPath should match paths beginning with ./): + silent file! foo.txt + + Assert ale#path#IsBufferPath(bufnr(''), './foo.txt'), 'No match for ./foo.txt' + +Execute(ale#path#IsBufferPath should match if our path ends with the test path): + silent file! foo/bar/baz.txt + + Assert ale#path#IsBufferPath(bufnr(''), 'bar/baz.txt'), 'No match for bar/baz.txt' + +Execute(ale#path#IsBufferPath should match paths with redundant slashes): + silent file! foo.txt + + Assert ale#path#IsBufferPath(bufnr(''), 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(''), '../../../../../../../../tmp/vG0hKyD/1/test.ts') + Assert ale#path#IsBufferPath(bufnr(''), '/tmp/vG0hKyD/1/test.ts') + Assert ale#path#IsBufferPath(bufnr(''), '/run/user/1000/vG0hKyD/1/test.ts') diff --git a/test/test_path_upwards.vader b/test/test_path_upwards.vader new file mode 100644 index 0000000..5e7d576 --- /dev/null +++ b/test/test_path_upwards.vader @@ -0,0 +1,50 @@ +After: + let g:ale_has_override = {} + +Execute(ale#path#Upwards should return the correct path components for 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('') + +Execute(ale#path#Upwards should return the correct path components for Windows): + let g:ale_has_override = {'win32': 1} + + 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') diff --git a/test/test_path_uri.vader b/test/test_path_uri.vader new file mode 100644 index 0000000..dbceac3 --- /dev/null +++ b/test/test_path_uri.vader @@ -0,0 +1,16 @@ +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#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..164e5aa --- /dev/null +++ b/test/test_pattern_options.vader @@ -0,0 +1,32 @@ +Before: + Save g:ale_pattern_options + Save g:ale_pattern_options_enabled + Save &filetype + +After: + Restore + + unlet! b:ale_enabled + unlet! b:some_option + +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', + \}} + + silent! file foobar.js + + call ale#pattern_options#SetOptions() + + Assert !exists('b:ale_enabled') + Assert !exists('b:some_option') + + silent! file bazboz.js + + call ale#pattern_options#SetOptions() + + AssertEqual 1, b:ale_enabled + AssertEqual 347, b:some_option + AssertEqual 'pattern_option_set_filetype', &filetype diff --git a/test/test_perlcritic_linter.vader b/test/test_perlcritic_linter.vader new file mode 100644 index 0000000..8b7cf1a --- /dev/null +++ b/test/test_perlcritic_linter.vader @@ -0,0 +1,62 @@ +" NOTE: We use the 'b:' forms below to ensure that we're properly using +" ale#Var() + +Given perl: + #!/usr/bin/env perl + use v5.10; + say 'Hi there!'; + + +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 + silent! unlet g:ale_perl_perlcritic_options + silent! unlet g:ale_perl_perlcritic_executable + silent! unlet g:ale_perl_perlcritic_showrules + let g:ale_perl_perlcritic_profile = '' + + " enable loading inside test container + silent! cd /testplugin + source ale_linters/perl/perlcritic.vim + + +After: + Restore + silent! unlet b:ale_perl_perlcritic_profile + silent! unlet b:ale_perl_perlcritic_options + silent! unlet b:ale_perl_perlcritic_executable + silent! unlet b:ale_perl_perlcritic_showrules + + +Execute(no g:ale_perl_perlcritic_showrules): + let b:ale_perl_perlcritic_showrules = 0 + + AssertEqual + \ "'perlcritic' --verbose '". '%l:%c %m\n' . "' --nocolor", + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) + + +Execute(yes g:ale_perl_perlcritic_showrules): + let b:ale_perl_perlcritic_showrules = 1 + + AssertEqual + \ "'perlcritic' --verbose '". '%l:%c %m [%p]\n' . "' --nocolor", + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) + + +Execute(set g:ale_perl_perlcritic_profile): + let b:ale_perl_perlcritic_profile = 'README.md' + + Assert + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) + \ =~# "--profile '.*/README.md'" + + +Execute(g:ale_perl_perlcritic_options): + let b:ale_perl_perlcritic_options = 'beep boop' + + AssertEqual + \ "'perlcritic' --verbose '". '%l:%c %m\n' . "' --nocolor beep boop", + \ ale_linters#perl#perlcritic#GetCommand(bufnr('')) diff --git a/test/test_phpcs_executable_detection.vader b/test/test_phpcs_executable_detection.vader new file mode 100644 index 0000000..786d324 --- /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 + \ 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_prepare_command.vader b/test/test_prepare_command.vader new file mode 100644 index 0000000..5707be7 --- /dev/null +++ b/test/test_prepare_command.vader @@ -0,0 +1,37 @@ +Before: + Save &shell + Save &shellcmdflag + +After: + Restore + let g:ale_has_override = {} + +Execute(sh should be used when the shell is fish): + " Set something else, so we will replace that too. + let &shellcmdflag = '-f' + + let &shell = 'fish' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand('foobar') + + let &shell = '/usr/bin/fish' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand('foobar') + + let &shell = '/usr/local/bin/fish' + + AssertEqual ['/bin/sh', '-c', 'foobar'], ale#job#PrepareCommand('foobar') + +Execute(Other shells should be used when set): + let &shell = '/bin/bash' + let &shellcmdflag = '-c' + + AssertEqual ['/bin/bash', '-c', 'foobar'], ale#job#PrepareCommand('foobar') + +Execute(cmd /c as a string should be used on Windows): + let &shell = 'who cares' + let &shellcmdflag = 'whatever' + + let g:ale_has_override = {'win32': 1} + + AssertEqual 'cmd /c foobar', ale#job#PrepareCommand('foobar') 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_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 new file mode 100644 index 0000000..ed1549a --- /dev/null +++ b/test/test_resolve_local_path.vader @@ -0,0 +1,17 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + +After: + call ale#test#RestoreDirectory() + +Execute(We should be able to find the local version of a file): + call ale#test#SetFilename('top/middle/bottom/dummy.txt') + + AssertEqual + \ expand('%:p:h:h:h:h') . '/top/example.ini', + \ ale#path#ResolveLocalPath(bufnr('%'), 'example.ini', '/global/config.ini') + +Execute(We shouldn't find anything for files which don't match): + AssertEqual + \ '/global/config.ini', + \ ale#path#ResolveLocalPath(bufnr('%'), 'missing.ini', '/global/config.ini') diff --git a/test/test_results_not_cleared_when_opening_loclist.vader b/test/test_results_not_cleared_when_opening_loclist.vader new file mode 100644 index 0000000..0c053b8 --- /dev/null +++ b/test/test_results_not_cleared_when_opening_loclist.vader @@ -0,0 +1,48 @@ +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': '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() + + AssertEqual 1, len(getloclist(0)) + + " 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)) 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 new file mode 100644 index 0000000..9730b74 --- /dev/null +++ b/test/test_semver_utils.vader @@ -0,0 +1,16 @@ +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') + +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]) 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..37cf43c --- /dev/null +++ b/test/test_shell_detection.vader @@ -0,0 +1,83 @@ +Before: + runtime ale_linters/sh/shell.vim + runtime ale_linters/sh/shellcheck.vim + +After: + call ale#linter#Reset() + + unlet! b:is_bash + unlet! b:is_sh + unlet! b:is_kornshell + +Given(A file with a Bash hashbang): + #!/bin/bash + +Execute(/bin/bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with /bin/sh): + #!/usr/bin/env sh -eu --foobar + +Execute(/bin/sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with bash as an argument to env): + #!/usr/bin/env bash + +Execute(/usr/bin/env bash should be detected appropriately): + AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a tcsh hash bang and arguments): + #!/usr/bin/env tcsh -eu --foobar + +Execute(tcsh should be detected appropriately): + AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'tcsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'tcsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a zsh hash bang and arguments): + #!/usr/bin/env zsh -eu --foobar + +Execute(zsh should be detected appropriately): + AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'zsh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'zsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a csh hash bang and arguments): + #!/usr/bin/env csh -eu --foobar + +Execute(zsh should be detected appropriately): + AssertEqual 'csh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'csh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'csh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file with a sh hash bang and arguments): + #!/usr/bin/env sh -eu --foobar + +Execute(sh should be detected appropriately): + AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Given(A file without a hashbang): + +Execute(The bash dialect should be used for shellcheck if b:is_bash is 1): + let b:is_bash = 1 + + AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The sh dialect should be used for shellcheck if b:is_sh is 1): + let b:is_sh = 1 + + AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The ksh dialect should be used for shellcheck if b:is_kornshell is 1): + let b:is_kornshell = 1 + + AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) diff --git a/test/test_should_do_nothing_conditions.vader b/test/test_should_do_nothing_conditions.vader new file mode 100644 index 0000000..4d6facf --- /dev/null +++ b/test/test_should_do_nothing_conditions.vader @@ -0,0 +1,12 @@ +Before: + Save &l:statusline + +After: + 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('')) diff --git a/test/test_statusline.vader b/test/test_statusline.vader new file mode 100644 index 0000000..7978a50 --- /dev/null +++ b/test/test_statusline.vader @@ -0,0 +1,141 @@ +Before: + 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: + Restore + + delfunction Counts + +Execute (Count should be 0 when data is empty): + AssertEqual Counts({}), ale#statusline#Count(bufnr('')) + +Execute (Count should read data from the cache): + let g:ale_buffer_info = {'44': {'count': Counts({'error': 1, 'warning': 2})}} + AssertEqual Counts({'error': 1, 'warning': 2}), ale#statusline#Count(44) + +Execute (The count should be correct after an update): + let g:ale_buffer_info = {'44': {}} + call ale#statusline#Update(44, []) + AssertEqual Counts({}), ale#statusline#Count(44) + +Execute (Count should be match the loclist): + let g:ale_buffer_info = { + \ bufnr(''): { + \ 'loclist': [ + \ {'bufnr': bufnr('') - 1, 'type': 'E'}, + \ {'bufnr': bufnr('') - 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') - 1, 'type': 'W'}, + \ {'bufnr': bufnr('') - 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') - 1, 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \ {'bufnr': bufnr('') + 1, 'type': 'E'}, + \ {'bufnr': bufnr('') + 1, 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') + 1, 'type': 'W'}, + \ {'bufnr': bufnr('') + 1, 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr('') + 1, 'type': 'I'}, + \ ], + \ }, + \} + 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 Counts({}), ale#statusline#Count(9001) + +Execute (Status() should return just errors for the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \]) + AssertEqual '2E', ale#statusline#Status() + +Execute (Status() should return just warnings for the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \]) + AssertEqual '3W', ale#statusline#Status() + +Execute (Status() should return errors and warnings for the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), [ + \ {'bufnr': bufnr(''), 'type': 'E'}, + \ {'bufnr': bufnr(''), 'type': 'E', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'W'}, + \ {'bufnr': bufnr(''), 'type': 'W', 'sub_type': 'style'}, + \ {'bufnr': bufnr(''), 'type': 'I'}, + \]) + AssertEqual '2E 3W', ale#statusline#Status() + +Execute (Status() should return the custom 'OK' string with the old format): + let g:ale_statusline_format = ['%sE', '%sW', 'OKIE'] + let g:ale_buffer_info = {bufnr(''): {}} + call ale#statusline#Update(bufnr(''), []) + 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_temporary_file_management.vader b/test/test_temporary_file_management.vader new file mode 100644 index 0000000..6d1f0df --- /dev/null +++ b/test/test_temporary_file_management.vader @@ -0,0 +1,111 @@ +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': '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' + AssertEqual 'rwxr-x---', getfperm(b:dir) + + " 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_tmpdir_init.vader b/test/test_tmpdir_init.vader new file mode 100644 index 0000000..68bb2b4 --- /dev/null +++ b/test/test_tmpdir_init.vader @@ -0,0 +1,2 @@ +Execute($TMPDIR should be set to a default value if unset): + AssertEqual '/tmp', $TMPDIR 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_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/top/ale-special-directory-name-dont-use-this-please/empty-file b/test/top/ale-special-directory-name-dont-use-this-please/empty-file new file mode 100644 index 0000000..e69de29 diff --git a/test/top/example.ini b/test/top/example.ini new file mode 100644 index 0000000..e69de29 diff --git a/test/top/middle/bottom/dummy.txt b/test/top/middle/bottom/dummy.txt 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..f8a97cb --- /dev/null +++ b/test/util/test_cd_string_commands.vader @@ -0,0 +1,15 @@ +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): + AssertEqual '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 ' . shellescape(g:dir) . ' && ', ale#path#BufferCdString(bufnr('')) diff --git a/test/vimrc b/test/vimrc new file mode 100644 index 0000000..8dadb4f --- /dev/null +++ b/test/vimrc @@ -0,0 +1,31 @@ +" vint: -ProhibitSetNoCompatible + +" Make most tests just set lists synchronously when run in Docker. +let g:ale_set_lists_synchronously = 1 + +" Load builtin plugins +" We need this because run_vim.sh sets -i NONE +set runtimepath=/home/vim,$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after,/testplugin,/vader + +" The following is just an example +filetype plugin indent on +syntax on +set shell=/bin/sh +set shellcmdflag=-c +set nocompatible +set tabstop=4 +set softtabstop=4 +set shiftwidth=4 +set expandtab +set backspace=2 +set nofoldenable +set foldmethod=syntax +set foldlevelstart=10 +set foldnestmax=10 +set ttimeoutlen=0 + +let g:mapleader=',' + +" Clear the TMPDIR value for tests. +" The plugin should set this to /tmp by default, which we will test. +let $TMPDIR = ''