Compare commits

..

252 Commits

Author SHA1 Message Date
w0rp
cedc3e1a1f Fix #560 #763 - Silence errors for setting signs, and do nothing for dirvish 2017-07-16 01:17:48 +01:00
w0rp
30aef6b19a Fix #749 - Use /bin/sh when the shell is fish 2017-07-10 21:36:42 +01:00
w0rp
bd0f31147e Fix #730 - Lint files on save even when nothing was fixed 2017-07-10 13:41:38 +01:00
daa84
74f2afbe2e Fix windows path check on rust linter (#736)
* Fix rust linter on windows

* Add windows path test

* Use ale#path#IsBufferPath to compare paths

* Fix errors
2017-07-07 17:04:16 +01:00
w0rp
d438f8f268 Fix #735 - Support old versions of Flow by only adding --respect-pragma for supported versions 2017-07-07 11:00:38 +01:00
w0rp
2f2af4315b #710 - Fix a parsing bug caused by the last fix 2017-07-07 11:00:26 +01:00
w0rp
a040878ebc #710 - Show warnings as warnings for ghc 2017-07-07 00:28:52 +01:00
w0rp
a72c1edfcc #732 - Use the configuration files when fixing files with rubocop 2017-07-06 23:08:38 +01:00
w0rp
acda19776a Initialize rubocop variables in one place 2017-07-06 23:08:24 +01:00
w0rp
e9a1cd600a Store the output of commands by default so I don't have to ask people to turn it on any more. 2017-07-04 00:16:53 +01:00
w0rp
0819c4cd56 Fix #216 - Filter out errors for other files for ansible-lint 2017-07-03 23:18:49 +01:00
w0rp
448600fe4f Merge pull request #716 from sobrinho/master
Fix brakeman handler when there is no output
2017-07-01 15:43:41 +01:00
w0rp
bb1f413bf3 Fix #706 - Skip fixers with jobs that return empty output, in case they have failed 2017-06-29 12:03:05 +01:00
w0rp
0302d2a328 Remove the script for runnning tests from git archives 2017-06-27 16:15:17 +01:00
w0rp
e51272e277 Do not include the code of conduct in git archives 2017-06-27 16:14:35 +01:00
Nick Krichevsky
14cca6d115 Remove style classification from E999 (#696)
* Remove style classification from E999

* Update test_flake8_handler to reflect E999 changes
2017-06-27 15:07:26 +01:00
w0rp
16ba9bd680 Fix #680 - Use --shadow-file to check for problems with mypy while you type 2017-06-27 10:06:03 +01:00
w0rp
499bf63dc3 #678 - Document the sign column color behaviour 2017-06-27 09:44:02 +01:00
w0rp
b9d91f0e9b Document :ALEInfo better 2017-06-26 22:08:55 +01:00
w0rp
d5c9a4eb87 #171 - Document every highlight 2017-06-26 21:49:20 +01:00
w0rp
d2ec53f817 Merge pull request #695 from jsivak/feature/fix_multiple_parens
Change regex to better handle messages with multiple groups of parentheses.
2017-06-26 09:25:56 +01:00
John Sivak
539a76c5ae Change regex to better handle messages with multiple groups of parentheses. 2017-06-25 20:19:39 -04:00
w0rp
1917e9157c Fix #694 - Ignore BEGIN failed errors for Perl only for certain errors 2017-06-25 21:49:57 +01:00
w0rp
3828ea5b26 Detect .git directories for finding the project root for C projects 2017-06-25 21:33:15 +01:00
w0rp
8b557f346c Move ale#handlers#c functions into ale#c 2017-06-25 20:34:23 +01:00
Lynn Dylan Hurley
7f6e5dc65b Add ruby fixer using rubocop --auto-correct (#689)
* add ruby fixer for `rubocop --auto-correct`
2017-06-25 20:04:14 +01:00
w0rp
7d73a1602b Explain how to use the airline extension better 2017-06-25 20:01:46 +01:00
John Sivak
a9b29fef28 Feature/restore display of symbol (#693)
* Add display of the pylint symbol name after the message.

* Update test to pass.
2017-06-25 18:22:13 +01:00
Jasper Woudenberg
c2f69b7750 Improve elm linter (#637)
* Improve elm linter

Some types of errors do not return nice JSON.
Show them on the first line instead of showing nothing.

* Remove unnecessary properties from elm linter

* Add a vader test for elm-make linter

* Test non-JSON elm-make errors are shown
2017-06-25 17:12:40 +01:00
w0rp
93473a4101 Fix #690 - Filter out errors from other files for Haskell 2017-06-25 17:08:57 +01:00
w0rp
229a1c092a #684 Handle tslint errors without the severity included, and use character instead of position for the columns 2017-06-25 16:40:44 +01:00
w0rp
492260c967 Fix the tests harder 2017-06-25 16:20:55 +01:00
w0rp
c2138a2656 Fix the tests for the fix on save feature 2017-06-25 16:14:04 +01:00
w0rp
ec3ddce4ac #665 - Replace the nomodified line with :w! 2017-06-25 15:59:38 +01:00
w0rp
2c6b571e66 Fix #665 - Stop prompts appearing when fixing files on save 2017-06-25 15:57:36 +01:00
w0rp
4eaa990fe8 Fix #684 - Use the JSON format for tslint, for consistency betwen versions, and handling of end line and column numbers 2017-06-25 13:56:51 +01:00
w0rp
8da5641355 Ask for let g:ale_history_log_output = 1 first in the issue template 2017-06-25 11:55:21 +01:00
w0rp
d1e23f7295 Fix #683 Recommend prettier and eslint over prettier-eslint 2017-06-24 17:32:43 +01:00
Gagbo
dc647fcc7f Add clangcheck Linter to cpp (#686)
Add a clangcheck linter
2017-06-24 16:10:04 +01:00
Gagbo
e98560a349 Added builddir option to clang-tidy to point to json folder (#688)
Detect compille_commands.json files for clang-tidy
2017-06-24 12:38:16 +01:00
w0rp
026c4f304e #681 Show extra kotlin errors at line 1 2017-06-24 12:35:01 +01:00
w0rp
1ea61162a0 Fix #687 - Check files on enter if they have changed 2017-06-24 12:24:31 +01:00
w0rp
fbf8ccb882 Fix #677 - Ignore errors from other files for cppcheck 2017-06-22 14:08:58 +01:00
w0rp
47401a6eda Fix the Perl tests in Docker 2017-06-22 13:51:18 +01:00
w0rp
ce2bfa88eb Fix #676 - Fix handling of Perl errors 2017-06-22 12:37:08 +01:00
w0rp
40f6ee4c39 Set the --no-color flag for Vint if we fail to parse the version number for some reason 2017-06-22 12:24:25 +01:00
w0rp
93539e10de Document the new phpcs options 2017-06-21 22:38:56 +01:00
w0rp
dab6f39eb0 Fix some escaping and make some tests set filenames consistently 2017-06-21 22:33:34 +01:00
Eric Stern
ab534c2995 Support project's local phpcs installation (#666)
* Use locally-installed PHPCS if available

* Add author

* Add configuration options

* Escape executable

* Add tests
2017-06-21 21:35:40 +01:00
w0rp
d2806fad60 Fix the standard and xo handlers so they call the eslint function 2017-06-21 11:15:05 +01:00
w0rp
50d952b07d Print messages about imports used when modules are turned off 2017-06-20 17:38:21 +01:00
w0rp
a105aa90a5 Fix #668 - Support eslint for TypeScript 2017-06-20 10:50:38 +01:00
w0rp
b96f5845ed Fix #667 - Do not add extra blank lines for add_blank_lines_for_python_control_statements 2017-06-20 09:39:58 +01:00
oaue
b44bd4e24f handle column number in javac linter (#660)
* handle column number in javac linter

* Updated tests with column number for javac errors.

* Updated tests with column number for javac errors.
2017-06-19 11:45:09 +01:00
w0rp
66b9d025bb #662 Fix kotlinc configuration name escaping 2017-06-19 11:39:42 +01:00
Pavel Kuropatkin
11e17669d3 TSLint: distinguish warnings from errors (#663)
* TSLint: distinguish warnings from errors

* Test for TSlint warning/error distinguishing code added.
2017-06-18 18:46:34 +01:00
w0rp
af1ab0b5a9 Add a non Code of Conduct 2017-06-18 18:24:44 +01:00
w0rp
8ab103504f Fix #658 - Clear highlights for all problem types 2017-06-18 18:20:05 +01:00
w0rp
7e79018b8c Fix #661 - Fix line highlights for style errors and warnings 2017-06-18 11:34:06 +01:00
w0rp
629ff513ec #659 - Add options for Python fixers, and cut down on duplicated documentation 2017-06-18 11:03:31 +01:00
David Alexander
fb682be199 Fix for Crystal support (#651)
* Strip color from Crystal compiler output

* Don't lint files if the file doesn't exist

* Lint files if they are readable
2017-06-15 09:30:34 +01:00
w0rp
f814be45b1 Fix #536 - Implement linter problem type re-mapping 2017-06-14 17:59:13 +01:00
w0rp
c2258e3684 Fix an ansible-lint test 2017-06-14 17:14:04 +01:00
w0rp
e455d8219e Fix #649 - Support tsserver linting for NeoVim 2017-06-14 17:08:39 +01:00
w0rp
25e4d1a353 #649 Output the tsserver command in ALEInfo 2017-06-14 16:53:21 +01:00
w0rp
07af1799b1 #430 Use the style sub_type for flake8 problems 2017-06-14 16:40:03 +01:00
w0rp
f6b0a28cba Split up the flake8 and ansible-lint handlers 2017-06-14 16:20:30 +01:00
w0rp
3442e58c8b Simplify the code for escaping strings for Windows 2017-06-14 11:05:49 +01:00
w0rp
f472e04b09 #538 - Set some end column indexes for flake8 2017-06-14 10:51:31 +01:00
w0rp
6f858590c2 Fix FusionScript documentation text alignment 2017-06-14 09:37:24 +01:00
Ryan
e8cc40b139 Add fusion-lint, documentation, and tests (#648)
* Add `fusion-lint` for first FusionScript linter

* Add documentation over `fusion-lint`

* Add tests for `fusion-lint` command callback
2017-06-14 09:35:11 +01:00
w0rp
ba83c476cd Document the tsserver linter 2017-06-13 17:59:09 +01:00
w0rp
ebbfb64221 Merge branch 'tsserver' 2017-06-13 17:54:07 +01:00
w0rp
aef58f598c Handle LSP responses for different files more consistently 2017-06-13 17:53:47 +01:00
w0rp
86c17e1834 Include package.json in eslint configuration files 2017-06-13 09:35:52 +01:00
Steven Humphrey
99263bdda4 Perlcritic column number and rule names (#640)
* Add column number to perlcritic linting output

This returns the column number of the perlcritic error so that ale can
show the column in addition to the line where perlcritic found an error.

* Add perlcritic configuration for rule names

This adds a configuration setting so that the name of the perlcritic
rule is shown [Rule::Name] after the error message.

This is useful to lookup the rule failure.

* Add a vader test for perlcritic#GetCommand
2017-06-11 21:13:47 +01:00
w0rp
5146332206 Add tsserver support 2017-06-09 09:47:19 +01:00
Mark Korondi
64ad51048d Support for GNU Awk linting (#638)
* GNU Awk linter support

* Documentation for awk linter
2017-06-08 18:26:21 +01:00
w0rp
62862c3347 Experimental code for showing results as soon as each linter completes 2017-06-08 17:30:21 +01:00
w0rp
8ce6d47ef6 Merge pull request #639 from marciomazza/patch-1
Fix typo in docs
2017-06-08 17:23:20 +01:00
Marcio Mazza
14d86f8763 Fix typo 2017-06-08 17:43:28 +02:00
w0rp
04190cbcfe #517 Support linter settings needed for LSP, undocumented for now 2017-06-08 13:52:29 +01:00
w0rp
d8d96fb0eb Fix #634 - Document eslint --fix behaviour with nested configuration files 2017-06-08 09:37:51 +01:00
w0rp
e93dba351c Fix #635 - Cancel previous jobs for fixing files when fixing files again 2017-06-08 09:24:15 +01:00
w0rp
817b6bbd2d Put ve-py3 before ve, so Python 3 executables will be preferred 2017-06-07 21:45:59 +01:00
José Luis Lafuente
1eec446620 Search python fixers in virtual environment (#632)
* Search python fixers in virtual environment

* Add tests for python fixers
2017-06-07 20:42:30 +01:00
w0rp
f61c6d4c0e Add support for generating essential tsserver messages 2017-06-07 17:05:34 +01:00
w0rp
71257979aa Merge pull request #633 from jlesquembre/fix_function_name
Fix function name
2017-06-07 16:46:59 +01:00
José Luis Lafuente
2ac670f293 Fix function name 2017-06-07 17:31:05 +02:00
w0rp
25e1aa43b8 Fix #631 - Fix some bad function names 2017-06-07 16:02:05 +01:00
w0rp
9ee7a6d57c Fix #630 - Initialize Prettier options 2017-06-07 15:33:41 +01:00
w0rp
7517fd8226 Move all functions for fixing things to autoload/ale/fixers, and only accept the lines of input where needed. 2017-06-07 14:02:29 +01:00
w0rp
edddb1910b Fix the stylelint tests to match the changes to the handler 2017-06-07 09:29:53 +01:00
w0rp
8f9828e5bf Merge pull request #628 from mziab/fix-stylelint-handler
Fixed stylelint not catching all errors.
2017-06-07 09:28:00 +01:00
w0rp
11e38efa83 Fix a bug which breaks the tests 2017-06-07 09:26:54 +01:00
w0rp
f30652a98f Allow ALEFix functions to be defined with only the buffer argument 2017-06-06 23:13:53 +01:00
w0rp
e4d886d4a7 Add a function for computing the number of arguments for a function 2017-06-06 22:27:20 +01:00
w0rp
eeea72e167 Fix #625 Ignore Perl errors from other files 2017-06-06 20:40:07 +01:00
Michał Ziąbkowski
e4708c356b Fixed stylelint not catching all errors.
The original regex failed to account for short lines being padded
to the length of the longest error.
2017-06-06 21:08:30 +02:00
w0rp
7db805b0cd #482 - Fix Flow handling with relative paths 2017-06-06 20:08:19 +01:00
w0rp
ef86a8a389 Make the test for the history more reliable 2017-06-06 19:54:10 +01:00
w0rp
9dadde190e Fix #461 - Allow multiple loclist windows to be opened, and replace some split windows 2017-06-06 17:31:13 +01:00
w0rp
e88eb6c415 Use BufWinEnter for g:ale_lint_on_enter instead 2017-06-06 17:01:17 +01:00
w0rp
3c5156d4a4 Simplify job cleanup code 2017-06-06 16:44:01 +01:00
w0rp
a0e0408ecc Complain about incorrect uses of expand('%...') 2017-06-06 10:22:52 +01:00
w0rp
d41f15bcbc #620 Check the Nim files on disk instead 2017-06-06 09:55:19 +01:00
w0rp
fcc17dffbe Fix a test 2017-06-06 09:54:17 +01:00
w0rp
02ac28dbe6 Fix #624 - Ask for the Vint version in the background 2017-06-06 09:47:22 +01:00
w0rp
f6109d2e4e Merge pull request #622 from blueyed/fix-typo
doc: fix typo: s/the the/to the/
2017-06-05 16:35:27 +01:00
Daniel Hahler
3be60bf034 doc: fix typo: s/the the/to the/ 2017-06-05 17:06:48 +02:00
w0rp
1a62e95733 Do not check files on insert leave 2017-06-05 13:55:18 +01:00
w0rp
dcbab18a35 Stop errors being generated when jobs are removed from the Dictionary before callbacks fire 2017-06-05 13:30:40 +01:00
Drew Neil
33b0852c84 Add :ALEFirst and :ALELast commands (#616)
* Add :ALEFirst and :ALELast commands

* Add documentation for ALEFirst and ALELast commands

* Add tests for ale#loclist_jumping#JumpToIndex()

* Fix the loclist jumping tests
2017-06-03 12:45:52 +01:00
w0rp
fcb5718712 Document that prettier and prettier-eslint are supported 2017-06-03 12:31:27 +01:00
Francis Agyapong
2c89a4c98a Add ktlint support (without formatting) for kotlin filetype (#610)
* Add ktlint support (without formatting) for kotlin filetype

* Fix code style and refactor to use ALE utility functions (GetMatches)

* Remove options for configuration file

* Refactor: Rename exec variable and use ale#Set for variable configuration
2017-06-02 19:41:46 +01:00
w0rp
7c68889bbc #574 Do not restore items with no columns for highlights from hidden buffers 2017-06-02 19:02:54 +01:00
w0rp
955452816a Merge pull request #614 from epilande/master
✏️ Fix link to stylelint styled-components
2017-06-02 18:54:31 +01:00
w0rp
8c5a7fb2ca Merge pull request #615 from w0rp/adriaanzon-patch-1
Suggest :ALELint instead of ale#Lint()
2017-06-02 18:40:37 +01:00
Adriaan Zonnenberg
2b9e320370 Suggest :ALELint instead of ale#Lint() 2017-06-02 16:08:54 +02:00
Emmanuel Pilande
e4649b50d6 ✏️ Fix link to stylelint styled-components 2017-06-02 04:54:38 -07:00
w0rp
fbd76fb63d Document the <Plug>(ale_fix) mapping 2017-06-01 21:13:05 +01:00
w0rp
0d3d5657ff #607 - Update the documentation for ale-fix to suggest an assignment which will work in vimrc 2017-06-01 21:08:43 +01:00
w0rp
d5ae9b50ea Fix #499 Set an explicit height for the quickfix list, and make the height configurable 2017-06-01 10:39:21 +01:00
cs86661
81f27a99c8 Set qflist/loclist window title properly ... (#588)
* Update list.vim

Set qflist/loclist window title properly ...

* Update list.vim

1. Remove redundant code.
2. Get absolute path from 'a:buffer'.

* Set the list window titles appropriately for each version of Vim, and add tests
2017-05-31 22:55:23 +01:00
w0rp
735a6a2a88 Fix #537 - Add support for balloons 2017-05-31 22:04:33 +01:00
w0rp
5e4c302b5b Fix #557 - Detect C project roots and include root directories with headers, or include directories 2017-05-31 20:01:47 +01:00
Jon Gjengset
88948e0ee3 Include span label in rust lints (#601)
* Include span label in rust lints

This turns relatively unhelpful error messages like

    mismatched types

into more expressive messages along the lines of

    mismatched types: expected bool, found integral variable

Fixes #597.

* Exclude rust lint span label if empty

* Use single-quoted strings in vimscript

* Add test for detailed rust errors

* Prune Cargo JSON

* Use matching error file name

* Byte offsets not char offsets
2017-05-31 18:16:49 +01:00
w0rp
42efd51723 Fix #596 - Report exceptions thrown by flake8 2017-05-31 15:20:12 +01:00
Jon Gjengset
5eb80f03a2 Include location list end column for rust lint (#602)
* Include location list end column for rust lint

Fixes #599.

* Include rust lint end_lnum for good measure

* Reverse engineer end_* for rust lint tests
2017-05-31 13:17:04 +01:00
w0rp
ab50b3a88a Fix #604 - Support highlights spanning many lines 2017-05-31 13:14:39 +01:00
w0rp
676a4049b3 #604 Add a function for creating positions needed for supporting highlights across many lines 2017-05-31 11:20:57 +01:00
w0rp
fd49f7df90 #604 Change match_id to match_id_list, for future highlights spanning more than 8 lines 2017-05-31 10:27:35 +01:00
w0rp
e72dc1acd5 Merge pull request #603 from craftgear/feature-make-prettier-option-enable
make prettier options enabled
2017-05-31 10:02:08 +01:00
w0rp
a90cf62995 Run the temporary file management test synchronously, so it will fail less 2017-05-31 10:01:46 +01:00
Shunsuke Watanabe
5d32366616 make prettier options enabled 2017-05-31 11:48:20 +09:00
w0rp
6fe8105a0e Merge pull request #600 from oalders/oalders-perl-warnings
Remove -X flag from perl defaults.
2017-05-30 22:30:37 +01:00
w0rp
b9f4b0373a #591 Store buffer variables when fixing filess, and read them back in ale#Var 2017-05-30 22:15:24 +01:00
w0rp
6ec965c8e4 #591 Support fixing files on save 2017-05-30 21:32:51 +01:00
Olaf Alders
fa02b1d259 Remove -X flag from perl defaults.
"-X Disables all warnings regardless of use warnings or $^W".  See
"perldoc perlrun" or http://perldoc.perl.org/perlrun.html

With the current defaults, warnings are squashed.  For example:

$ perl -X -Mwarnings -c -e'BEGIN { 42 + undef }'
-e syntax OK

$ perl -Mwarnings -c -e'BEGIN { 42 + undef }'
Use of uninitialized value in addition (+) at -e line 1.
-e syntax OK

So, it's not clear from the current defaults whether Ale wants to remove
warnings or enable them.  As it stands, it's trying to do both and the
disabling appears to win.

This commit enables warnings by default.
2017-05-30 16:07:21 -04:00
w0rp
bc317a7be5 Merge pull request #595 from daenney/go-default-linters
go: Remove `staticcheck` and `go build` defaults
2017-05-30 14:56:59 +01:00
Daniele Sluijters
bfad5c9dc4 go: Remove staticcheck and go build defaults
Fixes #594
2017-05-30 14:48:23 +02:00
w0rp
7a89d0c97e Refactor ALEFix code for work on events and tests to come 2017-05-30 11:06:02 +01:00
w0rp
50fc4b5521 Merge pull request #589 from bardzusny/ember-template-lint-handler-parsing-error
Ember-template-lint handler: properly handle template parsing errors.
2017-05-30 09:45:20 +01:00
Adrian Zalewski
7ed343965c Ember-template-lint handler: properly handle template parsing errors. 2017-05-28 21:19:47 +02:00
w0rp
945ed7d4e7 Add untested code for searching for C and C++ headers in basic projects 2017-05-28 11:05:14 +01:00
w0rp
505b591b22 Merge pull request #585 from gavocanov/master
kotlin linter support for maven/pom.xml
2017-05-28 10:37:36 +01:00
w0rp
dc775f236c Revert "Fix #501 - Do not run javac when it is just a stub asking you to install Java on Mac OSX"
This reverts commit 528355e2c6.
2017-05-28 00:52:04 +01:00
w0rp
c17346d402 Fix ALEInfo and some test issues 2017-05-27 23:51:27 +01:00
w0rp
aca5a00fb7 Fix #500 - Support defining aliases for linter names 2017-05-27 21:27:42 +01:00
w0rp
8e997ac231 Fix #584 - Fix Neovim line handling issues 2017-05-27 19:23:13 +01:00
w0rp
5825a65627 Merge branch 'add-fixer/prettier' 2017-05-27 18:31:58 +01:00
w0rp
62dae1cc6b Support both prettier and prettier-eslint 2017-05-27 18:31:52 +01:00
tunnckoCore
8e8113ff6f feat(fixer): add Prettier fixer (using Prettier-ESLint CLI) + docs 2017-05-27 18:03:14 +01:00
w0rp
c4f22186bd Refactor running of local Node programs with a helper function 2017-05-27 17:11:03 +01:00
Paolo Gavocanov
f71c60ede3 kotlin linter support for maven/pom.xml 2017-05-27 16:23:16 +02:00
Agata Naomichi
b934dc52b6 Fix file name checking in rust handler (#581)
* Fix file name checking in rust handler

* Add a test for rust hanler

* Remove unused variable
2017-05-27 00:35:57 +01:00
w0rp
00d3141962 Fix #577 Add an option preventing linting of large files 2017-05-26 21:21:15 +01:00
w0rp
28a62aab28 Fix #316 - Add tests to check the code used for autocmd events. The functions are already tested elsewhere 2017-05-26 17:36:21 +01:00
w0rp
9460e58c3b Fix #371 Allow ALE to be disabled in different buffers 2017-05-26 16:20:17 +01:00
w0rp
c77cf0e518 #371 Allow buffer variables to be set based on patterns 2017-05-26 15:59:43 +01:00
w0rp
7fe1119cf1 #576 Run the eslint.js file created by React with node on Windows 2017-05-26 10:02:48 +01:00
w0rp
c89587785b Fix #549 - escape strings more appropriately for use with cmd /c 2017-05-26 00:06:16 +01:00
w0rp
fb07971290 Remove a test file which is no longer used 2017-05-25 23:05:03 +01:00
w0rp
3840cebbc4 Automatically use eslint_d for eslint, when available 2017-05-25 22:34:59 +01:00
w0rp
c31cd12bdd Simplify the sandbox check, to save on execution time 2017-05-25 17:23:21 +01:00
w0rp
6f76a840f0 Merge pull request #579 from alphastorm/master
Fix minor typo in the g:ale_lint_on_insert_leave docs
2017-05-25 13:40:56 +01:00
w0rp
6b1f0c5d1f Merge pull request #578 from nOkuda/master
Translate pylint output column to 1-based index
2017-05-25 13:36:44 +01:00
w0rp
aabddea6dd Fix the Count example in the README 2017-05-25 13:33:29 +01:00
w0rp
5ee2ada8e9 Mention the Count argument in the documentation 2017-05-25 13:32:46 +01:00
Sunil Srivatsa
da8fd647bf Fix minor typo in the g:ale_lint_on_insert_leave docs 2017-05-25 01:47:59 -07:00
Nozomu Okuda
43098171ac Translate pylint output column to 1-based index
This should fix #575; also added vader tests to ensure that translation
is working properly.
2017-05-24 21:40:06 -06:00
w0rp
ed8f79987d #323 Document how to output ALE statuses 2017-05-24 10:38:20 +01:00
w0rp
92ade713f2 #323 Document ale#statusline#Count() instead, and encourage its use 2017-05-24 10:23:13 +01:00
w0rp
58880f33be #572 Handle cleared SignColumn highlights too 2017-05-23 17:25:13 +01:00
w0rp
1e72a7a130 Add a fixer for Python for automatically adding blank lines before control statements 2017-05-22 12:59:40 +01:00
w0rp
4526018344 Remove the test for highlight linking, because it just cannot be tested 2017-05-22 10:01:41 +01:00
w0rp
2e442a2cab Fix initialization of the use_global variable for eslint 2017-05-22 09:38:33 +01:00
w0rp
71bf2bfb94 Fix #572 - Link to whatever SignColumn links to for ALESignColumnWithoutErrors 2017-05-22 09:34:25 +01:00
w0rp
c8ce15d9f1 Fix a minor typo 2017-05-21 22:46:22 +01:00
w0rp
b67c103d06 #309 Add an option for changing the sign column color when problems are detected 2017-05-21 22:42:27 +01:00
w0rp
3a289dab6b #318 Do not capitalize the first character for cursor messages 2017-05-21 19:51:34 +01:00
w0rp
23ee0d0992 #149 - Set different highlights for info, style error, and style warning problems 2017-05-21 19:22:48 +01:00
w0rp
57ad32f986 Fix counting of warnings and style warnings 2017-05-21 18:58:26 +01:00
w0rp
d511b02ebe Make the job handling code handle the scripts being reloaded better 2017-05-21 16:16:06 +01:00
w0rp
ab44d05508 #149 - Support info and style problem types for status lines 2017-05-21 15:37:45 +01:00
w0rp
3532257a1a Detect more eslint configuration files for fixing errors 2017-05-20 23:41:54 +01:00
w0rp
f92bbab8cf #149 - Support Info, style error, and style warning types for problems for signs 2017-05-20 23:32:41 +01:00
w0rp
bf8bf06681 Merge branch 'error-fixing' 2017-05-20 19:02:56 +01:00
w0rp
74d879952c Document ALEFix 2017-05-20 19:02:36 +01:00
w0rp
3530180a73 Suggest functions for fixing issues for ALEFix 2017-05-20 19:02:36 +01:00
w0rp
59d9f5d458 Allow b:ale_fixers to be used 2017-05-20 19:02:36 +01:00
w0rp
ad52b9630d Fix Funcref fixers for NeoVim 2017-05-20 19:02:36 +01:00
w0rp
ed097cfcbd Allow funcref values and lambdas for ALEFix 2017-05-20 19:02:36 +01:00
w0rp
e80389f8d4 Add some more tools for fixing problems with Python files 2017-05-20 19:02:36 +01:00
w0rp
74691269ce Run a lint cycle after fixing problems 2017-05-20 19:02:36 +01:00
w0rp
18467a55b5 Don't modify files when fixing doesn't change anything. 2017-05-20 19:02:36 +01:00
w0rp
e6b132c915 Fix an off-by-one bug in ALEFix 2017-05-20 19:02:36 +01:00
w0rp
4214832ae2 Remove the code for checking if functions exist. It breaks autoload functions 2017-05-20 19:02:36 +01:00
w0rp
1f4d1800e0 Allow function aliases to be registered for fixing problems, and add some more argument checking for fixing problems 2017-05-20 19:02:36 +01:00
w0rp
ea1627f5ce Start experimenting with generic functions for fixing problems 2017-05-20 19:02:36 +01:00
w0rp
0b743389e5 Send modified lines to jobs, not the file contents 2017-05-20 19:02:36 +01:00
w0rp
05bab00c3c Allow strings to be used for selecting a single fix function for g:ale_fixers too 2017-05-20 19:02:36 +01:00
w0rp
8ebd15a54d Add commands to run ALEFix, and some tests to cover functionality so far. Add a simple autopep8 function. 2017-05-20 19:02:36 +01:00
w0rp
7d8390d43e Add experimental code for fixing errors 2017-05-20 19:02:36 +01:00
w0rp
0d797c203f Add an option to the script for running tests for only showing the tests which failed 2017-05-20 19:02:08 +01:00
w0rp
04e0dda17a Merge pull request #568 from Qusic/master
Add clang for objc and objcpp
2017-05-20 13:39:09 +01:00
w0rp
65fbf1cdff #563 Use a configurable list of directories for detecting virtualenv paths instead. 2017-05-20 13:34:53 +01:00
w0rp
d012fd1f09 Revert "Fix #556 Remove duplicate error messages from clang++"
This reverts commit 164c4efb32.
2017-05-20 12:57:41 +01:00
w0rp
0646b2861f Revert "#562 Join split JSON lines together for new Rust output"
This reverts commit 5790df1272.
2017-05-20 12:57:21 +01:00
w0rp
0f0d1709c5 #567 Try and fix NeoVim split line handling 2017-05-20 12:50:19 +01:00
Sander van Harmelen
455793dfd9 Improve performance when using gometalinter (#566)
* Improve performance when using gometalinter

Before this change when I opened a big project that had 6000+ warnings/errors it took ages to get the actual warnings/errors and it caused my CPU to be busy for quite some time. The call to gometalinter alone took about 24 seconds, but after that vim was struggling as well.

After this change the gometalinter call just takes 2 seconds and nothing noticable happens with the CPU and/or vim.

* Removed obsolete test

This logic is no longer done by the `ale` plugin, but by `gometalinter` itself.
2017-05-20 11:43:28 +01:00
Qusic
af6470c8d0 add clang for objc and objcpp 2017-05-20 00:58:09 +08:00
w0rp
bb1f045d59 Merge pull request #564 from adriaanzon/php-end-columns
Add end columns on php linter
2017-05-18 09:38:15 +01:00
q12321q
cdf0fb39e5 Add xmllint linter (#559)
* Add xmllint linter for xml
2017-05-18 09:31:12 +01:00
w0rp
3ca70cb841 Add a check to make tests fail less 2017-05-18 09:27:18 +01:00
w0rp
c41afa2b0d Clean up the rubocop file a little 2017-05-18 09:22:34 +01:00
w0rp
2fd4db91ce Merge pull request #561 from meunierd/override-rubocop-executable
Allow overriding rubocop executable.
2017-05-18 09:19:23 +01:00
Adriaan Zonnenberg
05970e1b28 Add end columns on php linter #538 2017-05-17 21:28:29 +02:00
Adriaan Zonnenberg
6299da7bd3 Break up php tests so the output is easier to read 2017-05-17 21:19:34 +02:00
Devon Meunier
3f926de76b Escape executable 2017-05-17 08:46:47 -04:00
w0rp
f7fc54262d Refactor special command parsing into its own file 2017-05-17 11:17:49 +01:00
w0rp
164c4efb32 Fix #556 Remove duplicate error messages from clang++ 2017-05-17 10:10:25 +01:00
w0rp
5790df1272 #562 Join split JSON lines together for new Rust output 2017-05-17 09:43:28 +01:00
w0rp
372a4dfd7e Merge pull request #546 from dawikur/master
Add cpplint linter
2017-05-17 09:12:36 +01:00
w0rp
3443994a52 #538 Set some end columns for some eslint problems 2017-05-16 22:57:15 +01:00
w0rp
e2860f8a26 #538 Fix an off-by-one bug with end columns 2017-05-16 19:46:19 +01:00
w0rp
1b53fa841b Fix some problems with LSP functions 2017-05-16 19:38:05 +01:00
Devon Meunier
9ca51ed035 Allow overriding rubocop executable. 2017-05-16 14:07:52 -04:00
w0rp
a65358cfce #538 Suport highlight sizes by looking at end column numbers 2017-05-16 18:12:49 +01:00
Dawid Kurek
9185a0d2e5 Add cpplint linter 2017-05-16 19:09:59 +02:00
w0rp
8712aee5dc Nag people more to include more logging for error reports 2017-05-15 21:27:40 +01:00
w0rp
4c5e97dd1c Fix #555 - Handle csslint errors without groups 2017-05-15 21:21:09 +01:00
w0rp
78a7df52c0 Clean up linters after running the checkstyle handlers 2017-05-15 20:59:50 +01:00
w0rp
42155049a5 Merge pull request #551 from meunierd/add-checkstyle-linter
Add checkstyle linter
2017-05-15 20:58:06 +01:00
w0rp
11a50b2580 Fix #553 - Filter out errors from other files for gometalinter 2017-05-15 20:43:55 +01:00
Devon Meunier
9baae52d1a Add checkstyle linter 2017-05-15 15:41:04 -04:00
w0rp
fa3a4b3ba2 Complain when shellescape is used instead of ale#Escape 2017-05-15 20:21:28 +01:00
w0rp
113f53a5d2 Merge pull request #496 from wizzup/haskell-ghc-mod
Haskell:  new ghc-mod linter
2017-05-15 19:57:50 +01:00
wisut hantanong
3f33dc7d98 Haskell: add ghc-mod linter 2017-05-15 19:46:02 +01:00
w0rp
d4466d4be7 Add some functions for sending LSP commands to a process, and automatically starting that process 2017-05-13 00:12:12 +01:00
w0rp
204e3ca36b Automatically remove jobs from the internal map after they are done 2017-05-12 23:56:58 +01:00
w0rp
5a947933d7 Refactor jobs into a Vim version agnostic API which can be used for other purposes 2017-05-12 21:16:15 +01:00
w0rp
2bafdb7e5a Run all tests in NeoVim, improve the test script, and make all tests pass for NeoVim 2017-05-12 20:38:52 +01:00
w0rp
07b2542c0d #549 Temporarily revert shell escaping changes, just for Windows 2017-05-12 09:20:16 +01:00
w0rp
fa54f7af97 Remove a dependency on eslint, ready for the new Docker image 2017-05-12 09:19:36 +01:00
w0rp
2f96f26038 Update the Dockerfile for experimenting with NeoVim testing 2017-05-11 08:32:45 +01:00
w0rp
ac707be619 Remove the tests which run C compilers. They are too expensive and difficult to maintain 2017-05-11 00:07:30 +01:00
Adriaan Zonnenberg
4e5a848d95 Merge pull request #533 from pbogut/add_php7_compatibility
Add PHP 7 compatibility
2017-05-09 18:34:38 +02:00
w0rp
6ea00af689 #540 Fix shell escaping pretty much everywhere 2017-05-08 22:59:25 +01:00
w0rp
28c6ec9cad #517 - Implement LSP chunked message parsing, sending messages to sockets, and callbacks 2017-05-08 22:18:28 +01:00
Pawel Bogut
d7bdaeeab0 Read errors from stdout only (and make sure they are displayed) 2017-05-08 21:17:54 +01:00
w0rp
cd79ced839 #517 Implement some LSP message handling 2017-05-07 23:54:14 +01:00
245 changed files with 9586 additions and 1962 deletions

View File

@@ -1,11 +0,0 @@
module.exports = {
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
},
rules: {
semi: 'error',
'space-infix-ops': 'warn',
radix: 'error',
}
}

2
.gitattributes vendored
View File

@@ -1,4 +1,5 @@
.* export-ignore
/CODE_OF_CONDUCT.md export-ignore
/CONTRIBUTING.md export-ignore
/Dockerfile export-ignore
/ISSUE_TEMPLATE.md export-ignore
@@ -7,4 +8,5 @@
/README.md export-ignore
/custom-checks export-ignore
/img export-ignore
/run-tests export-ignore
/test export-ignore

View File

@@ -7,4 +7,4 @@ branches:
- master
language: python
script: |
make test
./run-tests

3
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,3 @@
Codes of conduct are totally unnecessary and dumb.
Just don't be a jerk and have fun.

View File

@@ -1,25 +1,18 @@
FROM tweekmonster/vim-testbed:latest
RUN install_vim -tag v8.0.0000 -build \
-tag v8.0.0027 -build
RUN install_vim -tag v8.0.0027 -build \
-tag neovim:v0.1.7 -build
# the clang package includes clang-tidy
ENV PACKAGES="\
bash \
git \
python \
py-pip \
nodejs \
gcc \
g++ \
clang \
"
RUN apk --update add $PACKAGES && \
rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
RUN pip install vim-vint==0.3.9
RUN npm install -g eslint@3.7.1
RUN git clone https://github.com/junegunn/vader.vim vader && \
cd vader && git checkout c6243dd81c98350df4dec608fa972df98fa2a3af

View File

@@ -1,12 +1,12 @@
<!--
READ THIS FIRST: If you are experiencing any bug whatsoever dealing with
the output of linters, please use `let g:ale_history_log_output = 1` before
pasting output. It will capture the output of commands that are run.
For bugs, paste output from your clipboard after running :ALEInfoToClipboard
here. If that doesn't work for some reason, try running :ALEInfo and copying
the output from that here instead. If everything is broken, run around in
circles and scream.
If you are experiencing a bug where ALE is not correctly parsing the output of
commands, set g:ale_history_log_output to 1, and run ALE again, and then
:ALEInfo should include the full output of each command which ran.
Whatever the case, describe the your issue here.
-->

View File

@@ -1,53 +0,0 @@
SHELL := /usr/bin/env bash
IMAGE ?= w0rp/ale:30a9967dbdb1
CURRENT_IMAGE_ID = 30a9967dbdb1
DOCKER_FLAGS = --rm -v $(PWD):/testplugin -v $(PWD)/test:/home "$(IMAGE)"
tests = test/*.vader test/*/*.vader test/*/*/*.vader test/*/*/*/*.vader
test-setup:
docker images -q w0rp/ale | grep ^$(CURRENT_IMAGE_ID) > /dev/null || \
docker pull w0rp/ale
vader: test-setup
@:; \
vims=$$(docker run --rm $(IMAGE) ls /vim-build/bin | grep -E '^n?vim'); \
if [ -z "$$vims" ]; then echo "No Vims found!"; exit 1; fi; \
for vim in $$vims; do \
docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! $(tests)'; \
done
test: test-setup
@:; \
vims=$$(docker run --rm $(IMAGE) ls /vim-build/bin | grep -E '^n?vim'); \
if [ -z "$$vims" ]; then echo "No Vims found!"; exit 1; fi; \
EXIT=0; \
for vim in $$vims; do \
echo; \
echo '========================================'; \
echo "Running tests for $$vim"; \
echo '========================================'; \
echo; \
docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! $(tests)' || EXIT=$$?; \
done; \
echo; \
echo '========================================'; \
echo 'Running Vint to lint our code'; \
echo '========================================'; \
echo 'Vint warnings/errors follow:'; \
echo; \
set -o pipefail; \
docker run -a stdout $(DOCKER_FLAGS) vint -s /testplugin | sed s:^/testplugin/:: || EXIT=$$?; \
set +o pipefail; \
echo; \
echo '========================================'; \
echo 'Running custom checks'; \
echo '========================================'; \
echo 'Custom warnings/errors follow:'; \
echo; \
set -o pipefail; \
docker run -v $(PWD):/testplugin "$(IMAGE)" /testplugin/custom-checks /testplugin | sed s:^/testplugin/:: || EXIT=$$?; \
set +o pipefail; \
echo; \
exit $$EXIT;
.DEFAULT_GOAL := test

View File

@@ -15,6 +15,9 @@ back to a filesystem.
In other words, this plugin allows you to lint while you type.
ALE also supports fixing problems with files by running commands in the
background with a command `ALEFix`.
## Table of Contents
1. [Supported Languages and Tools](#supported-languages)
@@ -55,10 +58,11 @@ name. That seems to be the fairest way to arrange this table.
| 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/)|
| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangtidy](http://clang.llvm.org/extra/clang-tidy/), [cppcheck](http://cppcheck.sourceforge.net), [gcc](https://gcc.gnu.org/)|
| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html), [clangtidy](http://clang.llvm.org/extra/clang-tidy/), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [gcc](https://gcc.gnu.org/)|
| 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) |
@@ -73,15 +77,16 @@ name. That seems to be the fairest way to arrange this table.
| Erb | [erb](https://github.com/jeremyevans/erubi) |
| Erlang | [erlc](http://erlang.org/doc/man/erlc.html) |
| 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) |
| 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/), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools) |
| Haskell | [ghc](https://www.haskell.org/ghc/), [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/) |
| Java | [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/), [xo](https://github.com/sindresorhus/xo)
| 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`), [xo](https://github.com/sindresorhus/xo)
| JSON | [jsonlint](http://zaa.ch/jsonlint/) |
| Kotlin | [kotlinc](https://kotlinlang.org) see `:help ale-integration-kotlin` for configuration instructions
| 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) |
@@ -89,13 +94,15 @@ name. That seems to be the fairest way to arrange this table.
| 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/), [php -l](https://secure.php.net/), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer), [phpmd](https://phpmd.org) |
| 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 | [flake8](http://flake8.pycqa.org/en/latest/), [mypy](http://mypy-lang.org/), [pylint](https://www.pylint.org/) |
| 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/), [pylint](https://www.pylint.org/), [yapf](https://github.com/google/yapf) |
| 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`) |
@@ -110,11 +117,12 @@ name. That seems to be the fairest way to arrange this table.
| Swift | [swiftlint](https://swift.org/) |
| Texinfo | [proselint](http://proselint.com/)|
| Text^ | [proselint](http://proselint.com/), [vale](https://github.com/ValeLint/vale) |
| TypeScript | [tslint](https://github.com/palantir/tslint), typecheck |
| 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 | [yamllint](https://yamllint.readthedocs.io/) |
* *^ No linters for text or Vim help filetypes are enabled by default.*
@@ -135,6 +143,9 @@ documented in [the Vim help file](doc/ale.txt). For more information on the
options ALE offers, consult `:help ale-options` for global options and `:help
ale-linter-options` for options specified to particular linters.
ALE can fix files with the `ALEFix` command. Functions need to be configured
for different filetypes with the `g:ale_fixers` variable. See `:help ale-fix`.
<a name="installation"></a>
## 3. Installation
@@ -287,28 +298,41 @@ highlight clear ALEWarningSign
### 5.iv. How can I show errors or warnings in my statusline?
You can use `ALEGetStatusLine()` to integrate ALE into vim statusline.
To enable it, you should have in your `statusline` settings
[vim-airline](https://github.com/vim-airline/vim-airline) integrates with ALE
for displaying error information in the status bar. If you want to see the
status for ALE in a nice format, it is recommended to use vim-airline with ALE.
The airline extension can be enabled by adding the following to your vimrc:
```vim
%{ALEGetStatusLine()}
" Set this. Airline will handle the rest.
let g:airline#extensions#ale#enabled = 1
```
When errors are detected a string showing the number of errors will be shown.
You can customize the output format using the global list `g:ale_statusline_format` where:
If you don't want to use vim-airline, you can implement your own statusline
function without adding any other plugins. ALE provides a function for counting
the number of problems for this purpose, named `ale#statusline#Count`.
- The 1st element is for errors
- The 2nd element is for warnings
- The 3rd element is for when no errors are detected
e.g
Say you want to display all errors as one figure, and all non-errors as another
figure. You can do the following:
```vim
let g:ale_statusline_format = ['⨉ %d', '⚠ %d', '⬥ ok']
function! LinterStatus() abort
let l:counts = ale#statusline#Count(bufnr(''))
let l:all_errors = l:counts.error + l:counts.style_error
let l:all_non_errors = l:counts.total - l:all_errors
return l:counts.total == 0 ? 'OK' : printf(
\ '%dW %dE',
\ all_non_errors,
\ all_errors
\)
endfunction
set statusline=%{LinterStatus()}
```
![Statusline with issues](img/issues.png)
![Statusline with no issues](img/no_issues.png)
See `:help ale#statusline#Count()` for more information.
<a name="faq-echo-format"></a>
@@ -421,7 +445,7 @@ 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
[https://github.com/styled-components/stylelint-processor-styled-components](stylelint-processor-styled-components).
[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.
@@ -469,4 +493,4 @@ 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
`:call ale#Lint()`.
`:ALELint`.

View File

@@ -1,9 +0,0 @@
" Author: Bjorn Neergaard <bjorn@neersighted.com>
" Description: ansible-lint for ansible-yaml files
call ale#linter#Define('ansible', {
\ 'name': 'ansible',
\ 'executable': 'ansible',
\ 'command': 'ansible-lint -p %t',
\ 'callback': 'ale#handlers#python#HandlePEP8Format',
\})

View File

@@ -0,0 +1,48 @@
" Author: Bjorn Neergaard <bjorn@neersighted.com>
" 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 ==# '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] ==# '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',
\})

View File

@@ -5,7 +5,7 @@ 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 ' . shellescape(fnamemodify(bufname(a:buffer), ':p:h'))
\ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h'))
\ . ' ' . ale#Var(a:buffer, 'asm_gcc_options') . ' -'
endfunction

26
ale_linters/awk/gawk.vim Normal file
View File

@@ -0,0 +1,26 @@
" Author: kmarc <korondi.mark@gmail.com>
" 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'
\})

View File

@@ -10,11 +10,14 @@ if !exists('g:ale_c_clang_options')
endif
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 'clang -S -x c -fsyntax-only '
\ . '-iquote ' . shellescape(fnamemodify(bufname(a:buffer), ':p:h'))
\ . ' ' . ale#Var(a:buffer, 'c_clang_options') . ' -'
\ . '-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', {

View File

@@ -10,11 +10,14 @@ if !exists('g:ale_c_gcc_options')
endif
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 'gcc -S -x c -fsyntax-only '
\ . '-iquote ' . shellescape(fnamemodify(bufname(a:buffer), ':p:h'))
\ . ' ' . ale#Var(a:buffer, 'c_gcc_options') . ' -'
\ . '-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', {

View File

@@ -7,11 +7,14 @@ if !exists('g:ale_cpp_clang_options')
endif
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 'clang++ -S -x c++ -fsyntax-only '
\ . '-iquote ' . shellescape(fnamemodify(bufname(a:buffer), ':p:h'))
\ . ' ' . ale#Var(a:buffer, 'cpp_clang_options') . ' -'
\ . '-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', {

View File

@@ -0,0 +1,37 @@
" Author: gagbo <gagbobada@gmail.com>
" Description: clang-check linter for cpp files
" Set this option to manually set some options for clang-check.
let g:ale_cpp_clangcheck_options = get(g:, 'ale_cpp_clangcheck_options', '')
" Set this option to manually point to the build directory for clang-tidy.
" This will disable all the other clangtidy_options, since compilation
" flags are contained in the json
let g:ale_c_build_dir = get(g:, 'ale_c_build_dir', '')
function! ale_linters#cpp#clangcheck#GetCommand(buffer) abort
let l:user_options = ale#Var(a:buffer, 'cpp_clangcheck_options')
let l:extra_options = !empty(l:user_options)
\ ? l:user_options
\ : ''
" Try to find compilation database to link automatically
let l:user_build_dir = ale#Var(a:buffer, 'c_build_dir')
if empty(l:user_build_dir)
let l:user_build_dir = ale#c#FindCompileCommands(a:buffer)
endif
let l:build_options = !empty(l:user_build_dir)
\ ? ' -p ' . ale#Escape(l:user_build_dir)
\ : ''
return 'clang-check -analyze ' . '%s' . l:extra_options . l:build_options
endfunction
call ale#linter#Define('cpp', {
\ 'name': 'clangcheck',
\ 'output_stream': 'stderr',
\ 'executable': 'clang-check',
\ 'command_callback': 'ale_linters#cpp#clangcheck#GetCommand',
\ 'callback': 'ale#handlers#gcc#HandleGCCFormat',
\ 'lint_file': 1,
\})

View File

@@ -1,4 +1,5 @@
" Author: vdeurzen <tim@kompiler.org>, w0rp <devw0rp@gmail.com>
" Author: vdeurzen <tim@kompiler.org>, w0rp <devw0rp@gmail.com>,
" gagbo <gagbobada@gmail.com>
" Description: clang-tidy linter for cpp files
" Set this option to check the checks clang-tidy will apply.
@@ -8,15 +9,36 @@ let g:ale_cpp_clangtidy_checks = get(g:, 'ale_cpp_clangtidy_checks', ['*'])
" This will disable compile_commands.json detection.
let g:ale_cpp_clangtidy_options = get(g:, 'ale_cpp_clangtidy_options', '')
" Set this option to manually point to the build directory for clang-tidy.
" This will disable all the other clangtidy_options, since compilation
" flags are contained in the json
let g:ale_c_build_dir = get(g:, 'ale_c_build_dir', '')
function! ale_linters#cpp#clangtidy#GetCommand(buffer) abort
let l:check_list = ale#Var(a:buffer, 'cpp_clangtidy_checks')
let l:check_option = !empty(l:check_list)
\ ? '-checks=' . shellescape(join(l:check_list, ',')) . ' '
\ ? '-checks=' . ale#Escape(join(l:check_list, ',')) . ' '
\ : ''
let l:user_options = ale#Var(a:buffer, 'cpp_clangtidy_options')
let l:extra_options = !empty(l:user_options)
\ ? ' -- ' . l:user_options
\ : ''
let l:user_build_dir = ale#Var(a:buffer, 'c_build_dir')
" c_build_dir has the priority if defined
if empty(l:user_build_dir)
let l:user_build_dir = ale#c#FindCompileCommands(a:buffer)
endif
" We check again if user_builddir stayed empty after the
" c_build_dir_names check
" If we found the compilation database we override the value of
" l:extra_options
if empty(l:user_build_dir)
let l:extra_options = !empty(l:user_options)
\ ? ' -- ' . l:user_options
\ : ''
else
let l:extra_options = ' -p ' . ale#Escape(l:user_build_dir)
endif
return 'clang-tidy ' . l:check_option . '%s' . l:extra_options
endfunction

View File

@@ -0,0 +1,15 @@
" Author: Dawid Kurek https://github.com/dawikur
" Description: cpplint for cpp files
if !exists('g:ale_cpp_cpplint_options')
let g:ale_cpp_cpplint_options = ''
endif
call ale#linter#Define('cpp', {
\ 'name': 'cpplint',
\ 'output_stream': 'stderr',
\ 'executable': 'cpplint',
\ 'command': 'cpplint %s',
\ 'callback': 'ale#handlers#cpplint#HandleCppLintFormat',
\ 'lint_file': 1,
\})

View File

@@ -17,11 +17,14 @@ if !exists('g:ale_cpp_gcc_options')
endif
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 'gcc -S -x c++ -fsyntax-only '
\ . '-iquote ' . shellescape(fnamemodify(bufname(a:buffer), ':p:h'))
\ . ' ' . ale#Var(a:buffer, 'cpp_gcc_options') . ' -'
\ . '-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', {

View File

@@ -1,4 +1,4 @@
" Author: Jordan Andree <https://github.com/jordanandree>
" Author: Jordan Andree <https://github.com/jordanandree>, David Alexander <opensource@thelonelyghost.com>
" Description: This file adds support for checking Crystal with crystal build
function! ale_linters#crystal#crystal#Handle(buffer, lines) abort
@@ -24,8 +24,8 @@ function! ale_linters#crystal#crystal#Handle(buffer, lines) abort
endfunction
function! ale_linters#crystal#crystal#GetCommand(buffer) abort
let l:crystal_cmd = 'crystal build -f json --no-codegen -o '
let l:crystal_cmd .= shellescape(g:ale#util#nul_file)
let l:crystal_cmd = 'crystal build -f json --no-codegen --no-color -o '
let l:crystal_cmd .= ale#Escape(g:ale#util#nul_file)
let l:crystal_cmd .= ' %s'
return l:crystal_cmd

View File

@@ -4,7 +4,7 @@
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=' . shellescape(l:csslintrc)
\ ? '--config=' . ale#Escape(l:csslintrc)
\ : ''
return 'csslint --format=compact ' . l:config_option . ' %t'

View File

@@ -1,24 +1,13 @@
" Author: diartyz <diartyz@gmail.com>
let g:ale_css_stylelint_executable =
\ get(g:, 'ale_css_stylelint_executable', 'stylelint')
let g:ale_css_stylelint_options =
\ get(g:, 'ale_css_stylelint_options', '')
let g:ale_css_stylelint_use_global =
\ get(g:, 'ale_css_stylelint_use_global', 0)
call ale#Set('css_stylelint_executable', 'stylelint')
call ale#Set('css_stylelint_options', '')
call ale#Set('css_stylelint_use_global', 0)
function! ale_linters#css#stylelint#GetExecutable(buffer) abort
if ale#Var(a:buffer, 'css_stylelint_use_global')
return ale#Var(a:buffer, 'css_stylelint_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'css_stylelint', [
\ 'node_modules/.bin/stylelint',
\ ale#Var(a:buffer, 'css_stylelint_executable')
\)
\])
endfunction
function! ale_linters#css#stylelint#GetCommand(buffer) abort

View File

@@ -31,7 +31,7 @@ function! ale_linters#d#dmd#DUBCommand(buffer) abort
" To support older dub versions, we just change the directory to
" the directory where we found the dub config, and then run `dub describe`
" from that directory.
return 'cd ' . shellescape(fnamemodify(l:dub_file, ':h'))
return 'cd ' . ale#Escape(fnamemodify(l:dub_file, ':h'))
\ . ' && dub describe --import-paths'
endfunction
@@ -42,7 +42,7 @@ function! ale_linters#d#dmd#DMDCommand(buffer, dub_output) abort
for l:line in a:dub_output
if !empty(l:line)
" The arguments must be '-Ifilename', not '-I filename'
call add(l:import_list, '-I' . shellescape(l:line))
call add(l:import_list, '-I' . ale#Escape(l:line))
endif
endfor

View File

@@ -5,6 +5,7 @@ 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] ==# '['
let l:errors = json_decode(l:line)
@@ -20,7 +21,6 @@ function! ale_linters#elm#make#Handle(buffer, lines) abort
if l:file_is_buffer
call add(l:output, {
\ 'bufnr': a:buffer,
\ 'lnum': l:error.region.start.line,
\ 'col': l:error.region.start.column,
\ 'type': (l:error.type ==? 'error') ? 'E' : 'W',
@@ -29,9 +29,20 @@ function! ale_linters#elm#make#Handle(buffer, lines) abort
\})
endif
endfor
elseif l:line !=# '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
@@ -43,14 +54,14 @@ function! ale_linters#elm#make#GetCommand(buffer) abort
let l:dir_set_cmd = ''
else
let l:root_dir = fnamemodify(l:elm_package, ':p:h')
let l:dir_set_cmd = 'cd ' . shellescape(l:root_dir) . ' && '
let l:dir_set_cmd = 'cd ' . ale#Escape(l:root_dir) . ' && '
endif
" The elm-make compiler, at the time of this writing, uses '/dev/null' as
" a sort of flag to tell the compiler not to generate an output file,
" which is why this is hard coded here.
" Source: https://github.com/elm-lang/elm-make/blob/master/src/Flags.hs
let l:elm_cmd = 'elm-make --report=json --output='.shellescape('/dev/null')
let l:elm_cmd = 'elm-make --report=json --output='.ale#Escape('/dev/null')
return l:dir_set_cmd . ' ' . l:elm_cmd . ' %t'
endfunction

View File

@@ -6,7 +6,7 @@ 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 ' . shellescape(l:output_file)
return 'erlc -o ' . ale#Escape(l:output_file)
\ . ' ' . ale#Var(a:buffer, 'erlang_erlc_options')
\ . ' %t'
endfunction

View File

@@ -0,0 +1,41 @@
" Author: RyanSquared <vandor2012@gmail.com>
" 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',
\})

View File

@@ -6,9 +6,11 @@ if !exists('g:ale_go_gometalinter_options')
endif
function! ale_linters#go#gometalinter#GetCommand(buffer) abort
return 'gometalinter '
let l:filename = expand('#' . a:buffer . ':p')
return 'gometalinter --include=''^' . l:filename . '.*$'' '
\ . ale#Var(a:buffer, 'go_gometalinter_options')
\ . ' ' . shellescape(fnamemodify(bufname(a:buffer), ':p:h'))
\ . ' ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h'))
endfunction
function! ale_linters#go#gometalinter#GetMatches(lines) abort
@@ -21,11 +23,6 @@ function! ale_linters#go#gometalinter#Handler(buffer, lines) abort
let l:output = []
for l:match in ale_linters#go#gometalinter#GetMatches(a:lines)
" Omit errors from files other than the one currently open
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,

View File

@@ -1,22 +1,13 @@
" Author: Adrian Zalewski <aazalewski@hotmail.com>
" Description: Ember-template-lint for checking Handlebars files
let g:ale_handlebars_embertemplatelint_executable =
\ get(g:, 'ale_handlebars_embertemplatelint_executable', 'ember-template-lint')
let g:ale_handlebars_embertemplatelint_use_global =
\ get(g:, 'ale_handlebars_embertemplatelint_use_global', 0)
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
if ale#Var(a:buffer, 'handlebars_embertemplatelint_use_global')
return ale#Var(a:buffer, 'handlebars_embertemplatelint_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'handlebars_embertemplatelint', [
\ 'node_modules/.bin/ember-template-lint',
\ ale#Var(a:buffer, 'handlebars_embertemplatelint_executable')
\)
\])
endfunction
function! ale_linters#handlebars#embertemplatelint#GetCommand(buffer) abort

View File

@@ -0,0 +1,16 @@
" Author: wizzup <wizzup@gmail.com>
" Description: ghc-mod for Haskell files
call ale#linter#Define('haskell', {
\ 'name': 'ghc-mod',
\ 'executable': 'ghc-mod',
\ 'command': 'ghc-mod check %t',
\ 'callback': 'ale#handlers#haskell#HandleGHCFormat',
\})
call ale#linter#Define('haskell', {
\ 'name': 'stack-ghc-mod',
\ 'executable': 'stack',
\ 'command': 'stack exec ghc-mod check %t',
\ 'callback': 'ale#handlers#haskell#HandleGHCFormat',
\})

View File

@@ -1,21 +1,14 @@
" Author: KabbAmine <amine.kabb@gmail.com>, deathmaz <00maz1987@gmail.com>, diartyz <diartyz@gmail.com>
" Description: HTMLHint for checking html files
" CLI options
let g:ale_html_htmlhint_options = get(g:, 'ale_html_htmlhint_options', '--format=unix')
let g:ale_html_htmlhint_executable = get(g:, 'ale_html_htmlhint_executable', 'htmlhint')
let g:ale_html_htmlhint_use_global = get(g:, 'ale_html_htmlhint_use_global', 0)
call ale#Set('html_htmlhint_options', '--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
if ale#Var(a:buffer, 'html_htmlhint_use_global')
return ale#Var(a:buffer, 'html_htmlhint_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'html_htmlhint', [
\ 'node_modules/.bin/htmlhint',
\ ale#Var(a:buffer, 'html_htmlhint_executable')
\)
\])
endfunction
function! ale_linters#html#htmlhint#GetCommand(buffer) abort

View File

@@ -0,0 +1,46 @@
" Author: Devon Meunier <devon.meunier@gmail.com>
" 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',
\})

View File

@@ -6,27 +6,6 @@ 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', '')
" Detect if the javac command just shows an annoying popup for Mac OSX.
if has('macunix')
function s:GetIsJavacAnAppStoreStub() abort
let l:path = resolve(systemlist('which javac')[0])
for l:line in readfile(l:path)
" This string is present inside the executable for the popup.
if l:line =~? 'No Java runtime present'
return 1
endif
endfor
return 0
endfunction
let s:is_javac_an_app_store_stub = s:GetIsJavacAnAppStoreStub()
delfunction s:GetIsJavacAnAppStoreStub
else
let s:is_javac_an_app_store_stub = 0
endif
function! ale_linters#java#javac#GetImportPaths(buffer) abort
let l:pom_path = ale#path#FindNearestFile(a:buffer, 'pom.xml')
@@ -47,16 +26,11 @@ function! s:BuildClassPathOption(buffer, import_paths) abort
\)
return !empty(l:class_paths)
\ ? '-cp ' . shellescape(join(l:class_paths, s:classpath_sep))
\ ? '-cp ' . ale#Escape(join(l:class_paths, s:classpath_sep))
\ : ''
endfunction
function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort
" If running the command will just show a popup, then don't run it.
if s:is_javac_an_app_store_stub
return ''
endif
let l:cp_option = s:BuildClassPathOption(a:buffer, a:import_paths)
let l:sp_option = ''
@@ -64,7 +38,7 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort
let l:src_dir = ale#path#FindNearestDirectory(a:buffer, 'src/main/java')
if !empty(l:src_dir)
let l:sp_option = '-sourcepath ' . shellescape(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.
@@ -73,7 +47,7 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort
return 'javac -Xlint'
\ . ' ' . l:cp_option
\ . ' ' . l:sp_option
\ . ' -d ' . shellescape(l:class_file_directory)
\ . ' -d ' . ale#Escape(l:class_file_directory)
\ . ' ' . ale#Var(a:buffer, 'java_javac_options')
\ . ' %t'
endfunction
@@ -85,11 +59,14 @@ function! ale_linters#java#javac#Handle(buffer, lines) abort
" 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:symbol_pattern])
if empty(l:match[3])
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 ==# 'error: cannot find symbol'
let l:output[-1].text .= ': ' . l:match[2]

View File

@@ -1,97 +1,9 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: eslint for JavaScript files
let g:ale_javascript_eslint_executable =
\ get(g:, 'ale_javascript_eslint_executable', 'eslint')
let g:ale_javascript_eslint_options =
\ get(g:, 'ale_javascript_eslint_options', '')
let g:ale_javascript_eslint_use_global =
\ get(g:, 'ale_javascript_eslint_use_global', 0)
function! ale_linters#javascript#eslint#GetExecutable(buffer) abort
if ale#Var(a:buffer, 'javascript_eslint_use_global')
return ale#Var(a:buffer, 'javascript_eslint_executable')
endif
" Look for the kinds of paths that create-react-app generates first.
let l:executable = ale#path#ResolveLocalPath(
\ a:buffer,
\ 'node_modules/eslint/bin/eslint.js',
\ ''
\)
if !empty(l:executable)
return l:executable
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
\ 'node_modules/.bin/eslint',
\ ale#Var(a:buffer, 'javascript_eslint_executable')
\)
endfunction
function! ale_linters#javascript#eslint#GetCommand(buffer) abort
return shellescape(ale_linters#javascript#eslint#GetExecutable(a:buffer))
\ . ' ' . ale#Var(a:buffer, 'javascript_eslint_options')
\ . ' -f unix --stdin --stdin-filename %s'
endfunction
function! ale_linters#javascript#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'
" 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]
" 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
call add(l:output, {
\ 'bufnr': a:buffer,
\ 'lnum': l:match[1] + 0,
\ 'col': l:match[2] + 0,
\ 'text': l:text,
\ 'type': l:type ==# 'Warning' ? 'W' : 'E',
\})
endfor
return l:output
endfunction
call ale#linter#Define('javascript', {
\ 'name': 'eslint',
\ 'executable_callback': 'ale_linters#javascript#eslint#GetExecutable',
\ 'command_callback': 'ale_linters#javascript#eslint#GetCommand',
\ 'callback': 'ale_linters#javascript#eslint#Handle',
\ 'executable_callback': 'ale#handlers#eslint#GetExecutable',
\ 'command_callback': 'ale#handlers#eslint#GetCommand',
\ 'callback': 'ale#handlers#eslint#Handle',
\})

View File

@@ -1,25 +1,21 @@
" Author: Zach Perrault -- @zperrault
" Description: FlowType checking for JavaScript files
let g:ale_javascript_flow_executable =
\ get(g:, 'ale_javascript_flow_executable', 'flow')
let g:ale_javascript_flow_use_global =
\ get(g:, 'ale_javascript_flow_use_global', 0)
call ale#Set('javascript_flow_executable', 'flow')
call ale#Set('javascript_flow_use_global', 0)
function! ale_linters#javascript#flow#GetExecutable(buffer) abort
if ale#Var(a:buffer, 'javascript_flow_use_global')
return ale#Var(a:buffer, 'javascript_flow_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'javascript_flow', [
\ 'node_modules/.bin/flow',
\ ale#Var(a:buffer, 'javascript_flow_executable')
\)
\])
endfunction
function! ale_linters#javascript#flow#GetCommand(buffer) abort
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)
@@ -27,8 +23,21 @@ function! ale_linters#javascript#flow#GetCommand(buffer) abort
return ''
endif
return shellescape(ale_linters#javascript#flow#GetExecutable(a:buffer))
\ . ' check-contents --respect-pragma --json --from ale %s'
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
function! ale_linters#javascript#flow#Handle(buffer, lines) abort
@@ -51,7 +60,9 @@ function! ale_linters#javascript#flow#Handle(buffer, lines) abort
" 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 ==# 0 && l:message.loc.source ==# expand('#' . a:buffer . ':p')
if has_key(l:message, 'loc')
\&& l:line ==# 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
@@ -81,6 +92,10 @@ endfunction
call ale#linter#Define('javascript', {
\ 'name': 'flow',
\ 'executable_callback': 'ale_linters#javascript#flow#GetExecutable',
\ 'command_callback': 'ale_linters#javascript#flow#GetCommand',
\ 'command_chain': [
\ {'callback': 'ale_linters#javascript#flow#VersionCheck'},
\ {'callback': 'ale_linters#javascript#flow#GetCommand'},
\ ],
\ 'callback': 'ale_linters#javascript#flow#Handle',
\ 'add_newline': 1,
\})

View File

@@ -1,22 +1,13 @@
" Author: Chris Kyrouac - https://github.com/fijshion
" Description: JSHint for Javascript files
let g:ale_javascript_jshint_executable =
\ get(g:, 'ale_javascript_jshint_executable', 'jshint')
let g:ale_javascript_jshint_use_global =
\ get(g:, 'ale_javascript_jshint_use_global', 0)
call ale#Set('javascript_jshint_executable', 'jshint')
call ale#Set('javascript_jshint_use_global', 0)
function! ale_linters#javascript#jshint#GetExecutable(buffer) abort
if ale#Var(a:buffer, 'javascript_jshint_use_global')
return ale#Var(a:buffer, 'javascript_jshint_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'javascript_jshint', [
\ 'node_modules/.bin/jshint',
\ ale#Var(a:buffer, 'javascript_jshint_executable')
\)
\])
endfunction
function! ale_linters#javascript#jshint#GetCommand(buffer) abort
@@ -27,11 +18,11 @@ function! ale_linters#javascript#jshint#GetCommand(buffer) abort
\ get(g:, 'ale_jshint_config_loc', '')
\)
let l:command = shellescape(ale_linters#javascript#jshint#GetExecutable(a:buffer))
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 ' . shellescape(l:jshint_config)
let l:command .= ' --config ' . ale#Escape(l:jshint_config)
endif
let l:command .= ' -'

View File

@@ -1,62 +1,26 @@
" Author: Ahmed El Gabri <@ahmedelgabri>
" Description: standardjs for JavaScript files
let g:ale_javascript_standard_executable =
\ get(g:, 'ale_javascript_standard_executable', 'standard')
let g:ale_javascript_standard_options =
\ get(g:, 'ale_javascript_standard_options', '')
let g:ale_javascript_standard_use_global =
\ get(g:, 'ale_javascript_standard_use_global', 0)
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
if ale#Var(a:buffer, 'javascript_standard_use_global')
return ale#Var(a:buffer, 'javascript_standard_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'javascript_standard', [
\ 'node_modules/.bin/standard',
\ ale#Var(a:buffer, 'javascript_standard_executable')
\)
\])
endfunction
function! ale_linters#javascript#standard#GetCommand(buffer) abort
return shellescape(ale_linters#javascript#standard#GetExecutable(a:buffer))
return ale#Escape(ale_linters#javascript#standard#GetExecutable(a:buffer))
\ . ' ' . ale#Var(a:buffer, 'javascript_standard_options')
\ . ' --stdin %s'
endfunction
function! ale_linters#javascript#standard#Handle(buffer, lines) abort
" Matches patterns line the following:
"
" /path/to/some-filename.js:47:14: Strings must use singlequote.
" /path/to/some-filename.js:56:41: Expected indentation of 2 spaces but found 4.
" /path/to/some-filename.js:13:3: Parsing error: Unexpected token
let l:pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\)$'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
let l:type = 'Error'
let l:text = l:match[3]
call add(l:output, {
\ 'bufnr': a:buffer,
\ 'lnum': l:match[1] + 0,
\ 'col': l:match[2] + 0,
\ 'text': l:text,
\ 'type': 'E',
\})
endfor
return l:output
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_linters#javascript#standard#Handle',
\ 'callback': 'ale#handlers#eslint#Handle',
\})

View File

@@ -1,41 +1,26 @@
" Author: Daniel Lupu <lupu.daniel.f@gmail.com>
" Description: xo for JavaScript files
let g:ale_javascript_xo_executable =
\ get(g:, 'ale_javascript_xo_executable', 'xo')
let g:ale_javascript_xo_options =
\ get(g:, 'ale_javascript_xo_options', '')
let g:ale_javascript_xo_use_global =
\ get(g:, 'ale_javascript_xo_use_global', 0)
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
if ale#Var(a:buffer, 'javascript_xo_use_global')
return ale#Var(a:buffer, 'javascript_xo_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'javascript_xo', [
\ 'node_modules/.bin/xo',
\ ale#Var(a:buffer, 'javascript_xo_executable')
\)
\])
endfunction
function! ale_linters#javascript#xo#GetCommand(buffer) abort
return shellescape(ale_linters#javascript#xo#GetExecutable(a:buffer))
return ale#Escape(ale_linters#javascript#xo#GetExecutable(a:buffer))
\ . ' ' . ale#Var(a:buffer, 'javascript_xo_options')
\ . ' --reporter unix --stdin --stdin-filename %s'
endfunction
function! ale_linters#javascript#xo#Handle(buffer, lines) abort
" xo uses eslint and the output format is the same
return ale_linters#javascript#eslint#Handle(a:buffer, a:lines)
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_linters#javascript#xo#Handle',
\ 'callback': 'ale#handlers#eslint#Handle',
\})

View File

@@ -9,20 +9,53 @@ 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')
function! ale_linters#kotlin#kotlinc#GetCommand(buffer) abort
let s:classpath_sep = has('unix') ? ':' : ';'
function! ale_linters#kotlin#kotlinc#GetImportPaths(buffer) abort
" exec maven only if classpath is not set
if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') !=# ''
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
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')
if filereadable(expand(ale#Var(a:buffer, 'kotlin_kotlinc_config_file'), 1))
execute 'source ' . shellescape(expand(ale#Var(a:buffer, 'kotlin_kotlinc_config_file'), 1))
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 = shellescape(expand(ale#Var(a:buffer, 'kotlin_kotlinc_module_filename'), 1))
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
@@ -35,14 +68,20 @@ function! ale_linters#kotlin#kotlinc#GetCommand(buffer) abort
" We only get here if not using module or the module file not readable
if ale#Var(a:buffer, 'kotlin_kotlinc_classpath') !=# ''
let l:kotlinc_opts .= ' -cp ' . ale#Var(a:buffer, 'kotlin_kotlinc_classpath')
else
" get classpath from maven
let l:kotlinc_opts .= s:BuildClassPathOption(a:buffer, a:import_paths)
endif
let l:fname = ''
if ale#Var(a:buffer, 'kotlin_kotlinc_sourcepath') !=# ''
let l:fname .= expand(ale#Var(a:buffer, 'kotlin_kotlinc_sourcepath'), 1) . ' '
else
" Find the src directory for files in this project.
let l:src_dir = ale#path#FindNearestDirectory(a:buffer, 'src/main/java')
let l:fname .= expand(l:src_dir, 1) . ' '
endif
let l:fname .= shellescape(expand('#' . a:buffer . ':p'))
let l:fname .= ale#Escape(expand('#' . a:buffer . ':p'))
let l:command .= l:kotlinc_opts . ' ' . l:fname
return l:command
@@ -97,7 +136,7 @@ function! ale_linters#kotlin#kotlinc#Handle(buffer, lines) abort
let l:type_marker_str = l:type ==# 'warning' || l:type ==# 'info' ? 'W' : 'E'
call add(l:output, {
\ 'lnum': -1,
\ 'lnum': 1,
\ 'text': l:text,
\ 'type': l:type_marker_str,
\})
@@ -108,9 +147,11 @@ endfunction
call ale#linter#Define('kotlin', {
\ 'name': 'kotlinc',
\ 'output_stream': 'stderr',
\ 'executable': 'kotlinc',
\ 'command_callback': 'ale_linters#kotlin#kotlinc#GetCommand',
\ '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,
\})

View File

@@ -0,0 +1,54 @@
" Author: Francis Agyapong <francisagyapong2@gmail.com>
" Description: Lint kotlin files using ktlint
call ale#Set('kotlin_ktlint_executable', 'ktlint')
call ale#Set('kotlin_ktlint_rulesets', [])
call ale#Set('kotlin_ktlint_format', 0)
function! ale_linters#kotlin#ktlint#GetCommand(buffer) abort
let l:executable = ale#Var(a:buffer, 'kotlin_ktlint_executable')
let l:file_path = expand('#' . a:buffer . ':p')
let l:options = ''
" Formmatted content written to original file, not sure how to handle
" if ale#Var(a:buffer, 'kotlin_ktlint_format')
" let l:options = l:options . ' --format'
" endif
for l:ruleset in ale#Var(a:buffer, 'kotlin_ktlint_rulesets')
let l:options = l:options . ' --ruleset ' . l:ruleset
endfor
return l:executable . ' ' . l:options . ' ' . l:file_path
endfunction
function! ale_linters#kotlin#ktlint#Handle(buffer, lines) abort
let l:message_pattern = '^\(.*\):\([0-9]\+\):\([0-9]\+\):\s\+\(.*\)'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:message_pattern)
let l:line = l:match[2] + 0
let l:column = l:match[3] + 0
let l:text = l:match[4]
let l:type = l:text =~? 'not a valid kotlin file' ? 'E' : 'W'
call add(l:output, {
\ 'lnum': l:line,
\ 'col': l:column,
\ 'text': l:text,
\ 'type': l:type
\})
endfor
return l:output
endfunction
call ale#linter#Define('kotlin', {
\ 'name': 'ktlint',
\ 'executable': 'ktlint',
\ 'command_callback': 'ale_linters#kotlin#ktlint#GetCommand',
\ 'callback': 'ale_linters#kotlin#ktlint#Handle',
\ 'lint_file': 1
\})

View File

@@ -12,7 +12,7 @@ function! ale_linters#lua#luacheck#GetExecutable(buffer) abort
endfunction
function! ale_linters#lua#luacheck#GetCommand(buffer) abort
return shellescape(ale_linters#lua#luacheck#GetExecutable(a:buffer))
return ale#Escape(ale_linters#lua#luacheck#GetExecutable(a:buffer))
\ . ' ' . ale#Var(a:buffer, 'lua_luacheck_options')
\ . ' --formatter plain --codes --filename %s -'
endfunction

View File

@@ -44,10 +44,7 @@ endfunction
function! ale_linters#nim#nimcheck#GetCommand(buffer) abort
let l:directory = shellescape(fnamemodify(bufname(a:buffer), ':p:h'))
return 'nim check --path:' . l:directory
\ . ' --threads:on --verbosity:0 --colors:off --listFullPaths %t'
return 'nim check --verbosity:0 --colors:off --listFullPaths %s'
endfunction
@@ -56,5 +53,6 @@ call ale#linter#Define('nim', {
\ 'executable': 'nim',
\ 'output_stream': 'both',
\ 'command_callback': 'ale_linters#nim#nimcheck#GetCommand',
\ 'callback': 'ale_linters#nim#nimcheck#Handle'
\ 'callback': 'ale_linters#nim#nimcheck#Handle',
\ 'lint_file': 1,
\})

View File

@@ -0,0 +1,23 @@
" Author: Bang Lee <https://github.com/Qusic>
" 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',
\})

View File

@@ -0,0 +1,23 @@
" Author: Bang Lee <https://github.com/Qusic>
" 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',
\})

View File

@@ -5,7 +5,7 @@ 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', '-X -c -Mwarnings -Ilib')
\ 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')
@@ -17,20 +17,33 @@ function! ale_linters#perl#perl#GetCommand(buffer) abort
\ . ' %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'
call add(l:output, {
\ 'lnum': l:line,
\ 'text': l:text,
\ 'type': l:type,
\})
if ale#path#IsBufferPath(a:buffer, l:match[2])
\ && (
\ l:text !=# '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

View File

@@ -1,14 +1,29 @@
" Author: Vincent Lequertier <https://github.com/SkySymbol>
" Description: This file adds support for checking perl with perl critic
if !exists('g:ale_perl_perlcritic_showrules')
let g:ale_perl_perlcritic_showrules = 0
endif
function! ale_linters#perl#perlcritic#GetCommand(buffer) abort
let l:critic_verbosity = '%l:%c %m\n'
if g:ale_perl_perlcritic_showrules
let l:critic_verbosity = '%l:%c %m [%p]\n'
endif
return "perlcritic --verbose '". l:critic_verbosity . "' --nocolor"
endfunction
function! ale_linters#perl#perlcritic#Handle(buffer, lines) abort
let l:pattern = '\(.\+\) at \(.\+\) line \(\d\+\)'
let l:pattern = '\(\d\+\):\(\d\+\) \(.\+\)'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
call add(l:output, {
\ 'text': l:match[1],
\ 'lnum': l:match[3],
\ 'lnum': l:match[1],
\ 'col': l:match[2],
\ 'text': l:match[3],
\})
endfor
@@ -19,6 +34,6 @@ call ale#linter#Define('perl', {
\ 'name': 'perlcritic',
\ 'executable': 'perlcritic',
\ 'output_stream': 'stdout',
\ 'command': 'perlcritic --verbose 3 --nocolor',
\ 'command_callback': 'ale_linters#perl#perlcritic#GetCommand',
\ 'callback': 'ale_linters#perl#perlcritic#Handle',
\})

View File

@@ -4,17 +4,23 @@
function! ale_linters#php#php#Handle(buffer, lines) abort
" Matches patterns like the following:
"
" PHP Parse error: syntax error, unexpected ';', expecting ']' in - on line 15
" Parse error: syntax error, unexpected ';', expecting ']' in - on line 15
let l:pattern = '\v^%(Fatal|Parse) error:\s+(.+unexpected ''(.+)%(expecting.+)@<!''.*|.+) in - on line (\d+)'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
call add(l:output, {
let l:col = empty(l:match[2]) ? 0 : stridx(getline(l:match[3]), l:match[2]) + 1
let l:obj = {
\ 'lnum': l:match[3] + 0,
\ 'col': empty(l:match[2]) ? 0 : stridx(getline(l:match[3]), l:match[2]) + 1,
\ 'col': l:col,
\ 'text': l:match[1],
\})
\}
if l:col != 0
let l:obj.end_col = l:col + strlen(l:match[2]) - 1
endif
call add(l:output, l:obj)
endfor
return l:output

View File

@@ -1,15 +1,28 @@
" Author: jwilliams108 <https://github.com/jwilliams108>
" Author: jwilliams108 <https://github.com/jwilliams108>, Eric Stern <https://github.com/firehed>
" 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 'phpcs -s --report=emacs --stdin-path=%s ' . l:standard_option
return ale#Escape(l:executable)
\ . ' -s --report=emacs --stdin-path=%s ' . l:standard_option
endfunction
function! ale_linters#php#phpcs#Handle(buffer, lines) abort
@@ -36,7 +49,7 @@ endfunction
call ale#linter#Define('php', {
\ 'name': 'phpcs',
\ 'executable': 'phpcs',
\ 'executable_callback': 'ale_linters#php#phpcs#GetExecutable',
\ 'command_callback': 'ale_linters#php#phpcs#GetCommand',
\ 'callback': 'ale_linters#php#phpcs#Handle',
\})

View File

@@ -48,7 +48,7 @@ function! ale_linters#python#flake8#VersionCheck(buffer) abort
return ''
endif
let l:executable = shellescape(ale_linters#python#flake8#GetExecutable(a:buffer))
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'
@@ -89,17 +89,84 @@ function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort
let l:options = ale#Var(a:buffer, 'python_flake8_options')
return shellescape(ale_linters#python#flake8#GetExecutable(a:buffer))
return ale#Escape(ale_linters#python#flake8#GetExecutable(a:buffer))
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . 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 ==# 'W291' || l:code ==# '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] ==# 'F' || l:code ==# 'E999'
let l:item.type = 'E'
elseif l:code[:0] ==# 'E'
let l:item.type = 'E'
let l:item.sub_type = 'style'
elseif l:code[:0] ==# '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'},
\ {'callback': 'ale_linters#python#flake8#GetCommand', 'output_stream': 'both'},
\ ],
\ 'callback': 'ale#handlers#python#HandlePEP8Format',
\ 'callback': 'ale_linters#python#flake8#Handle',
\})

View File

@@ -7,19 +7,7 @@ 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
if !ale#Var(a:buffer, 'python_mypy_use_global')
let l:virtualenv = ale#python#FindVirtualenv(a:buffer)
if !empty(l:virtualenv)
let l:ve_mypy = l:virtualenv . '/bin/mypy'
if executable(l:ve_mypy)
return l:ve_mypy
endif
endif
endif
return ale#Var(a:buffer, 'python_mypy_executable')
return ale#python#FindExecutable(a:buffer, 'python_mypy', ['/bin/mypy'])
endfunction
function! ale_linters#python#mypy#GetCommand(buffer) abort
@@ -30,10 +18,10 @@ function! ale_linters#python#mypy#GetCommand(buffer) abort
let l:executable = ale_linters#python#mypy#GetExecutable(a:buffer)
return l:cd_command
\ . shellescape(l:executable)
\ . ale#Escape(l:executable)
\ . ' --show-column-numbers '
\ . ale#Var(a:buffer, 'python_mypy_options')
\ . ' %s'
\ . ' --shadow-file %s %t %s'
endfunction
function! ale_linters#python#mypy#Handle(buffer, lines) abort
@@ -69,5 +57,4 @@ call ale#linter#Define('python', {
\ 'executable_callback': 'ale_linters#python#mypy#GetExecutable',
\ 'command_callback': 'ale_linters#python#mypy#GetCommand',
\ 'callback': 'ale_linters#python#mypy#Handle',
\ 'lint_file': 1,
\})

View File

@@ -26,7 +26,7 @@ function! ale_linters#python#pylint#GetExecutable(buffer) abort
endfunction
function! ale_linters#python#pylint#GetCommand(buffer) abort
return shellescape(ale_linters#python#pylint#GetExecutable(a:buffer))
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'
@@ -36,7 +36,7 @@ function! ale_linters#python#pylint#Handle(buffer, lines) abort
" Matches patterns like the following:
"
" test.py:4:4: W0101 (unreachable) Unreachable code
let l:pattern = '\v^[^:]+:(\d+):(\d+): ([[:alnum:]]+) \((.*)\) (.*)$'
let l:pattern = '\v^[^:]+:(\d+):(\d+): ([[:alnum:]]+) \(([^(]*)\) (.*)$'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
@@ -57,7 +57,7 @@ function! ale_linters#python#pylint#Handle(buffer, lines) abort
call add(l:output, {
\ 'lnum': l:match[1] + 0,
\ 'col': l:match[2] + 1,
\ 'text': l:code . ': ' . l:match[5],
\ 'text': l:code . ': ' . l:match[5] . ' (' . l:match[4] . ')',
\ 'type': l:code[:0] ==# 'E' ? 'E' : 'W',
\})
endfor

View File

@@ -5,6 +5,10 @@ let g:ale_ruby_brakeman_options =
\ get(g:, 'ale_ruby_brakeman_options', '')
function! ale_linters#ruby#brakeman#Handle(buffer, lines) abort
if len(a:lines) == 0
return []
endif
let l:result = json_decode(join(a:lines, ''))
let l:output = []
@@ -40,7 +44,7 @@ function! ale_linters#ruby#brakeman#GetCommand(buffer) abort
return 'brakeman -f json -q '
\ . ale#Var(a:buffer, 'ruby_brakeman_options')
\ . ' -p ' . l:rails_root
\ . ' -p ' . ale#Escape(l:rails_root)
endfunction
function! s:FindRailsRoot(buffer) abort

View File

@@ -1,6 +1,18 @@
" Author: ynonp - https://github.com/ynonp
" Description: rubocop 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 emacs --force-exclusion '
\ . ale#Var(a:buffer, 'ruby_rubocop_options')
\ . ' --stdin ' . bufname(a:buffer)
endfunction
function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort
" Matches patterns line the following:
"
@@ -24,21 +36,9 @@ function! ale_linters#ruby#rubocop#Handle(buffer, lines) abort
return l:output
endfunction
function! ale_linters#ruby#rubocop#GetCommand(buffer) abort
return 'rubocop --format emacs --force-exclusion '
\ . ale#Var(a:buffer, 'ruby_rubocop_options')
\ . ' --stdin ' . bufname(a:buffer)
endfunction
" Set this option to change Rubocop options.
if !exists('g:ale_ruby_rubocop_options')
" let g:ale_ruby_rubocop_options = '--lint'
let g:ale_ruby_rubocop_options = ''
endif
call ale#linter#Define('ruby', {
\ 'name': 'rubocop',
\ 'executable': 'rubocop',
\ 'executable_callback': 'ale#handlers#rubocop#GetExecutable',
\ 'command_callback': 'ale_linters#ruby#rubocop#GetCommand',
\ 'callback': 'ale_linters#ruby#rubocop#Handle',
\})

View File

@@ -1,21 +1,12 @@
" Author: diartyz <diartyz@gmail.com>
let g:ale_sass_stylelint_executable =
\ get(g:, 'ale_sass_stylelint_executable', 'stylelint')
let g:ale_sass_stylelint_use_global =
\ get(g:, 'ale_sass_stylelint_use_global', 0)
call ale#Set('sass_stylelint_executable', 'stylelint')
call ale#Set('sass_stylelint_use_global', 0)
function! ale_linters#sass#stylelint#GetExecutable(buffer) abort
if ale#Var(a:buffer, 'sass_stylelint_use_global')
return ale#Var(a:buffer, 'sass_stylelint_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'sass_stylelint', [
\ 'node_modules/.bin/stylelint',
\ ale#Var(a:buffer, 'sass_stylelint_executable')
\)
\])
endfunction
function! ale_linters#sass#stylelint#GetCommand(buffer) abort

View File

@@ -1,21 +1,12 @@
" Author: diartyz <diartyz@gmail.com>
let g:ale_scss_stylelint_executable =
\ get(g:, 'ale_scss_stylelint_executable', 'stylelint')
let g:ale_scss_stylelint_use_global =
\ get(g:, 'ale_scss_stylelint_use_global', 0)
call ale#Set('scss_stylelint_executable', 'stylelint')
call ale#Set('scss_stylelint_use_global', 0)
function! ale_linters#scss#stylelint#GetExecutable(buffer) abort
if ale#Var(a:buffer, 'scss_stylelint_use_global')
return ale#Var(a:buffer, 'scss_stylelint_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'scss_stylelint', [
\ 'node_modules/.bin/stylelint',
\ ale#Var(a:buffer, 'scss_stylelint_executable')
\)
\])
endfunction
function! ale_linters#scss#stylelint#GetCommand(buffer) abort

View File

@@ -18,7 +18,7 @@ function! ale_linters#tex#chktex#GetCommand(buffer) abort
let l:command .= ' -v0 -p stdin -q'
if !empty(l:chktex_config)
let l:command .= ' -l ' . shellescape(l:chktex_config)
let l:command .= ' -l ' . ale#Escape(l:chktex_config)
endif
let l:command .= ' ' . ale#Var(a:buffer, 'tex_chktex_options')

View File

@@ -0,0 +1,9 @@
" Author: w0rp <devw0rp@gmail.com>
" 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',
\})

View File

@@ -1,47 +1,30 @@
" Author: Prashanth Chandra https://github.com/prashcr
" Description: tslint for TypeScript files
let g:ale_typescript_tslint_executable =
\ get(g:, 'ale_typescript_tslint_executable', 'tslint')
let g:ale_typescript_tslint_config_path =
\ get(g:, 'ale_typescript_tslint_config_path', '')
let g:ale_typescript_tslint_use_global =
\ get(g:, 'ale_typescript_tslint_use_global', 0)
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
if ale#Var(a:buffer, 'typescript_tslint_use_global')
return ale#Var(a:buffer, 'typescript_tslint_executable')
endif
return ale#path#ResolveLocalPath(
\ a:buffer,
return ale#node#FindExecutable(a:buffer, 'typescript_tslint', [
\ 'node_modules/.bin/tslint',
\ ale#Var(a:buffer, 'typescript_tslint_executable')
\)
\])
endfunction
function! ale_linters#typescript#tslint#Handle(buffer, lines) abort
" Matches patterns like the following:
"
" hello.ts[7, 41]: trailing whitespace
" hello.ts[5, 1]: Forbidden 'var' keyword, use 'let' or 'const' instead
"
let l:ext = '.' . fnamemodify(bufname(a:buffer), ':e')
let l:pattern = '.\+' . l:ext . '\[\(\d\+\), \(\d\+\)\]: \(.\+\)'
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,
\})
for l:error in json_decode(join(a:lines, ''))
if ale#path#IsBufferPath(a:buffer, l:error.name)
call add(l:output, {
\ 'type': (get(l:error, 'ruleSeverity', '') ==# 'WARNING' ? 'W' : 'E'),
\ 'text': l:error.failure,
\ 'lnum': l:error.startPosition.line + 1,
\ 'col': l:error.startPosition.character + 1,
\ 'end_lnum': l:error.endPosition.line + 1,
\ 'end_col': l:error.endPosition.character + 1,
\})
endif
endfor
return l:output
@@ -55,11 +38,12 @@ function! ale_linters#typescript#tslint#BuildLintCommand(buffer) abort
\)
let l:tslint_config_option = !empty(l:tslint_config_path)
\ ? '-c ' . shellescape(l:tslint_config_path)
\ ? ' -c ' . ale#Escape(l:tslint_config_path)
\ : ''
return ale_linters#typescript#tslint#GetExecutable(a:buffer)
\ . ' ' . l:tslint_config_option
\ . ' --format json'
\ . l:tslint_config_option
\ . ' %t'
endfunction

View File

@@ -0,0 +1,23 @@
" Author: w0rp <devw0rp@gmail.com>
" 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)
function! ale_linters#typescript#tsserver#GetExecutable(buffer) abort
return ale#node#FindExecutable(a:buffer, 'typescript_tsserver', [
\ 'node_modules/.bin/tsserver',
\])
endfunction
function! ale_linters#typescript#tsserver#Handle(buffer, lines) abort
return a:lines
endfunction
call ale#linter#Define('typescript', {
\ 'name': 'tsserver',
\ 'lsp': 'tsserver',
\ 'executable_callback': 'ale_linters#typescript#tsserver#GetExecutable',
\ 'callback': 'ale_linters#typescript#tsserver#Handle',
\})

View File

@@ -8,7 +8,7 @@ function! ale_linters#verilog#verilator#GetCommand(buffer) abort
call ale#engine#ManageFile(a:buffer, l:filename)
call writefile(getbufline(a:buffer, 1, '$'), l:filename)
return 'verilator --lint-only -Wall -Wno-DECLFILENAME ' . shellescape(l:filename)
return 'verilator --lint-only -Wall -Wno-DECLFILENAME ' . ale#Escape(l:filename)
endfunction
function! ale_linters#verilog#verilator#Handle(buffer, lines) abort

View File

@@ -4,18 +4,33 @@
" 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:vint_version = ale#semver#Parse(system('vint --version'))
let s:can_use_no_color_flag = ale#semver#GreaterOrEqual(s:vint_version, [0, 3, 7])
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])
function! ale_linters#vim#vint#GetCommand(buffer) abort
let l:warning_flag = ale#Var(a:buffer, 'vim_vint_show_style_issues') ? '-s' : '-w'
return 'vint '
\ . l:warning_flag . ' '
\ . (s:can_use_no_color_flag ? '--no-color ' : '')
\ . (l:can_use_no_color_flag ? '--no-color ' : '')
\ . s:enable_neovim
\ . s:format
\ . ' %t'
@@ -24,6 +39,9 @@ endfunction
call ale#linter#Define('vim', {
\ 'name': 'vint',
\ 'executable': 'vint',
\ 'command_callback': 'ale_linters#vim#vint#GetCommand',
\ 'command_chain': [
\ {'callback': 'ale_linters#vim#vint#VersionCommand', 'output_stream': 'stderr'},
\ {'callback': 'ale_linters#vim#vint#GetCommand', 'output_stream': 'stdout'},
\ ],
\ 'callback': 'ale#handlers#gcc#HandleGCCFormat',
\})

View File

@@ -0,0 +1,69 @@
" Author: q12321q <q12321q@gmail.com>
" 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
" </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',
\ })

View File

@@ -1,4 +1,4 @@
" Author: w0rp <devw0rp@gmail.com>
" Author: w0rp <devw0rp@gmail.com>, David Alexander <opensource@thelonelyghost.com>
" Description: Primary code path for the plugin
" Manages execution of linters when requested by autocommands
@@ -6,6 +6,13 @@ let s:lint_timer = -1
let s:queued_buffer_number = -1
let s:should_lint_file_for_buffer = {}
" 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() abort
@@ -14,6 +21,8 @@ function! ale#ShouldDoNothing() abort
return index(g:ale_filetype_blacklist, &filetype) >= 0
\ || (exists('*getcmdwintype') && !empty(getcmdwintype()))
\ || ale#util#InSandbox()
\ || !ale#Var(bufnr(''), 'enabled')
\ || ale#FileTooLarge()
endfunction
" (delay, [linting_flag])
@@ -76,7 +85,8 @@ function! ale#Lint(...) abort
" Check if we previously requested checking the file.
if has_key(s:should_lint_file_for_buffer, l:buffer)
unlet s:should_lint_file_for_buffer[l:buffer]
let l:should_lint_file = 1
" Lint files if they exist.
let l:should_lint_file = filereadable(expand('#' . l:buffer . ':p'))
endif
" Initialise the buffer information if needed.
@@ -95,6 +105,8 @@ function! ale#Lint(...) abort
call filter(l:linters, '!v:val.lint_file')
endif
call ale#engine#StopCurrentJobs(l:buffer, l:should_lint_file)
for l:linter in l:linters
call ale#engine#Invoke(l:buffer, l:linter)
endfor
@@ -119,7 +131,46 @@ endfunction
"
" 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
return getbufvar(str2nr(a:buffer), l:full_name, g:[l:full_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') ==? '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

21
autoload/ale/balloon.vim Normal file
View File

@@ -0,0 +1,21 @@
" Author: w0rp <devw0rp@gmail.com>
" 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: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

91
autoload/ale/c.vim Normal file
View File

@@ -0,0 +1,91 @@
" Author: gagbo <gagbobada@gmail.com>, w0rp <devw0rp@gmail.com>
" 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') ==# '.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 [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

View File

@@ -6,9 +6,7 @@ function! ale#cleanup#Buffer(buffer) abort
call ale#engine#RemoveManagedFiles(a:buffer)
" When buffers are removed, clear all of the jobs.
for l:job in get(g:ale_buffer_info[a:buffer], 'job_list', [])
call ale#engine#ClearJob(l:job)
endfor
call ale#engine#StopCurrentJobs(a:buffer, 1)
" Clear delayed highlights for a buffer being removed.
if g:ale_set_highlights

57
autoload/ale/command.vim Normal file
View File

@@ -0,0 +1,57 @@
" Author: w0rp <devw0rp@gmail.com>
" 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, <temp_dir>/<original_basename>
" 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, '%%', '<<PERCENTS>>', '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, <temp_dir>/<original_basename>
" 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, '<<PERCENTS>>', '%', '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

View File

@@ -7,15 +7,13 @@ function! s:GetMessage(linter, type, text) abort
let l:type = a:type ==# 'E'
\ ? g:ale_echo_msg_error_str
\ : g:ale_echo_msg_warning_str
" Capitalize the 1st character
let l:text = toupper(a:text[0]) . a:text[1:-1]
" Replace handlers if they exist
for [l:k, l:v] in items({'linter': a:linter, 'severity': l:type})
let l:msg = substitute(l:msg, '\V%' . l:k . '%', l:v, '')
endfor
return printf(l:msg, l:text)
return printf(l:msg, a:text)
endfunction
function! s:EchoWithShortMess(setting, message) abort
@@ -68,6 +66,10 @@ function! s:StopCursorTimer() abort
endfunction
function! ale#cursor#EchoCursorWarning(...) abort
if ale#ShouldDoNothing()
return
endif
" Only echo the warnings in normal mode, otherwise we will get problems.
if mode() !=# 'n'
return
@@ -110,6 +112,10 @@ function! ale#cursor#EchoCursorWarningWithDelay() abort
endfunction
function! ale#cursor#ShowCursorDetail() abort
if ale#ShouldDoNothing()
return
endif
" Only echo the warnings in normal mode, otherwise we will get problems.
if mode() !=# 'n'
return

View File

@@ -105,6 +105,22 @@ function! s:EchoCommandHistory() abort
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
@@ -120,8 +136,8 @@ function! ale#debugging#Info() abort
call extend(l:all_linters, ale#linter#GetAll(l:aliased_filetype))
endfor
let l:all_names = map(l:all_linters, 'v:val[''name'']')
let l:enabled_names = map(l:enabled_linters, 'v:val[''name'']')
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.
@@ -129,6 +145,7 @@ function! ale#debugging#Info() abort
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 ''

View File

@@ -7,7 +7,10 @@
" linter: The linter dictionary for the job.
" buffer: The buffer number for the job.
" output: The array of lines for the output of the job.
let s:job_info_map = {}
if !has_key(s:, 'job_info_map')
let s:job_info_map = {}
endif
let s:executable_cache_map = {}
" Check if files are executable, and if they are, remember that they are
@@ -26,184 +29,24 @@ function! s:IsExecutable(executable) abort
return 0
endfunction
function! ale#engine#ParseVim8ProcessID(job_string) abort
return matchstr(a:job_string, '\d\+') + 0
endfunction
function! s:GetJobID(job) abort
if has('nvim')
"In NeoVim, job values are just IDs.
return a:job
endif
" For Vim 8, the job is a different variable type, and we can parse the
" process ID from the string.
return ale#engine#ParseVim8ProcessID(string(a:job))
endfunction
function! ale#engine#InitBufferInfo(buffer) abort
if !has_key(g:ale_buffer_info, a:buffer)
" job_list will hold the list of jobs
" loclist holds the loclist items after all jobs have completed.
" lint_file_loclist holds items from the last run including linters
" which use the lint_file option.
" new_loclist holds loclist items while jobs are being run.
" temporary_file_list holds temporary files to be cleaned up
" temporary_directory_list holds temporary directories to be cleaned up
" history holds a list of previously run commands for this buffer
let g:ale_buffer_info[a:buffer] = {
\ 'job_list': [],
\ 'loclist': [],
\ 'lint_file_loclist': [],
\ 'new_loclist': [],
\ 'temporary_file_list': [],
\ 'temporary_directory_list': [],
\ 'history': [],
\ 'open_lsp_documents': [],
\}
endif
endfunction
" A map from timer IDs to Vim 8 jobs, for tracking jobs that need to be killed
" with SIGKILL if they don't terminate right away.
let s:job_kill_timers = {}
" Check if a job is still running, in either Vim version.
function! s:IsJobRunning(job) abort
if has('nvim')
try
" In NeoVim, if the job isn't running, jobpid() will throw.
call jobpid(a:job)
return 1
catch
endtry
return 0
endif
return job_status(a:job) ==# 'run'
endfunction
function! s:KillHandler(timer) abort
let l:job = remove(s:job_kill_timers, a:timer)
" For NeoVim, we have to send SIGKILL ourselves manually, as NeoVim
" doesn't do it properly.
if has('nvim')
let l:pid = 0
" We can fail to get the PID here if the job manages to stop already.
try
let l:pid = jobpid(l:job)
catch
endtry
if l:pid > 0
if has('win32')
" Windows
call system('taskkill /pid ' . l:pid . ' /f')
else
" Linux, Mac OSX, etc.
call system('kill -9 ' . l:pid)
endif
endif
else
call job_stop(l:job, 'kill')
endif
endfunction
function! ale#engine#ClearJob(job) abort
if get(g:, 'ale_run_synchronously') == 1
call remove(s:job_info_map, a:job)
return
endif
let l:job_id = s:GetJobID(a:job)
if has('nvim')
call jobstop(a:job)
else
" We must close the channel for reading the buffer if it is open
" when stopping a job. Otherwise, we will get errors in the status line.
if ch_status(job_getchannel(a:job)) ==# 'open'
call ch_close_in(job_getchannel(a:job))
endif
" Ask nicely for the job to stop.
call job_stop(a:job)
endif
" If a job doesn't stop immediately, queue a timer which will
" send SIGKILL to the job, if it's alive by the time the timer ticks.
if s:IsJobRunning(a:job)
let s:job_kill_timers[timer_start(100, function('s:KillHandler'))] = a:job
endif
if has_key(s:job_info_map, l:job_id)
call remove(s:job_info_map, l:job_id)
endif
endfunction
function! s:StopPreviousJobs(buffer, linter) abort
if !has_key(g:ale_buffer_info, a:buffer)
" Do nothing if we didn't run anything for the buffer.
return
endif
let l:new_job_list = []
for l:job in g:ale_buffer_info[a:buffer].job_list
let l:job_id = s:GetJobID(l:job)
if has_key(s:job_info_map, l:job_id)
\&& s:job_info_map[l:job_id].linter.name ==# a:linter.name
" Stop jobs which match the buffer and linter.
call ale#engine#ClearJob(l:job)
else
" Keep other jobs in the list.
call add(l:new_job_list, l:job)
endif
endfor
" Update the list, removing the previously run job.
let g:ale_buffer_info[a:buffer].job_list = l:new_job_list
endfunction
function! s:GatherOutputVim(channel, data) abort
let l:job_id = s:GetJobID(ch_getjob(a:channel))
if !has_key(s:job_info_map, l:job_id)
return
endif
call add(s:job_info_map[l:job_id].output, a:data)
endfunction
function! s:GatherOutputNeoVim(job, data, event) abort
let l:job_id = s:GetJobID(a:job)
if !has_key(s:job_info_map, l:job_id)
return
endif
" Join the lines passed to ale, because Neovim splits them up.
" a:data is a list of strings, where every item is a new line, except the
" first one, which is the continuation of the last item passed last time.
call ale#engine#JoinNeovimOutput(s:job_info_map[l:job_id].output, a:data)
endfunction
function! ale#engine#JoinNeovimOutput(output, data) abort
if empty(a:output)
call extend(a:output, a:data)
else
" Extend the previous line, which can be continued.
let a:output[-1] .= get(a:data, 0, '')
" Add the new lines.
call extend(a:output, a:data[1:])
endif
endfunction
" Register a temporary file to be managed with the ALE engine for
" a current job run.
function! ale#engine#ManageFile(buffer, filename) abort
@@ -255,27 +98,76 @@ function! ale#engine#RemoveManagedFiles(buffer) abort
let g:ale_buffer_info[a:buffer].temporary_directory_list = []
endfunction
function! s:HandleExit(job) abort
if a:job ==# 'no process'
" Stop right away when the job is not valid in Vim 8.
function! s:GatherOutput(job_id, line) abort
if has_key(s:job_info_map, a:job_id)
call add(s:job_info_map[a:job_id].output, a:line)
endif
endfunction
function! s:HandleLoclist(linter_name, buffer, loclist) abort
" 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 !=# 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')
let l:linting_is_done = empty(g:ale_buffer_info[a:buffer].job_list)
\ && !get(g:ale_buffer_info[a:buffer], 'waiting_for_tsserver', 0)
if l:linting_is_done
" Automatically remove all managed temporary files and directories
" now that all jobs have completed.
call ale#engine#RemoveManagedFiles(a:buffer)
" 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 ale#linter#Get(getbufvar(a:buffer, '&filetype'))
let l:name_map[l:linter.name] = 1
endfor
call filter(
\ g:ale_buffer_info[a:buffer].loclist,
\ 'get(l:name_map, v:val.linter_name)',
\)
endif
call ale#engine#SetResults(a:buffer, g:ale_buffer_info[a:buffer].loclist)
if l:linting_is_done
" Call user autocommands. This allows users to hook into ALE's lint cycle.
silent doautocmd User ALELint
endif
endfunction
function! s:HandleExit(job_id, exit_code) abort
if !has_key(s:job_info_map, a:job_id)
return
endif
let l:job_id = s:GetJobID(a:job)
if !has_key(s:job_info_map, l:job_id)
return
endif
let l:job_info = s:job_info_map[l:job_id]
let l:job_info = s:job_info_map[a:job_id]
let l:linter = l:job_info.linter
let l:output = l:job_info.output
let l:buffer = l:job_info.buffer
let l:next_chain_index = l:job_info.next_chain_index
" Call the same function for stopping jobs again to clean up the job
" which just closed.
call s:StopPreviousJobs(l:buffer, l:linter)
if g:ale_history_enabled
call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code)
endif
" Remove this job from the list.
call ale#job#Stop(a:job_id)
call remove(s:job_info_map, a:job_id)
call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val !=# a:job_id')
" Stop here if we land in the handle for a job completing if we're in
" a sandbox.
@@ -283,6 +175,10 @@ function! s:HandleExit(job) abort
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
@@ -290,62 +186,59 @@ function! s:HandleExit(job) abort
" 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, l:job_id, l:output[:])
call ale#history#RememberOutput(l:buffer, a:job_id, l:output[:])
endif
let l:linter_loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output)
let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output)
" 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(l:buffer, l:linter, l:linter_loclist)
call s:HandleLoclist(l:linter.name, l:buffer, l:loclist)
endfunction
" Add the loclist items from the linter.
" loclist items for files which are checked go into a different list,
" and are kept between runs.
if l:linter.lint_file
call extend(g:ale_buffer_info[l:buffer].lint_file_loclist, l:linter_loclist)
else
call extend(g:ale_buffer_info[l:buffer].new_loclist, l:linter_loclist)
endif
function! s:HandleLSPResponse(response) abort
let l:is_diag_response = get(a:response, 'type', '') ==# 'event'
\ && get(a:response, 'event', '') ==# 'semanticDiag'
if !empty(g:ale_buffer_info[l:buffer].job_list)
" Wait for all jobs to complete before doing anything else.
if !l:is_diag_response
return
endif
" Automatically remove all managed temporary files and directories
" now that all jobs have completed.
call ale#engine#RemoveManagedFiles(l:buffer)
let l:buffer = bufnr(a:response.body.file)
" Combine the lint_file List and the List for everything else.
let l:combined_list = g:ale_buffer_info[l:buffer].lint_file_loclist
\ + g:ale_buffer_info[l:buffer].new_loclist
let l:info = get(g:ale_buffer_info, l:buffer, {})
" Sort the loclist again.
" We need a sorted list so we can run a binary search against it
" for efficient lookup of the messages in the cursor handler.
call sort(l:combined_list, 'ale#util#LocItemCompare')
if empty(l:info)
return
endif
" Now swap the old and new loclists, after we have collected everything
" and sorted the list again.
let g:ale_buffer_info[l:buffer].loclist = l:combined_list
let g:ale_buffer_info[l:buffer].new_loclist = []
let l:info.waiting_for_tsserver = 0
call ale#engine#SetResults(l:buffer, g:ale_buffer_info[l:buffer].loclist)
let l:loclist = ale#lsp#response#ReadTSServerDiagnostics(a:response)
" Call user autocommands. This allows users to hook into ALE's lint cycle.
silent doautocmd User ALELint
call s:HandleLoclist('tsserver', l:buffer, l:loclist)
endfunction
function! ale#engine#SetResults(buffer, loclist) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
let l:job_list = get(l:info, 'job_list', [])
let l:waiting_for_tsserver = get(l:info, 'waiting_for_tsserver', 0)
let l:linting_is_done = empty(l:job_list) && !l:waiting_for_tsserver
" 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)
if l:linting_is_done
call ale#sign#RemoveDummySignIfNeeded(a:buffer)
endif
endif
if g:ale_set_quickfix || g:ale_set_loclist
call ale#list#SetLists(a:buffer, a:loclist)
if l:linting_is_done
call ale#list#CloseWindowIfNeeded(a:buffer)
endif
endif
if exists('*ale#statusline#Update')
@@ -364,37 +257,29 @@ function! ale#engine#SetResults(buffer, loclist) abort
endif
endfunction
function! s:SetExitCode(job, exit_code) abort
let l:job_id = s:GetJobID(a:job)
function! s:RemapItemTypes(type_map, loclist) abort
for l:item in a:loclist
let l:key = l:item.type
\ . (get(l:item, 'sub_type', '') ==# 'style' ? 'S' : '')
let l:new_key = get(a:type_map, l:key, '')
if !has_key(s:job_info_map, l:job_id)
return
endif
if l:new_key ==# 'E'
\|| l:new_key ==# 'ES'
\|| l:new_key ==# 'W'
\|| l:new_key ==# 'WS'
\|| l:new_key ==# 'I'
let l:item.type = l:new_key[0]
let l:buffer = s:job_info_map[l:job_id].buffer
call ale#history#SetExitCode(l:buffer, l:job_id, a:exit_code)
if l:new_key ==# 'ES' || l:new_key ==# 'WS'
let l:item.sub_type = 'style'
elseif has_key(l:item, 'sub_type')
call remove(l:item, 'sub_type')
endif
endif
endfor
endfunction
function! s:HandleExitNeoVim(job, exit_code, event) abort
if g:ale_history_enabled
call s:SetExitCode(a:job, a:exit_code)
endif
call s:HandleExit(a:job)
endfunction
function! s:HandleExitVim(channel) abort
call s:HandleExit(ch_getjob(a:channel))
endfunction
" Vim returns the exit status with one callback,
" and the channel will close later in another callback.
function! s:HandleExitStatusVim(job, exit_code) abort
call s:SetExitCode(a:job, a:exit_code)
endfunction
function! ale#engine#FixLocList(buffer, linter, loclist) abort
function! ale#engine#FixLocList(buffer, linter_name, loclist) abort
let l:new_loclist = []
" Some errors have line numbers beyond the end of the file,
@@ -423,15 +308,28 @@ function! ale#engine#FixLocList(buffer, linter, loclist) abort
\ '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,
\ 'linter_name': a:linter_name,
\}
if has_key(l:old_item, 'detail')
let l:item.detail = l:old_item.detail
endif
if l:item.lnum == 0
" When errors appear at line 0, put them at line 1 instead.
" 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.lnum > l:last_line_number
" When errors go beyond the end of the file, put them at the end.
@@ -441,6 +339,12 @@ function! ale#engine#FixLocList(buffer, linter, loclist) abort
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
@@ -450,52 +354,6 @@ function! ale#engine#EscapeCommandPart(command_part) abort
return substitute(a:command_part, '%', '%%', 'g')
endfunction
function! s:TemporaryFilename(buffer) abort
let l:filename = fnamemodify(bufname(a:buffer), ':t')
if empty(l:filename)
" If the buffer's filename is empty, create a dummy filename.
let l:ft = getbufvar(a:buffer, '&filetype')
let l:filename = 'file' . ale#filetypes#GuessExtension(l:ft)
endif
" Create a temporary filename, <temp_dir>/<original_basename>
" 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#engine#FormatCommand(buffer, command) 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, '%%', '<<PERCENTS>>', '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', '\=shellescape(l:filename)', 'g')
endif
if l:command =~# '%t'
" Create a temporary filename, <temp_dir>/<original_basename>
" The file itself will not be created by this function.
let l:temporary_file = s:TemporaryFilename(a:buffer)
let l:command = substitute(l:command, '%t', '\=shellescape(l:temporary_file)', 'g')
endif
" Finish formatting so %% becomes %.
let l:command = substitute(l:command, '<<PERCENTS>>', '%', 'g')
return [l:temporary_file, l:command]
endfunction
function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort
if empty(a:temporary_file)
" There is no file, so we didn't create anything.
@@ -522,15 +380,7 @@ function! s:RunJob(options) abort
let l:next_chain_index = a:options.next_chain_index
let l:read_buffer = a:options.read_buffer
let [l:temporary_file, l:command] = ale#engine#FormatCommand(l:buffer, l:command)
if l:read_buffer && 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(l:buffer)
let l:command = l:command . ' < ' . shellescape(l:temporary_file)
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
@@ -538,85 +388,51 @@ function! s:RunJob(options) abort
let l:read_buffer = 0
endif
if !has('nvim')
" 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.
let l:command = has('win32')
\ ? 'cmd /c ' . l:command
\ : split(&shell) + split(&shellcmdflag) + [l:command]
" 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 ==# 'stderr'
let l:job_options.err_cb = function('s:GatherOutput')
elseif l:output_stream ==# '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 = len(s:job_info_map) + 1
let l:job_id = len(s:job_info_map) + 1
while has_key(s:job_info_map, l:job)
let l:job += 1
while has_key(s:job_info_map, l:job_id)
let l:job_id += 1
endwhile
elseif has('nvim')
if l:output_stream ==# 'stderr'
" Read from stderr instead of stdout.
let l:job = jobstart(l:command, {
\ 'on_stderr': function('s:GatherOutputNeoVim'),
\ 'on_exit': function('s:HandleExitNeoVim'),
\})
elseif l:output_stream ==# 'both'
let l:job = jobstart(l:command, {
\ 'on_stdout': function('s:GatherOutputNeoVim'),
\ 'on_stderr': function('s:GatherOutputNeoVim'),
\ 'on_exit': function('s:HandleExitNeoVim'),
\})
else
let l:job = jobstart(l:command, {
\ 'on_stdout': function('s:GatherOutputNeoVim'),
\ 'on_exit': function('s:HandleExitNeoVim'),
\})
endif
else
let l:job_options = {
\ 'in_mode': 'nl',
\ 'out_mode': 'nl',
\ 'err_mode': 'nl',
\ 'close_cb': function('s:HandleExitVim'),
\}
if g:ale_history_enabled
" We only need to capture the exit status if we are going to
" save it in the history. Otherwise, we don't care.
let l:job_options.exit_cb = function('s:HandleExitStatusVim')
endif
if l:output_stream ==# 'stderr'
" Read from stderr instead of stdout.
let l:job_options.err_cb = function('s:GatherOutputVim')
elseif l:output_stream ==# 'both'
" Read from both streams.
let l:job_options.out_cb = function('s:GatherOutputVim')
let l:job_options.err_cb = function('s:GatherOutputVim')
else
let l:job_options.out_cb = function('s:GatherOutputVim')
endif
" Vim 8 will read the stdin from the file's buffer.
let l:job = job_start(l:command, l:job_options)
let l:job_id = ale#job#Start(l:command, l:job_options)
endif
let l:status = 'failed'
let l:job_id = 0
" Only proceed if the job is being run.
if has('nvim')
\ || get(g:, 'ale_run_synchronously') == 1
\ || (l:job !=# 'no process' && job_status(l:job) ==# 'run')
if l:job_id
" Add the job to the list of jobs, so we can track them.
call add(g:ale_buffer_info[l:buffer].job_list, l:job)
call add(g:ale_buffer_info[l:buffer].job_list, l:job_id)
let l:status = 'started'
let l:job_id = s:GetJobID(l:job)
" Store the ID for the job in the map to read back again.
let s:job_info_map[l:job_id] = {
\ 'linter': l:linter,
@@ -636,10 +452,11 @@ function! s:RunJob(options) abort
" 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]) . ' ' . shellescape(l:command[2])
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
\ : l:command
\)
call s:HandleExit(l:job)
call l:job_options.exit_cb(l:job_id, v:shell_error)
endif
endfunction
@@ -730,17 +547,87 @@ function! s:InvokeChain(buffer, linter, chain_index, input) abort
endif
endfunction
function! ale#engine#StopCurrentJobs(buffer, include_lint_file_jobs) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})
let l:new_job_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)
endif
endif
endfor
" Update the List, so it includes only the jobs we still need.
let l:info.job_list = l:new_job_list
" Ignore current LSP commands.
" We should consider cancelling them in future.
let l:info.lsp_command_list = []
endfunction
function! s:CheckWithTSServer(buffer, linter, executable) abort
let l:info = g:ale_buffer_info[a:buffer]
let l:open_documents = l:info.open_lsp_documents
let l:is_open = index(l:open_documents, a:linter.name) >= 0
let l:command = ale#job#PrepareCommand(a:executable)
let l:job_id = ale#lsp#StartProgram(a:executable, l:command, function('s:HandleLSPResponse'))
if !l:job_id
if g:ale_history_enabled
call ale#history#Add(a:buffer, 'failed', l:job_id, l:command)
endif
return
endif
if !l:is_open
if g:ale_history_enabled
call ale#history#Add(a:buffer, 'started', l:job_id, l:command)
endif
call add(l:open_documents, a:linter.name)
call ale#lsp#SendMessageToProgram(
\ a:executable,
\ ale#lsp#tsserver_message#Open(a:buffer),
\)
endif
call ale#lsp#SendMessageToProgram(
\ a:executable,
\ ale#lsp#tsserver_message#Change(a:buffer),
\)
let l:request_id = ale#lsp#SendMessageToProgram(
\ a:executable,
\ ale#lsp#tsserver_message#Geterr(a:buffer),
\)
if l:request_id != 0
let l:info.waiting_for_tsserver = 1
endif
endfunction
function! ale#engine#Invoke(buffer, linter) abort
" Stop previous jobs for the same linter.
call s:StopPreviousJobs(a:buffer, a:linter)
if empty(a:linter.lsp) || a:linter.lsp ==# 'tsserver'
let l:executable = has_key(a:linter, 'executable_callback')
\ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer)
\ : a:linter.executable
let l:executable = has_key(a:linter, 'executable_callback')
\ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer)
\ : a:linter.executable
" Run this program if it can be executed.
if s:IsExecutable(l:executable)
call s:InvokeChain(a:buffer, a:linter, 0, [])
" Run this program if it can be executed.
if s:IsExecutable(l:executable)
if a:linter.lsp ==# 'tsserver'
call s:CheckWithTSServer(a:buffer, a:linter, l:executable)
else
call s:InvokeChain(a:buffer, a:linter, 0, [])
endif
endif
endif
endfunction
@@ -773,13 +660,24 @@ function! ale#engine#WaitForJobs(deadline) abort
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 in l:job_list
if job_status(l:job) ==# 'run'
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
@@ -807,8 +705,8 @@ function! ale#engine#WaitForJobs(deadline) abort
" Check again to see if any jobs are running.
for l:info in values(g:ale_buffer_info)
for l:job in l:info.job_list
if job_status(l:job) ==# 'run'
for l:job_id in l:info.job_list
if ale#job#IsRunning(l:job_id)
let l:has_new_jobs = 1
break
endif

33
autoload/ale/events.vim Normal file
View File

@@ -0,0 +1,33 @@
" Author: w0rp <devw0rp@gmail.com>
function! ale#events#SaveEvent() abort
let l:should_lint = g:ale_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')
endif
endfunction
function! s:LintOnEnter() abort
if g:ale_enabled && g:ale_lint_on_enter && has_key(b:, 'ale_file_changed')
call remove(b:, 'ale_file_changed')
call ale#Queue(0, 'lint_file')
endif
endfunction
function! ale#events#EnterEvent() abort
call s:LintOnEnter()
endfunction
function! ale#events#FileChangedEvent(buffer) abort
call setbufvar(a:buffer, 'ale_file_changed', 1)
if bufnr('') == a:buffer
call s:LintOnEnter()
endif
endfunction

378
autoload/ale/fix.vim Normal file
View File

@@ -0,0 +1,378 @@
" 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
call writefile(l:data.output, 'fix_test_file')
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 writefile(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 ==# 'stderr'
let l:job_options.err_cb = function('s:GatherOutput')
elseif l:output_stream ==# '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 ==# '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 !=# '' && l:fixing_flag !=# '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 ==# ''
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

View File

@@ -0,0 +1,154 @@
" Author: w0rp <devw0rp@gmail.com>
" 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.',
\ },
\ 'autopep8': {
\ 'function': 'ale#fixers#autopep8#Fix',
\ 'suggested_filetypes': ['python'],
\ 'description': 'Fix PEP8 issues with autopep8.',
\ },
\ '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.',
\ },
\ '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.',
\ },
\}
" 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:first_for_filetype = 1
let l:first_generic = 1
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)
if l:first_for_filetype
let l:first_for_filetype = 0
echom 'Try the following fixers appropriate for the filetype:'
echom ''
endif
echom printf('%s - %s', string(l:key), s:entries[l:key].description)
endif
endfor
for l:key in sort(keys(s:entries))
if empty(s:entries[l:key].suggested_filetypes)
if l:first_generic
if !l:first_for_filetype
echom ''
endif
let l:first_generic = 0
echom 'Try the following generic fixers:'
echom ''
endif
echom printf('%s - %s', string(l:key), s:entries[l:key].description)
endif
endfor
if l:first_for_filetype && l:first_generic
echom 'There is nothing in the registry to suggest.'
else
echom ''
echom 'See :help ale-fix-configuration'
endif
endfunction

View File

@@ -0,0 +1,26 @@
" Author: w0rp <devw0rp@gmail.com>
" 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',
\ ['/bin/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

View File

@@ -0,0 +1,37 @@
" Author: w0rp <devw0rp@gmail.com>
" 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#Escape(l:executable)
\ . ' --config ' . ale#Escape(l:config)
\ . ' --fix %t',
\ 'read_temporary_file': 1,
\}
endfunction

View File

@@ -0,0 +1,12 @@
" Author: w0rp <devw0rp@gmail.com>
" 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

View File

@@ -0,0 +1,25 @@
" Author: w0rp <devw0rp@gmail.com>
" 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

View File

@@ -0,0 +1,26 @@
" Author: w0rp <devw0rp@gmail.com>
" 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',
\ ['/bin/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

View File

@@ -0,0 +1,26 @@
" Author: tunnckoCore (Charlike Mike Reagent) <mameto2011@gmail.com>,
" w0rp <devw0rp@gmail.com>
" 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

View File

@@ -0,0 +1,26 @@
" Author: tunnckoCore (Charlike Mike Reagent) <mameto2011@gmail.com>,
" w0rp <devw0rp@gmail.com>
" 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

View File

@@ -0,0 +1,19 @@
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')
return ale#Escape(l:executable) . l:exec_args
\ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '')
\ . ' --auto-correct %t'
endfunction
function! ale#fixers#rubocop#Fix(buffer) abort
return {
\ 'command': ale#fixers#rubocop#GetCommand(a:buffer),
\ 'read_temporary_file': 1,
\}
endfunction

View File

@@ -0,0 +1,26 @@
" Author: w0rp <devw0rp@gmail.com>
" 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',
\ ['/bin/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)
\ ? ' --style ' . ale#Escape(l:config)
\ : ''
return {
\ 'command': ale#Escape(l:executable) . ' --no-local-style' . l:config_options,
\}
endfunction

View File

@@ -4,16 +4,17 @@ 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 = '^\[.\{-}:\(\d\+\)\]: (\(.\{-}\)) \(.\+\)'
let l:pattern = '\v^\[(.+):(\d+)\]: \(([a-z]+)\) (.+)$'
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[3] . ' (' . l:match[2] . ')',
\ 'type': l:match[2] ==# 'error' ? 'E' : 'W',
\})
if ale#path#IsBufferPath(a:buffer, l:match[1])
call add(l:output, {
\ 'lnum': str2nr(l:match[2]),
\ 'type': l:match[3] ==# 'error' ? 'E' : 'W',
\ 'text': l:match[4],
\})
endif
endfor
return l:output

View File

@@ -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

View File

@@ -42,7 +42,7 @@ function! ale#handlers#css#HandleStyleLintFormat(buffer, lines) abort
" 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+([^ ]+)$'
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)

View File

@@ -0,0 +1,110 @@
" Author: w0rp <devw0rp@gmail.com>
" 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)
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)
if ale#Has('win32') && l:executable =~? 'eslint\.js$'
" For Windows, if we detect an eslint.js script, we need to execute
" it with node, or the file can be opened with a text editor.
let l:head = 'node ' . ale#Escape(l:executable)
else
let l:head = ale#Escape(l:executable)
endif
let l:options = ale#Var(a:buffer, 'javascript_eslint_options')
return l:head
\ . (!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]
" 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 ==# '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

View File

@@ -6,10 +6,11 @@ function! ale#handlers#haskell#HandleGHCFormat(buffer, lines) abort
"
"Appoint/Lib.hs:8:1: warning:
"Appoint/Lib.hs:8:1:
let l:pattern = '^[^:]\+:\(\d\+\):\(\d\+\):\(.*\)\?$'
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)
@@ -30,21 +31,29 @@ function! ale#handlers#haskell#HandleGHCFormat(buffer, lines) abort
continue
endif
let l:errors = matchlist(l:match[3], '\(warning:\|error:\)\(.*\)')
if len(l:errors) > 0
let l:type = l:errors[1]
let l:text = l:errors[2]
else
let l:type = ''
let l:text = l:match[3]
if !ale#path#IsBufferPath(a:buffer, l:match[1])
continue
endif
let l:type = l:type ==# '' ? 'E' : toupper(l:type[0])
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] ==# ' ' ? l:match[4][1:] : l:match[4]
endif
if l:ghc_type ==? 'Warning'
let l:type = 'W'
else
let l:type = 'E'
endif
call add(l:output, {
\ 'lnum': l:match[1] + 0,
\ 'col': l:match[2] + 0,
\ 'lnum': l:match[2] + 0,
\ 'col': l:match[3] + 0,
\ 'text': l:text,
\ 'type': l:type,
\})

View File

@@ -1,37 +0,0 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Error handling for flake8, etc.
function! ale#handlers#python#HandlePEP8Format(buffer, lines) abort
" Matches patterns line the following:
"
" Matches patterns line the following:
"
" stdin:6:6: E111 indentation is not a multiple of four
" test.yml:35: [EANSIBLE0002] Trailing whitespace
let l:pattern = '\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 ==# 'W291' || l:code ==# 'W293' || l:code ==# 'EANSIBLE002')
\ && !ale#Var(a:buffer, 'warn_about_trailing_whitespace')
" Skip warnings for trailing whitespace if the option is off.
continue
endif
if l:code ==# 'I0011'
" Skip 'Locally disabling' message
continue
endif
call add(l:output, {
\ 'lnum': l:match[1] + 0,
\ 'col': l:match[2] + 0,
\ 'text': l:code . ': ' . l:match[4],
\ 'type': l:code[:0] ==# 'E' ? 'E' : 'W',
\})
endfor
return l:output
endfunction

View File

@@ -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

View File

@@ -8,13 +8,13 @@ if !exists('g:ale_rust_ignore_error_codes')
endif
" returns: a list [lnum, col] with the location of the error or []
function! s:FindErrorInExpansion(span, file_name) abort
if a:span.file_name ==# a:file_name
return [a:span.line_start, a:span.byte_start]
function! s: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:file_name)
return s:FindErrorInExpansion(a:span.expansion.span, a:buffer)
endif
return []
@@ -22,7 +22,6 @@ endfunction
" A handler function which accepts a file name, to make unit testing easier.
function! ale#handlers#rust#HandleRustErrorsForFile(buffer, full_filename, lines) abort
let l:filename = fnamemodify(a:full_filename, ':t')
let l:output = []
for l:errorline in a:lines
@@ -46,27 +45,29 @@ function! ale#handlers#rust#HandleRustErrorsForFile(buffer, full_filename, lines
endif
for l:span in l:error.spans
let l:span_filename = fnamemodify(l:span.file_name, ':t')
if (
\ l:span.is_primary
\ && (l:span_filename ==# l:filename || l:span_filename ==# '<anon>')
\ && (ale#path#IsBufferPath(a:buffer, l:span.file_name) || l:span.file_name ==# '<anon>')
\)
call add(l:output, {
\ 'lnum': l:span.line_start,
\ 'end_lnum': l:span.line_end,
\ 'col': l:span.byte_start,
\ 'text': l:error.message,
\ '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, l:filename)
let l:root_cause = s:FindErrorInExpansion(l:span, a:buffer)
if !empty(l:root_cause)
call add(l:output, {
\ 'lnum': l:root_cause[0],
\ 'col': l:root_cause[1],
\ '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]),
\})

View File

@@ -6,16 +6,52 @@ 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
" This map holds highlights to be set when buffers are opened.
" We can only set highlights for whatever the current buffer is, so we will
" wait until the buffer is entered again to show the highlights, unless
" the buffer is in focus when linting completes.
let s:buffer_highlights = {}
let s:buffer_restore_map = {}
" 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
function! ale#highlight#UnqueueHighlights(buffer) abort
if has_key(s:buffer_highlights, a:buffer)
@@ -28,24 +64,16 @@ function! ale#highlight#UnqueueHighlights(buffer) abort
endfunction
function! s:GetALEMatches() abort
let l:list = []
for l:match in getmatches()
if l:match['group'] ==# 'ALEError' || l:match['group'] ==# 'ALEWarning'
call add(l:list, l:match)
endif
endfor
return l:list
return filter(getmatches(), 'v:val.group =~# ''^ALE''')
endfunction
function! s:GetCurrentMatchIDs(loclist) abort
let l:current_id_map = {}
for l:item in a:loclist
if has_key(l:item, 'match_id')
let l:current_id_map[l:item.match_id] = 1
endif
for l:id in get(l:item, 'match_id_list', [])
let l:current_id_map[l:id] = 1
endfor
endfor
return l:current_id_map
@@ -73,7 +101,7 @@ function! ale#highlight#UpdateHighlights() abort
endif
" Remove anything with a current match_id
call filter(l:loclist, '!has_key(v:val, ''match_id'')')
call filter(l:loclist, '!has_key(v:val, ''match_id_list'')')
" Restore items from the map of hidden items,
" if we don't have some new items to set already.
@@ -83,15 +111,34 @@ function! ale#highlight#UpdateHighlights() abort
if g:ale_enabled
for l:item in l:loclist
let l:col = l:item.col
let l:group = l:item.type ==# 'E' ? 'ALEError' : 'ALEWarning'
let l:line = l:item.lnum
let l:size = 1
if l:item.type ==# 'W'
if get(l:item, 'sub_type', '') ==# 'style'
let l:group = 'ALEStyleWarning'
else
let l:group = 'ALEWarning'
endif
elseif l:item.type ==# 'I'
let l:group = 'ALEInfo'
elseif get(l:item, 'sub_type', '') ==# 'style'
let l:group = 'ALEStyleError'
else
let l:group = 'ALEError'
endif
" Rememeber the match ID for the item.
" This ID will be used to preserve loclist items which are set
" many times.
let l:item.match_id = matchaddpos(l:group, [[l:line, l:col, l:size]])
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.
"
" We will remember the IDs we set, so we can preserve some
" highlights when linting buffers after linting files.
let l:item.match_id_list = map(
\ ale#highlight#CreatePositions(l:line, l:col, l:end_line, l:end_col),
\ 'matchaddpos(l:group, v:val)'
\)
endfor
endif
endfunction
@@ -104,8 +151,8 @@ function! ale#highlight#BufferHidden(buffer) abort
" Remove match_ids, as they must be re-calculated when buffers are
" shown again.
for l:item in l:loclist
if has_key(l:item, 'match_id')
call remove(l:item, 'match_id')
if has_key(l:item, 'match_id_list')
call remove(l:item, 'match_id_list')
endif
endfor

View File

@@ -26,6 +26,11 @@ function! ale#history#Add(buffer, status, job_id, command) abort
endfunction
function! s:FindHistoryItem(buffer, job_id) abort
" Stop immediately if there's nothing set up for the buffer.
if !has_key(g:ale_buffer_info, a:buffer)
return {}
endif
" 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(g:ale_buffer_info[a:buffer].history[:])

311
autoload/ale/job.vim Normal file
View File

@@ -0,0 +1,311 @@
" Author: w0rp <devw0rp@gmail.com>
" 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 ==# '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 ==# '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 ==# '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) ==# '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)) ==# '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 !=# 'nl' && a:options.mode !=# '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
" 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) ==# '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)) ==# '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

View File

@@ -4,7 +4,7 @@
let s:linters = {}
" Default filetype aliaes.
" Default filetype aliases.
" The user defined aliases will be merged with this Dictionary.
let s:default_ale_linter_aliases = {
\ 'Dockerfile': 'dockerfile',
@@ -23,7 +23,7 @@ let s:default_ale_linter_aliases = {
" rpmlint is disabled by default because it can result in code execution.
let s:default_ale_linters = {
\ 'csh': ['shell'],
\ 'go': ['go build', 'gofmt', 'golint', 'gosimple', 'go vet', 'staticcheck'],
\ 'go': ['gofmt', 'golint', 'go vet'],
\ 'help': [],
\ 'rust': ['cargo'],
\ 'spec': [],
@@ -50,7 +50,9 @@ function! ale#linter#PreProcess(linter) abort
endif
let l:obj = {
\ 'add_newline': get(a:linter, 'add_newline', 0),
\ 'name': get(a:linter, 'name'),
\ 'lsp': get(a:linter, 'lsp', ''),
\ 'callback': get(a:linter, 'callback'),
\}
@@ -62,7 +64,27 @@ function! ale#linter#PreProcess(linter) abort
throw '`callback` must be defined with a callback to accept output'
endif
if has_key(a:linter, 'executable_callback')
let l:needs_executable = 0
let l:needs_address = 0
let l:needs_command = 0
if l:obj.lsp ==# 'tsserver'
let l:needs_executable = 1
elseif l:obj.lsp ==# 'lsp'
let l:needs_address = 1
elseif !empty(l:obj.lsp)
throw '`lsp` must be either `''lsp''` or `''tsserver''` if defined'
else
let l:needs_executable = 1
let l:needs_command = 1
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 == ''lsp'''
endif
elseif has_key(a:linter, 'executable_callback')
let l:obj.executable_callback = a:linter.executable_callback
if !s:IsCallback(l:obj.executable_callback)
@@ -78,7 +100,13 @@ function! ale#linter#PreProcess(linter) abort
throw 'Either `executable` or `executable_callback` must be defined'
endif
if has_key(a:linter, 'command_chain')
if !l:needs_command
if has_key(a:linter, 'command')
\|| has_key(a:linter, 'command_callback')
\|| has_key(a:linter, 'command_chain')
throw '`command` and `command_callback` and `command_chain` cannot be used when `lsp` is set'
endif
elseif has_key(a:linter, 'command_chain')
let l:obj.command_chain = a:linter.command_chain
if type(l:obj.command_chain) != type([])
@@ -138,6 +166,20 @@ function! ale#linter#PreProcess(linter) abort
\ . 'should be set'
endif
if !l:needs_address
if has_key(a:linter, 'address_callback')
throw '`address_callback` cannot be used when lsp != ''lsp'''
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
let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout')
if type(l:obj.output_stream) != type('')
@@ -164,6 +206,13 @@ function! ale#linter#PreProcess(linter) abort
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
@@ -242,7 +291,7 @@ function! s:GetLinterNames(original_filetype) abort
endfunction
function! ale#linter#Get(original_filetypes) abort
let l:combined_linters = []
let l:possibly_duplicated_linters = []
" Handle dot-seperated filetypes.
for l:original_filetype in split(a:original_filetypes, '\.')
@@ -256,14 +305,33 @@ function! ale#linter#Get(original_filetypes) abort
elseif type(l:linter_names) == type([])
" Select only the linters we or the user has specified.
for l:linter in l:all_linters
if index(l:linter_names, l:linter.name) >= 0
call add(l:filetype_linters, l:linter)
endif
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:combined_linters, l:filetype_linters)
call extend(l:possibly_duplicated_linters, l:filetype_linters)
endfor
return l:combined_linters
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

View File

@@ -12,49 +12,60 @@ function! ale#list#IsQuickfixOpen() abort
endfunction
function! ale#list#SetLists(buffer, loclist) abort
let l:title = expand('#' . a:buffer . ':p')
if g:ale_set_quickfix
call setqflist(a:loclist)
if has('nvim')
call setqflist(a:loclist, ' ', l:title)
else
call setqflist(a:loclist)
call setqflist([], 'r', {'title': l:title})
endif
elseif g:ale_set_loclist
" If windows support is off, bufwinid() may not exist.
if exists('*bufwinid')
" Set the results on the window for the buffer.
call setloclist(bufwinid(str2nr(a:buffer)), a:loclist)
" We'll set result in the current window, which might not be correct,
" but is better than nothing.
let l:win_id = exists('*bufwinid') ? bufwinid(str2nr(a:buffer)) : 0
if has('nvim')
call setloclist(l:win_id, a:loclist, ' ', l:title)
else
" Set the results in the current window.
" This may not be the same window we ran the linters for, but
" it's better than nothing.
call setloclist(0, a:loclist)
call setloclist(l:win_id, a:loclist)
call setloclist(l:win_id, [], 'r', {'title': l:title})
endif
endif
" If we don't auto-open lists, bail out here.
if !g:ale_open_list && !g:ale_keep_list_window_open
return
endif
" If we have errors in our list, open the list. Only if it isn't already open
if len(a:loclist) > 0 || g:ale_keep_list_window_open
if (g:ale_open_list && !empty(a:loclist)) || g:ale_keep_list_window_open
let l:winnr = winnr()
if !ale#list#IsQuickfixOpen()
if g:ale_set_quickfix
copen
elseif g:ale_set_loclist
lopen
endif
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 !=# winnr()
wincmd p
endif
endif
endfunction
" Only close if the list is totally empty (relying on Vim's state, not our
" own). This keeps us from closing the window when other plugins have
" populated it.
elseif !g:ale_keep_list_window_open && g:ale_set_quickfix && len(getqflist()) == 0
cclose
elseif !g:ale_keep_list_window_open && len(getloclist(0)) == 0
function! ale#list#CloseWindowIfNeeded(buffer) abort
if g:ale_keep_list_window_open || !g:ale_open_list
return
endif
" 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
elseif g:ale_set_loclist && empty(getloclist(0))
lclose
endif
endfunction

View File

@@ -64,3 +64,16 @@ function! ale#loclist_jumping#Jump(direction, wrap) abort
call cursor(l:nearest)
endif
endfunction
function! ale#loclist_jumping#JumpToIndex(index) abort
let l:info = get(g:ale_buffer_info, bufnr('%'), {'loclist': []})
let l:loclist = l:info.loclist
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

285
autoload/ale/lsp.vim Normal file
View File

@@ -0,0 +1,285 @@
" Author: w0rp <devw0rp@gmail.com>
" 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
" data: The message data received so far.
" address: An address only set for server connections.
" executable: An executable only set for program connections.
" job: A job ID only set for running programs.
let l:conn = {
\ 'data': '',
\ 'address': '',
\ 'executable': '',
\ 'job_id': -1,
\}
call add(s:connections, l:conn)
return l:conn
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! 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 has_key(a:conn, 'callback')
call ale#util#GetFunction(a:conn.callback)(l:response)
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 = filter(s:connections[:], 'v:val.address ==# l:address')[0]
call ale#lsp#HandleMessage(l:conn, a:message)
endfunction
function! s:HandleCommandMessage(job_id, message) abort
let l:conn = filter(s:connections[:], 'v:val.job_id == a:job_id')[0]
call ale#lsp#HandleMessage(l:conn, a:message)
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, callback) abort
if !executable(a:executable)
return 0
endif
let l:matches = filter(s:connections[:], 'v:val.executable ==# a:executable')
" Get the current connection or a new one.
let l:conn = !empty(l:matches) ? l:matches[0] : s:NewConnection()
let l:conn.executable = a:executable
let l:conn.callback = a:callback
if !ale#job#IsRunning(l:conn.job_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.job_id
endif
if l:job_id <= 0
return 0
endif
let l:conn.job_id = l:job_id
return l:job_id
endfunction
" Send a message to a server with a given executable, and a command for
" running the executable.
"
" 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#SendMessageToProgram(executable, message) abort
let [l:id, l:data] = ale#lsp#CreateMessageData(a:message)
let l:matches = filter(s:connections[:], 'v:val.executable ==# a:executable')
" No connection is currently open.
if empty(l:matches)
return 0
endif
" Get the current connection or a new one.
let l:conn = l:matches[0]
let l:conn.executable = a:executable
if get(l:conn, 'job_id', 0) == 0
return 0
endif
call ale#job#SendRaw(l:conn.job_id, l:data)
return l:id == 0 ? -1 : l:id
endfunction
" Connect to an address and set up a callback for handling responses.
function! ale#lsp#ConnectToAddress(address, callback) abort
let l:matches = filter(s:connections[:], 'v:val.address ==# a:address')
" Get the current connection or a new one.
let l:conn = !empty(l:matches) ? l:matches[0] : s:NewConnection()
if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) !=# 'open'
let l:conn.channnel = ch_open(a:address, {
\ 'mode': 'raw',
\ 'waittime': 0,
\ 'callback': function('s:HandleChannelMessage'),
\})
endif
if ch_status(l:conn.channnel) ==# 'fail'
return 0
endif
let l:conn.callback = a:callback
return 1
endfunction
" Send a message to a server at a given address.
" 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#SendMessageToAddress(address, message) abort
if a:0 > 1
throw 'Too many arguments!'
endif
if !a:message[0] && a:0 == 0
throw 'A callback must be set for messages which are not notifications!'
endif
let [l:id, l:data] = ale#lsp#CreateMessageData(a:message)
let l:matches = filter(s:connections[:], 'v:val.address ==# a:address')
" No connection is currently open.
if empty(l:matches)
return 0
endif
let l:conn = l:matches[0]
if ch_status(l:conn.channnel) !=# 'open'
return 0
endif
" Send the message to the server
call ch_sendraw(l:conn.channel, l:data)
return l:id == 0 ? -1 : l:id
endfunction

View File

@@ -0,0 +1,64 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Language Server Protocol message implementations
"
" Messages in this movie will be returned in the format
" [is_notification, method_name, params?]
function! ale#lsp#message#Initialize(root_uri) abort
" TODO: Define needed capabilities.
return [0, 'initialize', {
\ 'processId': getpid(),
\ 'rootUri': a:root_uri,
\ '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(uri, language_id, version, text) abort
return [1, 'textDocument/didOpen', {
\ 'textDocument': {
\ 'uri': a:uri,
\ 'languageId': a:language_id,
\ 'version': a:version,
\ 'text': a:text,
\ },
\}]
endfunction
function! ale#lsp#message#DidChange(uri, version, text) abort
" For changes, we simply send the full text of the document to the server.
return [1, 'textDocument/didChange', {
\ 'textDocument': {
\ 'uri': a:uri,
\ 'version': a:version,
\ },
\ 'contentChanges': [{'text': a:text}]
\}]
endfunction
function! ale#lsp#message#DidSave(uri) abort
return [1, 'textDocument/didSave', {
\ 'textDocument': {
\ 'uri': a:uri,
\ },
\}]
endfunction
function! ale#lsp#message#DidClose(uri) abort
return [1, 'textDocument/didClose', {
\ 'textDocument': {
\ 'uri': a:uri,
\ },
\}]
endfunction

View File

@@ -0,0 +1,67 @@
" Author: w0rp <devw0rp@gmail.com>
" 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(params) abort
let l:filename = a:params.uri
let l:loclist = []
for l:diagnostic in a: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:filename, 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

View File

@@ -0,0 +1,37 @@
" Author: w0rp <devw0rp@gmail.com>
" 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

22
autoload/ale/node.vim Normal file
View File

@@ -0,0 +1,22 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Functions for working with Node executables.
" 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

Some files were not shown because too many files have changed in this diff Show More