mirror of
https://github.com/dense-analysis/ale.git
synced 2025-12-07 21:24:33 +08:00
Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cedc3e1a1f | ||
|
|
30aef6b19a | ||
|
|
bd0f31147e | ||
|
|
74f2afbe2e | ||
|
|
d438f8f268 | ||
|
|
2f2af4315b | ||
|
|
a040878ebc | ||
|
|
a72c1edfcc | ||
|
|
acda19776a | ||
|
|
e9a1cd600a | ||
|
|
0819c4cd56 | ||
|
|
448600fe4f | ||
|
|
bb1f413bf3 | ||
|
|
0302d2a328 | ||
|
|
e51272e277 | ||
|
|
14cca6d115 | ||
|
|
16ba9bd680 | ||
|
|
499bf63dc3 | ||
|
|
b9d91f0e9b | ||
|
|
d5c9a4eb87 | ||
|
|
d2ec53f817 | ||
|
|
539a76c5ae | ||
|
|
1917e9157c | ||
|
|
3828ea5b26 | ||
|
|
8b557f346c | ||
|
|
7f6e5dc65b | ||
|
|
7d73a1602b | ||
|
|
a9b29fef28 | ||
|
|
c2f69b7750 | ||
|
|
93473a4101 | ||
|
|
229a1c092a | ||
|
|
492260c967 | ||
|
|
c2138a2656 | ||
|
|
ec3ddce4ac | ||
|
|
2c6b571e66 | ||
|
|
4eaa990fe8 | ||
|
|
8da5641355 | ||
|
|
d1e23f7295 | ||
|
|
dc647fcc7f | ||
|
|
e98560a349 | ||
|
|
026c4f304e | ||
|
|
1ea61162a0 | ||
|
|
fbf8ccb882 | ||
|
|
47401a6eda | ||
|
|
ce2bfa88eb | ||
|
|
40f6ee4c39 | ||
|
|
93539e10de | ||
|
|
dab6f39eb0 | ||
|
|
ab534c2995 | ||
|
|
d2806fad60 | ||
|
|
50d952b07d | ||
|
|
a105aa90a5 | ||
|
|
b96f5845ed | ||
|
|
b44bd4e24f | ||
|
|
66b9d025bb | ||
|
|
11e17669d3 | ||
|
|
af1ab0b5a9 | ||
|
|
8ab103504f | ||
|
|
7e79018b8c | ||
|
|
629ff513ec | ||
|
|
fb682be199 | ||
|
|
f814be45b1 | ||
|
|
c2258e3684 | ||
|
|
e455d8219e | ||
|
|
25e4d1a353 | ||
|
|
07af1799b1 | ||
|
|
f6b0a28cba | ||
|
|
3442e58c8b | ||
|
|
f472e04b09 | ||
|
|
6f858590c2 | ||
|
|
e8cc40b139 | ||
|
|
ba83c476cd | ||
|
|
ebbfb64221 | ||
|
|
aef58f598c | ||
|
|
86c17e1834 | ||
|
|
99263bdda4 | ||
|
|
5146332206 | ||
|
|
64ad51048d | ||
|
|
62862c3347 | ||
|
|
8ce6d47ef6 | ||
|
|
14d86f8763 | ||
|
|
04190cbcfe | ||
|
|
d8d96fb0eb | ||
|
|
e93dba351c | ||
|
|
817b6bbd2d | ||
|
|
1eec446620 | ||
|
|
f61c6d4c0e | ||
|
|
71257979aa | ||
|
|
2ac670f293 | ||
|
|
25e1aa43b8 | ||
|
|
9ee7a6d57c | ||
|
|
7517fd8226 | ||
|
|
edddb1910b | ||
|
|
8f9828e5bf | ||
|
|
11e38efa83 | ||
|
|
f30652a98f | ||
|
|
e4d886d4a7 | ||
|
|
eeea72e167 | ||
|
|
e4708c356b | ||
|
|
7db805b0cd | ||
|
|
ef86a8a389 | ||
|
|
9dadde190e | ||
|
|
e88eb6c415 | ||
|
|
3c5156d4a4 | ||
|
|
a0e0408ecc | ||
|
|
d41f15bcbc | ||
|
|
fcc17dffbe | ||
|
|
02ac28dbe6 | ||
|
|
f6109d2e4e | ||
|
|
3be60bf034 | ||
|
|
1a62e95733 | ||
|
|
dcbab18a35 | ||
|
|
33b0852c84 | ||
|
|
fcb5718712 | ||
|
|
2c89a4c98a | ||
|
|
7c68889bbc | ||
|
|
955452816a | ||
|
|
8c5a7fb2ca | ||
|
|
2b9e320370 | ||
|
|
e4649b50d6 | ||
|
|
fbd76fb63d | ||
|
|
0d3d5657ff | ||
|
|
d5ae9b50ea | ||
|
|
81f27a99c8 | ||
|
|
735a6a2a88 | ||
|
|
5e4c302b5b | ||
|
|
88948e0ee3 | ||
|
|
42efd51723 | ||
|
|
5eb80f03a2 | ||
|
|
ab50b3a88a | ||
|
|
676a4049b3 | ||
|
|
fd49f7df90 | ||
|
|
e72dc1acd5 | ||
|
|
a90cf62995 | ||
|
|
5d32366616 | ||
|
|
6fe8105a0e | ||
|
|
b9f4b0373a | ||
|
|
6ec965c8e4 | ||
|
|
fa02b1d259 | ||
|
|
bc317a7be5 | ||
|
|
bfad5c9dc4 | ||
|
|
7a89d0c97e | ||
|
|
50fc4b5521 | ||
|
|
7ed343965c | ||
|
|
945ed7d4e7 | ||
|
|
505b591b22 | ||
|
|
dc775f236c | ||
|
|
c17346d402 | ||
|
|
aca5a00fb7 | ||
|
|
8e997ac231 | ||
|
|
5825a65627 | ||
|
|
62dae1cc6b | ||
|
|
8e8113ff6f | ||
|
|
c4f22186bd | ||
|
|
f71c60ede3 | ||
|
|
b934dc52b6 | ||
|
|
00d3141962 | ||
|
|
28a62aab28 | ||
|
|
9460e58c3b | ||
|
|
c77cf0e518 | ||
|
|
7fe1119cf1 | ||
|
|
c89587785b | ||
|
|
fb07971290 | ||
|
|
3840cebbc4 | ||
|
|
c31cd12bdd | ||
|
|
6f76a840f0 | ||
|
|
6b1f0c5d1f | ||
|
|
aabddea6dd | ||
|
|
5ee2ada8e9 | ||
|
|
da8fd647bf | ||
|
|
43098171ac | ||
|
|
ed8f79987d | ||
|
|
92ade713f2 | ||
|
|
58880f33be | ||
|
|
1e72a7a130 | ||
|
|
4526018344 | ||
|
|
2e442a2cab | ||
|
|
71bf2bfb94 | ||
|
|
c8ce15d9f1 | ||
|
|
b67c103d06 | ||
|
|
3a289dab6b | ||
|
|
23ee0d0992 | ||
|
|
57ad32f986 | ||
|
|
d511b02ebe | ||
|
|
ab44d05508 | ||
|
|
3532257a1a | ||
|
|
f92bbab8cf | ||
|
|
bf8bf06681 | ||
|
|
74d879952c | ||
|
|
3530180a73 | ||
|
|
59d9f5d458 | ||
|
|
ad52b9630d | ||
|
|
ed097cfcbd | ||
|
|
e80389f8d4 | ||
|
|
74691269ce | ||
|
|
18467a55b5 | ||
|
|
e6b132c915 | ||
|
|
4214832ae2 | ||
|
|
1f4d1800e0 | ||
|
|
ea1627f5ce | ||
|
|
0b743389e5 | ||
|
|
05bab00c3c | ||
|
|
8ebd15a54d | ||
|
|
7d8390d43e | ||
|
|
0d797c203f | ||
|
|
04e0dda17a | ||
|
|
65fbf1cdff | ||
|
|
d012fd1f09 | ||
|
|
0646b2861f | ||
|
|
0f0d1709c5 | ||
|
|
455793dfd9 | ||
|
|
af6470c8d0 | ||
|
|
bb1f045d59 | ||
|
|
cdf0fb39e5 | ||
|
|
3ca70cb841 | ||
|
|
c41afa2b0d | ||
|
|
2fd4db91ce | ||
|
|
05970e1b28 | ||
|
|
6299da7bd3 | ||
|
|
3f926de76b | ||
|
|
f7fc54262d | ||
|
|
164c4efb32 | ||
|
|
5790df1272 | ||
|
|
372a4dfd7e | ||
|
|
3443994a52 | ||
|
|
e2860f8a26 | ||
|
|
1b53fa841b | ||
|
|
9ca51ed035 | ||
|
|
a65358cfce | ||
|
|
9185a0d2e5 | ||
|
|
8712aee5dc | ||
|
|
4c5e97dd1c | ||
|
|
78a7df52c0 | ||
|
|
42155049a5 | ||
|
|
11a50b2580 | ||
|
|
9baae52d1a | ||
|
|
fa3a4b3ba2 | ||
|
|
113f53a5d2 | ||
|
|
3f33dc7d98 | ||
|
|
d4466d4be7 | ||
|
|
204e3ca36b | ||
|
|
5a947933d7 | ||
|
|
2bafdb7e5a | ||
|
|
07b2542c0d | ||
|
|
fa54f7af97 | ||
|
|
2f96f26038 | ||
|
|
ac707be619 | ||
|
|
4e5a848d95 | ||
|
|
6ea00af689 | ||
|
|
28c6ec9cad | ||
|
|
d7bdaeeab0 | ||
|
|
cd79ced839 |
11
.eslintrc.js
11
.eslintrc.js
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: "module",
|
||||
},
|
||||
rules: {
|
||||
semi: 'error',
|
||||
'space-infix-ops': 'warn',
|
||||
radix: 'error',
|
||||
}
|
||||
}
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -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
|
||||
|
||||
@@ -7,4 +7,4 @@ branches:
|
||||
- master
|
||||
language: python
|
||||
script: |
|
||||
make test
|
||||
./run-tests
|
||||
|
||||
3
CODE_OF_CONDUCT.md
Normal file
3
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Codes of conduct are totally unnecessary and dumb.
|
||||
|
||||
Just don't be a jerk and have fun.
|
||||
11
Dockerfile
11
Dockerfile
@@ -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
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
|
||||
53
Makefile
53
Makefile
@@ -1,53 +0,0 @@
|
||||
SHELL := /usr/bin/env bash
|
||||
IMAGE ?= w0rp/ale
|
||||
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 $(IMAGE)
|
||||
|
||||
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
|
||||
68
README.md
68
README.md
@@ -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()}
|
||||
```
|
||||
|
||||

|
||||

|
||||
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`.
|
||||
|
||||
@@ -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',
|
||||
\})
|
||||
48
ale_linters/ansible/ansible_lint.vim
Normal file
48
ale_linters/ansible/ansible_lint.vim
Normal 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',
|
||||
\})
|
||||
@@ -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 ' . fnameescape(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
26
ale_linters/awk/gawk.vim
Normal 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'
|
||||
\})
|
||||
@@ -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 ' . fnameescape(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', {
|
||||
|
||||
@@ -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 ' . fnameescape(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', {
|
||||
|
||||
@@ -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 ' . fnameescape(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', {
|
||||
|
||||
37
ale_linters/cpp/clangcheck.vim
Normal file
37
ale_linters/cpp/clangcheck.vim
Normal 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,
|
||||
\})
|
||||
@@ -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
|
||||
|
||||
15
ale_linters/cpp/cpplint.vim
Normal file
15
ale_linters/cpp/cpplint.vim
Normal 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,
|
||||
\})
|
||||
@@ -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 ' . fnameescape(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', {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=' . fnameescape(l:csslintrc)
|
||||
\ ? '--config=' . ale#Escape(l:csslintrc)
|
||||
\ : ''
|
||||
|
||||
return 'csslint --format=compact ' . l:config_option . ' %t'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,7 +31,7 @@ function! ale_linters#d#dmd#DUBCommand(buffer) abort
|
||||
" To support older dub versions, we just change the directory to
|
||||
" the directory where we found the dub config, and then run `dub describe`
|
||||
" from that directory.
|
||||
return 'cd ' . fnameescape(fnamemodify(l:dub_file, ':h'))
|
||||
return 'cd ' . ale#Escape(fnamemodify(l:dub_file, ':h'))
|
||||
\ . ' && dub describe --import-paths'
|
||||
endfunction
|
||||
|
||||
@@ -42,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' . fnameescape(l:line))
|
||||
call add(l:import_list, '-I' . ale#Escape(l:line))
|
||||
endif
|
||||
endfor
|
||||
|
||||
|
||||
@@ -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 ' . fnameescape(l:root_dir) . ' && '
|
||||
let l:dir_set_cmd = 'cd ' . ale#Escape(l:root_dir) . ' && '
|
||||
endif
|
||||
|
||||
" The elm-make compiler, at the time of this writing, uses '/dev/null' as
|
||||
" a sort of flag to tell the compiler not to generate an output file,
|
||||
" which is why this is hard coded here.
|
||||
" 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
|
||||
|
||||
@@ -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 ' . fnameescape(l:output_file)
|
||||
return 'erlc -o ' . ale#Escape(l:output_file)
|
||||
\ . ' ' . ale#Var(a:buffer, 'erlang_erlc_options')
|
||||
\ . ' %t'
|
||||
endfunction
|
||||
|
||||
41
ale_linters/fuse/fusionlint.vim
Normal file
41
ale_linters/fuse/fusionlint.vim
Normal 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',
|
||||
\})
|
||||
@@ -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')
|
||||
\ . ' ' . fnameescape(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[0])
|
||||
continue
|
||||
endif
|
||||
|
||||
call add(l:output, {
|
||||
\ 'lnum': l:match[2] + 0,
|
||||
\ 'col': l:match[3] + 0,
|
||||
|
||||
@@ -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
|
||||
@@ -35,13 +26,23 @@ function! ale_linters#handlebars#embertemplatelint#Handle(buffer, lines) abort
|
||||
let l:file_errors = values(l:input_json)[0]
|
||||
|
||||
for l:error in l:file_errors
|
||||
call add(l:output, {
|
||||
\ 'bufnr': a:buffer,
|
||||
\ 'lnum': l:error.line,
|
||||
\ 'col': l:error.column,
|
||||
\ 'text': l:error.rule . ': ' . l:error.message,
|
||||
\ 'type': l:error.severity == 1 ? 'W' : 'E',
|
||||
\})
|
||||
if has_key(l:error, 'fatal')
|
||||
call add(l:output, {
|
||||
\ 'bufnr': a:buffer,
|
||||
\ 'lnum': 1,
|
||||
\ 'col': 1,
|
||||
\ 'text': l:error.message,
|
||||
\ 'type': l:error.severity == 1 ? 'W' : 'E',
|
||||
\})
|
||||
else
|
||||
call add(l:output, {
|
||||
\ 'bufnr': a:buffer,
|
||||
\ 'lnum': l:error.line,
|
||||
\ 'col': l:error.column,
|
||||
\ 'text': l:error.rule . ': ' . l:error.message,
|
||||
\ 'type': l:error.severity == 1 ? 'W' : 'E',
|
||||
\})
|
||||
endif
|
||||
endfor
|
||||
|
||||
return l:output
|
||||
|
||||
16
ale_linters/haskell/ghc-mod.vim
Normal file
16
ale_linters/haskell/ghc-mod.vim
Normal 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',
|
||||
\})
|
||||
@@ -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
|
||||
|
||||
46
ale_linters/java/checkstyle.vim
Normal file
46
ale_linters/java/checkstyle.vim
Normal 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',
|
||||
\})
|
||||
@@ -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')
|
||||
|
||||
@@ -41,23 +20,17 @@ 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 map(l:class_paths, 'fnameescape(v:val)')
|
||||
call extend(
|
||||
\ l:class_paths,
|
||||
\ split(ale#Var(a:buffer, 'java_javac_classpath'), s:classpath_sep),
|
||||
\)
|
||||
|
||||
return !empty(l:class_paths)
|
||||
\ ? '-cp ' . 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 = ''
|
||||
|
||||
@@ -65,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 ' . fnameescape(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.
|
||||
@@ -74,7 +47,7 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort
|
||||
return 'javac -Xlint'
|
||||
\ . ' ' . l:cp_option
|
||||
\ . ' ' . l:sp_option
|
||||
\ . ' -d ' . fnameescape(l:class_file_directory)
|
||||
\ . ' -d ' . ale#Escape(l:class_file_directory)
|
||||
\ . ' ' . ale#Var(a:buffer, 'java_javac_options')
|
||||
\ . ' %t'
|
||||
endfunction
|
||||
@@ -86,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]
|
||||
|
||||
@@ -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 fnameescape(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',
|
||||
\})
|
||||
|
||||
@@ -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 fnameescape(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,
|
||||
\})
|
||||
|
||||
@@ -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 = fnameescape(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 ' . fnameescape(l:jshint_config)
|
||||
let l:command .= ' --config ' . ale#Escape(l:jshint_config)
|
||||
endif
|
||||
|
||||
let l:command .= ' -'
|
||||
|
||||
@@ -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 fnameescape(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',
|
||||
\})
|
||||
|
||||
|
||||
@@ -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 fnameescape(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',
|
||||
\})
|
||||
|
||||
@@ -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 ' . fnameescape(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 = fnameescape(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,
|
||||
\})
|
||||
|
||||
54
ale_linters/kotlin/ktlint.vim
Normal file
54
ale_linters/kotlin/ktlint.vim
Normal 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
|
||||
\})
|
||||
@@ -12,7 +12,7 @@ function! ale_linters#lua#luacheck#GetExecutable(buffer) abort
|
||||
endfunction
|
||||
|
||||
function! ale_linters#lua#luacheck#GetCommand(buffer) abort
|
||||
return 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
|
||||
|
||||
@@ -44,10 +44,7 @@ endfunction
|
||||
|
||||
|
||||
function! ale_linters#nim#nimcheck#GetCommand(buffer) abort
|
||||
let l:directory = fnameescape(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,
|
||||
\})
|
||||
|
||||
23
ale_linters/objc/clang.vim
Normal file
23
ale_linters/objc/clang.vim
Normal 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',
|
||||
\})
|
||||
23
ale_linters/objcpp/clang.vim
Normal file
23
ale_linters/objcpp/clang.vim
Normal 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',
|
||||
\})
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
\})
|
||||
|
||||
@@ -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
|
||||
let l:pattern = '\vPHP %(Fatal|Parse) error:\s+(.+unexpected ''(.+)%(expecting.+)@<!''.*|.+) in - on line (\d+)'
|
||||
|
||||
" 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
|
||||
@@ -23,7 +29,7 @@ endfunction
|
||||
call ale#linter#Define('php', {
|
||||
\ 'name': 'php',
|
||||
\ 'executable': 'php',
|
||||
\ 'output_stream': 'both',
|
||||
\ 'command': 'php -l -d display_errors=1 --',
|
||||
\ 'output_stream': 'stdout',
|
||||
\ 'command': 'php -l -d error_reporting=E_ALL -d display_errors=1 --',
|
||||
\ 'callback': 'ale_linters#php#php#Handle',
|
||||
\})
|
||||
|
||||
@@ -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',
|
||||
\})
|
||||
|
||||
@@ -48,7 +48,7 @@ function! ale_linters#python#flake8#VersionCheck(buffer) abort
|
||||
return ''
|
||||
endif
|
||||
|
||||
let l:executable = fnameescape(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 fnameescape(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',
|
||||
\})
|
||||
|
||||
@@ -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
|
||||
\ . fnameescape(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,
|
||||
\})
|
||||
|
||||
@@ -26,16 +26,49 @@ function! ale_linters#python#pylint#GetExecutable(buffer) abort
|
||||
endfunction
|
||||
|
||||
function! ale_linters#python#pylint#GetCommand(buffer) abort
|
||||
return fnameescape(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'
|
||||
endfunction
|
||||
|
||||
function! ale_linters#python#pylint#Handle(buffer, lines) abort
|
||||
" Matches patterns like the following:
|
||||
"
|
||||
" test.py:4:4: W0101 (unreachable) Unreachable code
|
||||
let l:pattern = '\v^[^:]+:(\d+):(\d+): ([[:alnum:]]+) \(([^(]*)\) (.*)$'
|
||||
let l:output = []
|
||||
|
||||
for l:match in ale#util#GetMatches(a:lines, l:pattern)
|
||||
"let l:failed = append(0, l:match)
|
||||
let l:code = l:match[3]
|
||||
|
||||
if (l:code ==# 'C0303')
|
||||
\ && !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] + 1,
|
||||
\ 'text': l:code . ': ' . l:match[5] . ' (' . l:match[4] . ')',
|
||||
\ 'type': l:code[:0] ==# 'E' ? 'E' : 'W',
|
||||
\})
|
||||
endfor
|
||||
|
||||
return l:output
|
||||
endfunction
|
||||
|
||||
call ale#linter#Define('python', {
|
||||
\ 'name': 'pylint',
|
||||
\ 'executable_callback': 'ale_linters#python#pylint#GetExecutable',
|
||||
\ 'command_callback': 'ale_linters#python#pylint#GetCommand',
|
||||
\ 'callback': 'ale#handlers#python#HandlePEP8Format',
|
||||
\ 'callback': 'ale_linters#python#pylint#Handle',
|
||||
\ 'lint_file': 1,
|
||||
\})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
\})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ' . fnameescape(l:chktex_config)
|
||||
let l:command .= ' -l ' . ale#Escape(l:chktex_config)
|
||||
endif
|
||||
|
||||
let l:command .= ' ' . ale#Var(a:buffer, 'tex_chktex_options')
|
||||
|
||||
9
ale_linters/typescript/eslint.vim
Normal file
9
ale_linters/typescript/eslint.vim
Normal 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',
|
||||
\})
|
||||
@@ -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 ' . fnameescape(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
|
||||
|
||||
|
||||
23
ale_linters/typescript/tsserver.vim
Normal file
23
ale_linters/typescript/tsserver.vim
Normal 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',
|
||||
\})
|
||||
@@ -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 ' . fnameescape(l:filename)
|
||||
return 'verilator --lint-only -Wall -Wno-DECLFILENAME ' . ale#Escape(l:filename)
|
||||
endfunction
|
||||
|
||||
function! ale_linters#verilog#verilator#Handle(buffer, lines) abort
|
||||
|
||||
@@ -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',
|
||||
\})
|
||||
|
||||
69
ale_linters/xml/xmllint.vim
Normal file
69
ale_linters/xml/xmllint.vim
Normal 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',
|
||||
\ })
|
||||
@@ -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
21
autoload/ale/balloon.vim
Normal 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
91
autoload/ale/c.vim
Normal 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
|
||||
@@ -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
57
autoload/ale/command.vim
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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 ''
|
||||
|
||||
@@ -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', '\=fnameescape(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', '\=fnameescape(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 . ' < ' . fnameescape(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
33
autoload/ale/events.vim
Normal 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
378
autoload/ale/fix.vim
Normal 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
|
||||
154
autoload/ale/fix/registry.vim
Normal file
154
autoload/ale/fix/registry.vim
Normal 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
|
||||
26
autoload/ale/fixers/autopep8.vim
Normal file
26
autoload/ale/fixers/autopep8.vim
Normal 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
|
||||
37
autoload/ale/fixers/eslint.vim
Normal file
37
autoload/ale/fixers/eslint.vim
Normal 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
|
||||
12
autoload/ale/fixers/generic.vim
Normal file
12
autoload/ale/fixers/generic.vim
Normal 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
|
||||
25
autoload/ale/fixers/generic_python.vim
Normal file
25
autoload/ale/fixers/generic_python.vim
Normal 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
|
||||
26
autoload/ale/fixers/isort.vim
Normal file
26
autoload/ale/fixers/isort.vim
Normal 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
|
||||
26
autoload/ale/fixers/prettier.vim
Normal file
26
autoload/ale/fixers/prettier.vim
Normal 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
|
||||
26
autoload/ale/fixers/prettier_eslint.vim
Normal file
26
autoload/ale/fixers/prettier_eslint.vim
Normal 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
|
||||
19
autoload/ale/fixers/rubocop.vim
Normal file
19
autoload/ale/fixers/rubocop.vim
Normal 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
|
||||
26
autoload/ale/fixers/yapf.vim
Normal file
26
autoload/ale/fixers/yapf.vim
Normal 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
|
||||
@@ -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
|
||||
|
||||
20
autoload/ale/handlers/cpplint.vim
Normal file
20
autoload/ale/handlers/cpplint.vim
Normal 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
|
||||
@@ -10,17 +10,20 @@ function! ale#handlers#css#HandleCSSLintFormat(buffer, lines) abort
|
||||
"
|
||||
" These errors can be very massive, so the type will be moved to the front
|
||||
" so you can actually read the error type.
|
||||
let l:pattern = '^.*: line \(\d\+\), col \(\d\+\), \(Error\|Warning\) - \(.\+\) (\([^)]\+\))$'
|
||||
let l:pattern = '\v^.*: line (\d+), col (\d+), (Error|Warning) - (.+)$'
|
||||
let l:output = []
|
||||
|
||||
for l:match in ale#util#GetMatches(a:lines, l:pattern)
|
||||
let l:text = l:match[4]
|
||||
let l:type = l:match[3]
|
||||
let l:errorGroup = l:match[5]
|
||||
|
||||
let l:group_match = matchlist(l:text, '\v^(.+) \((.+)\)$')
|
||||
|
||||
" Put the error group at the front, so we can see what kind of error
|
||||
" it is on small echo lines.
|
||||
let l:text = '(' . l:errorGroup . ') ' . l:text
|
||||
if !empty(l:group_match)
|
||||
let l:text = '(' . l:group_match[2] . ') ' . l:group_match[1]
|
||||
endif
|
||||
|
||||
call add(l:output, {
|
||||
\ 'lnum': l:match[1] + 0,
|
||||
@@ -39,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)
|
||||
|
||||
110
autoload/ale/handlers/eslint.vim
Normal file
110
autoload/ale/handlers/eslint.vim
Normal 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
|
||||
@@ -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,
|
||||
\})
|
||||
|
||||
@@ -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
|
||||
6
autoload/ale/handlers/rubocop.vim
Normal file
6
autoload/ale/handlers/rubocop.vim
Normal 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
|
||||
@@ -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]),
|
||||
\})
|
||||
|
||||
@@ -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,12 +151,16 @@ 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
|
||||
|
||||
let s:buffer_restore_map[a:buffer] = l:loclist
|
||||
let s:buffer_restore_map[a:buffer] = filter(
|
||||
\ copy(l:loclist),
|
||||
\ 'v:val.bufnr == a:buffer && v:val.col > 0'
|
||||
\)
|
||||
|
||||
call clearmatches()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
@@ -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
311
autoload/ale/job.vim
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
285
autoload/ale/lsp.vim
Normal 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
|
||||
64
autoload/ale/lsp/message.vim
Normal file
64
autoload/ale/lsp/message.vim
Normal 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
|
||||
67
autoload/ale/lsp/response.vim
Normal file
67
autoload/ale/lsp/response.vim
Normal 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
|
||||
37
autoload/ale/lsp/tsserver_message.vim
Normal file
37
autoload/ale/lsp/tsserver_message.vim
Normal 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
22
autoload/ale/node.vim
Normal 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
Reference in New Issue
Block a user